Python est un langage bien adapté pour bricoler des prototypes à base de modules Yoctopuce: le code est compact, donc assez vite écrit, et on trouve facilement des librairies en open source pour interfacer les éléments externes qui ne seraient pas supportés directement par les modules Yoctopuce. Aujourd'hui, nous allons vous montrer un petit exemple de ce type...
L'idée du projet est de transformer un instrument MIDI en un jeu pour entraîner la mémoire visuelle et auditive, sur le principe du jeu Simon. L'instrument joue de manière répétée un nombre croissant de notes, en montrant leur emplacement, et demande à l'utilisateur de répéter la séquence exacte. Au fur et à mesure que la séquence augmente, l'utilisateur apprend ainsi à jouer une mélodie.
Réalisation
Mis à part l'instrument MIDI, ce projet nécessite un écran aussi grand que l'instrument, pour pouvoir montrer à l'utilisateur où se trouve chaque note qu'il doit jouer. C'est là qu'interviennent les modules Yoctopuce: ils nous permettent de piloter une série de dalles RGB NeoPixel disposées en ligne, formant un écran de 60cm de long que l'on peut disposer juste à côté des notes. Voici le schéma de l'installation:
Schéma de notre installation
Plutôt qu'un banal piano MIDI, nous utilisons un xylophone MIDI parce que c'est plus ludique et parce que nous avons cette semaine un stagiaire qui en a un dans sa cave. Si on avait choisi un piano MIDI, on aurait pu l'utiliser aussi pour faire entendre les notes à jouer, mais dans le cas d'un xylophone MIDI, on doit utiliser une boîte à sons séparée pour produire le son de l'instrument.
Un xylophone MIDI fait maison, basé sur une interface trigger MIDI
Programmation
Pour interagir avec l'instrument MIDI, on utilise le package rtmidi2, qui fournit un accès assez bas niveau mais qui a l'avantage de s'installer très facilement avec PyPi. Pour recevoir en temps réel les notes jouées sur l'instrument MIDI, il suffit d'ouvrir l'interface MIDI et d'enregistrer une fonction de callback qui sera appelée à chaque événement MIDI reçu:
midiIn = MidiIn()
midiIn.open_port()
midiIn.callback = midiCallback
def midiCallback(msg: list, timestamp: float):
msgtype, channel = splitchannel(msg[0])
if msgtype == NOTEON:
note, velocity = msg[1], msg[2]
if velocity > 10:
note -= BaseNote
NoteOn(note, velocity)
Notre code n'a besoin de gérer que les événements de type activation de note (NOTEON). De plus, comme les piezos sous les touches du xylophone se déclenchent parfois à cause des vibrations sur les touches voisines, on ne garde que les événements suffisamment importants (velocity > 10, sur un maximum de 127). On soustrait à l'indice de la note MIDI l'indice de la note la plus basse sur notre clavier, de sorte à ce que la touche la plus à gauche soit pour nous la note zéro.
Pour jouer des notes, il suffit d'appeler la méthode send_noteon(). Par contre, comme il y a plusieurs ports de sortie MIDI sur le PC, il faut préciser quelle est l'interface à utiliser:
midiOut = MidiOut()
print("MIDI ports:", get_out_ports())
midiOut.open_port('MIDIOUT2 (ESI MIDIMATE eX) 2')
midiOut.send_noteon(0, note + BaseNote, velocity)
Pour afficher les notes à jouer, on va réutiliser notre afficheur NeoPixel construit pour un précédent projet. Les pixels sont représentés comme un tableau de couleurs HSL. On définit dans un autre tableau Python la forme des notes de la gamme sur le clavier, par les coordonnées des lignes de pixels à allumer, et on l'utilise dans une fonction qui peut dessiner n'importe quelle note à la luminosité choisie:
leds = [ 0 ] * n
def showNote(note, luminosity):
shape = NoteSize[note % 12]
color = min(255, max(10, luminosity))
color += NoteColor[note % 12] * 0x100
x = 62 - NotePos[note % 12] - 37 * (note // 12)
for top, bottom in shape:
for y in range(top, bottom):
leds[x * 8 + y] = color
x -= 1
Pour un meilleur rendu, on a fixé une feuille de papier japon sur les dalles NeoPixel, qui forme un excellent diffuseur:
Photo de notre affichage du clavier
Au démarrage, on affiche le clavier en faible intensité. Ensuite, on peut appeler cette fonction pour allumer la note à jouer, ou montrer les notes jouées en allumant la note proportionnellement à la force de frappe. Pour obtenir un effet d'extinction progressive de la note, qui reproduit l'extinction naturelle du son, on garde pour chaque note un compte à rebours du temps d'allumage, et on éteint progressivement la note durant les quatre dernières itérations. Voici donc la fonction de mise à jour de l'affichage, qui est appelée en boucle:
# update display
keyboardDisplay.set_hslColorArray(0, leds)
# decay luminosity
for i in range(NoteCount):
time = NoteTime[i]
time -= 1
NoteTime[i] = time
if time > 4:
showNote(i, NoteVelocity[i])
elif time >= 0:
showNote(i, NoteVelocity[i] * time // 4)
Le résultat final
Vous trouverez le code complet, avec en plus la logique du jeu de mémorisation et l'affichage du nom des notes, sur notre repository GitHub. Et voici une petite vidéo du résultat: