Ensuite, comme vous l'avez sans doute remarqué, le son ne s'arrête jamais, pire que ça encore, il commence sans appuyer sur une touche du clavier. On va remédier à ce problème. On aura encore quelques subtilités en perspective.
Nous corrigerons également quelques petites mal-façons.
Vous pouvez récupérer le fichier du dernier tutoriel sur mon compte github.
Nous allons commencer par doubler notre son. Pour cela, il va falloir créer un nouveau port et un nouveau buffer.
Par soucis de simplicité par la suite, je vais remplacer toutes les occurrences de outPort par outPortLeft. Ensuite, je copie tout ce qui concerne outPortLeft en outPortRight.
Dans le process, je renomme outBuffer en outBufferLeft. A la fin du process, j'ajoute après le
outBufferLeft = ....
une nouvelle ligne
outBufferRight[i] = outBufferLeft[i];
Ceci permettra d'avoir le son dans chaque canal mais ce n'est pas du tout la meilleure façon de faire. A vous de voir comment changer cela ... Maintenant que l'on a un son sur 2 canaux, il vous sera facile d'avoir 8 canaux comme dans un système 7.1. Que de rêves! Que de rêve!
Donc, maintenant, je vais m'attaquer à un autre problème, celui du son qui ne s'arrête jamais. Certain trouverons peut être cela utile, mais en ce qui me concerne, je n'en ai pas l'utilité, surtout quand on sait que le 'sustain' existe. Pour que tout le monde soit content, je vais créer une variable booléenne qui laissera où non le son en fonction.
Puisque l'événement Midi nous donne une information indiquant la force de frappe de la note, nous allons l'utiliser comme une demande de volume.
Certain clavier envoie un signal NoteOff quand on relâche la touche, alors que d'autres envoie un NoteOn avec une vélocité de 0. Il faudra donc tenir compte des 2 solutions.
Je vous avais expliqué, dernièrement, que NoteOn était capable de gérer 16 canaux. Bonne nouvelle, NoteOff aussi. Je vais donc commencer à utiliser cette fonctionnalité. Jusque maintenant, le programme réagissait à tous les canaux confondus. Pour notre synthétiseur, je vais lui offrir la possibilité de répondre à un canal au choix où à tous. Pour cela, j'ajoute encore une variable globale. Ses valeurs de 1 à 16 correspondront au canaux midi individuels et la valeur 0 à tous les canaux ensemble.
Pour des raisons de simplicité, je rajoute également une variable pour le volume que j'initialise à 0 dans le main. Il suffira de multiplier la valeur de base par volume et de diviser par 100. La valeur de vélocité va de 0 à 127, ce qui pourrait donner un gain de 127%. Attention, ça risque de saturer... En tout cas, pas avant d'avoir dit au programme de récupérer la valeur du volume dans le troisième élément de l'événement.
Pour en revenir à nos commandes midi, voila comment sont codé les NoteOn et NoteOff. Il y a donc 3 octets. Le premier détermine quel type de commande, le deuxième, la note, et enfin le dernier, la vélocité. Dans le premier octet, non seulement il y a la fonction à réaliser mais il y a aussi le canal. Pour mieux comprendre, on va regarder les valeurs en hexadécimale.
NoteOn => 144 à 159 en décimale => 90 à 9F en hexadécimale.
NoteOff => 128 à 143 en décimale => 80 à 8F en hexadécimale.
Pas besoin de faire un dessin, on a vite compris.
On coupe l'octet en 2 et on obtient l'ordre d'un côté et le canal de l'autre...
Dans le programme, on remplace le if (... 144 && ... 159) par un switch en bon et due forme qui prendra les ordres en compte.
Dans la partie NoteOn, si le volume envoyé est 0, pas de soucis, le son s'arrête. Il reste donc à traiter quand on reçoit le NoteOff. On teste, donc, dans le switch, si la valeur est 0x80 et on met la valeur 0 dans le volume, à condition d'être sur le bon canal, mais aussi que la note lâchée corresponde à celle qui est en cours.
Voila, nous avons fait le tour de ce que je voulais faire. Maintenant, je vais corriger deux ou trois petites choses mais qui sont, en réalité, très importante.
Le serveur Jack n'est pas une petite bête toute gentille comme on pourrait le croire ici. Il est d’ailleurs possible que certain d'entre vous aient rencontré quelques difficulté à faire fonctionner le programme.
Je vous avais montré au premier chapitre que Jack avait besoin qu'on lui renseigne ou trouver la fonction process pour qu'il puisse gérer les buffers dès qu'ils sont vides. Et bien, il n'y a pas que ça que Jack doit traiter via des fonctions Callback.
Vous allez me dire que j'aurais du vous en parler plus tôt, mais c'est un choix que j'ai fait pour que vous puissiez en profiter le plus vite possible.
Le serveur Jack n'échappe pas à la règle, il peut lui arriver de se retrouver face à une situation imprévue, surtout avec des programmeurs humains... Il existe donc une fonction callback pour cela.
Je vais donc créer la fonction :
void error(const char* description)Et dedans, on y met ce que l'on veut comme afficher la description.
Beaucoup de programmeur arrête le cours du programme. Je ne suis pas particulièrement adepte de cette pratique.
Ce callback est commun à tous les clients que vous pourriez créer dans votre programme et agit dès le début, c'est pourquoi je vais la déclarer juste avant la création de mon client via la fonction suivante:
jack_set_error_function(error);Maintenant que ça c'est fait, je vais parler d'une autre fonction callback. Je ne sais pas très bien comment cela se pourrait mais Jack demande qu'on lui fournisse une fonction qui va permettre à notre client de s'adapter à une nouvelle fréquence d’échantillonnage. Le fameux frameRate que l'on initialise à chaque passage dans la boucle du process.... ???? !!!!! ???? .... MMMMMmmm! Voila une chose qui va être optimisée, plus d'appel à cette fonction. Coooool! Donc, la fonction :
int sampleRateChanged(jack_nframes_t nframes, void* arg)Tiens, cela ressemble à la fonction process. C'est vrai mais ici le nframes ne représente pas le buffer mais bien la fréquence d'échantillonnage. On va donc mettre à jour sampleRate et arrêter de le faire dans process. Il faut bien sure renseigner la fonction avec:
jack_set_sample_rate_callback(client,sampleRateChanged,0);Le dernier callback nécessaire est celui qui va réagir si le serveur Jack s'est arrêté pour une raison ou une autre. Pour le moment, on ne peut pas faire grand chose, mais sachez qu'il est tout à fait possible de redémarrer le serveur Jack et de refaire toutes les connections. Dans ce cas ci, je préfère quitter le programme.
void jackShutdown(void* arg)Et le référencement:
jack_on_shutdown(client,jackShutdown,0);Voila, j'ai fait le tour de ce que voulais vous présenter dans cette partie du tutoriel.
A bientôt!
Dagal.
Aucun commentaire:
Enregistrer un commentaire