Maintenant il est temps de jeter un coup d'oeil au client simple dans son ensemble, et de s'assurer que nous comprenons comment tout fonctionne. Nous regarderons le code poitn par point. Le code ci-dessous est notre simple client légèrement modifié.
Les includes
#include <stdio.h> //#include <errno.h> //#include <unistd.h> //#include <stdlib.h> //#include <string.h> #include <jack/jack.h>
stdio.h pour printf et fprintf, et jack.h pour JACK. Les includes commenté sont intégéré dans notre aplication, nous n'en avons pas besoin mais on ne sais jamais.
Les ports
jack_port_t *input_port; jack_port_t *output_port;
Les pointeurs sont prêts à tenir les ports - un pour l'entrée, un pour la sortie.
La partie importante
int
process (jack_nframes_t nframes, void *arg)
{
jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) jack_port_get_buffer (output_port, nframes);
jack_default_audio_sample_t *in = (jack_default_audio_sample_t *) jack_port_get_buffer (input_port, nframes);
memcpy (out, in, sizeof (jack_default_audio_sample_t) * nframes);
return 0;
}
Cette fonction est appelée par JACK quand le traitement doit être fait. Elle doit avoir un retour du type int, et prend les paramètres montrés (nframes de jack_nframes_t, vide * arg). Nous verrons plus tard comment nous informons JACK que c'est la fonction que nous voulons qu'elle emploie. Cette fonction lit les données disponibles de son port d'entrée et les copies vers son port de sortie.
Nous avons nframes comme paramètre, c'est le nombre de frames disponibles sur tous nos ports d'entrée, et le nombre de frames qu'on s'attend à écrire à nos ports de sortie. jack_default_audio_sample_t est utilisé pour contenir des données audio, et nous déclarons deux pointeurs de ce type, in et out.
Les Buffers sont alors récupéré en utilisant jack_port_get_buffer, qui renvoie un pointeur au buffer associé au port spécifié, d'une longueur donnée. La partie au milieu des deux premières lignes dirige le pointeur renvoyé dans un pointeur de données du type jack_default_audio_sample_t.
memcpy est alors utilisé pour copier les données disponibles, la longueur est calculée en utilisant sizeof (jack_default_audio_sample_t) * des nframes) de l'entrée vers la sortie.
Vous pourriez vous demander ce que sont les paramètres. Je vous dirai : nframes est le nombre de frames que vous avez besoin de traiter. C'est assez simple, mais que diriez-vous du pointeur appelé arg ? C'est peu un plus compliqué.
La documentation est peu une vague sur ce point, mais il s'avère que c'est là maniére de spécifier nos propres arguments dans la méthode.
Changement du taux d'échantillonnage
La prochaine fonction définie est également utilisée comme un callback. Cette fois, elle est appellée quand le taux d'échantillonnage change. Voici le code :
int
srate (jack_nframes_t nframes, void *arg)
{
printf ("the sample rate is now %lu/sec\n", nframes);
return 0;
}
Évidemment, il pourrait y avoir plus de code ici, particulièrement si nous voulons faire plus que copier l'entrée vers la sortie. Le simple client ne s'occupe pas vraiment du taux d'échantillonnage parce qu'il déplace simplement l'entrée vers la sortie, les taux d'échantillonnage s'assortiront toujours.
Le premier paramètre (nframes) est le nombre de frames par seconde, le nouveau taux d'échantillonnage. Le deuxième paramètre est notre pointeur arg.
Quand les choses se passent mal
Et voici une autre fonction de callback. Celle-ci est appelé quand il y a un problème.
void
error (const char *desc)
{
fprintf (stderr, "JACK error: %s\n", desc);
}
desc est simplement une description de l'erreur.
Arrêt
Celui-ci est appelé quand le client de JACK obtient shutdown. Voici le code :
void
jack_shutdown (void *arg)
{
exit (1);
}
Partie principale
Vous devez normalement "maitriser" le language C, mais juste au cas où : Main est la méthode qui est exécutée quand vous lancez votre programme compilé. Dans le cas du simple client, main créé des ports et un client JACK, connecte les ports à d'autres ports et dirige nos callbacks vers JACK.
Plutôt que d'essayer d'expliquer cette méthode, j'ai ajouté des commentaires dans le code. Mes commentaires ont une en-tête avec "KIRBY :".
int
main (int argc, char *argv[])
{
/*KIRBY: Create a JACK client. This is our connection to the JACK daemon.*/
jack_client_t *client;
/*KIRBY: A pointer for an array of ports. Remember, we already saw this being
used to hold the array of available ports
See the previous chapter*/
const char **ports;
/*KIRBY: This doesn't really need an explanation*/
if (argc < 2) {
fprintf (stderr, "usage: jack_simple_client \n");
return 1;
}
/* tell the JACK server to call error() whenever it
experiences an error. Notice that this callback is
global to this process, not specific to each client.
This is set here so that it can catch errors in the
connection process
*/
/*KIRBY: Nuff said.*/
jack_set_error_function (error);
/* try to become a client of the JACK server */
/*KIRBY: This is where our pointer "client" gets something to point to.
You will notice that the functions called later take a client as
a parameter - this is what we pass.*/
if ((client = jack_client_new (argv[1])) == 0) {
fprintf (stderr, "jack server not running?\n");
return 1;
}
/* tell the JACK server to call `process()' whenever
there is work to be done.
*/
jack_set_process_callback (client, process, 0);
/* tell the JACK server to call `srate()' whenever
the sample rate of the system changes.
*/
jack_set_sample_rate_callback (client, srate, 0);
/* tell the JACK server to call `jack_shutdown()' if
it ever shuts down, either entirely, or if it
just decides to stop calling us.
*/
jack_on_shutdown (client, jack_shutdown, 0);
/* display the current sample rate. once the client is activated
(see below), you should rely on your own sample rate
callback (see above) for this value.
*/
printf ("engine sample rate: %lu\n", jack_get_sample_rate (client));
/* create two ports */
input_port = jack_port_register (client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
output_port = jack_port_register (client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
/* tell the JACK server that we are ready to roll */
/*KIRBY: So, once we are in a position to start doing whatever it is we do, this is how we announce that.*/
if (jack_activate (client)) {
fprintf (stderr, "cannot activate client");
return 1;
}
/* connect the ports. Note: you can't do this before
the client is activated, because we can't allow
connections to be made to clients that aren't
running.
*/
/*KIRBY: We already discussed this. Go back a chapter if you missed it.*/
if ((ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput)) == NULL) {
fprintf(stderr, "Cannot find any physical capture ports\n");
exit(1);
}
if (jack_connect (client, ports[0], jack_port_name (input_port))) {
fprintf (stderr, "cannot connect input ports\n");
}
free (ports);
if ((ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical|JackPortIsInput)) == NULL) {
fprintf(stderr, "Cannot find any physical playback ports\n");
exit(1);
}
/*KIRBY: This is our modified bit. Groovy, eh?*/
int i=0;
while(ports[i]!=NULL){
if (jack_connect (client, jack_port_name (output_port), ports[i])) {
fprintf (stderr, "cannot connect output ports\n");
}
i++;
}
free (ports);
/* Since this is just a toy, run for a few seconds, then finish */
/*KIRBY: We changed that, too. Now we run until we get killed.*/
for(;;)
sleep (1);
/*KIRBY: Close the client*/
jack_client_close (client);
exit (0);
}