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);}}
Aucun commentaire:
Enregistrer un commentaire