Bonjour ou bonsoir,
Après une très longue période d'improductivité, je vous propose un petit programme qui va vous permettre d'afficher la courbe du son de sortie de l'un de vos programme, ou, pourquoi pas, de votre micro ou guitare préféré.
Au moment où j'écris ces quelques lignes, le programme ne supporte qu'un seul canal, mais rien ne vous empêche de lancer le programme à plusieurs reprises et de les lier à différentes sources.
Comme tout l'ensemble de ce que je fais, je ne propose pas de version compilée, à vous de le faire, en sachant que de mon côté, je suis sous GNU/Linux en 64 bits.
Vous trouverez les sources dans mon Github. à l'adresse suivante:
https://github.com/Dagal/JackOscillogram
A bientôt pour la suite de l'aventure.
Dagal.
Blog de programmation de l'audio.
En ce moment, un synthétiseur se développe petit à petit.
lundi 22 décembre 2014
dimanche 14 décembre 2014
Baby come back!
Bonjour ou bonsoir!
Voilà plusieurs mois que je n'ai rien posté, j'avais plus important à faire.
Maintenant, je suis papa...
Dagal
samedi 9 août 2014
Un Mixeur pour Jack
Bonjour ou bonsoir,
Je pensais écrire un petit programme pour mixer différentes sources de Jack, mais c'est vraiment inutile car pour une utilisation directe, il existe un petit programme bien fait qui s'appelle jackmix, tout simplement.
Dans le cas où l'on veut faire de l'enregistrement, il y a ardour.
Par contre, il y a un outil manquant sous GNU/Linux, c'est un semblant de "Ableton", très connu des studios d'enregistrement.
Pour y arriver sous Linux, il faut utiliser plusieurs programmes lié par Jack. C'est bien mais c'est lourd.
Il serait intéressant de créer un "GNUbleton", .... Mais ceci est un autre sujet de discussion.
A bientôt.
Dagal.
Je pensais écrire un petit programme pour mixer différentes sources de Jack, mais c'est vraiment inutile car pour une utilisation directe, il existe un petit programme bien fait qui s'appelle jackmix, tout simplement.
Dans le cas où l'on veut faire de l'enregistrement, il y a ardour.
Par contre, il y a un outil manquant sous GNU/Linux, c'est un semblant de "Ableton", très connu des studios d'enregistrement.
Pour y arriver sous Linux, il faut utiliser plusieurs programmes lié par Jack. C'est bien mais c'est lourd.
Il serait intéressant de créer un "GNUbleton", .... Mais ceci est un autre sujet de discussion.
A bientôt.
Dagal.
Plus de "Tac", Un premier release est créé sur le GitHub
Salut à toutes et tous,
Je suis heureux, je n'ai plus de "Tac" dans mes hauts parleurs. Juste une petite erreur dans la gestion de volume qui ne se faisait pas tout à fait au bon endroit dans mon process.
Maintenant que cela fonctionne comme je le veux, j'ai créé un premier Release sur le GitHub: https://github.com/Dagal/JackSynth/releases/tag/v0.1.0-alpha
Je ne fourni pas de version compilée, à vous de le faire. Il vous suffit d'ouvrir le projet dans Code::Block sous GNU/Linux comme toujours.
Maintenant que cela fonctionne, je vais pouvoir m'attaquer à d'autres effets de base, tel que le portamento (sustain), ou bien la réverbération ou encore le chorus.
A bientôt.
Dagal.
Je suis heureux, je n'ai plus de "Tac" dans mes hauts parleurs. Juste une petite erreur dans la gestion de volume qui ne se faisait pas tout à fait au bon endroit dans mon process.
Maintenant que cela fonctionne comme je le veux, j'ai créé un premier Release sur le GitHub: https://github.com/Dagal/JackSynth/releases/tag/v0.1.0-alpha
Je ne fourni pas de version compilée, à vous de le faire. Il vous suffit d'ouvrir le projet dans Code::Block sous GNU/Linux comme toujours.
Maintenant que cela fonctionne, je vais pouvoir m'attaquer à d'autres effets de base, tel que le portamento (sustain), ou bien la réverbération ou encore le chorus.
A bientôt.
Dagal.
jeudi 7 août 2014
Programmation du Pitch Bend en réaction au message midi correspondant.
Bonjour ou bonsoir,
Ça fait un bail que je n'ai pas écrit de nouveaux articles, juste ajouter des messages par ci par là.
Ce temps, je l'ai consacré à l'étude du pitch bend. Vous savez, la petite molette sur la gauche d'un synthétiseur, avec laquelle vous pouvez changer la fréquence et ainsi ennuyer votre entourage à souhait.
Les numéros du message midi vont de E0 à EF en hexadécimal. Cette commande est suivie de deux valeurs codée sur 7 bits, ce qui donne une seule valeur codée sur 14 bits. Le premier est le LSB et le deuxième le MSB. La plupart des synthétiseur n'utilise que le MSB, ce qui est largement suffisent quand on veut avoir une variation sur 2 demi tons, comme c'est plus souvent le cas. C'est beaucoup moins suffisent quand on veut travailler sur l'octave en entier voir plus encore.
En ce moment, la variation maximum est de 2 demi tons dans chaque sens, et est codée directement dans la classe du son en statique. Souvenez vous de la molette, elle agit sur tout le clavier, il est donc normal que cet effet soit commun à tous les sons.
Le meilleur endroit pour travailler sur la fréquence en temps réel est la où l'on charge la valeur de base de variation d'angle. Puisque l'angle est un "multiple" de la fréquence, on peut appliquer directement l'effet dessus.
Un petit truc à retenir pour la suite:
- Si on travaille sur la fréquence, on modifie le delta angle.
- Si on travaille sur la hauteur, on modifie la valeur de sortie directement.
Voici le lien vous permettant de télécharger gratuitement le synthétiseur à son stade d'avancement le plus élevé du moment. (La dernière version, pour ceux qui n'auraient pas compris.)
https://github.com/Dagal/JackSynth
A bientôt.
Dagal.
Ça fait un bail que je n'ai pas écrit de nouveaux articles, juste ajouter des messages par ci par là.
Ce temps, je l'ai consacré à l'étude du pitch bend. Vous savez, la petite molette sur la gauche d'un synthétiseur, avec laquelle vous pouvez changer la fréquence et ainsi ennuyer votre entourage à souhait.
Les numéros du message midi vont de E0 à EF en hexadécimal. Cette commande est suivie de deux valeurs codée sur 7 bits, ce qui donne une seule valeur codée sur 14 bits. Le premier est le LSB et le deuxième le MSB. La plupart des synthétiseur n'utilise que le MSB, ce qui est largement suffisent quand on veut avoir une variation sur 2 demi tons, comme c'est plus souvent le cas. C'est beaucoup moins suffisent quand on veut travailler sur l'octave en entier voir plus encore.
En ce moment, la variation maximum est de 2 demi tons dans chaque sens, et est codée directement dans la classe du son en statique. Souvenez vous de la molette, elle agit sur tout le clavier, il est donc normal que cet effet soit commun à tous les sons.
Le meilleur endroit pour travailler sur la fréquence en temps réel est la où l'on charge la valeur de base de variation d'angle. Puisque l'angle est un "multiple" de la fréquence, on peut appliquer directement l'effet dessus.
Un petit truc à retenir pour la suite:
- Si on travaille sur la fréquence, on modifie le delta angle.
- Si on travaille sur la hauteur, on modifie la valeur de sortie directement.
Voici le lien vous permettant de télécharger gratuitement le synthétiseur à son stade d'avancement le plus élevé du moment. (La dernière version, pour ceux qui n'auraient pas compris.)
https://github.com/Dagal/JackSynth
A bientôt.
Dagal.
lundi 21 juillet 2014
Nouvelle technique = nouveaux problèmes!
Bonjour ou bonsoir.
Je suis en train de changer ma façon de procéder pour la gestion de la boucle de son et de suppression des sons arrêtés.
C'est un peu plus compliqué que ce que je pensais mais cela ira bien tout de même. Encore un peu de patience avant le nouveau tuto, mais cela en vaut la chandelle puisque nous n'aurons plus de craquements dans les hauts-parleurs.
Soyez patient, vous aussi, et vous ne serez pas déçu.
A bientôt.
Dagal.
Je suis en train de changer ma façon de procéder pour la gestion de la boucle de son et de suppression des sons arrêtés.
C'est un peu plus compliqué que ce que je pensais mais cela ira bien tout de même. Encore un peu de patience avant le nouveau tuto, mais cela en vaut la chandelle puisque nous n'aurons plus de craquements dans les hauts-parleurs.
Soyez patient, vous aussi, et vous ne serez pas déçu.
A bientôt.
Dagal.
samedi 19 juillet 2014
Ca croustille dans les hauts-parleurs...
Un léger craquement se fait entendre à certain moment. Qu'en est il?
Quand on lance un premier son, il n'y a pas de craquement.
Quand on arrête ce son, un craquement se fait entendre (ou pas). Cela est du au fait que l'on arrête le son n'importe quand. Il faudrait l'arrêter lorsque sa valeur est proche de 0. Donc, plutôt que d'effacer un son, il serait plus judicieux de demander à la classe de stopper le son puis que celle ci donne l'autorisation d'effacement.
Quand on lance un son alors qu'un autre son est déjà en cours, il y a également un craquement, de même lorsqu'on le stoppe. Ceci est du au fait que l'on fait la moyenne de tous les sons produit. Au moment où l'on ajoute un son, il y a une division du volume générale. Cette division crée un changement trop brutale de la valeur, ce qui crée ce son indésirable. Le mieux serait de simplement additionner les sons et de ne pas en faire la moyenne. C'est au musicien à ajuster son volume général pour ne pas avoir de distorsion. En fait, on ne parle pas vraiment de volume général mais plutôt de gain. D'ailleurs, un effet de distorsion contrôlé peu s'avérer intéressent dans certain cas.
Je vais donc travailler sur ceci avant de proposer la suite du tutoriel, car notre classe SynthSound en a besoin de suite.
A bientôt.
Dagal.
Quand on lance un premier son, il n'y a pas de craquement.
Quand on arrête ce son, un craquement se fait entendre (ou pas). Cela est du au fait que l'on arrête le son n'importe quand. Il faudrait l'arrêter lorsque sa valeur est proche de 0. Donc, plutôt que d'effacer un son, il serait plus judicieux de demander à la classe de stopper le son puis que celle ci donne l'autorisation d'effacement.
Quand on lance un son alors qu'un autre son est déjà en cours, il y a également un craquement, de même lorsqu'on le stoppe. Ceci est du au fait que l'on fait la moyenne de tous les sons produit. Au moment où l'on ajoute un son, il y a une division du volume générale. Cette division crée un changement trop brutale de la valeur, ce qui crée ce son indésirable. Le mieux serait de simplement additionner les sons et de ne pas en faire la moyenne. C'est au musicien à ajuster son volume général pour ne pas avoir de distorsion. En fait, on ne parle pas vraiment de volume général mais plutôt de gain. D'ailleurs, un effet de distorsion contrôlé peu s'avérer intéressent dans certain cas.
Je vais donc travailler sur ceci avant de proposer la suite du tutoriel, car notre classe SynthSound en a besoin de suite.
A bientôt.
Dagal.
jeudi 17 juillet 2014
Eureka! Le midi fonctionne correctement
Parfois, le fait d'écrire que cela ne va pas, débloque tout. C'est en tout cas le cas ici, et j'en suis heureux... Enfin pas tant que cela. Je vous ai raconté des bêtises au début du tutoriel, je vais devoir aller le modifier.
Ceux qui ont la chance de découvrir le tutoriel après n'y verront que du feu, par contre, pour les autres, je suis extrêmement confus.
Heureusement la modification est mineure, alors cela sera facile à corriger.
A bientôt.
Dagal.
Ceux qui ont la chance de découvrir le tutoriel après n'y verront que du feu, par contre, pour les autres, je suis extrêmement confus.
Heureusement la modification est mineure, alors cela sera facile à corriger.
A bientôt.
Dagal.
Quelle galère, cette gestion de la polyphonie...
Bonjour ou bonsoir.
Et bien! Je ne pensais pas que la polyphonie d'un synthétiseur pouvait donner autant de fils à retordre.
La classe pour le son a l'air d'être prête et fonctionnelle, mais je ne veux pas la publier tant que je ne suis pas certain qu'elle corresponde à nos besoins.
Là où se trouve le soucis, c'est au moment où il faut effacer le son.
J'ai fini par me rendre compte que mes messages midi ne sont pas correcte lorsque deux touches sont appuyées en même temps, et même parfois quand une seule touche est appuyée.
Il va donc falloir revoir ma facon de récupérer les événements midi.
Donc, ne vous tracassez pas trop, je n'abandonne pas le projet, que du contraire. Je vais même tenter de vous tenir au courant au fur et à mesure de mes évolutions.
A bientôt
Dagal.
Et bien! Je ne pensais pas que la polyphonie d'un synthétiseur pouvait donner autant de fils à retordre.
La classe pour le son a l'air d'être prête et fonctionnelle, mais je ne veux pas la publier tant que je ne suis pas certain qu'elle corresponde à nos besoins.
Là où se trouve le soucis, c'est au moment où il faut effacer le son.
J'ai fini par me rendre compte que mes messages midi ne sont pas correcte lorsque deux touches sont appuyées en même temps, et même parfois quand une seule touche est appuyée.
Il va donc falloir revoir ma facon de récupérer les événements midi.
Donc, ne vous tracassez pas trop, je n'abandonne pas le projet, que du contraire. Je vais même tenter de vous tenir au courant au fur et à mesure de mes évolutions.
A bientôt
Dagal.
samedi 12 juillet 2014
Programmation d'un synthétiseur Jack sous GNU/Linux en C++ (Septième partie) - La polyphonie
Vous voila avec un petit synthétiseur capable de jouer une note par l'intermédiaire d'une source midi. Vous pouvez changer le volume général et changer la position gauche/droite, et le son produit est sensible à la force de frappe sur la touche du clavier. Ouiiiii! C'est coool!
En même temps, c'est pas terrible, il n'est pas possible de jouer plusieurs son en même temps. Pas de polyphonie. Pas d'accord possible. Non! Ce n'est pas possible, je ne suis pas d'accord... C'est un générateur de son et il n'a presque pas de son! Non mais allo quoi! ...
La polyphonie, vous l'aurez compris, c'est ce qui vous permet de jouer plusieurs sons en même temps. Un humain normal pourrait faire un accord avec ses dix doigts, et donc jouer dix notes en même temps. Si maintenant, vous avez des notes soutenues avec la pédale, vous pouvez très très vite monter très haut. Il va donc falloir trouver la valeur idéale. Tout dépend de votre ordinateur, de la complexité du son et ce que vous voulez faire en même temps sur votre machine.
Le mieux, c'est que cela soit paramétrable. Le plus facile, c'est encore de créer énormément de variables globales. Je ne vais donc pas passer mon temps à créer un fichier de configuration, cela ne rentre pas directement dans le cadre de ce tutoriel. Cela dit, si vous le demandez, je me ferai un plaisir de vous faire plaisir.
Je ferme la grosse parenthèse et je reviens à ce qui nous préoccupe pour le moment, j'ai nommé "La Polyphonie". Ce sujet va être un peu plus compliqué, même si très peu de calcul entrera en jeu. Il va falloir découper le travail en deux grandes parties. En effet, le Midi et l'Audio vont tout deux avoir pas mal de travail. Commençons donc par le Midi.
Il va falloir retenir tout ce qui est nécessaire à chaque note jouée. Pour cela, je vais créer une structure ..... Voyons nous sommes en train de programmer en C++, ne serait il pas plus intéressent de travailler avec des classes? Bon, je me suis convaincu. C'est le moment où les classes vont prendre une belle part de notre synthétiseur.
Réfléchissons peu mais réfléchissons bien... Il nous faut:
- Une note de musique (0-127) pour connaître la fréquence.
- Un volume (0-127) pour la vélocité et plus tard l'aftertouch
Pour le moment, c'est tout ce que l'on a en entrée.
Pour la sortie, on aura besoin:
- Un baseSound
Si on imagine que l'on peut jouer 128 sons en même temps comme certain synthétiseur moderne, il va falloir faire attention au nombre de calculs. Je vais donc remplacer une partie de mes calculs par une table des notes.
Il va falloir modifier l'existant pour pouvoir gérer toutes ces entrées, mais aussi il faudra mixer tous ces nouveaux son ensemble sans perte, sans distorsion. Je sens que nous allons bien nous amuser maintenant. Les choses sérieuses vont commencer.
Après cette marche à suivre, je vais écrire trois articles:
- La création de la classe
- La gestion du midi et la mise à jour de la classe
- L'utilisation de la classe pour créer les sons et puis les mixer ensemble
A bientôt!
Dagal.
vendredi 11 juillet 2014
Programmation d'un synthétiseur Jack sous GNU/Linux en C++ (Sixième partie) - Le Panorama
Bonjour ou bonsoir à toutes et tous.
Cette fois, je vais vous expliquer comment réaliser un "pan" pour votre synthétiseur préféré, le vôtre.
Le "pan", c'est le petit bouton rotatif qui va envoyer votre son plutôt à gauche ou plutôt à droite. Il ne faut pas le confondre avec la "balance", qui, elle, a en entrée un signal stéréo et ressort réparti différemment sur les deux canaux.
Pour commencer, récupérez le fichier de travail sur le Gist approprié de mon GitHub.
En pratique, le Control Change 10(0AH) correspond à l'effet de pan.
0 complètement à gauche
64 au centre
127 complètement à droite
Vous connaissez déjà comment tester le numéro du Control Change, je passerai donc ce paragraphe.
En ce qui concerne la gestion du pan, rien de bien compliqué, on crée une nouvelle variable pan qu'on initialise par défaut à 64. On modifie sa valeur dans le switch secondaire Control Change.
Pour la partie audio, il ne faut pas jouer sur baseSound comme avec le volumeGeneral, mais directement avec les outBuffer(Left/Right)[i]
outBufferLeft[i] = ((pan>=64)?((127-pan)/63.0):1) * baseSound;
autre solution plus traditionnelle:
if (pan >= 64) then outBufferLeft[i] = baseSound * ((127-pan)/63.0);
else outBufferLeft[i] = baseSound;
Je vous laisse penser au calcul à réaliser pour le côté droit, il est plus facile, et bien sûre, vous retrouverez le fichier complet, commenté et fonctionnel dans un Gist de mon Github.
Merci de me lire.
A bientôt.
Dagal.
Cette fois, je vais vous expliquer comment réaliser un "pan" pour votre synthétiseur préféré, le vôtre.
Le "pan", c'est le petit bouton rotatif qui va envoyer votre son plutôt à gauche ou plutôt à droite. Il ne faut pas le confondre avec la "balance", qui, elle, a en entrée un signal stéréo et ressort réparti différemment sur les deux canaux.
Pour commencer, récupérez le fichier de travail sur le Gist approprié de mon GitHub.
En pratique, le Control Change 10(0AH) correspond à l'effet de pan.
0 complètement à gauche
64 au centre
127 complètement à droite
Vous connaissez déjà comment tester le numéro du Control Change, je passerai donc ce paragraphe.
En ce qui concerne la gestion du pan, rien de bien compliqué, on crée une nouvelle variable pan qu'on initialise par défaut à 64. On modifie sa valeur dans le switch secondaire Control Change.
Pour la partie audio, il ne faut pas jouer sur baseSound comme avec le volumeGeneral, mais directement avec les outBuffer(Left/Right)[i]
outBufferLeft[i] = ((pan>=64)?((127-pan)/63.0):1) * baseSound;
autre solution plus traditionnelle:
if (pan >= 64) then outBufferLeft[i] = baseSound * ((127-pan)/63.0);
else outBufferLeft[i] = baseSound;
Je vous laisse penser au calcul à réaliser pour le côté droit, il est plus facile, et bien sûre, vous retrouverez le fichier complet, commenté et fonctionnel dans un Gist de mon Github.
Merci de me lire.
A bientôt.
Dagal.
jeudi 10 juillet 2014
Programmation d'un synthétiseur Jack sous GNU/Linux en c++ (Cinquième partie) - Le Volume
Bonjour ou bonsoir à tous.
Dernièrement, nous avons vu comment comment créer un client Jack, d'y inclure différents port audio et Midi, nous avons vu aussi, comment créer un son, comment lire des données Midi et contrôler le volume du son ainsi créé.
Tout ce qu'un synthétiseur de base est censé faire est déjà implémenté. Je vais, maintenant, m'occuper du reste, progressivement.
Vous pouvez récupérer le fichier de travail dans le gist ad hoc.
Qu'est ce donc, le reste? Toutes fonctions supplémentaire que l'on pourrait trouver ne seraient que des effets. Il y a des effets simple à programmer, mais il y en a d'autres qui vont demander pas mal de réflexion. Je vais m'attaquer à ce qui est plus simple pour commencer, vu que je découvre l'audio en même temps que vous, ou presque ...
Trêve de clavardage, je m'en vais commencer à imaginer deux ou trois effets de bases à programmer maintenant. J'ai près de moi la liste des commandes Midi. Je ne vais pas les réaliser dans l'ordre, mais je compte bien les passer toutes en revues.
Il y a une commande nommée Control Change qui permet de modifier le son d'un canal donné. En hexadécimal, cette commande vaut B0 à BF. Pour la filtrer, il suffit d'ajouter à notre switch un test sur B0.
La commande Control Change admet deux variables, la première qui détermine le numéro du contrôleur à changer et le second, la valeur que l'on veut lui attribuer.
Pour gérer cette liste de contrôleur, je vais créer un nouveau switch, à l'intérieure du switch principal, nous pourrons ainsi réagir en fonction du contrôleur demandé.
Je pense que le volume est l'effet le plus facile à mettre en place. Il suffit de créer une nouvelle variable volumeGeneral et de lui donner la valeur reçue.
Pour la partie audio, il suffira de multiplier baseSound par volumeGeneral et de diviser par 127 (ou par 100 si on veut pouvoir avoir un peu de gain, attention à la distorsion...).
A vous de voir comment le réaliser...
Comme à chaque fois, je vous livre ma version hébergée sur Github.
A bientôt.
Dagal.
Dernièrement, nous avons vu comment comment créer un client Jack, d'y inclure différents port audio et Midi, nous avons vu aussi, comment créer un son, comment lire des données Midi et contrôler le volume du son ainsi créé.
Tout ce qu'un synthétiseur de base est censé faire est déjà implémenté. Je vais, maintenant, m'occuper du reste, progressivement.
Vous pouvez récupérer le fichier de travail dans le gist ad hoc.
Qu'est ce donc, le reste? Toutes fonctions supplémentaire que l'on pourrait trouver ne seraient que des effets. Il y a des effets simple à programmer, mais il y en a d'autres qui vont demander pas mal de réflexion. Je vais m'attaquer à ce qui est plus simple pour commencer, vu que je découvre l'audio en même temps que vous, ou presque ...
Trêve de clavardage, je m'en vais commencer à imaginer deux ou trois effets de bases à programmer maintenant. J'ai près de moi la liste des commandes Midi. Je ne vais pas les réaliser dans l'ordre, mais je compte bien les passer toutes en revues.
Il y a une commande nommée Control Change qui permet de modifier le son d'un canal donné. En hexadécimal, cette commande vaut B0 à BF. Pour la filtrer, il suffit d'ajouter à notre switch un test sur B0.
La commande Control Change admet deux variables, la première qui détermine le numéro du contrôleur à changer et le second, la valeur que l'on veut lui attribuer.
Pour gérer cette liste de contrôleur, je vais créer un nouveau switch, à l'intérieure du switch principal, nous pourrons ainsi réagir en fonction du contrôleur demandé.
Comme vous pouvez le voir, j'ai préparé le terrain pour le réglage du volume général, donc pas le volume d'une seule note mais bien de toutes.case 0xb0:// Control Change// On teste pour savoir si le canal est correcteif ((canal == 0) || (canalEvent == canal)){// La norme midi dit que la commande Control Change est suivie de// deux octets.// Le premier pour le numéro du contrôleur et le second pour sa valeurunsigned char controller = inEvent.buffer[1];unsigned char valeur = inEvent.buffer[2];cout << "Control Change " << controller;cout << " Value " << valeur << endl;// Action en fonction du numéro de contrôleur.switch (controller){case 07:// Gestion du volume général du canal du sonbreak;default:cout << "Control Change non traité." << endl;break;}}break;
Je pense que le volume est l'effet le plus facile à mettre en place. Il suffit de créer une nouvelle variable volumeGeneral et de lui donner la valeur reçue.
Pour la partie audio, il suffira de multiplier baseSound par volumeGeneral et de diviser par 127 (ou par 100 si on veut pouvoir avoir un peu de gain, attention à la distorsion...).
A vous de voir comment le réaliser...
Comme à chaque fois, je vous livre ma version hébergée sur Github.
A bientôt.
Dagal.
lundi 7 juillet 2014
Programmation d'un synthétiseur Jack sous GNU/Linux en C++ (Quatrième partie) - Double Canal
Dans ce chapitre, nous allons doubler notre son pour avoir deux canaux et pouvoir, ainsi, par la suite, gérer la Stéréo.
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 :
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:
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.
dimanche 6 juillet 2014
Troisième partie du tutoriel sur la programmation d'un son sous Jack en Linux - Contrôle du Son par le Midi
Salut à vous tous.
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.
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"):
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.
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:
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.
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.
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.
samedi 5 juillet 2014
Deuxième partie du tutoriel sur la programmation, en Jack sous linux, d'une sinusoïde - Le Premier Son
Maintenant que vous avez bien compris comment on prépare un client Jack, je vais vous montrer comment émettre un son dans vos hauts-parleurs.
Reprenez votre fichier avec le client que vous venez de créer avec moi.
Il va falloir maintenant bien comprendre comment fonctionne Jack pour arriver à lui faire faire ce qui nous intéresse.
Vous avez vu dans le chapitre précédent que le programme tourne en boucle sans rien faire. Et bien il en sera de même jusqu'à la fin car Jack fonctionne grâce à des fonctions Callback, ce qui va lui permettre de travailler uniquement quand c'est le moment.
Et le moment, c'est quand avec Jack? Depuis le temps qu'on fait rien d'intéressent...
Bon, je sens que vous êtes impatient. Je le vois à votre regard désabusé. Rassurez vous, cela arrivera vite maintenant... Regardons cela ensemble!
J'ai donc parlé de Callback précédemment. Une fonction Callback est une fonction qui va être appelée automatiquement lorsqu'un événement survient. Dans notre cas, il en existe une qui va s'exécuter à chaque fois que le buffer de sortie doit être rempli. On avait déjà parlé de ce buffer auparavant dans la première partie de ce tutoriel. Pour déterminer quelle sera cette fonction, on utilise cette procédure:
jack_set_process_callback(nomduclient, nomdelaprocedure, 0);
dans notre cas présent ce sera:
jack_set_process_callback(client, process, 0);
que vous allez insérer juste avant l'activation du client sinon vous aurez droit à une belle erreur qui n'est pas juste.
Bien sur, il faut créer la fonction à proprement parler.
int process(jack_nframes_t nframes, void* arg)
Déclaration pas trop difficile, je pense.
nframes représente le nombre d'échantillons dans notre buffer (intéressent, n'est ce pas?)
arg représente un argument de l'utilisateur non nécessaire au fonctionnement de Jack. D’ailleurs dans la déclaration de notre Callback, on passait 0 en argument.
Et maintenant, voyons ce que l'on peut faire dans cette nouvelle fonction...
Nous connaissons le nombre de frames (échantillons) de notre buffer, c'est bien, mais nous ne connaissons toujours pas celui de notre fréquence d'échantillonage. Rappelez vous ... 16000 44100 48000 192000, et j'en passe bien d'autre. Et cela, ce n'est pas le programme client qui le décide, c'est le serveur Jackd. Il faut quand même bien que tous les programmes s'accordent entre eux, n'est il pas? Donc pour récupérer cette valeur nous allons le demander à Jack.
jack_nframes_t sampleRate = jack_get_sample_rate(client);
Hop! Hop! Hop! On a pas accès à la variable client, il faut la déclarer en globale. Que c'est triste! Vivement le C++! On pourrait utiliser la variable utilisateur, mais cela nous compliquerais le travail pour pas grand chose. De toute façon, ce n'est pas la seule chose mal faite ici, et ce n'est pas le but non plus.
Vous aussi vous avez remarqué cela... On demande au serveur en s'adressant au client... Etrange...
On connait le client et le client connait le serveur!
La boucle est bouclée...
Bon! On connait la fréquence d’échantillonnage et le nombre de sample du buffer. On ne connait pas encore la fréquence du son que nous voulons, et bien on va en inventer une.
Prenons un nombre au hasard: 440Hz
Ehhhh! C'est la fréquence du La3 international. Le La de référence, le son que l'on entend quand on décroche le téléphone fixe... Cool!
Imaginons que nous avons l’échantillonnage à 44100 et le buffer à 128. Comment allons procéder pour avoir un beau son?
44100 / 440 = 100.2272727272727272727272727272727272727
C'est le nombre de sample pour une révolution complete de notre fréquence.
Une révolution complète, c'est en fait 2xPI = 6.28 radian (un tour complet du cercle).
Donc 100.227 correspond à 6.28! mmmmmm!
Si je divise ce 6.28 par mes 100.227, je vais donc connaître l'angle entre 2 samples. Cela aussi c'est bon à savoir.
Pour résumer:
delta angle = 2 x PI x 440 / 44100 = 0.0626893772145
On comprend,maintenant, pourquoi Jack travaille avec des nombres réels.
C'est le moment de nous intéresser à notre buffer de sortie. On va tenter de le remplir correctement.
Avant de le remplir, on va demander à Jack de nous dire où il se trouve.
Reprenez votre fichier avec le client que vous venez de créer avec moi.
Il va falloir maintenant bien comprendre comment fonctionne Jack pour arriver à lui faire faire ce qui nous intéresse.
Vous avez vu dans le chapitre précédent que le programme tourne en boucle sans rien faire. Et bien il en sera de même jusqu'à la fin car Jack fonctionne grâce à des fonctions Callback, ce qui va lui permettre de travailler uniquement quand c'est le moment.
Et le moment, c'est quand avec Jack? Depuis le temps qu'on fait rien d'intéressent...
Bon, je sens que vous êtes impatient. Je le vois à votre regard désabusé. Rassurez vous, cela arrivera vite maintenant... Regardons cela ensemble!
J'ai donc parlé de Callback précédemment. Une fonction Callback est une fonction qui va être appelée automatiquement lorsqu'un événement survient. Dans notre cas, il en existe une qui va s'exécuter à chaque fois que le buffer de sortie doit être rempli. On avait déjà parlé de ce buffer auparavant dans la première partie de ce tutoriel. Pour déterminer quelle sera cette fonction, on utilise cette procédure:
jack_set_process_callback(nomduclient, nomdelaprocedure, 0);
dans notre cas présent ce sera:
jack_set_process_callback(client, process, 0);
que vous allez insérer juste avant l'activation du client sinon vous aurez droit à une belle erreur qui n'est pas juste.
Bien sur, il faut créer la fonction à proprement parler.
int process(jack_nframes_t nframes, void* arg)
Déclaration pas trop difficile, je pense.
nframes représente le nombre d'échantillons dans notre buffer (intéressent, n'est ce pas?)
arg représente un argument de l'utilisateur non nécessaire au fonctionnement de Jack. D’ailleurs dans la déclaration de notre Callback, on passait 0 en argument.
Et maintenant, voyons ce que l'on peut faire dans cette nouvelle fonction...
Nous connaissons le nombre de frames (échantillons) de notre buffer, c'est bien, mais nous ne connaissons toujours pas celui de notre fréquence d'échantillonage. Rappelez vous ... 16000 44100 48000 192000, et j'en passe bien d'autre. Et cela, ce n'est pas le programme client qui le décide, c'est le serveur Jackd. Il faut quand même bien que tous les programmes s'accordent entre eux, n'est il pas? Donc pour récupérer cette valeur nous allons le demander à Jack.
jack_nframes_t sampleRate = jack_get_sample_rate(client);
Hop! Hop! Hop! On a pas accès à la variable client, il faut la déclarer en globale. Que c'est triste! Vivement le C++! On pourrait utiliser la variable utilisateur, mais cela nous compliquerais le travail pour pas grand chose. De toute façon, ce n'est pas la seule chose mal faite ici, et ce n'est pas le but non plus.
Vous aussi vous avez remarqué cela... On demande au serveur en s'adressant au client... Etrange...
On connait le client et le client connait le serveur!
La boucle est bouclée...
Bon! On connait la fréquence d’échantillonnage et le nombre de sample du buffer. On ne connait pas encore la fréquence du son que nous voulons, et bien on va en inventer une.
Prenons un nombre au hasard: 440Hz
Ehhhh! C'est la fréquence du La3 international. Le La de référence, le son que l'on entend quand on décroche le téléphone fixe... Cool!
Imaginons que nous avons l’échantillonnage à 44100 et le buffer à 128. Comment allons procéder pour avoir un beau son?
44100 / 440 = 100.2272727272727272727272727272727272727
C'est le nombre de sample pour une révolution complete de notre fréquence.
Une révolution complète, c'est en fait 2xPI = 6.28 radian (un tour complet du cercle).
Donc 100.227 correspond à 6.28! mmmmmm!
Si je divise ce 6.28 par mes 100.227, je vais donc connaître l'angle entre 2 samples. Cela aussi c'est bon à savoir.
Pour résumer:
delta angle = 2 x PI x 440 / 44100 = 0.0626893772145
On comprend,maintenant, pourquoi Jack travaille avec des nombres réels.
C'est le moment de nous intéresser à notre buffer de sortie. On va tenter de le remplir correctement.
Avant de le remplir, on va demander à Jack de nous dire où il se trouve.
jack_default_audio_sample_t* outBuffer = (jack_default_audio_sample_t*)jack_port_get_buffer(outPort, nframes);
Il ne faut pas oublier de placer notre outPort en global également, sinon...
Bon, on a le buffer, on va le remplir. Pour ce faire, une boucle for s'impose du premier élément du buffer jusqu'au dernier.
On crée une variable globale currentAngle, qui est mise à 0 dans le main. A chaque passage dans la boucle, on ajoute la valeur de deltaAngle à notre currentAngle.
Ensuite, il ne reste plus qu'à remplir le buffer.
On veut une sinusoïde donc on va utiliser la fonction sin de la librairie de math.
Le résultat sera stocké dans notre buffer.
Je vous laisse chercher un peu avant de simplement copier le code ci dessous.
N'oubliez pas de retourner la valeur 0 à la fin de la procédure, sinon jack va vous ennuyer et vous ne parviendrez pas à connecter votre sortie à une entrée.
Le prochain tutoriel expliquera comment jouer d'autres notes de musique en ajoutant une entrée midi à notre programme.
A bientôt pour la suite.
Dagal.
#include <jack/jack.h>#include <iostream>#include <unistd.h>#include <math.h>using namespace std;jack_client_t* client;jack_port_t* outPort;jack_default_audio_sample_t currentAngle;int process(jack_nframes_t nframes, void* arg){jack_nframes_t sampleRate = jack_get_sample_rate(client);jack_default_audio_sample_t* outBuffer =(jack_default_audio_sample_t*)jack_port_get_buffer(outPort, nframes);jack_nframes_t i;for (i = 0; i < nframes; ++i){currentAngle += 2 * 3.1415926353 * 440 / sampleRate;outBuffer[i] = sin(currentAngle);}return 0;}int main(){jack_status_t jackStatus;currentAngle = 0;cout << "On commence par créer le client lui même." << endl;client = jack_client_open("MonClientJack",JackServerName,&jackStatus,"default");cout << "Ensuite, il faut créer un port audio de sortie." << endl;outPort = jack_port_register(client,"SortieAudio1",JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput|JackPortIsTerminal,0);cout << "Référencement de la fonction process" << endl;jack_set_process_callback(client, process, 0);cout << "On active notre client fraichement créé." << endl;jack_activate(client);cout << "Maintenant on rentre dans une boucle infinie." << endl;while (true){// On attends un peu et puis on passe à la suite!usleep(1);}}
Créer un son sinusoïdal en c++ sous linux a l'aide de la librairie Jackd - La base
Aujourd'hui, j'ai envie de parler de la création de son sous GNU/Linux.
Encore un domaine où je ne connais rien...
J'ai décidé d'utiliser la librairie Jack, ce qui permet de faire de l'audio en temps réel sans trop se casser la tête.
Quand je dis que je ne connais rien, je ne suis pas loin de la vérité, en effet, il y a à peine quelques jours que j'ai vraiment commencé à étudier cette branche de l'informatique.
Bon, mis à part tout cela, entrons dans le vif du sujet et oublions le reste qui nous entoure, la concentration est de mise.
Tout d’abord, il faut se poser les bonnes questions:
Encore un domaine où je ne connais rien...
J'ai décidé d'utiliser la librairie Jack, ce qui permet de faire de l'audio en temps réel sans trop se casser la tête.
Quand je dis que je ne connais rien, je ne suis pas loin de la vérité, en effet, il y a à peine quelques jours que j'ai vraiment commencé à étudier cette branche de l'informatique.
Bon, mis à part tout cela, entrons dans le vif du sujet et oublions le reste qui nous entoure, la concentration est de mise.
Tout d’abord, il faut se poser les bonnes questions:
- Comment créer un son?
- Comment l'intégrer à Jack?
- Comment le contrôler?
Trois questions très courtes, certes, mais qui vont nécessiter chacune un développement bien complexe. C'est parti...
- Comment créer un son?
Waaaaahhhhhh! Mais vous êtes fou? Ohhh! Oui!
Mais non, pas tant que cela. Vous allez voir, c'est pas sorcier!
Comment représente-t'on une sinusoïde graphiquement?
Il est possible de la créer par la géométrie et un peu de main levée. C'est ce que je vais vous montrer parce que techniquement, c'est la même méthode que le programme utilisera.
Comme vous pouvez le voir dans l'image ci dessus, si l'on reporte les angles du cercle sur une droite horizontale et que pour chacun de ces angles, on reporte la hauteur (y) du point d'intersection de l'angle avec le cercle, une sinusoïde apparaîtra comme par enchantement sur la droite.
Concrètement, en informatique, une portion d'angle comme si dessus correspondra à ce que l'on appelle un échantillon (sample en anglais). Le termes échantillon est très important en musique informatique. Plus on en a et plus le son sera beau. En effet, si sur 1s on prend 1 seul échantillon ou bien 10, on devine facilement qu'il y aura une différence de qualité. En effet, imaginez que dans le dessin ci dessus je n'aie pris qu'un seul échantillon, auriez vous pu dessiner la sinusoïde?
Oui, mais alors, combien il faut d'échantillon, et dans quel espace de temps?
En téléphonie, on considère que 8kHz est suffisant pour entendre correctement la voix.
En HiFi, on considère qu'il faut travailler entre 20Hz et 20kHz
En Studio, par contre on travaille au minimum sur la base de 24kHz
Pour avoir la possibilité de reconnaître la fréquence d'un son, il faut impérativement avoir au minimum 2 fois le nombre de sa fréquence, donc:
Pour le téléphone, il faut 16.000 échantillons par seconde.
En HiFi, 40.000 et au studio, au minimum 48.000. La tendance en ce moment dans les studios est de 192.000 échantillons par seconde.
Le CD audio a 44100 échantillons par seconde.
Le DAT a 48000 échantillons par seconde.
Maintenant que l'on connait le nombre d'échantillons nécessaire, il serait temps de savoir aussi comment on va encoder cette valeur d'échantillon. Heureusement pour nous, Jack s'occupe de cela en grande partie. Il nous suffira de lui donner les échantillons sous forme de nombre flottant 32 bits et puis Jack se fera un plaisir de convertir tout cela pour que ce soit compréhensible par notre matériel.
Concrètement, si sur notre carte interne de l'ordinateur, qui travaille probablement en 44100, on veut sortir un son à 1kHz, il va falloir faire un petit calcul. Quel sera l'angle du cercle pour notre échantillon?????
Pour un son, inaudible, de 1Hz, on fera 44100 échantillons.
Donc, pour un son de 1000Hz, on fera 44100/1000 = 44,1 échantillons (Parfait, pour notre oreille, ou presque) par révolution complète
Pour un son à 10kHz, on aura plus que 4,41 échantillons par révolution (Pas terrible, mais l'oreille humaine n'est plus aussi sensible aux différences,...).
- Comment l'intégrer à Jack?
L'ordinateur est une machine qui ne peut faire qu'une chose à la fois, grosso modo. Pourtant, on lui demande beaucoup plus que cela. En ce moment, je vous écris, j'écoute une radio sur le net, mon horloge tourne, j'ai une console, QtCreator, un gestionnaire de fichier et un programme qui me dit à combien mon processeur est surchargé, sans compter tout ce qui tourne en arrière plan sans qu'on en voit directement la couleur.
Pfffff! On s'en fout, on veut faire de la musique!!!!!!!
Ben oui! Moi aussi!
J'y arrive... Jack, comme tout les autres programmes doit gérer le temps du processeur pour faire croire aux pauvres humains que nous sommes que l'ordinateur fait plusieurs choses en même temps. Jack n'a donc pas la main mise sur l'ordinateur en permanence. Il va donc devoir gérer au mieux son temps de parole.
Les ordinateurs actuels sont capable de créer des sons plus vite qu'il ne sont joué. Jack utilise cette capacité afin de remplir un buffer de sortie régulièrement, qui sera joué par l'ordinateur de manière différée.
Différée, dites vous?
Malheureusement, oui. C'est la hantise de tout les musiciens informatisés. Rassurez vous, le système de Jack est reconnu comme l'un des meilleurs moteur audio du moment. ASIO du Microsoft Windows ne lui arrive pas à la cheville, et pourtant beaucoup de gens l'utilise aujourd'hui.
Plus le buffer est petit et moins le son sera différé, mais plus l'ordinateur sera surchargé et donc sensible aux "déraillements du disque".
Si vous ne l'avez jamais fait, installer donc Jackd2, configurer le et entraînez vous à l'utiliser avec différent logiciel.
Nous allons, maintenant pouvoir passer à la partie pratique de ce tutoriel, nous connaissons tout ce qu'il nous faut pour commencer lentement.
Pour la programmation, je ne vais pas passer mon temps à créer des classes, mais je vais tout de même utiliser le compilateur du c++ afin de profiter tout de même de certaines fonctionnalités que j'affectionne tout particulièrement.
#include <jack/jack.h>#include <iostream>#include <unistd.h>using namespace std;int main(){jack_status_t jackStatus;cout << "On commence par créer le client lui même." << endl;jack_client_t* client = jack_client_open("MonClientJack",JackServerName,&jackStatus,"default");cout << "Ensuite, il faut créer un port audio de sortie." << endl;jack_port_t* outPort = jack_port_register(client,"SortieAudio1",JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput|JackPortIsTerminal,0);cout << "On active notre client fraichement créé." << endl;jack_activate(client);cout << "Maintenant on rentre dans une boucle infinie." << endl;while (true){// On attends un peu et puis on passe à la suite!usleep(1);}}
Et voici nos premières lignes de code. Un peu d'explication s'impose...
On fait du Jack, on a donc besoin de jack/jack.h
iostream pour la fonction cout
et unistd.h pour la fonction usleep.
jack_status_t jackStatus; Pour le moment, on aura pas vraiment besoin de ceci en dehors de la création du client, je le laisse donc volontairement de côté.
jack_client_t* client = jack_client_open(.....);
Cette fonction va créer physiquement un nouveau client Jack. Si vous utilisez patchage pour faire vos connections entre les différentes sources vous verrez un nouveau rectangle s'afficher avec pour nom, celui créé ici même.
jack_port_t* outPort = jack_port_register( ..... );
C'est ici que l'on va créer le port de sortie.
"SortieAudio1" c'est le nom du port de sortie dans patchage, par exemple.
JACK_DEFAULT_AUDIO_TYPE pour dire qu'on utilise l'audio et pas le midi
JackPortIsOutput pour dire que c'est un port de sortie
JackPortIsTerminal pour dire que c'est la fin d'une chaine qui peut être utilisée par un autre programme.
Ensuite on active le client par jack_activate(client);
En dernier, on crée une boucle infinie car sinon le programme se terminerais directement. Pour ne pas que notre programme vole toutes les ressources de l'ordinateur, on le fait attendre un peu, pour qu'il puisse s'acquitter de ses autres tâches.
Et maintenant, on compile:
g++ creationclient.cpp -ljack -o creationclient
le -ljack est là pour dire qu'il faut prendre Jack en compte pour le linkage.
Si vous ouvrez le programme Patchage vous verrez un nouveau rectangle nommé MonClientJack avec une sortie nommée SortieAudio1. Si vous le désirez, vous pouvez même connecter cette sortie à n'importe quelle entrée audio.
Malheureusement, vous pouvez la connecter à autant d'entrée que vous voulez, aucun son ne sortira de notre programme.
Dans le prochain article, je reprendrai ce qui a été écrit dans celui ci afin de sortir enfin un son de nos haut-parleur.
A bientôt.
Dagal.
On fait du Jack, on a donc besoin de jack/jack.h
iostream pour la fonction cout
et unistd.h pour la fonction usleep.
jack_status_t jackStatus; Pour le moment, on aura pas vraiment besoin de ceci en dehors de la création du client, je le laisse donc volontairement de côté.
jack_client_t* client = jack_client_open(.....);
Cette fonction va créer physiquement un nouveau client Jack. Si vous utilisez patchage pour faire vos connections entre les différentes sources vous verrez un nouveau rectangle s'afficher avec pour nom, celui créé ici même.
jack_port_t* outPort = jack_port_register( ..... );
C'est ici que l'on va créer le port de sortie.
"SortieAudio1" c'est le nom du port de sortie dans patchage, par exemple.
JACK_DEFAULT_AUDIO_TYPE pour dire qu'on utilise l'audio et pas le midi
JackPortIsOutput pour dire que c'est un port de sortie
JackPortIsTerminal pour dire que c'est la fin d'une chaine qui peut être utilisée par un autre programme.
Ensuite on active le client par jack_activate(client);
En dernier, on crée une boucle infinie car sinon le programme se terminerais directement. Pour ne pas que notre programme vole toutes les ressources de l'ordinateur, on le fait attendre un peu, pour qu'il puisse s'acquitter de ses autres tâches.
Et maintenant, on compile:
g++ creationclient.cpp -ljack -o creationclient
le -ljack est là pour dire qu'il faut prendre Jack en compte pour le linkage.
Si vous ouvrez le programme Patchage vous verrez un nouveau rectangle nommé MonClientJack avec une sortie nommée SortieAudio1. Si vous le désirez, vous pouvez même connecter cette sortie à n'importe quelle entrée audio.
Malheureusement, vous pouvez la connecter à autant d'entrée que vous voulez, aucun son ne sortira de notre programme.
Dans le prochain article, je reprendrai ce qui a été écrit dans celui ci afin de sortir enfin un son de nos haut-parleur.
A bientôt.
Dagal.
vendredi 4 juillet 2014
Linux Audio en C++
Comme son nom l'indique, dans ce blog, je vais y mettre tout ce qui me sera utile pour la programmation en MIDI et en Audio.
Quelques formule de math pour couronner le tout.
A suivre.
Quelques formule de math pour couronner le tout.
A suivre.
Inscription à :
Articles (Atom)