Tirasse MIDI : du besoin à la réalisation

Aujourd'hui, c'est la fête de la musique, alors je vous ai concocté un article aux petits oignons, qui lie informatique et musique 😉

Contexte

Etant non seulement informaticien, mais également musicien, et plus particulièrement organiste, j’ai déjà intégré des périphériques MIDI standards pour obtenir un orgue portable, disposant d’un pédalier Roland PK9, d’un clavier auxiliaire M-Audio Keystation 61, et d’un orgue portable Viscount Cantorum VI. Cet ensemble fonctionne déjà convenablement, mais un de ses inconvénients est de ne disposer que de peu de jeux disponibles pour le pédalier (Prinzipal 16, Oktave 8 et Gedackt 8). J’ai déjà résolu le problème en complétant le système avec un module d’orgue Viscount Accupipe CM100, mais cet ajout nécessite de compléter le système avec une table de mixage et des enceintes, qui sont des éléments à transporter en plus lors du déplacement de l’instrument. Un autre type de solution est implémenté sur la plupart des orgues à tuyaux : les couplages. Ces derniers permettent simplement lors de l’appui sur une pédale du pédalier, de jouer également la note avec les jeux de l’un ou l’autre clavier, ou de coupler deux claviers. La plupart des orgues liturgiques électroniques “monoblocs” en possèdent, mais mon orgue portable n’en dispose visiblement pas nativement. J’ai donc décidé de m’en développer un, en m’appuyant sur le standard MIDI qui est implémenté dans chaque instrument. Cet article va donc relater cette expérience, du besoin à la réalisation.


L’orgue en version légère, branché sans la tirasse

Le besoin et sa réponse théorique

J’ai déjà expliqué le besoin dans ses grandes lignes. Néanmoins, du fait de l’utilisation d’un système externe, le besoin détaillé est plus complexe qu’il n’y paraît. Les principales exigences de la solution sont les suivantes :

  • Pouvoir monter / câbler simplement l’ensemble de la solution
  • Pouvoir identifier simplement chaque source de note du système (pédalier, clavier grand orgue, clavier positif)
  • Avoir la fonctionnalité de tirasse proprement dite
  • Pouvoir activer et désactiver simplement les tirasses
  • Pouvoir désigner un ou plusieurs synthétiseurs de sortie

Voyons maintenant les réponses que l’on peut apporter à chaque exigence.

Câblage de la solution : l’ensemble des périphériques disposent de prises MIDI DIN 5 broches ; en fonction du périphérique, jusqu’à 3 prises peuvent être disponibles (MIDI IN, MIDI OUT et MIDI THROUGH). Le standard MIDI a prévu de ce fait des possibilités de chaînage des instruments, mais établir un chaînage est difficile, d’autant plus que tous les instruments n’ont pas les trois prises. J’ai donc opté pour ne pas avoir un chaînage MIDI traditionnel, mais de passer en configuration “en étoile”, comme ce que l’on voit dans les réseaux, la tirasse faisant office de pseudo-routeur MIDI. Le seul inconvénient de cette méthode est qu’il nécessite l’achat de plusieurs contrôleurs MIDI/USB, ce qui consomme des ports USB.


Schéma des connexions sans et avec la tirasse

Identification des sources de note du système : le standard MIDI spécifie plusieurs types de messages, dont notamment des messages NOTE ON et NOTE OFF, prenant chacun deux paramètres, à savoir un numéro de canal (de 0 à 15) et une vélocité (force d’appui sur la touche). Le plus simple est de permettre un apprentissage de la source de note à la volée, de sorte à n’avoir qu’à activer l’apprentissage, puis à appuyer sur une note : le périphérique est alors identifié par le contrôleur auquel il est attaché et par le numéro de canal véhiculé dans le message. Il est d’ailleurs intéressant de fournir cette information à l’utilisateur pour simplifier l’identification des contrôleurs pour les fonctionnalités de sélection des périphériques de contrôles de tirasse et de synthétiseurs

Fonctionnalité de tirasse : cette fonctionnalité est en fait l’envoi (ou non) de messages NOTE ON et NOTE OFF vers les synthétiseurs, et ce pour chaque type de source (grand orgue, positif, pédalier), l’ensemble des périphériques étant configuré de sorte à avoir un numéro de canal MIDI unique utilisé globalement sur l’ensemble du système pour chaque type de source. En pratique, implémenter cette fonctionnalité n’est pas tout à fait trivial : pour y parvenir, on peut par exemple maintenir en mémoire deux types d’états pour chaque source et note : l’état de pression sur la touche (enfoncée ou pas) et l’état d’entente de la note (entendue ou non).

Activation/désactivation des tirasses : le plus souvent, les tirasses sont implémentées sur les orgues sous forme de commutateur au pied. Et il se trouve que le pédalier Roland PK9 dispose, en plus des pédales pour les notes, de 4 commutateurs, sur lesquels on peut associer des messages MIDI de type CONTROL_CHANGE. Un message CONTROL_CHANGE dispose d’un numéro de contrôle et d’une valeur. La plupart des numéros de contrôles sont dédiés à des fonctionnalités spécifiques de par la norme MIDI, mais certains numéros sont laissés à l’usage général; et c’est le cas des numéros de contrôle 16 à 19. Le fait est que l’on peut assigner un numéro de contrôle à chaque commutateur sur le pédalier, ce qui permet l’utilisation de ces commutateurs pour activer et désactiver les tirasses. Toutefois, il faut éviter de risquer d’activer ou de désactiver une tirasse par mégarde via un autre périphérique qui pourrait utiliser les messages CONTROL_CHANGE à d’autres fins de propagation. Donc il s’agit de définir quel périphérique a le droit d’envoyer ces messages, en listant les périphériques et en permettant au niveau du logiciel de tirasse de sélectionner le ou les périphériques pris en compte.

Désignation des synthétiseurs de sortie : il s’agit de disposer d’une fonction de sortie de sorte à pouvoir utiliser plusieurs modules de synthétiseur (par exemple à la fois le Cantorum et l’Accupipe). En ayant ce contrôle, on dispose donc d’une plus grande souplesse dans l’utilisation des synthétiseurs et on profite des possibilités de multiplexage offertes par la position centrale de la tirasse MIDI.

De là, on peut chercher à implémenter la tirasse.

Implémentation de la tirasse

J’ai choisi d’implémenter la tirasse dans le language Java, car le Java est portable sur un grand nombre d’environnements (Linux, Windows, mais aussi Raspberry Pi) et dispose d’une API pour les accès MIDI, dans javax.sound.midi.

Cet accès a pour point d’entrée la classe MidiSystem, qui fournit une méthode statique getMidiDeviceInfo. A partir des infos fournies, on arrive à obtenir des Receivers et des Transmitters. Les receivers implémentent une fonction send qui permet de recevoir et donc traiter un message MIDI. Les transmitters sont assez semblables, dans la mesure où ils encapsulent un receiver pour lui passer des messages MIDI.

Il suffit donc de récupérer l’ensemble des transmitters et des receivers que le système fournit pour les proposer à l’utilisateur.

Etant donné que la tirasse devra également être une sorte de périphérique virtuel MIDI, j’ai décidé d’implémenter l’interface MidiDevice. Le device incorpore les fonctionnalités les plus complexes, comme l’établissement de la liste des périphériques, le calcul des états des notes entendues, la gestion indirecte des messages MIDI, ce en coordination avec l’implémentation d’un Receiver, d’un Transmitter et d’un MidiDevice.Info. De la sorte, ce développement s’intègre donc dans l’environnement de l’API proposée par Java.

A côté de cela, on retrouve des classes plus simples mais qui restent néanmoins très importantes comme les classes représentant les informations d’une source (MidiDevice.Info et numéro de canal), les sources elles-mêmes (avec leur état d’apprentissage et leur éventuelle information de source, ainsi que les états des notes pressées et des notes entendues), et les couplages (avec l’instance de Source qui fait office de source de couplage, celle qui fait office de cible de couplage, et l’activation ou non du couplage). Pour les classes de source et de couplage, comme leurs nombres d’instances sont limités, les classes intègrent directement un accès aux instances via des propriétés statiques et une méthode statique pour obtenir toutes les instances de chaque classe.

Pour interagir avec l’utilisateur, j’ai décidé de préparer une interface graphique implémentée dans un package séparé. Les éléments significatifs de l’interface graphique reprennent les éléments fonctionnels sur lesquels l’utilisateur doit agir, comme le widget pour déclencher l’assignement des sources, le widget pour gérer et visualiser l’état des couplages, les widgets pour gérer les listes de périphériques considérés comme synthétiseurs et comme périphériques “control changers”. On notera que les interactions entre les classes “moteur” et les classes d’interface utilisateur ont nécessité également l’implémentation d’événements personnalisés et de listeners à ces événements. Enfin, j’ai utilisé l’API java.util.logging pour créer un widget pour ajouter à l’affichage le log de certaines informations utiles (par exemple les assignations des contrôleurs MIDI aux sources).

Tests et débuggage

Le code principal écrit, il s’agissait de le débugger et de le tester, sans pour autant utiliser de suite directement le matériel, afin de minimiser un éventuel risque de casse. J’ai donc chercher à utiliser des périphériques MIDI virtuels :

Cependant, Java n’a pas détecté ces périphériques virtuels. Pour que je puisse les utiliser j’ai dû charger le module kernel snd-virmidi, qui simule des périphériques midi que Java reconnaît. J’ai ensuite branché vmpk et fluidsynth sur les périphériques fournis par virmidi et j’ai ainsi pu débugger la tirasse. Pour mieux visualiser les connexions et ne pas me tromper, j’ai utilisé une interface graphique pour la gestion des connexions : aconnectgui. Cette configuration avec un clavier et un synthétiseur a suffi car vmpk peut changer à la demande le numéro de canal, et au niveau de fluidsynth, le synthétiseur a une configuration telle que le canal 1 dispose de sons de piano et le canal 10 dispose de sons de percussions. Cependant, cette configuration de test n’est pas parfaite, car les interfaces Midi sont reconnues comme des sortes de sous-interface, ce qui pose des problèmes pour des messages tels que Control Change et l’activation de la tirasse depuis VMPK.


Tirasse GO=>POS activée par clic dans l’interface de la tirasse. On entend à la fois les percussions et le piano générés par fluidsynth lorsqu’on appuie sur des touches dans VMPK. En haut, on a le câblage virtuel, et ce sans liaison directe entre VMPK et fluidsynth, la liaison étant assurée par le programme de Tirasse.

Une fois que ces premiers tests étaient réussis, j’ai continué en utilisant un autre type de tests : des tests automatisés réalisés avec JUnit, qui sont à mi-chemin entre des tests unitaires et des tests d’acceptation.

En effet, j’ai pu réaliser des tests automatisés car j’ai écrit une classe fille de la classe de Device où j’ai surchargé les méthodes qui faisaient les appels à l’environnement midi, comme par exemple les fonctions de découverte des transmitters/receivers et de distribution des messages aux périphériques MIDI. Ainsi, j’avais la main pour injecter des messages en entrée et contrôler les états en sortie, ce qui m’a permis de corriger des erreurs que je n’aurais probablement pas détecté tout de suite. Ces tests pourront éventuellement éviter également des régressions si l’on vient à faire évoluer la tirasse dans le futur.


Schéma des classes, exécution des tests. Le code source des tests n’a pas été commenté pour en montrer plus sur le screenshot. On voit l’initialisation de l’environnement de test et la surcharge de certaines méthodes qui devraient interagir en temps normal avec l’environnement.

Enfin, j’ai cherché à faire des tests sur une architecture matérielle réelle. Je me suis heurté tout d’abord à des problèmes qui pourraient être liés à une mauvaise reconnaissance de mes contrôleurs MIDI sous Windows (je n’ai pas cherché trop longtemps à vrai dire) et je suis passé ensuite sur un raspberry Pi 2, qui était ma véritable cible. J’ai rencontré un problème non prévu : mon hub USB n’était pas correctement reconnu par le Pi et donc j’ai été obligé de brancher directement mes contrôleurs MIDI en USB sur le Pi...où il m’aurait fallu 5 ports USB au lieu de 4 (clavier, souris et les 3 contrôleurs). L’astuce est de se passer de la souris en activant son émulation par le clavier.

Avec le Pi, les tests ont été plus probants, mais j’ai découvert qu’il y avait un problème dans les messages émis par le clavier midi : un dump réalisé avec adump m’a montré que le message NOTE_OFF n’était pas envoyé, mais qu’à la place un message NOTE_ON avec une vélocité à 0 était envoyé, ce qui m’a amené à devoir gérer également cette possibilité de traitement des notes.

En ce qui concerne les messages envoyés par le pédalier, j’ai constaté que les messages CONTROL CHANGE étaient bien envoyés, mais qu’ils étaient envoyés avec une différence de valeur entre le moment où on appuie sur le commutateur et le moment où on le relâche. J’ai donc dû également modifier le code pour ne réagir qu’avec une seule valeur de CONTROL CHANGE pour un contrôle.


Le programme de tirasse fonctionnant depuis un Raspberry Pi. Les tirasses ont été déclenchées depuis les commutateurs du pédalier.

Conclusion

En définitive, je suis arrivé à une implémentation fonctionnelle. J'ai néanmoins dû changer mes contrôleurs USB-MIDI pour des contrôleurs USB-MIDI de meilleure qualité car il arrivait que les signaux envoyés n'étaient pas corrects lorsque je jouais vite (problème de NOTE OFF récupéré sur un mauvais numéro de canal).

J'ai également réalisé une évolution, à savoir l'implémentation de web services en Java pour manipuler la tirasse, consommés par un programme C utilisant ncurses, de sorte à pouvoir obtenir une interface utilisable en mode texte via un port série.

Un commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.