Related


Links

Am I free?


Social bookmarking

Delicious Bookmark this on Delicious


Tweets
Flickr randomness


Badges and junk

Support The Commons

Valid HTML 4.01 Strict
Valid CSS!













Now it's time to take a look at the simple client as a whole, and make sure we understand how it all works. We'll look at the code bit by bit, and make sure we know what it does. The code below is our slightly modified simple client.

The includes

#include <stdio.h>

//#include <errno.h>
//#include <unistd.h>
//#include <stdlib.h>
//#include <string.h>

#include <jack/jack.h>

Simple enough - stdio.h for printf and fprintf, and jack.h for JACK. The commented includes were included in the original simple client, but never needed. We'll keep them around, though, because the chances are we will want them soon.

The ports

jack_port_t *input_port;
jack_port_t *output_port;

Pointers ready to hold ports - one for input, one for output.

The important bit

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;      
}

Now, this really is the important bit. This function gets called by JACK when there is processing to be done. It must have an return type of int, and take the parameters shown (jack_nframes_t nframes, void *arg), although the name is up to us. We'll see how we inform JACK that this is the function we want it to use later on, but for now lets have a look at what it does.

From a high-level perspective, this function reads the available data from its input port and copies it to its output port. That's all.

How it does this is more interesting. We are given nframes as a parameter, and this is the number of frames available on all of our input ports, and the number of frames we are expected to write to our output ports. jack_default_audio_sample_t is used to hold audio data, and we declare two pointers of this type, in and out - one point to available input and the other to point to our output buffer. The buffers are then retrieved using jack_port_get_buffer, which returns a pointer to the buffer associated with the given port, of the given length. The bit in the middle of the first two lines just casts the returned pointer into a pointer to data of type jack_default_audio_sample_t.

memcpy is then used to copy the available data - length calculated using sizeof (jack_default_audio_sample_t) * nframes) from the input to the output.

And then we return. Simple.

You might be wondering what the parameters are. I'll tell you: nframes is the number of frames that need dealing with. That's simple enough, but what about the pointer called arg? This is a little more complicated - the documentation is a little vague on this point, but it appears that this is there so that we can pass our own arguments to the method. Don't worry about this yet, though. We'll deal with it properly later.

Change of sample rate

The next function defined in the simple client is also used as a callback. This time, it is called when the sample rate changes. Here is the code:

int
srate (jack_nframes_t nframes, void *arg)

{
	printf ("the sample rate is now %lu/sec\n", nframes);
	return 0;
}

Obviously, there could be more code in here - especially if we're doing more than copying input to output. The simple client, however, doesn't really care about the sample rate because it is simply moving the input to the output - the sample rates will always match.

The first parameter (nframes) is the number of frames per second - the new sample rate. The second parameter is our arg pointer again.

When things go wrong

And here's another callback function. This one gets called when there has been a problem.

void
error (const char *desc)
{
	fprintf (stderr, "JACK error: %s\n", desc);
}

Which is simple enough. desc is simply a description of the error.

Shutdown

This one gets called when the jack client gets shut down. Here's the code:

void
jack_shutdown (void *arg)
{
	exit (1);
}

Main

You should be comfortable with C already, but just in case: main is the method that gets executed when you run your compiled program. In the case of the simple client, main needs to create ports and a jack client, connect the ports to other ports and pass our callbacks to JACK. Rather than try to explain this method and then show you the code (or vice versa), I've added extra comments in the code. My comments are prefixed with "KIRBY:", so you can tell which bits are from me and which bits are from the JACK developers.

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);
}

And that's that. Now I want to make some noise.

Top - Previous - Next