Dans l'espoir de devenir un meilleur musicien, j'ai mis au point un dispositif qui se branche dans un clavier MIDI. Une note s'affiche sur une portée, et je dois jouer cette note sur le clavier. Si j'appuie sur la bonne touche, je gagne un point, et une nouvelle note s'affiche.
Si j'appuie sur la mauvaise touche, un petit clavier apparaît à l'écran pour m'indiquer quelle touche il faut appuyer et, bien entendu, je ne récolte aucun point. Je dois quand même appuyer sur la bonne touche afin de passer à la question suivante. De plus, juste après avoir joué "ma" note, le clavier joue la note que j'aurais dû jouer, ce qui me permet de comparer les deux notes de façon auditive.
Ce dispositif est constitué d'une carte Arduino Uno, d'un écran LCD PCD8544 (Nokia 5110) et d'une interface MIDI de fabrication maison qui est essentiellement constituée d'un octocoupleur, d'une diode de quelques résistances et de connecteurs DIN.
Le circuit
Je reproduis ici le schéma de mon interface MIDI, fabriquée en 2012... La partie "MIDI IN" est essentielle pour détecter la note jouée sur le clavier. La partie "MIDI OUT" permet à l'Arduino de faire jouer, par l'entremise du clavier, la note qu'il fallait jouer, ce qui aide l'utilisateur à constater s'il a joué la bonne note ou non.
Vous pouvez également vous procurer un module MIDI prêt à l'emploi (recherchez "MIDI Shield Arduino").
En ce qui concerne l'afficheur LCD, le circuit est assez complexe puisque j'utilise un circuit intégré 4050 afin d'abaisser à 3,3 V les signaux logiques générés par l'Arduino Uno. Voici le schéma, mais vous trouverez beaucoup plus d'information en consultant cet article consacré à l'utilisation d'un écran Nokia 5110 avec un Arduino.
Installation de la bibliothèque MIDI
J'ai utilisé la bibliothèque MIDI afin de simplifier au maximum la gestion de la communication entre l'Arduino et le clavier MIDI.
Installation des bibliothèques pour l'afficheur LCD
Pour l'affichage sur l'écran Nokia 5150, j'ai utilisé la bibliothèque Adafruit-PCD8544-Nokia-5110-LCD ainsi que la bibliothèque Adafruit-GFX.
Le sketch
Une part importante du sketch concerne la gestion de l'écran LCD. Au moyen d'un logiciel de dessin, j'ai créé quatre minuscules fichiers .bmp: la portée avec une clé de sol, la portée avec une clé de fa, le symbole bémol, et le symbole dièse. J'ai ensuite chargé ces fichiers .bmp dans l'outil en ligne image2ccp afin de les transformer en information utilisable dans un sketch Arduino. Il s'agit des quatre constantes "cleDeSol", "cleDeFa", "bemol" et "diese" énoncées au début du programme.
Pour les autres éléments affichés sur l'écran, j'ai utilisé les outils de base de la bibliothèque GFX (rectangles pour dessiner le clavier, rectangle à coin arrondi pour les notes, etc.)
Comme son nom l'indique, la routine "nouvelleQuestion" est responsable de créer aléatoirement une nouvelle note à placer sur la portée. Cette nouvelle note est décrite au moyen de 3 variables: "clef" détermine si la portée est en clé de sol ou en clé de fa, "altération" détermine si la note est accompagnée d'un bémol ou d'une dièse, et "numéro" indique la position de la note sur la portée.
Lorsque l'utilisateur appuie sur une touche du clavier, il faut vérifier si la note qu'il a généré correspond à celle qui est affichée sur le portée; c'est fait dans la fonction "compareNotes", grâce à deux tableaux qui établissent une relation entre chaque position sur la portée et son numéro de note MIDI (ce sont les variables notes_fa et notes_sol).
-
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************* | |
Gadget MIDI pour améliorer la lecture des notes sur une portée. | |
Une note s'affiche sur une portée sur un écran Nokia, et on | |
doit la jouer sur un clavier MIDI.. | |
Pour plus d'infos: | |
https://electroniqueamateur.blogspot.com/2020/08/sentrainer-lire-les-notes-sur-une.html | |
*********************************************************************/ | |
#include <MIDI.h> // https://github.com/FortySevenEffects/arduino_midi_library | |
#include <SPI.h> // Pour l'afficheur | |
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library | |
#include <Adafruit_PCD8544.h> // https://github.com/adafruit/Adafruit-PCD8544-Nokia-5110-LCD-library | |
MIDI_CREATE_DEFAULT_INSTANCE(); | |
Adafruit_PCD8544 display = Adafruit_PCD8544(5, 4, 3); | |
// D/C broche 5,CSE ou CS broche 4, RST broche 3 | |
// mages bitmap éalisées avec l'outil en ligne http://javl.github.io/image2cpp/ | |
// la portée en clé de sol | |
const unsigned char cleDeSol [] PROGMEM = { | |
// 'cledesol', 45x36px | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, | |
0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x28, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, | |
0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x01, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb5, 0x80, 0x00, | |
0x00, 0x00, 0x01, 0xa4, 0x40, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x01, 0x84, | |
0x40, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, | |
0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
}; | |
// la portée en clé de fa | |
const unsigned char cleDeFa [] PROGMEM = { | |
// 'cledefa', 45x36px | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x06, 0x39, 0x00, 0x00, | |
0x00, 0x00, 0x07, 0x98, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0x8c, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, | |
0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
}; | |
// symbol bémol | |
const unsigned char bemol [] PROGMEM = { | |
// 'bemol', 4x7px | |
0x80, 0x80, 0xa0, 0xd0, 0x90, 0xa0, 0xc0 | |
}; | |
// symbole dièse | |
const unsigned char diese [] PROGMEM = { | |
// 'dièse', 7x7px | |
0x14, 0x14, 0x7e, 0x28, 0xfc, 0x50, 0x50 | |
}; | |
// numéro de note midi associé à chaque position de la portée en clé de fa (numero 0 = MIDI 33, etc.) | |
const int notes_fa[] = {33, 35, 36, 38, 40, 41, 43, 45, 47, 48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67, 69}; | |
// numéro de note midi associé à chaque position de la portée en clé de sol(numero 0 = MIDI 53, etc.) | |
const int notes_sol[] = {53, 55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72, 74, 76, 77, 79, 81, 83, 84, 86, 88, 89}; | |
// variables globales | |
int numero; //position de la note sur la portée: 0 à 21 | |
int alteration; // 0: rien 1: bémol 2: dièse | |
int clef; // 0: sol 1: fa | |
int erreur = 0; // 1 si notre dernière réponse était fausse | |
int pointage = 0; // nombre de bonnes réponses depuit le début | |
int total = 0; // nombre de questions posées depuis le début | |
int bonneNote; // le numéro MIDI de la note qu'il faut jouer | |
void dessineNote() { | |
const int marge = 0; // nombre de pixels à gauche avant de commencer à dessiner | |
display.clearDisplay(); | |
// on dessine la portée | |
if (clef) { //clef de fa | |
display.drawBitmap(marge, 8, cleDeFa, 45, 36, BLACK); | |
} | |
else { // clef de sol | |
display.drawBitmap(marge, 8, cleDeSol, 45, 36, BLACK); | |
} | |
//on dessine la note | |
display.drawRoundRect(marge + 26, 42 - 2 * numero, 8, 5, 4, BLACK); | |
// s'il y a lieu, on dessine les petits traits supplémentaires | |
// note à l'extérieur de la portée | |
if (numero == 0) { | |
display.drawLine(marge + 24, 44, marge + 34, 44, BLACK); | |
} | |
if (numero < 3) { | |
display.drawLine(marge + 24, 40, marge + 34, 40, BLACK); | |
} | |
if (numero < 5) { | |
display.drawLine(marge + 24, 36, marge + 34, 36, BLACK); | |
} | |
if (numero > 15) { | |
display.drawLine(marge + 24, 12, marge + 34, 12, BLACK); | |
} | |
if (numero > 17) { | |
display.drawLine(marge + 24, 8, marge + 34, 8, BLACK); | |
} | |
if (numero > 19) { | |
display.drawLine(marge + 24, 4, marge + 34, 4, BLACK); | |
} | |
// on dessine l'altération, s'il y a lieu | |
if (alteration == 1) { // bemol | |
display.drawBitmap(marge + 19, 41 - 2 * numero, bemol, 4, 7, BLACK); | |
} | |
if (alteration == 2) { // dièse | |
display.drawBitmap(marge + 18, 42 - 2 * numero, diese, 7, 7, BLACK); | |
} | |
// si notre dernière tentative était mauvaise, on affiche un clavier qui montre | |
// comment jouer la note. | |
if (erreur) { | |
dessineClavier(); | |
} | |
// on affiche le pointage | |
display.setCursor(46, 8); // coordonnées du point de départ du texte | |
display.setTextColor(BLACK); | |
display.setTextSize(1); // taille par défaut | |
display.print(String(pointage)); | |
display.print("/"); | |
display.print(String(total)); | |
display.display(); | |
} | |
void dessineClavier() { | |
const int origx = 48; | |
const int origy = 28; | |
const int largeurBlanche = 5; | |
// les touches blanches | |
for (int i = 0; i <= 6; i++) { | |
display.drawRect(origx + i * largeurBlanche, origy, largeurBlanche , 12, BLACK); | |
} | |
//les touches noires | |
for (int i = 0; i <= 1; i++) { | |
display.fillRect(origx + largeurBlanche / 2 + 1 + i * largeurBlanche, origy, 4 , 7, BLACK); | |
} | |
for (int i = 0; i <= 2; i++) { | |
display.fillRect(origx + 7 * largeurBlanche / 2 + 1 + i * largeurBlanche, origy, 4 , 7, BLACK); | |
} | |
// le repère qui indique la touche à enfoncer: | |
switch (bonneNote % 12) { | |
case 0: // do | |
display.fillCircle(origx + largeurBlanche / 2 , origy + 14, 2, BLACK); | |
break; | |
case 1: // do# réb | |
display.fillCircle(origx + largeurBlanche , origy - 4, 2, BLACK); | |
break; | |
case 2: // ré | |
display.fillCircle(origx + 3 * largeurBlanche / 2 , origy + 14, 2, BLACK); | |
break; | |
case 3: // ré# mib | |
display.fillCircle(origx + 2 * largeurBlanche , origy - 4, 2, BLACK); | |
break; | |
case 4: // mi | |
display.fillCircle(origx + 5 * largeurBlanche / 2 , origy + 14, 2, BLACK); | |
break; | |
case 5: // fa | |
display.fillCircle(origx + 7 * largeurBlanche / 2 , origy + 14, 2, BLACK); | |
break; | |
case 6: // fa# solb | |
display.fillCircle(origx + 4 * largeurBlanche , origy - 4, 2, BLACK); | |
break; | |
case 7: // sol | |
display.fillCircle(origx + 9 * largeurBlanche / 2 , origy + 14, 2, BLACK); | |
break; | |
case 8: // sol# lab | |
display.fillCircle(origx + 5 * largeurBlanche , origy - 4, 2, BLACK); | |
break; | |
case 9: // la | |
display.fillCircle(origx + 11 * largeurBlanche / 2 , origy + 14, 2, BLACK); | |
break; | |
case 10: // la# sib | |
display.fillCircle(origx + 6 * largeurBlanche , origy - 4, 2, BLACK); | |
break; | |
case 11: // si | |
display.fillCircle(origx + 13 * largeurBlanche / 2 , origy + 14, 2, BLACK); | |
break; | |
} | |
display.display(); | |
} | |
// on choisit au hasard la nouvelle note à jouer | |
void nouvelleQuestion() { | |
numero = random(22); // position de la note sur la portée (0 à 21); | |
alteration = random(3); // 0: rien 1: bemol 2: dièse | |
clef = random(2); // 0: clé de sol 1: clé de fa | |
// ajustement: on ne veut pas de mi dièse, de fa bémol, de si dièse et de do bémol | |
if (alteration == 1) { // bémol | |
if (clef) { // clé de fa | |
if ((numero % 7 == 2) || (numero % 7 == 5) ) { // do ou fa | |
alteration = 0; // on enlève le bémol. | |
} | |
} | |
else { // clé de sol | |
if ((numero % 7 == 0) || (numero % 7 == 4) ) { // do ou fa | |
alteration = 0; // on enlève le bémol. | |
} | |
} | |
} | |
if (alteration == 2) { // dièse | |
if (clef) { // clé de fa | |
if ((numero % 7 == 1) || (numero % 7 == 4) ) { // si ou mi | |
alteration = 0; // on enlève la dièse | |
} | |
} | |
else { // clé de sol | |
if ((numero % 7 == 3) || (numero % 7 == 6) ) { // si ou mi | |
alteration = 0; // on enlève la dièse | |
} | |
} | |
} | |
dessineNote(); // affichage de la question | |
} | |
// on compare la note jouée avec celle qu'il fallait jouer | |
int compareNotes(byte pitch) { | |
// on convertit la variable numero (position sur la portée) en numéro de note MIDI | |
if (clef) { // clé de fa | |
bonneNote = notes_fa[numero]; | |
} | |
else { // clé de sol | |
bonneNote = notes_sol[numero]; | |
} | |
if (alteration == 1) { // bémol | |
bonneNote = bonneNote - 1; | |
} | |
if (alteration == 2) { // dièse | |
bonneNote = bonneNote + 1; | |
} | |
// on joue la bonne note | |
delay(200); // petit délai pour séparer la note jouée de la note corrigée | |
MIDI.sendNoteOff(pitch, 0, 1); | |
MIDI.sendNoteOn(bonneNote, 100, 1); | |
delay(100); | |
MIDI.sendNoteOff(bonneNote, 0, 1); | |
return (bonneNote % 12 == pitch % 12); // on ne vérifie pas si c'est le bon octave | |
/* Si vous désirez que la note soit acceptée uniquement si elle est jouée dans | |
le bon octave, utilisez plutôt: | |
return (bonneNote == pitch); | |
*/ | |
} | |
// éxécuté lorsque l'utilisateur appuie sur une touche | |
void handleNoteOn(byte channel, byte pitch, byte velocity) | |
{ | |
if (erreur == 0) { | |
total = total + 1; | |
} | |
if (compareNotes(pitch)) { // bonne réponse | |
// on additionne 1 au pointage | |
if (erreur == 0) { | |
pointage = pointage + 1; | |
} | |
erreur = 0; | |
// on fait apparaître une nouvelle question | |
nouvelleQuestion(); | |
} | |
else { // mauvaise réponse | |
erreur = 1; | |
dessineNote(); | |
} | |
} | |
void setup() { | |
// initialisation de l'écran | |
display.begin(); // initialisation de l'afficheur | |
display.setContrast(60); // réglage du contraste (40-60) variable selon votre écran | |
display.clearDisplay(); // ça efface à la fois le buffer et l'écran | |
display.display(); | |
// initialisation MIDI | |
MIDI.setHandleNoteOn(handleNoteOn); | |
MIDI.begin(MIDI_CHANNEL_OMNI); | |
// initialisation du générateur de nombres aléatoires | |
randomSeed(analogRead(0)); | |
// affichage de la première question | |
nouvelleQuestion(); | |
} | |
void loop() { | |
MIDI.read(); // on vérifie si une note est jouée sur le clavier. | |
} |
-
À lire aussi:
- Communication MIDI (out) avec une carte Arduino
- MIDI par usb avec Arduino Leonardo
- Fabrication d'un module MIDI (in et out) pour Arduino
- MIDI sans fil avec Arduino
- Clavier MIDI à base d'Arduino
- Pédalier d'orgue MIDI à base d'Arduino
- Flûte à bec MIDI à base d'Arduino
- Arpégiateur MIDI à base d'Arduino
- Séquenceur MIDI à base d'Arduino
- Identificateur d'accords MIDI