Go Down

Topic: Lire des signaux MIDI (Read 2682 times) previous topic - next topic

UniseV

Apr 29, 2013, 10:44 pm Last Edit: Apr 30, 2013, 02:57 pm by UniseV Reason: 1
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 :
Code: [Select]
/*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
EN: Libraries are my Gurus, they make me believe anything they want !
FR: Les librairies sont mes gourous, elles me font croire ce qu'elles veulent !

marcha

Salut,

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


Code: [Select]
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.

skywodd

Bonjour,


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 ...
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

marcha

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

Pas mal d'info ici http://www.lehomestudio.org/midi.html

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 :.
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

marcha

Je te laisse ta libre interprétation de l'idiotie de la norme MIDI :) 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.


Artouste


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)


UniseV

#7
Apr 30, 2013, 04:02 pm Last Edit: Apr 30, 2013, 04:05 pm by UniseV Reason: 1
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...
EN: Libraries are my Gurus, they make me believe anything they want !
FR: Les librairies sont mes gourous, elles me font croire ce qu'elles veulent !

UniseV

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

Code: [Select]
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
}
EN: Libraries are my Gurus, they make me believe anything they want !
FR: Les librairies sont mes gourous, elles me font croire ce qu'elles veulent !

skywodd

#9
Apr 30, 2013, 05:08 pm Last Edit: Apr 30, 2013, 05:13 pm by skywodd Reason: 1

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 :smiley-mr-green:
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 :smiley-sweat:

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 ... :smiley-sweat:
J'ai mélangé sysex classique, fichier midi et sysex RT ... ... bon bon bon, pause café ! :smiley-mr-green:
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

marcha

#10
May 01, 2013, 10:16 am Last Edit: May 01, 2013, 10:19 am by marcha Reason: 1
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.




skywodd

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

Un petit :
Code: [Select]
while(Serial.available() < 1);
devrait déjà empêcher pas mal de probléme de lecture.
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

UniseV

#12
May 02, 2013, 05:02 pm Last Edit: May 02, 2013, 05:14 pm by UniseV Reason: 1
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  ;)

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  :smiley-roll-sweat:
EN: Libraries are my Gurus, they make me believe anything they want !
FR: Les librairies sont mes gourous, elles me font croire ce qu'elles veulent !

fdufnews



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  ;)

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  :smiley-roll-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.

UniseV

#14
May 03, 2013, 10:40 am Last Edit: May 03, 2013, 12:56 pm by UniseV Reason: 1
INSPIRER.... EXPIRER.... Merci fdufnews  ;)

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.
EN: Libraries are my Gurus, they make me believe anything they want !
FR: Les librairies sont mes gourous, elles me font croire ce qu'elles veulent !

Go Up