For this blog entry, I felt a good topic to cover would be additive synthesis and its implementation in C. Based on Fourier theory, additive synthesis concerns combining simple periodic waveforms (such as sine or cosine waves) together that result in more complex sounds. Put another way, any waveform can be expressed as a sum of sine waves due to the principle of the harmonic series which states that any sound contains an infinite number of overtones, or partials, as a diverging series. These overtones, of a certain frequency and amplitude, when expressed as simple sinusoidal waves form the building blocks of a more complex waveform. Mathematically this can be defined (for partials k = 1 to N, w = (2πf)/sample rate, and ø as the phase offset) as:
We can now, for example, create a sawtooth wave using this equation. But first, here is a snippet of C code that generates a simple sine wave and outputs it to a raw binary data file (these can be read by most audio editing software like Audacity) as well as a text file that can be plotted (I used Gnuplot):
Here is the graph of the sine wave as well as the soundfile (converted to .wav from .raw):
Now let’s move on to generating the sawtooth wave and we’ll see how the simple sinusoidal wave above is transformed into a more complex waveform through the use of the Fourier series equation. I set the total number of partials to 30 for this demonstration, and sawtooth waves contain all partials of the harmonic series with the amplitude of each equal to 1/partial (ak = 1/k). We also set the phase offset as ø = -π/2.
Here is the graph and the soundfile output of the sawtooth wave:
Now for something really funky! If we go back to the Fourier series equation above as we defined it, both amplitude and frequency were invarying. This is, as we know, not how sound behaves and really isn’t very interesting. To generate much more interesting and complex waveforms, we set both the amplitude (a) and the frequency (f) as finite, time-varying functions. I set up my code to read in a breakpoint file for both amplitude and frequency, each containing a few random values at arbitrary time locations to illustrate this. (A breakpoint file contains data set up similar to coordinates, or points, that can be plotted. In this case we have a amplitude vs. time and frequency vs. time.) Here is the plot of the amplitude stream …
… and the frequency stream …
… and the C code that implements this:
The code that reads the breakpoint data uses linear interpolation to find the value at time n / sample rate. The normalize_buffer routine adjusts the resulting amplitude to fit within the values of -1 and +1 and then scales it to the desired amplitude set by the user (in this case 0.7). Following is a snapshot of the resulting waveform (just before the 1 second mark) in Gnuplot as well as the soundfile output; the result of just summing together simple sinusoidal waves as seen in the first Gnuplot above!
This is a short and simple example of how flexible and powerful the Fourier series can be at creating textures and complex waveforms through additive synthesis. The possibilities just escalate from here, depending on the time-varying functions that determine the amplitude and frequencies applied to the individual sinusoidal waves, i.e. the harmonics.
It must be pointed out that the code used to implement these routines above is brute force and not optimal. For instance, to calculate these 3-second waves at a 44.1 kHz sampling rate with 30 partials requires 30 iterations through 132,300 (3 * 44,100) samples of data for a total of 3,969,000 loop iterations! Calculating sine and cosine values is actually quite a costly operation, so this is unacceptable for most practical purposes. One alternative is wavetable synthesis, whereby the data for one period of a basic wave is stored in a table and can then be looked up and utilized by the program. This is much more efficient (though depending on the size of the table, there can be a loss in precision) as redundant trigonometric calculations don’t have to be repeatedly calculated. This is potentially another topic for another time however.
For now, hope this was an interesting look into additive synthesis using the Fourier series and how with relatively few lines of code we can make some pretty cool things happen.