Table des matières du kit Midi     Index du kit Midi

BMidi

Hérité de: aucun

Déclaré dans: be/midi/Midi.h

Bibliothèque: libmidi.so

Résumé

BMidi est une classe abstraite qui définit les protocoles et les mises en œuvre par défaut des fonctions dont la plupart des autres classes du kit héritent. Les fonctions définies par BMidi se classent en quatre catégories:


Connexions

Les données MIDI circulent dans votre programme, d'un objet dérivé de BMidi à un autre, chacun exécutant sa propre tâche: envoyer les données vers un port MIDI, les écrire dans un fichier, les modifier, les transmettre, et ainsi de suite... Vous organisez une arborescence d'objets BMidi avec la fonction BMidi Connect(): celle-ci ne requiert qu'un simple argument BMidi. En appelant Connect(), vous reliez la sortie de l'objet appelant à l'entrée de l'objet en argument: donc l'appelant est la source et l'argument la destination.

Vous désirez relier un clavier MIDI à votre ordinateur, le jouer, et en avoir le jeu enregistré dans un fichier: connectez un objet BMidiPort (qui lit les données du port MIDI), à un objet BMidiStore (celui-ci mémorise les données qui lui sont envoyées et peut les écrire dans un fichier):

   /* Connexion de la sortie d'un BMidiPort à l'entrée d'un BMidiStore. */
   BMidiPort m_port;
   BMidiStore m_store;
   
   m_port.Connect(&m_store);

Vous devez démarrer la lecture du port MIDI par BMidiPort en appelant la fonction Start() (ceci est expliqué plus loin).

L'enregistrement terminé, vous pouvez le rejouer par reconnexion des objet en sens inverse:

   /* Nous déconnectons d'abord,
    * bien que cela ne soit pas absolument nécessaire.
    */
   m_port.Disconnect(&m_store);
   m_store.Connect(&m_port);

Dans cette configuration, un appel de Start() à m_store redirigerait les données MIDI vers BMidiPort (et de là vers un synthétiseur, par exemple, pour leur restitution).

Sources et destinations

Un objet BMidi peut être la source de n'importe quel nombre d'autres objets:

   a_midi.Connect(&b_object);
   a_midi.Connect(&c_object);
   a_midi.Connect(&d_object);

Une source peut obtenir la liste de ses destinations au moyen de la fonction Connections().

Un objet BMidi peut être la destination de n'importe quel nombre de sources:

   b_midi.Connect(&e_object);
   c_midi.Connect(&e_object);
   d_midi.Connect(&e_object);

Par contre, une destination ne peut pas obtenir la liste de ses sources.


Production de messages MIDI

Si votre classe doit produire de nouvelles données (donc en ne filtrant ni jouant des données reçues), elle doit mettre en œuvre la fonction Run(). Une implémentation de Run() comprend une boucle while() qui retransmet, par appel d'une fonction Spray, un message MIDI ou plus (en règle générale un seul) à chaque itération. La boucle doit se baser sur le test de la valeur du booléen retourné par la fonction KeepRunning().

En gros, une mise en œuvre de Run() ressemble à ceci:

   void MyMidi::Run()
   {
      while (KeepRunning()) {
         /* Génère un message et l'émet. Par exemple... */
         SprayNoteOn(...);
      }
      /* Vous DEVEZ sortir quand KeepRunning() retourne false. */
   }

 
La fonction Run() doit s'achever quand KeepRunning() retourne la valeur false. Sinon, vous perdriez des threads.


Pour indiquer à l'objet qu'il doit exécuter sa fonction Run(), appelez sa fonction Start(); vous n'appellerez jamais Run() directement: Start() force l'objet à engendrer un thread (son thread "Run") dans lequel il exécute Run(). Pour arrêter l'exécution de l'objet, vous appellerez sa fonction Stop().

Souvenez-vous que la fonction Run() est uniquement nécessaire aux classes qui introduisent de nouveaux évènements MIDI dans une exécution. A titre d'exemples, la fonction Run() de BMidiStore émet des messages en rapport aux données MIDI que cette classe mémorise, et BMidiPort lit des données sur le port MIDI et renvoie des messages en conséquence.

Un autre point à garder en mémoire est que la fonction Run() peut anticiper le temps réel. Elle n'est pas obligée de générer et d'émettre ses données à l'instant précis où celles-ci doivent être jouées. Ceci est détaillé plus loin dans le paragraphe "Temps".

 
La classe BMidiSynthFile diffère des autres dans sa façon de mettre en œuvre et d'utiliser sa fonction Run(). En particulier, elle n'engendre pas de thread Run. Voyez la classe BMidiSynthFile pour plus de renseignements.



Fonctions Spray

Les fonctions Spray envoient des données aux objets BMidi connectés à la sortie de l'objet actif. Il y a des fonctions Spray distinctes pour chaque type de message MIDI: SprayNoteOn(), SprayNoteOff(), SprayPitchBend(), et les autres... On en rencontre toujours dans les boucles Run() de BMidi, mais aussi ailleurs: par exemple, si vous définissez un filtre MIDI, vous utiliserez les fonctions Spray pour la mise en œuvre des hooks MIDI de l'objet (ce qui est expliqué plus loin).


Fonctions MIDI Hook

Les fonctions MIDI Hook sont des hooks appelés au niveau des connexions d'un objet lorsque celui-ci émet des données MIDI. Ces fonctions récupèrent les noms des messages MIDI correspondants: NoteOn()répond à un message Note On, NoteOff() à Note Off, et ainsi de suite... Par exemple, ceci...

   a_midi.Connect(&b_midi);
   a_midi.SprayNoteOn(...);

...provoque l'appel de la fonction b_midi NoteOn(). Les arguments transmis à NoteOn() sont repris directement de l'appel à SprayNoteOn().

BMidi ne prévoit pas de mise en œuvre par défaut pour chaque hook MIDI. Il faut déterminer les réponses aux messages MIDI au niveau de chaque classe dérivée de BMidi.

Chaque objet BMidi engendre automatiquement un thread "affluent" lors de sa création. C'est dans ce thread que les hooks MIDI appelés par les fonctions Spray s'exécutent. Le thread de flux est toujours actif: les fonctions Start() et Stop() ne l'affectent pas. Dès sa construction, l'objet est prêt à recevoir des données.

Appels directs de hooks MIDI

Vous pouvez également fournir des données MIDI à un objet BMidi par appel direct à une fonction MIDI Hook. Supposons par exemple que vous désirez simplement jouer une note sur le synthétiseur General MIDI. Vous n'avez pas besoin de créer votre propre classe BMidi pour uniquement mettre en application une fonction Run() qui relaiera la note. Vous pouvez plutôt procéder ainsi:

   BMidiSynth midiSynth;
   
   /* Initialisation de BMidiSynth selon la description donnée dans la classe. */
   ...
   /* Joue la note. */
   midiSynth.NoteOn(...);

Gardez à l'esprit que lorsque vous appelez un hook directement, il s'exécute de façon synchrone dans le thread d'appel. De plus, l'objet peut également recevoir des messages MIDI par son thread de flux (ce n'est pas un problème pour les classes définies par le kit Midi).

Création d'un filtre MIDI

Certains objets BMidi agissent en tant que filtres: ils reçoivent des données, les modifient et les retransmettent ensuite. Pour cela, appelez les fonctions Spray appropriées depuis l'implémentation des hooks MIDI. Voici la mise en application de la fonction NoteOn() pour une classe nommée Transposer, qui récupère chaque "Note On", la transpose au demi-ton supérieur, et la transmet ensuite:

   void Transposer::NoteOn(uchar channel, uchar keyNumber, 
                     uchar velocity, uint32 time)
   {
      uchar new_key = max(keyNumber + 1, 127);
      SprayNoteOn(channel, new_key, velocity, time);
   }

Il y a une différence subtile mais importante entre une classe filtre et une classe d'exécution (cette dernière étant une classe conçue pour rendre effectivement réelles les données MIDI qu'elle reçoit). Cette différence tient au temps, ce qui est traité dans le paragraphe suivant. Le présent développement sous-entend qu'il ne paraît pas judicieux de donner à un unique objet la possibilité de filtrer et exécuter des données MIDI. BMidiStore et BMidiPort sont l'une et l'autre des classes d'exécution (les objets de ces classes "jouent" les données qu'ils reçoivent: en les mémorisant pour la première, en les envoyant sur le port MIDI pour la seconde). Dans ni l'une ni l'autre de ces classes les hooks MIDI n'émettent de données.


Temps

Chaque fonction Spray et MIDI hook récupère une valeur time comme dernier argument. Cet argument indique quand le message représenté par la fonction doit être joué. Il est énoncé en ticks (millièmes de secondes). Le tick 0 correspond au moment de la mise en marche de l'ordinateur, moment où l'horloge démarre automatiquement. Pour connaître la valeur actuelle de son décompte (en ticks), appelez la fonction globale system_time(), définie dans le kit Kernel et divisez la valeur obtenue par 1.000 (system_time()renvoie des millionièmes de secondes).

L'usage est d'appliquer l'argument time à l'entrée d'un objet. Autrement dit, un hook MIDI doit rechercher cet argument, attendre l'instant voulu et faire alors ce qu'il doit. Toutefois, ceci s'applique uniquement aux classes dérivées de BMidi conçues pour exécuter des données MIDI. Les objets qui filtrent seulement des données ne doivent pas appliquer cet argument.

Pour appliquer l'argument time, appelez la fonction SnoozeUntil(), en lui communiquant la valeur de time. Ainsi, une fonction d'exécution NoteOn() devrait ressembler à ceci:

   void MyPerformer::NoteOn(uchar channel, uchar keyNumber, 
                  uchar velocity, uint32 time)
   {
      SnoozeUntil(time);
      /* Joue la note maintenant. */
   }

Si time désigne un tick déjà écoulé, la fonction SnoozeUntil() revient immédiatement, sinon elle demande au thread de flux de rester en attente jusqu'au moment (du tick) voulu.


Synchronisation

Si la fonction Run() est utilisée, il vous faudra générer vous-même une valeur temporelle pour la transmettre comme dernier argument à chaque fonction Spray appelée. La valeur générée dépend du fonctionnement de la classe: temps réel ou anticipé.

Fonctionnement en temps réel

Si votre classe exige que les données MIDI soient traitées immédiatement, il vous faut utiliser la macro B_NOW en tant qu'argument time transmis à vos fonctions Spray. B_NOW contient simplement une requête à system_time() divisé par 1.000 et converti en uint. Utiliser B_NOW comme argument time indique que les données doivent être traitées au moment (tick) de leur génération. Ceci ne se produira vraisemblablement pas: le temps que le hook MIDI soit appelé et les données traitées, quelques ticks se seront écoulés. Dans ce cas, les appels des hooks MIDI à SnoozeUntil() établiront que le moment est déjà passé et reviendront immédiatement, garantissant aux données le traitement le plus rapide possible.

Fonctionnement en temps anticipé

Si vous générez des données en avance de leur moment d'exécution, vous devrez calculer une valeur temporelle afin qu'elles soient ensuite jouées à leur instant exact. Par exemple, si vous voulez créer une classe qui émet une note tous les 100 millièmes de secondes, vous devriez mettre en place quelque chose comme ceci:

   void MyTicker::Run()
   {
      uint32 when = B_NOW;
      uchar key_num;
   
      while (KeepRunning()) {
         
         /* Joue une nouvelle note. */
         SprayNoteOn(1, 60, 64, when);
         
         /* Relâche cette note après 99 ticks. */
         when += 99;
         SprayNoteOff(1, 60, 0, when);
         
         /* Incrémente la variable when afin que la note suivante
          * arrive 100 ticks après la précédente.
          */
         when += 1;
      }
   }

Lorsqu'un objet MyTicker est sollicité, il génère une séquence Note On/Note Off, et la retransmet aux objets auxquels il est associé. Quelque part, dans les coulisses, un objet d'exécution va appliquer la valeur temporelle en appelant SnoozeUntil().

Mais qu'est-ce qui empêche MyTicker de s'emballer et de générer à toute vitesse des milliers, voire des millions de notes (lesquelles ne sont pas prévues pour sonner pendant des heures)? Il se trouve que les fonctions Spray communiquent les données aux hooks MIDI par l'intermédiaire des ports Kernel et que ceux-ci n'acceptent qu'un seul message. Ainsi, tant que l'un des hooks MIDI appelle SnoozeUntil(), l'objet émetteur n'aura jamais plus d'un message d'avance.

Une caractéristique intéressante de ce dispositif est que si vous reliez une série d'objets BMidi qui ne font pas appel à SnoozeUntil(), vous pourrez préparer des données MIDI plus rapidement qu'en temps réel. Supposons par exemple que vous voulez envoyer des données à partir d'un objet BMidiStore, les filtrer, pour ensuite les mémoriser dans un autre objet BMidiStore. Les hooks MIDI de BMidiStore n'appellent pas SnoozeUntil(): donc, les données partent du premier objet et atteignent, via le filtre, leur destination le plus rapidement possible, vous permettant de traiter en quelque secondes des heures de musique en temps réel. Bien entendu, si vous ajoutez un objet d'exécution dans ce circuit (afin d'écouter les données pendant leur élaboration), la circulation des données y sera asservie, ainsi que c'est expliqué plus haut.


Fonctions Hook

Run()
Renferme la boucle qui génère et retransmet les messages MIDI.

Les fonctions MIDI Hook (NoteOn(), NoteOff(), etc...) sont énumérées dans le paragraphe "Hooks MIDI et fonctions Spray".


Constructeur et Destructeur


BMidi()

                                                         
  

BMidi(void)

Crée et retourne un nouvel objet BMidi. Le thread "affluent" de l'objet est engendré et démarré dans cette fonction: autrement dit, les objets BMidi peuvent recevoir des messages dès leur création. D'autre part, le thread "Run" n'est créé qu'à l'appel de Start().


~BMidi()

                                                         
  

virtual ~BMidi()

Détruit les threads de flux et "Run" après que ceux-ci aient obtenu des point d'arrêt convenables (ainsi que c'est défini ci-dessous), supprime la liste des connexions (mais pas les objets listés), puis l'objet BMidi.

Le thread de flux est arrêté après lecture de tous les messages MIDI Hook alors en attente. Les messages ne sont plus acceptés le temps que la file d'attente du flux soit purgée. Le thread Run est autorisé à finir son cycle en cours dans la boucle run et est alors appelé à s'interrompre (à la manière de la fonction Stop()).

Bien que le destructeur supprime les connexions établies par l'objet BMidi, il ne doit pas supprimer celles que d'autres objets auraient faites sur lui. Voici, par exemple, une séquence incorrecte:

   /* NE FAITES PAS CECI... */
   a_midi->Connect(b_midi);
   b_midi->Connect(c_midi);
   ...
   delete b_midi;

L'appel à delete déconnecte b_midi de c_midi, mais ne doit pas dissocier a_midi de b_midi. Vous devez supprimer explicitement ces connexions en entrée:

   /* ...FAITES PLUTOT CELA */
   a_midi->Connect(b_midi);
   b_midi->Connect(c_midi);
   ...
   a_midi->Disconnect(b_midi);
   delete b_midi;

Voir aussi: Stop()


Fonctions membres


AllNotesOff()

                                                         
  

virtual void AllNotesOff(bool controlMsgOnly, uint32 time = B_NOW)

Envoie un message Control Change B_ALL_NOTES_OFF, avec signature temporelle, aux 16 canaux MIDI. Si channelMsgOnly contient la valeur false, la fonction envoie aussi un message Note Off sur toutes les touches de chaque canal.


Connect()

                                                         
  

void Connect(BMidi *toObject)

Connecte la sortie de l'objet BMidi à l'entrée de toObject; la sortie d'un objet BMidi peut être reliée à n'importe quel nombre d'autres objets. Chaque objet en connexion reçoit un appel MIDI Hook lorsque l'objet BMidi émet des messages.

Tout objet donné comme argument dans un appel à Connect() doit par la suite être déconnecté par un appel à Disconnect(). Veillez particulièrement à déconnecter les objets lors de la suppression d'un objet, ainsi que c'est indiqué dans le paragraphe concernant le destructeur.

Voir aussi: ~BMidi(), Connections(), IsConnected()


Connections()

                                                         
  

BList *Connections(void) const

Renvoie BList qui énumère les objets connectés à la sortie de l'objet.


Disconnect()

                                                         
  

void Disconnect(BMidi *toObject)

Supprime la connexion de l'objet BMidi à toObject. Cette connexion doit résulter d'un appel préalable à la fonction Connect() avec les mêmes destinataire et argument.

Voir aussi: Connect()


IsConnected()

                                                         
  

bool IsConnected(BMidi *toObject) const

Renvoie true si l'argument fait partie de la liste des destinataires de toObject.


IsRunning()

                                                         
  

bool IsRunning(void) const

Renvoie true si la boucle Run() de l'objet est active: autrement dit, l'objet a reçu un appel de la fonction Start(), et n'a pas été appelé par Stop() (ou encore n'est pas sorti de la boucle).


KeepRunning()

                                                         
  

bool KeepRunning(void)

Utilisé par la fonction Run() pour tester sa boucle while(), comme expliqué dans la description de la classe. Cette fonction doit être uniquement appelée à partir de Run().


Run()

                                                         
  

virtual void Run(void)

Le dispositif qui génére les données pour une classe dérivée de BMidi est mis en place dans la fonction Run() (voir le paragraphe "Production de messages MIDI"). Une précision: votre implémentation de Run() doit s'achever quand KeepRunning() renvoie la valeur false:

   void MyMidi::Run()
   {
      while (KeepRunning()) {
         /* Génère un message et l'émet. */
      }
      /* Vous DEVEZ sortir quand KeepRunning() retourne false. */
   }


SnoozeUntil()

                                                         
  

void SnoozeUntil(uint32 tick) const

Met le thread appellant en attente jusqu'à ce que tick millièmes de secondes se soient écoulés depuis le boot de l'ordinateur. Cette fonction est prévue pour être utilisée dans une mise en œuvre des fonctions MIDI Hook (voyez le paragraphe "Temps").


Start()

                                                         
  

virtual status_t Start(void)

Provoque la création par l'objet de sa boucle Run (là où s'exécute la fonction Run()) et revient ensuite immédiatement. Vous pouvez redéfinir cette fonction dans une classe dérivée de BMidi afin d'assurer votre propre mode de démarrage. Assurez-vous de bien appeler la version dérivée de cette fonction dans votre mise en œuvre.

RETURN CODES


Stop()

                                                         
  

virtual void Stop(void)

Arrête la production de données MIDI de l'objet. Appeler Stop() demande à la fonction KeepRunning() de renvoyer la valeur false, provoquant ainsi l'achèvement de la boucle Run. Stop() peut revenir avant la fin du thread. Cette fonction n'affecte pas les données en entrée: l'objet restera en mesure de recevoir des messages MIDI par ses hooks MIDI.

Vous pouvez redéfinir cette fonction dans une classe dérivée de BMidi pour établir un arrêt, ou pour effectuer un nettoyage après exécution (ce sont deux exemples). Assurez-vous toutefois d'appeler la version dérivée de cette fonction à partir de votre mise en œuvre.


Hooks MIDI et fonctions Spray

Les règles d'utilisation des hooks MIDI et des fonctions Spray sont décrites ci-dessous. La norme MIDI définit l'usage de ces fonctions (ainsi que les valeurs de leurs arguments) et ceci n'est donc pas repris ici. Consultez les paragraphes "Fonctions Spray" et "Fonctions MIDI Hook" pour obtenir plus de précisions sur l'usage de ces fonctions (l'article "Texte en sortie" de la classe BMidiText peut également vous renseigner sur certains paramètres).


ChannelPressure(), SprayChannelPressure()

                                                         
  

virtual void ChannelPressure( uchar channel, uchar pressure, uint32 time = B_NOW )

protected:
                                                         
  

void SprayChannelPressure( uchar channel, uchar pressure, uint32 time )


ControlChange(), SprayControlChange()

                                                         
  

virtual void ControlChange( uchar channel, uchar controlNumber,
      uchar controlValue, uint32 time = B_NOW )

protected:
                                                         
  

void SprayControlChange( uchar channel, uchar controlNumber,
      uchar controlValue, uint32 time )

Voir aussi: AllNotesOff()


KeyPressure(), SprayKeyPressure()

                                                         
  

virtual void KeyPressure( uchar channel, uchar note,
      uchar pressure, uint32 time = B_NOW )

protected:
                                                         
  

void SprayKeyPressure( uchar channel, uchar note, uchar pressure, uint32 time )


NoteOff(), SprayNoteOff()

                                                         
  

virtual void NoteOff( uchar channel, uchar note,
      uchar velocity, uint32 time = B_NOW )

protected:
                                                         
  

void SprayNoteOff( uchar channel, uchar note, uchar velocity, uint32 time )


NoteOn(), SprayNoteOn()

                                                         
  

virtual void NoteOn( uchar channel, uchar note, uchar velocity,
      uint32 time = B_NOW )

protected:
                                                         
  

void SprayNoteOn( uchar channel, uchar note, uchar velocity, uint32 time )


PitchBend(), SprayPitchBend()

                                                         
  

virtual void PitchBend( uchar channel, uchar lsb, uchar msb, uint32 time = B_NOW )

protected:
                                                         
  

void SprayPitchBend( uchar channel, uchar lsb, uchar msb, uint32 time )


ProgramChange(), SprayProgramChange()

                                                         
  

virtual void ProgramChange( uchar channel, uchar progNum, uint32 time = B_NOW )

protected:
                                                         
  

void SprayProgramChange( uchar channel, uchar progNum, uint32 time )


SystemCommon(), SpraySystemCommon()

                                                         
  

virtual void SystemCommon( uchar status, uchar data1,
      uchar data2, uint32 time = B_NOW )

protected:
                                                         
  

void SpraySystemCommon( uchar status, uchar data1, uchar data2, uint32 time )


SystemExclusive(), SpraySystemExclusive()

                                                         
  

virtual void SystemExclusive( void *data, size_t dataLength,uint32 time = B_NOW )

protected:
                                                         
  

void SpraySystemExclusive( void *data, size_t dataLength, uint32 time )


SystemRealTime(), SpraySystemRealTime()

                                                         
  

virtual void SystemRealTime( uchar status, uint32 time = B_NOW )

protected:
                                                         
  

void SpraySystemRealTime( uchar status, uint32 time )


TempoChange(),SprayTempoChange()

                                                         
  

virtual void TempoChange( int32 beatsPerMinute, uint32 time = B_NOW )

protected:
                                                         
  

void SprayTempoChange( int32 beatsPerMinute, uint32 time )


Table des matières du kit Midi     Index du kit Midi


The Be Book,
...in lovely HTML...
for BeOS Release 5.

Copyright © 2000 Be, Inc. All rights reserved..