Lire des signaux MIDI

Bonjour,

Je suis en train de développer une petit programme pour “décoder” le signal MIDI qui sort de mon Piano numérique.

Je cherche principalement à décoder des NoteOn/NoteOff, qui sont des mots de 3 bytes (24 bits), du genre 0x90407F.
Le problème c’est que je suis pollué par des “SYSTEME REALTIME MESSAGES”, plus exactement des “Timing clock”, composé d’un petit mot de 8 bits (0xF8), qui sert normalement à synchroniser les équipements MIDI entre eux.

Comment ignorer cette pollution, qui de plus est bien plus quantitative que mes VRAI messages ?
De plus, apparemment ces petits 0xF8 peuvent même venir s’intercaler A L’INTERIEUR d’un mot plus grand, du style : 0x90F8407F.

Voici le type de code que je comptais utiliser :

/*Receive Midi
*/

byte commandByte;
byte noteByte;
byte velocityByte;

void setup(){
  Serial.begin(31250);
}

void checkMIDI(){
  do{
    if (Serial.available()){
      commandByte = Serial.read();//read first byte
      noteByte = Serial.read();//read next byte
      velocityByte = Serial.read();//read final byte
    }
  }
  while (Serial.available() > 24);//when three bytes available
}
    

void loop(){
  checkMIDI();
}

J’ai bien une idée, mais ça ne me parait pas être la bonne solution.

UniseV

Salut,

Il te faut faire une machine d'état. ça ressemble à ça:

void loop(){
  static int state = 0;
  static byte note;

  byte b = Serial.read();
  if(b==0xF8) {
    // ignorer ou mesurer le temps pour synchroniser qqch
  }
  else {
    switch(state) {
    case 1:
      if(b==NOTE_ON) state = 2;
      // if(b==NOTE_OFF) state = ...;
      // ...
      break;
    case 2: // Attend la note
      note = b;
      state = 3;
      break;
    case 3: // Attend la vélocité
      byte velocity = b;
      // faire qqch
      state = 1; // retour à l'état initial
    }
  }
}

L'avantage avec le protocol midi (si mes souvenir sont bon) c'est que les commandes ont le msb à 1. Ce qui te permettrait
de simplifier cet exemple et de te resynchroniser sur une commande en cas de perte de donnée.

Bonjour,

UniseV:
De plus, apparemment ces petits 0xF8 peuvent même venir s'intercaler A L'INTERIEUR d'un mot plus grand, du style : 0x90F8407F.

Tu est sûr de toi ? Parce que ça me semble bizarre ...
Si les messages midi ne sont pas atomique il est impossible de les interpréter correctement ...

Les messages temps réel sont intercalables n'importe quand. Cela concerne les codes 0xF8 à 0xFF

Pas mal d'info ici Le Homestudio - Techniques homestudio et musiques électroniques - La norme MIDI

Dans ce cas c'est vraiment idiot comme truc ...
Tu ne peut pas différencier une valeur en taille variable d'une commande temps réel si les deux se mélanges ...
Et ignorer tout les 0xF8 me semble un peu trop radicale comme solution ...

Il faudrait trouver un note d'application concernant les sysex temps réel et leurs gestions, je vois pas d'autre solution :.

Je te laisse ta libre interprétation de l'idiotie de la norme MIDI :slight_smile: et t'invite à lire la spec en lien.

Pour faire court. Les paramètres d'une commande NOTE_ON sont sur 7 bits MSB à 0 ce qui permet de les différentier
des commandes.

skywodd:
Dans ce cas c'est vraiment idiot comme truc ...
Tu ne peut pas différencier une valeur en taille variable d'une commande temps réel si les deux se mélanges ...
Et ignorer tout les 0xF8 me semble un peu trop radicale comme solution ...

Il faudrait trouver un note d'application concernant les sysex temps réel et leurs gestions, je vois pas d'autre solution :.

bonjour skywodd
non , j'avais détecté trop tard cette specificité en achetant un EMU 1X1 pour remettre en état un Korg M1 avec midiox
http://www.creative.com/emu/products/product.aspx?pid=19089

le filtrage sur un seul octet sur MSB semble assez trivial

edit : je viens de lire la reponse de marcha 8)

marcha,

Le problème de ta machine d'état, c'est qu'elle prend 3 tour de LOOP pour lire un mot... si j'ai bien compris.

EDIT: et il faut implémenter aussi un filtrage des RealTimeClock DANS les mots...

Bon voila ce que je compte faire, mais c'est pas très joli :

void setup()
{
  Serial.begin(31250);        // Begin Serial MIDI
}

void loop()
{
  static unsigned char charTmp,cmd,note,velo,newMsg;   // Characters for MIDI words

  if (Serial.available() > 0) { // Only if serial data arrived...
    charTmp = Serial.read();    // Read the first char...
    if (charTmp==0xF8) { // ...If it is a RealTimeClock Message...
      // ...just ignore it
    }
    else                 // But if it is NOT a RTC-M...
    {
      if (charTmp==0x90 || charTmp==0xB0) // It can be a NoteOn or SustainOn...
      {
        cmd=charTmp;     // Save the command message
        charTmp = Serial.read(); // Next char should be the NOTE...

        if (charTmp==0xF8) { // ...But if there is a RTC-M instead...                       
          note = Serial.read();   // ...ignore it and take the next one...
        }
        else note = charTmp; // ... else, take the char that was firstly read.

        charTmp = Serial.read(); // Next char should be the VELOCITY...

        if (charTmp==0xF8) { // ...But if there is a RTC-M instead...                       
          velo = Serial.read();   // ...ignore it and take the next one...
        }
        else velo = charTmp; // ... else, take the char that was firstly read.

        newMsg=1;   // To inform LOOP about new message avaliable
      }
      else {
        // Unrecognized MIDI CMD byte
      }
    }
  }

  if (newMsg==1) {
    // The new message is treated here
    newMsg=0;
  }
  // Normal LOOP CODE
}

marcha:
Pour faire court. Les paramètres d'une commande NOTE_ON sont sur 7 bits MSB à 0 ce qui permet de les différentier
des commandes.

La syntaxe des commandes midi n'est pas un truc que je garde en tête :grin:
Mais il n'y a pas de commande midi qui prend des arguments pouvant être >128 ?

J'avais bricolé fut un temps un parseur de fichier midi et je gérai des valeurs >128 par moment ...
C'est peut être bien tout sur 7 bits effectivement ... faut dire que j'avais codé mon truc en gérant que très sommairement les sysex classique :sweat_smile:

Edit: j'ai le cerveau lent parfois ... en midi (version fichier) il y a le "delta temps" (qui correspondant au délai entre deux events midi) avant la commande.
C'est pour ça que je parlait de valeur "en taille variable", c'est une spécificité de la version fichier ... :sweat_smile:
J'ai mélangé sysex classique, fichier midi et sysex RT ... ... bon bon bon, pause café ! :grin:

Attention, Serial.read() n’est pas bloquant, il va te retourner -1 si il n’y a pas de donnée “available”

Au début de ton programme tu teste bien Serial.available() > 0, il est fort probable que Serial.available() te
retourne 1 lors de l’arrivée d’un octet car ton programme ne faisant rien d’autre il va réagir à l’arrivée du
premier octet MIDI reçu. Quelques microsecondes plus tard tu fais charTmp = Serial.read();
qui devrait te retourner -1 car il faut environ 320 microsecondes entre 2 bytes MIDI à 31250 bauds

Tu pourrais donc créer une fonction d’attente active d’un octet MIDI, mais je pense que la voie d’une machine
d’état comme dans mon premier exemple te permet plus facilement de faire autre chose avec ton cpu dans loop()

EDIT: mon premier exemple n’est pas fonctionnel (c’était illustratif). Si tu as besoin je peux y apporter quelques corrections pour en faire une base utilisable.

Bien vu marcha, j’avais pas fait gaffe à ca.

Un petit :

while(Serial.available() < 1);

devrait déjà empêcher pas mal de probléme de lecture.

Je reformule pour vérifier que j'arrive bien à vous suivre :

Le problème de mon CODE :
Si je n'ai reçu que le premier octet d'un NOTEON par exemple, je risque de ne pas encore avoir reçu le(s) octet(s) suivant(s), au moment ou j'essaye de les lire.

Le problème de la de l'exemple de machine d’états de marcha :
Tu ne lis qu'un octet par tour de boucle LOOP... et c'est la que skywdd propose de boucler sur la machine d’état tant qu'on a des octets à traiter dans le buffer.

Ca me va pas mal, il faut juste être certain que même avec une liaison série qui communique en "continu", il reste assez de temps pour jouer le reste de la LOOP...

En tout cas j'en ai assez pour sortir un nouveau CODE :wink:

EDIT: Hey, ça veut dire aussi qu'il faut que je prévois le cas ou je reçois PLUSIEURS NoteOn dans le même tour de boucle... car le reste de mon LOOP ne sera pas toujours vide :cold_sweat:

UniseV:
Ca me va pas mal, il faut juste être certain que même avec une liaison série qui communique en "continu", il reste assez de temps pour jouer le reste de la LOOP...

En tout cas j'en ai assez pour sortir un nouveau CODE :wink:

EDIT: Hey, ça veut dire aussi qu'il faut que je prévois le cas ou je reçois PLUSIEURS NoteOn dans le même tour de boucle... car le reste de mon LOOP ne sera pas toujours vide :cold_sweat:

Garde la tête froide, tu t'emballes.
Le MIDI ce n'est pas très rapide je serais surpris, vu la boucle actuelle, que tu reçoives plusieurs caractères en un tour de boucle.

INSPIRER.... EXPIRER.... Merci fdufnews :wink:

Pour faire mes tests je vais attendre de recevoir mon petit shield LCD-RVB de chez Adafruit, comme ça je pourrais utiliser l'entrée Série "hard" de l'Arduino pour le MIDI et l'écran du shield en I2C pour me donner quelques ordres d'idée, comme :

  • Combien de BYTE je traite au maximum par tour de boucle.
  • Est-ce que VRAIMENT ya des Messages Temps-réel qui viennent s'intercaler DANS des Messages plus long (NoteOn par exemple)
  • Que se passe-t-il quand j'envoi PLEINS de notes simultanées...

Ça devrait déjà m'informer pas mal sur l'utilisation réelle du truc.

UniseV:
INSPIRER.... EXPIRER.... Merci fdufnews :wink:

Pour faire mes tests je vais attendre de recevoir mon petit shield LCD-RVB de chez Adafruit, comme ça je pourrais utiliser l'entrée Série "hard" de l'Arduino pour le MIDI et l'écran du shield en I2C pour me donner quelques ordres d'idée, comme :

  • Combien de BYTE je traite au maximum par tour de boucle.
  • Est-ce que VRAIMENT ya des Messages Temps-réel qui viennent s'intercaler DANS des Messages plus long (NoteOn par exemple)
  • Que se passe-t-il quand j'envoi PLEINS de notes simultanées...

Ça devrait déjà m'informer pas mal sur l'utilisation réelle du truc.

bonjour
déjà tu peux faire du log des événements midi pour regarder ce qui transite
sur PC terminal Terminal
permet d'utiliser du port com en valeur custom (31250 pour du midi)
ici un test midi out arduino ---> terminal sur PC
http://cjoint.com/13mi/CEdnRhfsNoQ_midimsg1.jpg

  • Combien de BYTE je traite au maximum par tour de boucle.

Un seul avec une machine d'état, plusieurs si tu fais des attentes actives (par ex: 3 pour un NOTE ON)

  • Est-ce que VRAIMENT ya des Messages Temps-réel qui viennent s'intercaler DANS des Messages plus long (NoteOn par exemple)

Je crois que cela dépends de la configuration de l'appareil MIDI qui émet. Mais part du principe que oui

  • Que se passe-t-il quand j'envoi PLEINS de notes simultanées...

L'appareil qui émet va les envoyer à la suite dans la limite du débit MIDI 31250 bauds.
Donc tu les recevra également à la suite. (pas simultanément).

Voici une machine d'état un peu plus aboutie (mais non testée) que mon premier exemple.

#define NOTE_OFF 0x80
#define NOTE_ON 0x90

void setup() {
}

void note_on(byte note, byte velocity) {
}

void note_off(byte note, byte velocity) {
}

void process(byte b) {
  static byte command; // dernière commande MIDI reçue
  static byte arg_cpt; // compteur d'argument
  static byte arg1; // mémoire pour l'argument 1
  static byte arg2; // mémoire pour l'argument 2 (pas nécessaire, mais pour faire homogène)

  if(b>=0xF8) return; // ignore les messages temps réel
  if(b & 0x80) { // si commande MIDI
    command = b; // mémorise la dernière commande
    arg_cpt = 0; // initialise le compteur d'argument  
  }
  else if(arg_cpt==0) { // si 1er argument
    arg1 = b;
    arg_cpt++;
    // traiter ici les commandes à 1 argument (par ex: aftertouch)
  }
  else if(arg_cpt==1) { // si 2ème argument
    arg2 = b;
    arg_cpt++;
    // on a reçu les 2 arguments pour NOTE_ON ou NOTE_OFF, on délegue à des fonctions pour clarifier le code
    if(command==NOTE_ON) note_on(arg1, arg2);
    else if(command==NOTE_OFF) note_off(arg1, arg2);
  }
  // ignore les argument d'une commande avec plus que 2 arguments (si ça existe, me souviens plus)
}

void loop() {
  int b = Serial.read();
  if(b!=-1) process(b);
  // permet de faire autre chose ici
}

A vue de nez, le temps de la fonction process devrait demander max 20 microsecondes par byte MIDI, donc
largement en dessous des 320.

Prends garde que dans les fonctions note_on ou note_off tu ne prenne pas trop de temps.

Merci Marcha,

C'est vraiment sympa d'avoir pris de ton temps pour écrire ce message et ce code, ça sera mon point de départ :slight_smile:

UniseV

Je t'en prie. ça me rappelle de vieux souvenirs (1990) à cette époque j'avais fait un light show midi basé sur un 8051.

Qu'est-ce que tu es en train de faire toi ?

J'ajoute des bandes de LED à mon Piano numérique... alors autant qu'elle réagisse en fonction de la musique :wink:

J'ai commencé à développer et j'ai un soucis, je n'ai que le premier NoteOn qui est lu correctement, ensuite ma boucle se "décale" et n'arrive plus à déchiffrer correctement les trame MIDI, voici l'état du machin :

#define ardLed 13

void setup()
{
  pinMode(ardLed,OUTPUT);
  digitalWrite(ardLed,LOW);

  Serial.begin(31250);        // Begin Serial MIDI
}

unsigned long lastMid,lastMsg;
byte state=0;   // Characters for MIDI words
byte cmd,newMsg;   // Characters for MIDI words
int note,velo;   // Characters for MIDI words
byte charTmp;   // Characters for MIDI words

void loop()
{
  if (Serial.available() > 0) { // Only if serial data arrived...
    digitalWrite(ardLed,HIGH);  // Light the Arduino LED for debug.
    lastMid=millis();

    charTmp = Serial.read();    // Read the first char...

    if (charTmp==0xF8 || charTmp==0xFE) { // ...If it is a RealTimeClock Message...      ...just ignore it
    }

    else                 // But if it is NOT a RTC-M...
    {
      switch(state){
      case 2:
        velo=charTmp;     // Save the velocity byte...
        newMsg=1;         //  ... tell the main loop there is a new message
        break;
      case 1:
        note=charTmp;    // Save the note byte
        state=2;
        break;
      case 0:
        cmd=charTmp;     // Save the command byte
        state=1;
        break;
      }
    }
  }

  if (newMsg==1) {    // If there is a new message...
    newMsg=0;
    state=0;
    if (cmd==0x90) {     // ... And if this is a NoteOn...
                       // Use here the note & velo data
    }
    else {     // ... but if not...
                // ... there is a bug.
    }

  }

  // Normal LOOP CODE

  if (millis()>lastMid+5) digitalWrite(ardLed,LOW);   // Switch off the Arduino LED if there is 5ms without MIDI message
}

J'ai viré tout le "code Leds" pour ne laisser que le "code MIDI", si quelque chose vous saute aux yeux.

EDIT : Je précise que mon Piano n'envoi pas de NoteOff à proprement dit, mais simplement des NoteOn avec une velocity à 0.

EDIT 2: Artouste, je n'ai pas réussi à voir mes signaux MIDI dans ton soft, je pense que je ne comprends pas bien comment "raccorder" le tout.