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.
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.
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 :.
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 :.
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 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
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 ...
J'ai mélangé sysex classique, fichier midi et sysex RT ... ... bon bon bon, pause café !
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.
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
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
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
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.
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.
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.
J'ajoute des bandes de LED à mon Piano numérique... alors autant qu'elle réagisse en fonction de la musique
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.