D'abord, nous enlevons du simple client les chose dont nous n'avons besoin (input, par exemple) et ajoutons ce dont nous avons besoin.
La structure
#include < stdlib.h >
#include < string.h >
#include < unistd.h >
#include < stdio.h >
#include < jack/jack.h >
const double PI = 3.14;
/*Our output port*/
jack_port_t *output_port;
typedef jack_default_audio_sample_t sample_t;
/*The current sample rate*/
jack_nframes_t sr;
/*one cycle of our sound*/
sample_t* cycle;
/*samples in cycle*/
jack_nframes_t samincy;
/*the current offset*/
long offset=0;
/*frequency of our sound*/
int tone=262;
int process (jack_nframes_t nframes, void *arg){
/*grab our output buffer*/
sample_t *out = (sample_t *) jack_port_get_buffer (output_port, nframes);
/*CODE GOES HERE!*/
return 0;
}
int srate (jack_nframes_t nframes, void *arg){
printf ("the sample rate is now %lu/sec\n", nframes);
sr=nframes;
return 0;
}
void error (const char *desc){
fprintf (stderr, "JACK error: %s\n", desc);
}
void jack_shutdown (void *arg){
exit (1);
}
int main (int argc, char *argv[]){
jack_client_t *client;
const char **ports;
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
*/
jack_set_error_function (error);
/* try to become a client of the JACK server */
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));
sr=jack_get_sample_rate (client);
/* create two ports */
output_port = jack_port_register (client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
/*CODE GOES HERE!*/
/* tell the JACK server that we are ready to roll */
if (jack_activate (client)) {
fprintf (stderr, "cannot activate client");
return 1;
}
/* connect the ports*/
if ((ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical|JackPortIsInput)) == NULL) {
fprintf(stderr, "Cannot find any physical playback ports\n");
exit(1);
}
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);
/* 3 seconds of bleep is plenty*/
sleep (3);
jack_client_close (client);
free(cycle);
exit (0);
}
Comme vous pouvez voir, les seuls endroit où nous devons modifier le code est marqué par le commentaire CODE GOES HERE!.
Créer de l'audio
Donc comment créer du son ? le métronome m'a donné une idée. Tout ce que nous voulons c'est faire un bip, ainsi un sinus semble être le choix évident, mais il y a quelques choses nous devons figurer d'abord.
Premièrement, comment savoir quelle partie du sinus nous devons produire dans le processus de callback ? Nous ne pouvons pas simplement commencer à 0 chaque fois parce que nous ne pouvons pas finir à 0 la dernière fois. Si nous faisons cela, nous obtiendrons un buzz. Ce que j'ai décidé de faire, c'est de créer un ensemble d'échantillons qui contient un cycle du son, que nous utiliserons plus tard pour le copier dans le buffer de sortie.
Rappelez-vous que nous pouvons déterminer de combien d'échantillons un cycle a besoin pour une taux d'échantillonnage spécifié ? Il est également important de rappeler que la fonction sin() utilise un angle en radians (2 Pi radians = 360 degrés).
Ainsi, la fonction sin() a une enveloppe de 2 pi, mais nous avons besoin du cycle pour être réparti dans notre ensemble. Comment mesurons-nous ceci ? pour l'échantillon 0 dans notre cycle, nous voulons sin(0), et au dernier échantillon dans notre cycle n, nous voulons sin(2 pi).
Comment faire ? il faut un multiplicateur qui a ces propriétés : multiplié par 0, nous obtenons 0 et multiplié par le nombre d'échantillons dans le cycle, nous obtenons 2 pi.Le multiplicateur est simplement (2*Pi)/samincy, où samincy est le nombre d'échantillons dans le cycle.
Voici le code qui remplace le deuxième commentaire CODE GOES HERE :
/*Create 1 cycle of the wave*/
/*Calculate the number of samples in one cycle of the wave*/
samincy=(sr/tone);
/*Calculate our scale multiplier*/
sample_t scale = 2 * PI / samincy;
/*Allocate the space needed to store one cycle*/
cycle = (sample_t *) malloc (samincy * sizeof(sample_t));
/*Exit if allocation failed (more sense from Jussi)*/
if(cycle == NULL) {
fprintf(stderr,"memory allocation failed\n");
return 1;
}
/*And fill it up*/
for(int i=0;i < samincy;i++){
cycle[i]=sin(i*scale);
}
La variable de tone est déclarée en haut à 262Hz.
Obtenir du son
Ainsi, nous avons un simple cycle de son, nous devons envoyer cela à notre port de sortie répété encore et encore. Rappelez-vous que nous ne pouvons pas simplement commencer à 0 à chaque fois
La façon la plus simple de faire ceci est de stocker la position courante dans le cycle. Puis, nous copions le cycle échantillon par échantillon dans le buffer du port de sortie, commençant à l'offset et lui additionnant 1 à chaque fois.
Quand l'offset atteint la fin du cycle, nous définissons juste de nouveau à 0. De cette façon, si le nombre d'échantillons à produire n'est pas un multiple entier du nombre d'échantillons dans le cycle, nous pouvons encore être sûrs que l'onde continuera correctement.
Voici le code qui remplace le premier commentaire CODE GOES HERE :
int process (jack_nframes_t nframes, void *arg){
/*grab our output buffer*/
sample_t *out = (sample_t *) jack_port_get_buffer (output_port, nframes);
/*For each required sample*/
for(jack_nframes_t i=0;i < nframes;i++){
/*Copy the sample at the current position in the cycle to the buffer*/
out[i]=cycle[offset];
/*and increment the offset, wrapping to 0 if needed*/
/*(Dumb increment fixed thanks to Jussi Sainio)*/
offset++;
if(offset==samincy)
offset=0;
}
return 0;
}
Et voilà. Nous avons 3 secondes de son à 262Hz, nous avons 3 secondes de "bip" à un pitch donné envoyé à tous les ports de sortie physiques disponibles.
Avant que nous finissions, avez-vous noté quelque chose de spécial au sujet de 262Hz ? Cela correspond à un C moyen, voici un échantillon d'un piano jouant un C moyen, donné par Paul Newbury de l'université du Sussex.
J'ai déclaré des variables dedans pour les boucles (for(int i=0 ;...), ainsi vous devrez vous assurer que votre compilateur puisse gérer cela, ou modifiez le code légèrement. Si vous employez GCC de GNU :
$gcc -std=c99 -o playc `pkg-config --cflags --libs jack` playc.c
Voici le code source