Skyduino:~#
Articles
arduino, C/C++, programmation

[Toute plateforme] Classe c++ de lecture midi avec support des fichiers multi-pistes

Bonjour tout le monde !

Comme promis dans mon précédent article voila la version améliorée de ma classe de lecture de fichiers midi.
Cette nouvelle version est un peu (beaucoup) plus complexe en interne de façon à gérer les fichiers contenant plusieurs pistes midi simultanément.

La classe n’en reste pas moins totalement générique et indépendante de la plateforme sur laquelle on souhaite la porter !

Le code source est disponible ici :
https://github.com/skywodd/generic_midi_parser_multivoice

Démonstration sur plateforme arduino :

Le hardware reste le même que pour le version précédente :
Une carte arduino,
Une prise midi,
Une shield avec carte SD (ici une MD-shield shield)

(J’ai utilisé une MD-shield de watterott à la place de ma ethernet shield, le chipset ethernet consommait trop de courant inutilement)

Et voici le résultat :

La vidéo est découpé en plusieurs « level » de 1 à 4.
Tout d’abord une musique à 1 piste très simple (Openning de Garei Zero),
Puis une musique un peu plus compliqué avec 2 pistes entrelacées (Openning de Shakugan no shana),
Suivit d’une musique un peu plus hard avec 3 pistes simultanées (Théme de shakugan no shana),
Et pour finir une bonne grosse musique bien hardcore avec 7 pistes simultanées à pleine vitesse (Negima 1000% Sparking) !
(oui ce ne sont que des musiques d’anime)

Je vous conseil de sauter directement à la dernière musique, c’est la plus intéressante d’un point de vue technique 😉
Pensez aussi à monter le volume, ma caméra à eu du mal à enregistrer le son …

Et avant d’oublier, le plus important : THE FUCKING MANUAL !
(Cette documentation est aussi valable pour la version précédente, mais attention certaines fonctions n’existe que dans cette version)

Les types de données :
uint8_t -> entier non signé sur 8 bits
uint16_t -> entier non signé sur 16 bits
int16_t -> entier signé sur 16 bits
uint32_t -> entier non signé sur 32 bits

Les définitions statique :
#define MAX_TRACKS_NUMBERS 12
MAX_TRACKS_NUMBERS représente le nombre maximum de pistes que peut lire simultanément le lecteur.
Par défaut j’ai mis 12, ce qui devrait suffire pour pas mal de fichiers midi.

Les constantes :
-> Les messages d’erreurs (en sortie de getErrno, ou en argument du callback d’erreur)
NO_ERROR : Aucune erreurs a déclarer, tout va bien de le meilleur des mondes possible
BAD_FILE_HEADER : L’entête de fichier midi est invalide ou corrompu
BAD_TRACK_HEADER : L’entête de la piste midi est invalide ou corrompu
BAD_META_EVENT : Un creeper méta-event corrompu vient de péter à la tronche du lecteur
BAD_FILE_STRUCT : Le fichier midi est corrompu jusqu’à la moelle
NO_MULTIPLE_SONG_SUPPORT : Le fichier midi est du type 2 soit « ensemble de fichiers midi », type non supporté
NO_SMPTE_SUPPORT : Le fichier midi utilise la norme SMPTE pour les timings, norme que je n’ai pas implémenté par pure fainéantise

-> Les types de meta-event
META_TEXT : Du texte quelconque, inclut dans le fichier midi
META_COPYRIGHT : Le copyright (ou le copyleft :)) du fichier midi
META_TRACK_NAME : Le nom du fichier, ou de la piste midi
META_INSTRUMENT : Le nom de l’instrument utilisé
META_LYRICS : Les paroles de la musique en cours
META_MARKER : heu … j’ai oublié ^^
META_CUE_POINT : Les infos qui vont bien pour faire une boucle sur un morceau du fichier
META_CHANNEL : heu … j’ai aussi oublié ^^ …
META_SEQUENCER : Des informations spécifiques à un séquenceur midi particulier
META_SYSEX : Des commandes sysex pour faire un peu ce qu’on veut

Le constructeur :
GenericMidiParser(uint8_t (*file_read_fnct)(void),
void (*file_fseek_fnct)(uint32_t address),
uint32_t (*file_ftell_fnct)(void), uint8_t (*file_eof_fnct)(void),
void (*us_delay_fnct)(uint32_t us),
void (*assert_error_callback)(uint8_t errorCode));

Les arguments du constructeur :
file_read_fnct : pointeur sur fonction de prototype : uint8_t file_read_fnct(void)
Fonction permettant de lire un octet depuis la source de données et de déplacer le curseur de lecture d’un cran.

file_seek_fnct : pointeur sur fonction de prototype : void file_fseek_fnct(uint32_t address)
Fonction permettant de ce déplacer à l’adresse « address » dans la source de données.

file_ftell_fnct : pointeur sur fonction de prototype : uint32_t file_ftell_fnct(void)
Fonction retournant l’adresse courante du curseur de lecture dans la source de données.

file_eof_fnct : pointeur sur fonction de prototype : uint8_t file_eof_fnct(void)
Fonction retournant true si la fin du fichier a été atteinte, false sinon.

us_delay_fnct : pointeur sur fonction de prototype : void us_delay_fnct(uint32_t us)
Fonction permettant d’attendre « us » micro secondes (fonction impérativement bloquante).

assert_error_callback : pointeur sur fonction de prototype : void assert_error_callback(uint8_t errorCode)
Fonction à appeler lors d’une erreur d’exécution, l’argument « errorCode » contiendra le code d’erreur.

Les callback sur événements midi :

void setNoteOnCallback(void (*note_on_callback)(uint8_t channel, uint8_t key, uint8_t velocity));
Cette fonction permet de définir une fonction de prototype : void note_on_callback(uint8_t channel, uint8_t key, uint8_t velocity)
Comme fonction a appeler lors d’un événement midi de type Note On.

void setNoteOffCallback(void (*note_off_callback)(uint8_t channel, uint8_t key, uint8_t velocity));
Cette fonction permet de définir une fonction de prototype : void note_off_callback(uint8_t channel, uint8_t key, uint8_t velocity)
Comme fonction a appeler lors d’un événement midi de type Note Off.

void setKeyAfterTouchCallback(void (*key_after_touch_callback)(uint8_t channel, uint8_t key, uint8_t pressure));
Cette fonction permet de définir une fonction de prototype : void key_after_touch_callback(uint8_t channel, uint8_t key, uint8_t pressure)
Comme fonction a appeler lors d’un événement midi de type Key After Touch.

void setControlChangeCallback(void (*control_change_callback)(uint8_t channel, uint8_t controller, uint8_t data));
Cette fonction permet de définir une fonction de prototype : void control_change_callback(uint8_t channel, uint8_t controller, uint8_t data)
Comme fonction a appeler lors d’un événement midi de type Control Change.

void setPatchChangeCallback(void (*patch_change_callback)(uint8_t channel, uint8_t instrument));
Cette fonction permet de définir une fonction de prototype : void patch_change_callback(uint8_t channel, uint8_t instrument)
Comme fonction a appeler lors d’un événement midi de type Patch Change aussi appelé Program Change.

void setChannelAfterTouchCallback(void (*channel_after_touch_callback)(uint8_t channel, uint8_t pressure));
Cette fonction permet de définir une fonction de prototype : void channel_after_touch_callback(uint8_t channel, uint8_t pressure)
Comme fonction a appeler lors d’un événement midi de type Channel After Touch.

void setPitchBendCallback(void (*pitch_bend_callback)(uint8_t channel, uint16_t bend));
Cette fonction permet de définir une fonction de prototype : void pitch_bend_callback(uint8_t channel, uint16_t bend)
Comme fonction a appeler lors d’un événement midi de type Pitch Bend.

void setMetaCallback(void (*meta_callback)(uint8_t metaType, uint8_t dataLength));
Cette fonction permet de définir une fonction de prototype : void meta_callback(uint8_t metaType, uint8_t dataLength)
Comme fonction a appeler lors d’un événement midi de type Meta Event.

void setMetaOnChannelCallback(void (*meta_onChannel_prefix)(uint8_t channel));
Cette fonction permet de définir une fonction de prototype : void meta_onChannel_prefix(uint8_t channel)
Comme fonction a appeler lors d’un événement midi de type On Channel Prefix.

void setMetaOnPortCallback(void (*meta_onPort_prefix)(uint8_t channel));
Cette fonction permet de définir une fonction de prototype : void meta_onPort_prefix(uint8_t channel)
Comme fonction a appeler lors d’un événement midi de type On Port Prefix.

void setTimeSignatureCallback(void (*time_signature_callback)(uint8_t numerator, uint8_t denominator, uint8_t metronomeTick, uint8_t note32NdNumber));
Cette fonction permet de définir une fonction de prototype : void time_signature_callback(uint8_t numerator, uint8_t denominator, uint8_t metronomeTick, uint8_t note32NdNumber)
Comme fonction a appeler lors d’un événement midi de type Time Signature.

void setKeySignatureCallback(void (*key_signature_callback)(uint8_t sharpsFlats, uint8_t majorMinor));
Cette fonction permet de définir une fonction de prototype : void key_signature_callback(uint8_t sharpsFlats, uint8_t majorMinor)
Comme fonction a appeler lors d’un événement midi de type Key Signature.

Les fonctions « générales » :

uint32_t readByte();
Lit un octet depuis la source de données, tout en modifiant les informations internes de la piste.
UTILISER CETTE FONCTION UNIQUEMENT POUR LIRE DES DONNÉES DANS LE CADRE D’UN META-EVENT TEXT OU ASSIMILE !

void readBytes(uint8_t* buf, uint8_t len);
Lit »len » octet depuis la source de données et les places dans le buffer « buf » tout en modifiant les informations internes de la piste.
UTILISER CETTE FONCTION UNIQUEMENT POUR LIRE DES DONNÉES DANS LE CADRE D’UN META-EVENT TEXT OU ASSIMILE !

void dropBytes(uint8_t len);
Simule une lecture de « len » octet depuis la source de données (sans les lire physiquement)
UTILISER CETTE FONCTION UNIQUEMENT POUR LIRE DES DONNÉES DANS LE CADRE D’UN META-EVENT TEXT OU ASSIMILE !

Les fonctions « de contrôle » (fonctions a appeler via une interruption obligatoirement) :

void play();
Lance la lecture du fichier midi.

void pause();
Met en pause la lecture du fichier midi.

void resume();
Relance la lecture du fichier midi.

void stop();
Stop complétement la lecture du fichier midi.

Les fonctions « spécial » :

uint8_t getErrno() const;
Retourne le dernier code d’erreur rencontré.

uint32_t getTempo() const;
Retourne la valeur courante du tempo.

void setTempo(uint32_t tempo);
Permet de modifier la valeur de tempo.
(couplé avec getTempo() il est par exemple possible d’appliquer un coefficient au tempo).

Si vous lisez cette ligne c’est que vous avez eu le courage de lire toute la documentation jusqu’au bout. Vous avez mérité une bonne boisson 😉

Enjoy ! 🙂

Publicités

Discussion

16 réflexions sur “[Toute plateforme] Classe c++ de lecture midi avec support des fichiers multi-pistes

  1. C’est penible, il va falloir que je ressorte mon vieux KORG M1 de la cave , juste pour voir si ça tombe bien en marche !
    😀

    Publié par Artouste | 19 avril 2012, 17 h 33 min
  2. Nice one S_d. Video and presentation are all excellent. Now If I had found this months it would have helped heaps, but then I would not have learned so much other stuff re C++ etc, fantastic effort.
    Rob

    Publié par robwlakes | 21 avril 2012, 1 h 07 min
  3. Hello Skywodd
    çà faisait longtemps, mais je me disais bien que tu nous cogitais quelque chose.
    Si j’ai bien compris la différence entre les 2 versions, la première ne lit que les fichiers Midi avec une seule piste, et la 2e les multipiste. Est-ce que la version 1 lirait alors les fichiers format 0 qui ont les pistes regroupées sur une seule ?
    D’autre part, je ne vois ni afficheur, ni clavier, mais je suppose que çà va venir?
    Donc, pour le moment, on met une SD avec un fichier, on branche, et la lecture démarre?
    Je ne trouve pas non plus la doc!
    Je vois par contre qu’il faut la version 1.00 de l’IDE Arduino.
    Je vais me mettre à jour, et essayer de comprendre pour tester; çà a en tout cas l’air d’être du bon boulot!
    Papyduino

    Publié par Papyduino | 3 mai 2012, 7 h 02 min
    • Yep salut papyduino 🙂

      >> Si j’ai bien compris la différence entre les 2 versions, la première ne lit que les fichiers Midi avec une seule piste, et la 2e les multipiste.
      Ouaip, la 1er version lit les fichiers avec une seul piste, et la 2eme lit les fichiers avec plusieurs pistes simultanément.

      >> Est-ce que la version 1 lirait alors les fichiers format 0 qui ont les pistes regroupées sur une seule ?
      La version 1 lit les fichiers midi 0 sans probléme, j’ai ajouté un #define justement pour ça (le IGNORE_FILE_FORMAT).

      >> D’autre part, je ne vois ni afficheur, ni clavier, mais je suppose que çà va venir?
      Je n’est pas prévu de clavier afficheur ou autre, j’ai juste travaillé sur le principal, le coeur du parseur.
      Maintenant que le gros du travail est fait, libre à chacun d’ajouter e qu’il veut autour.
      Pour le moment j’ai énormément de travail donc je ne peut pas vraiment avancer sur la version lcd + sd + bouton.

      >> Donc, pour le moment, on met une SD avec un fichier, on branche, et la lecture démarre?
      Oui pour le moment on met un fichier test.mid sur la carte et au démarage il est lu directement.

      >> Je ne trouve pas non plus la doc!
      Le gros morceaux de texte en fin d’article c’est la doc ^^ »
      La classe étant abstraite c’est assez compliqué à expliquer le fonctionnement ^^ »

      >> Je vois par contre qu’il faut la version 1.00 de l’IDE Arduino.
      >> Je vais me mettre à jour, et essayer de comprendre pour tester; çà a en tout cas l’air d’être du bon boulot!
      En théorie ça doit pouvoir compiler avec 0023, mais bon, mieux vaut utiliser arduino 1.0 pour une meilleur compatibilité.

      Publié par skywodd | 3 mai 2012, 12 h 05 min
      • Hi Skywodd,

        I am using the Babel Fish to ry to follow your commentary, so I am hoping I dont offend you by either my interpretation or subsequent comments based on what I have tried to understand. My French language is infintesimally small. An extremely poor student in year 8.

        My understanding of midi files is that the single track format is very rarely used these days, but the multi track is sometimes appears a single track to the ear but the first track defines things like and tempo and the folowing track(s) define the sequence of musical midi-events on the other other track(s). So what we might think is a single track midi file (to our ear) is actually at least two tracks, one to define the basic parameters of the piece eg key signature and time signature and the other track to say what notes to play and how long each note should last.

        I have played multi track files that I have discovered have only two tracks, a « technical track » (very short and just covering the essentials of time signature, key signature and tempo) plus a second track that has several different instruments condensed into another, single track. Yet other midi files do have quite separate midi tracks for each instrument, and have more than (the usual minimum of) two tracks.

        What really confuses me though is when I load a two track midi file, with mutiple instruments (channels) on the second track, the midi player (eg Home Studio) will separate them into distinct channels. What is going on here?

        This appears to me to make parsing somewhat mysterious?
        How can I write a parser that will handle this situation? Need I worry?
        Can you shed any light on this problem?

        Rob

        Publié par Rob | 3 mai 2012, 15 h 08 min
  4. @ROB: My parser dispatch each midi events by channel, included meta event.
    I don’t understand what’s your problem, you want to write your own midi parser ?

    Publié par skywodd | 3 mai 2012, 15 h 15 min
    • Ah yes to dispatch by Channel is one thing, but there can be be many Channels within one Track and some tracks, usually the introductory track contains just the basic musical parameters, but I am still coming to grips with the way a midi editor such as Home Studio can break up a single midi track into multiple channels of instruments, even though they are on a single midi track.
      Do you dispatch mutiple midi channels from across multiple midi tracks?

      cheers, Rob

      Publié par Rob | 3 mai 2012, 15 h 26 min
      • I dispatch midi events by tracks for delta time but by channels for callback.
        Each tracks are processing in parallel, but callback functions have only the midi channel of event as arguments, not the tracks numbers.

        Publié par skywodd | 3 mai 2012, 15 h 35 min
      • It sounds like you have multiple tracks being interpretted (sequentially but in parallel as far as the ear is concerned) and the overseeing program just sees the midi events (via callback ) by channel. In other words the effect of multiple (or single) tracks is hidden?

        Publié par Rob | 3 mai 2012, 15 h 41 min
  5. @ROB Yes
    Final user don’t have to deal with tracks.
    The file can have 1 or 16 tracks, the user will just have callback with « midi event x on channel y with param z »

    Publié par skywodd | 3 mai 2012, 16 h 19 min
    • That is pretty damn good. The whole track question is resolved away, just note events appearing. I am about to add IR control to my parser. Start, begin here, stop there, repeat this, slow down , speed up etc are some of the functions I am planning on.

      Publié par Rob (on my Android :-) | 3 mai 2012, 16 h 36 min
  6. Bonjour,
    Pour un spectacle avec une bande son, je dois envoyer la durée depuis le départ sur une horloge pour que les figurants partent au bon moment. Comment récupérer la durée sur une bande son, de préférence en clair ?
    Merci de ton éclairage toujours pertinent.

    Publié par Bernard Barrois | 30 octobre 2016, 17 h 01 min
  7. bonjour
    je suis musicien et pas du tout bidouilleur informatique mais je cherche depuis pas mal de temps un lecteur de midifile multicanal qui puisse être mis en route par un autre séquencer (ou pas ) en gros qu’il puisse être maitre ou esclave avec une carte SD pour mettre plusieurs morceaux de suite.( avec un affichage minimum, les morceaux en midifile sont travaillé sur un PC.
    pendant mes recherches, Je suis tombé par hasard sur votre appareil qui à déjà quelques années et je ne comprend pas grand chose aux explications mais cette appareils merveilleux est-il encore d’actualité ?

    Publié par bobinet | 20 avril 2017, 17 h 20 min

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Skyduino devient Carnet du Maker

Le site Carnet du Maker remplace désormais le blog Skyduino pour tout ce qui touche à l'Arduino, l'informatique et au DIY.