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 lets try and play a note. First, we'll strip the simple client of anything we don't need (input, for a start) and add anything that we do need.

Thanks to Jussi Sainio for spotting and correcting a couple of mistakes - one was a shameful error incrementing the offset and the other was an only slightly less embarrassing lack of error checking.

The skeleton

The code below is what we're starting with. It's just the simple client stripped down for what we need.

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

As you can see, the only places where we really need to do any work are noted with the comment CODE GOES HERE!. We'll take a look at each of these in a moment.

Creating the audio

So how do we create the sound? Well, looking through the metronome gave me an idea. All we want to do is make a beep, so a sine wave seems like an obvious choice, but there are some things we need to figure out first.

Firstly, how do we know which part of the sine we should be outputting in the process callback? We can't just start at 0 each time because we might not have finished at 0 the last time. If we do this, we'll get some buzzing as the wave gets chopped. What I've decided to do, is to create an array of samples that contains one cycle of the sound, which we'll later use to copy into the output buffer.

Remember that we can work out how many samples one cycle needs at a given sample rate? We'll need that.

It's also important that we remember that the sin() function takes an angle in radians (2 Pi radians = 360 degrees).

So, the sin() function wraps at 2 Pi, but we need the cycle to be spread over our array. How do we scale this? Think of it like this: for sample 0 in our cycle, we want sin(0), and at the last sample in our cycle n, we want sin(2 Pi). Got that? We want a multiplier that has these properties: multiplied by 0, we get 0 and multiplied by the number of samples in the cycle, we get 2 Pi. Still not got it? OK, the multiplier is simply (2*Pi)/samincy, where samincy is the number of samples in the cycle.

Here's the code that replaces the second CODE GOES HERE comment:

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

Not so hard, eh? The tone variable is declared at the top as 262Hz. [Edit: almost. See below for another reader-prompted update/aside.

]

Getting the audio out

So, now we have a single cycle of sound, we need to send that to our output port repeated over and over. And remember that we can't just start at 0 every time!

The easiest way I could think of doing this was to store the current position in the cycle. Then, we copy the cycle sample by sample into the buffer of the output port, starting at the offset and adding one to it each time. When the offset reaches the end of the cycle, we just set it back to 0. This way, if the number of samples to be output is not a whole multiple of the number of samples in the cycle, we can still be sure that the wave will carry on correctly.

Here's my code, replacing the first CODE GOES HERE! comment:

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

And that's it. We have 3 seconds of output at 262Hz. Nice and easy, really - we have a 3 second bleep at a given pitch sent to all available physical output ports.

Before we finish, have you noticed anything special about 262Hz? It's middle C - depending on who you ask, of course! Try it out, and play middle C on an instrument (real or digital) and see for yourself. If you have no instruments, here is a sample of a piano playing middle C, kindly donated by Paul Newbury of the University of Sussex.

I've declared variables in for loops here (for(int i=0;...), so you'll need to make sure your compiler can handle that, or alter the code slightly. If you use GNU gcc (which you should), this will do the job:

$gcc -std=c99 -o playc `pkg-config --cflags --libs jack` playc.c

You probably don't want to have to copy and paste all of this, so here is the source code.

Update September 2009

Davorin Šego wrote to me asking about the frequencies. Here's what he said:

I've read your tutorials on programming with JACK audio API and
enjoyed them a lot. They are really helpful and easy to read. I've
started writing some code based on your "Playing a note" article and I
have a question about the next bit of code:

/*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;


Samincy is of type jack_nframes_t which is basically an unsigned
integer. If your tone is 262Hz (int tone=262) and the sampling rate is
for example 44100Hz, then the number of samples is 44100 / 262 =
168.320611. But when you cast it to int you get 168. 44100 / 168 =
262.5, which means your tone frequency is not 262Hz but 262.5Hz. It
looks like you can't squeeze 262Hz tone in exactly 168 samples (also
the scale should be 2 * PI / 168.320611). Am I missing something?
Sorry to bother you and all, but I'm really interested in your
thoughts on this.

And he's right. It gets worse, too. The higher the frequency, the smaller the number of samples it takes to represent a cycle at a given sample frequency.

It all comes down to using integers, as Davorin spotted. We can't even just swap to floats/doubles because we can't store a fractional sample at the end of the cycle array.

Here's a plot of the percentage frequency error against target frequency (at a sample rate of 44.1KHz):

It's a nice plot. It repeatedly sweeps down to zero error as we reach note frequencies that are factors of the sample rate.

I stopped half the sample rate (22050) since there's no point trying to create tones at a higher frequency (Nyquist).

There are a few reasons I've left this "unfixed":

And how to fix it? Well, there are options, but the simplest is probably to create a lookup table that is longer than a single cycle. If you create 100 cycles, say, you are now only miscalculating a few samples per 100 cycles. Here's the graph again, this time based on a 100 cycle LUT:

As you might guess, the error is two orders of magnitude less.

Top - Previous - Next