/*********************************************************************

  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.

}