Introduction

How do you generate sound programmatically? beep is a simple note-playing program I’ve written using the PortAudio library, written in C. (Ok, the title is a pun: I really mean the C programming language.) It is a minimal working example of sound generation: it is simple enough to understand very quickly, and is easy to play around with.

Try it

A prerequisite to compile and run beep is that you must have a C compiler, and the PortAudio dev libraries installed. On Ubuntu this can be installed by running something like:

sudo apt-get install portaudio19-dev

(For other operating systems, see PortAudio’s website.)

To download and complile beep:

git clone git@github.com:notfed/beep.git beep
cd beep
make

To play a 440Hz sine wave, run

./beep 440

Generating sounds in C (low-level)

To make sounds on a computer, we tell our speaker to vibrate. (Obviously.)

                /. 
                / \
                /  \\
                /   \\
                /     \\
                /      \\
                /       \\
                /         \\
                /          \\
                /           \\
                /            \\
                /            \\
                /             \\
                /             ||
                /             || <- ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
                /             ||
                /            //
                /           //
                /         //
                /       //
                /      //
                /     //
                /   //
                /  //
                //

		\______________/
		       v

It works as follows: 44100 times per second—44100 frames per second—our speaker asks our program, “choose any number for v from -1.0 to 1.0”. We are in power to answer. We can return the same number for every frame, but then the speaker won’t move. We can give it -1,+1,-1,+1,-1+1 (rapidly moving back and forth 44100 times per second) but it will be too high pitched (44kHz) to hear. We can set v equal to the result of a function the frame number: f(n)=sin(2*PI*440*n/44100), and, like mathematical magic, we will hear a clean tone.

That’s how low-level APIs for generating sound usually work, and that’s the API that PortAudio exposes to us. It’s nice that PortAudio offers such a low-level API, because it gives us power. But, in order to generate note-worthy sounds, we’ll want to add a minimal layer of abstraction.

Generating sounds in C (with an abstraction)

Instead of thinking of our interface to the speaker as simply an infinite stream of frames, we might want to think of it as a stream of notes. We can define a “note” as a structure which has a duration, a frequency, a waveform shape, and amplitude (volume). For example, imagine a 1-second-long, 440Hz sine wave, at 100% volume…that’s a note. Ideally, we’d like to be able to play a note with something like the following:

/* set up a note to play */
beep_note note = {
    .frequency = freq,
    .amplitude = 1.0,
    .duration = 1000,
    .waveform = beep_waveform_sine
};

/* play the note */
beep_note_play(&note);

We’ll call a note a beep_note and define it as:

typedef struct beep_note
{
  float frequency;
  float amplitude;
  float duration;
  beep_waveform waveform;
} beep_note;

When we’re actively playing notes, we’ll need to remember which frame we’re on. So, we’ll create an object (beep_head) to keep track of which note is playing and which frame we’re at. (To keep things simple, for now, we can only play one note at a time; we can’t compose notes.)

typedef struct beep_head
{
  beep_note note;
  int frame;
} beep_head;

Our note will need a waveform (beep_waveform). Examples of waveforms are sine, triangle, sawtooth, square—but a waveform could be any math formula. We can think of a waveform as a function which takes a beep_head as an input and produces a frame value as output:

typedef float (*beep_waveform)(struct beep_head*);

For a sine wave, this will be f(frame)=sin(2*PI*frequency*frame/44100)*amplitude). In C:

static float beep_waveform_sine(beep_note_active *active)
{
   beep_note *note = &active->note;
   return note->amplitude * sin(2 * M_PI * note->frequency * active->frame / 44100);
}

The final code can be found on GitHub.

Future enhancements include adding envelopes, and supporting note composition, but the purpose of this program was to keep things simple.