beep - Generating sounds in C
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:
(For other operating systems, see PortAudio’s website.)
To download and complile beep
:
To play a 440Hz sine wave, run
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:
We’ll call a note a beep_note
and define it as:
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.)
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:
For a sine wave, this will be f(frame)=sin(2*PI*frequency*frame/44100)*amplitude)
. In C:
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.