Je vais vous expliquer, aujourd'hui, comment changer interactivement la fréquence du son qui sort de notre programme.
Il existe plusieurs façon de faire pour interagir. Puisque mon but premier concerne la musique, je ne m'arrêterai que sur une seule, la demande du son provient d'une source midi.
Une source Midi ... C'est quoi? Cela peut être un clavier maître, un synthétiseur, un panneau de contrôle, un clavier virtuel, un séquenceur logiciel ou physique, ... Le choix est grand, et donc vous avez tous la possibilité d'envoyer un signal midi à votre application.
Le principe de connexion des entrées/sorties Midi est exactement le même que pour la partie audio.
Il existe 2 sortes de signaux Midi, Alsa ou Jack. Il existe aussi OSS mais qui n'a plus de raison d'être.
En gros:
Alsa travaille directement avec le matériel et Jack avec les logiciels, bien que chacun soit en mesure de faire ce que l'autre fait.
Vous ne possédez pas tous du matériel Midi, le choix se porte donc plus facilement vers Jack. Cela tombe bien, c'est le sujet de notre tuto. Pour ceux qui ont du matériel Midi et qui aimeraient l'utiliser sous Jack, il existe un petit logiciel nommé A2J (alsa to jack) qui permet de voir votre matériel dans la liste des entrées/sorties disponible.
Concrètement, créer un port d'entrée, cette fois, mais midi et pas audio.
Rien de bien difficile, n'est ce pas? Je vous laisse l'insérer par vous même dans votre programme, et regardez bien les petites différences subtiles entre les 2 ports.jack_port_t* inPort = jack_port_register(client,"EntreeMidi1",JACK_DEFAULT_MIDI_TYPE,JackPortIsInput|JackPortIsTerminal,0);
La partie déclaration étant terminée, nous allons pouvoir nous pencher sur le process, qui va nous demander un peu plus de réflexion qu'avant.
Si vous compilez votre programme vous verrez un nouveau rectangle avec une entrée midi jack, que vous pouvez déjà connecter à un port de sortie Midi.
Dans la fonction process, vous devez demander à Jack de vous fournir l'adresse du buffer d'entrée Midi. C'est comme pour le buffer audio de sortie, sauf que nous allons récupérer un void* au lieu d'un jack_default_audio_sample_t*. A vous de jouer...
Maintenant, on voudrait connaître le nombre d’événements midi survenu depuis le dernier process. Ceci se fait via une fonction qui se trouve dans une autre partie de la bibliothèque Jack ("jack/midiport.h"):
jack_nframes_t eventCount = jack_midi_get_event_count(inBuffer);Maintenant que nous connaissons le nombre d'événements midi, il va falloir les traiter. On pourrait créer une boucle pour traiter les données, mais il n'en sera rien, nous allons profiter qu'une boucle existe déjà. Il ne faut pas sortir de notre tête que la fonction process est exécutée de très nombreuse fois. Si on peut éviter des boucles multiples, il vaut mieux le faire. Le point commun des 2 port est qu'ils dépendent tous les 2 de nframes, comme notre boucle.
Il est évident que nous auront rarement, pour ne pas dire jamais, nframes événements dans le buffer. Vous vous imaginez en train de taper 44100 notes différentes en une secondes sur un synthétiseur.... Pas facile n'est ce pas?
Depuis peu, nous connaissons le nombre réel d'événement, nous allons donc l'utiliser pour déterminer ce que l'on fait ou pas dans notre boucle. Histoire de savoir où on se trouve nous allons créer une variable eventIndex qui sera incrémentée juste avant la fin de la boucle.
jack_nframes_t eventIndex = 0;
Une simple comparaison entre eventIndex et eventCount nous dira si on travaille ou non. On récupère donc l'événement suivant dans la file d'attente comme ceci:
jack_midi_event_get(&inEvent, inBuffer, eventIndex);inEvent est un pointeur vers une structure qui représente l'événement en question.
inBuffer, c'est notre buffer d'entrée,
et le 0 n'a aucun intérêt pour le moment.
Donc, il faut créer la variable inEvent avant de lancer la fonction.
Une fois que l'événement est stocké dans la variable inEvent, il faut la décortiquer. Dans cette structure, on y trouve 3 chose: la taille (size), les éléments (buffer) et la position dans le temps(time). La position dans le temps ne nous intéresse pas puisqu'on veut réagir dès qu'un événement se produit. Il nous reste donc le buffer et sa taille.
Le buffer est composé d'une liste d'octets non signés(unsigned char). Lorsque le premier octet est >= 128(80H) alors nous avons une commande sinon, c'est une données.
Quel est donc la commande qui va changer la fréquence de notre son?
Ouch! La commande qui change la fréquence! Euh! Il n'y en a pas....
Par contre, il y en a une qui nous donne une représentation symbolique d'une note de musique, et qui donne en prime la force de frappe de la touche (la vélocité - souvent interprétée comme le volume de la note).
Cette commande est comprise entre 144(90H) et 159(9FH). En midi, on parle également de canal, il y en a 16, de 1 à 16, mais en informatique, on compte de 0(00H) à 15(0FH). Donc 90H correspond à NoteOn canal 1 et 9FH correspond à NoteOn canal 16.
Pour nous simplifier la vie nous allons tester si le premier octet est compris entre 144 et 159, ce qui fera écouter notre programme sur tous les canaux en même temps.
if((inEvent.buffer[0] >= 144) && (inEvent.buffer[0] <= 159))On s'intéresse maintenant au deuxième octet, la note jouée, la hauteur de la note, et donc indirectement la fréquence jouée.
Puisque cet octet est une donnée ses valeurs vont de 0 à 127, il y a donc 128 notes différente possible. Je ne connais pas de clavier avec autant de touche...
La fréquence que nous avions choisie valait 440Hertz. A quoi correspond elle dans notre codage ici. Et bien, c'est un standard défini par Sony et quelques autres fabricants, c'est la note numéro 69 (La3). On se demanderait bien pourquoi...
La fréquence du La3 vaut 440, le La4 vaut le double et le La2, la moitié et ainsi de suite.
La0 55 , La1 110, La2 220, La3 440, La4 880, ....
Une formule s'il vous plait:
La(x) = La0 * 2^x;
Et si on veut une autre note?
Si vous compter les notes blanches et noires, vous en trouverez 12 par octave.
Au lieu de faire 2^x, on fait 2^(x+note/12)
Après plusieurs transformations de la formule en prenant 440Hertz comme référence de base, on obtient la formule suivante.
frequence = 440 * 2 ^ ((note - 69) / 12);
En gardant la valeur de 440 dans notre formule, cela nous permettra par la suite de faire un réglage, ou désaccorder notre instrument de musique. On peut créer une variable tuneFrequence initialisée à 440.
frequence = tuneFrequence * 2^((note - 69) / 12);
Joli tout ça! Reste plus qu'à mettre en pratique. Attention à la fonction de puissance qui est définie dans "math.h"
A vous de jouer...
Pour que vous soyez encore moins tenté de tricher, je n'ai pas inclut le code directement sur le blog mais dans un gist sur GitHub que vous trouverez ici.
A bientôt pour la suite!
Dagal.
Aucun commentaire:
Enregistrer un commentaire