What’s a wavetable?
First, let’s define what a wavetable is and what wavetable synthesis means. A wavetable is a collection of samples of one cycle of a periodic waveform. Let’s break that down. A periodic signal is one that repeats on a defined period over and over infinitely such that
x = x[0+period]=x[0+2*period]
So, if we have a copy of one cycle of that waveform, we have everything we need to play it over and over and get the signal for however long we repeat the cycle. If this were an analog system, like a tape player, we could have this one cycle on a loop of tape that we just keep playing over and over. In the digital world, we store a copy of this one cycle in a wavetable, which is a collection of samples that represent that signal. We can have any number of samples in the wavetable, 1,2,128, 3 million. The more samples we have, the better the wavetable represents the original, but in the end it can be played back as long as there are enough samples to accurately play back the signal at the sample frequency. So, wavetable synthesis is fundamentally like the tape loop, but it loops through it’s digital copy over and over. Okay, let’s talk about what can go wrong with wavetable synthesis.
What could possibly go wrong?
Fundamentally, aliasing occurs because there are frequencies in a signal greater than half the sample frequency (the rate at which the synth spits out samples), also known as the Nyquist Rate or Frequency. The digital synthesis engine ends up generating more than the signal that you’re trying to generate by producing aliases of the signal below the Nyquist Rate which are mirror images of frequencies above the Nyquist Rate. This is a fundamental principal of digital signal processing that you should read about here or elsewhere. In my case, I could hear low frequencies under, and sometimes over, the frequency that I was trying to generate.
For example, let’s say we made a wavetable that is one cycle of a sawtooth wave. From a frequency perspective, a sawtooth wave is a signal composed of a fundamental frequency and its harmonics up to infinity each with an amplitude decreasing at an exponential rate. It’s not as hard to understand as that statment sounds. Let’s say the fundamental note frequency is 100Hz, a nice bass tone. The square wave contains energy at 2 times the fundamental frequency at 1/2 the power, energy at 3 times the fundamental frequency at 1/3 the power, energy at 4 times the fundamental frequency at 1/4 the power, … and on and on. So the 100Hz sawtooth wave contains 100Hz, 200Hz, 300Hz, 400Hz, 500Hz, and all the way up to infinity. You can imagine the problem then if I construct a wavetable from an ideal wavetable. Even if I play back the wavetable at 100Hz, I get frequencies up to infinity. Frequencies above the Nyquist rate fold back around the Nyquist rate and are heard as aliasing. So, how do you remedy this situation? What if I could make a sawtooth wave that only had frequencies up to the Nyquist Rate? That’s exactly what a Bandlimited Wavetable is.
Side Note: Fourier Analysis
As an aside, this business of a signal being composed of various frequency components, is a fundamental discipline of Digital Signal Processing called Fourier Analysis. Much too complicated to go into in detail here, a nice explanation can be found on Wikipedia. Fundamentally, Fourier Analysis allows the decomposition of any signal into constituent distinct frequency components. All signals no matter how complicated can be broken down into individual frequency components. The only signal with only one frequency component is a purely sinusoidal signal. So, all signals can be broken down into individual sinusoidal frequency components. The process works in both directions. At the same time, any signal can be generated by combining together individual sinusoidal frequency components. This is how additive synthesis works. In real time, different sinusoids are added together to produce a more complicated signal. When I say, “added”, it means literally added together, summed. Fourier Analysis has been rightfully called one of the most important discoveries in all of electrical engineering.
DIY Bandlimited Wavetable
In generating a sawtooth wavetable then, we need to perform the addition to make the new wavetable and sample it how ever many times we would like. In my synth, I’ve toyed with various wavetable sizes, but currently, the wavetable has 128 samples in it. If you’re making a computer synth, then storing thousands of sample won’t be a problem, but in the hardware world, thousands of samples can get pricey. The more samples, the less noise there will be in the signal, but noise can also be reduced using interpolation (I’ll explain in another post).
A digital sine wave at a certain frequency has the formula:
x(frequency,t) = sine(2*pi*frequency*t)
where t is a sample instant. If we have 128 samples, then it is 0/128, 1/128, 2/128,…. to build a sine wavetable.
Then, to build a sawtooth wavetable, the formula for each sample looks like this:
Wavetable entry 1
x(1) = (1/1)sin(2*pi*1*1/128) + (1/2)sin(2*pi*2*1/128) +(1/3)sin(2*pi*3*1/128)+….
Generically, it is:
x(n)= sum (f) from 0 to Nyquist Rate of (1/f)sin(2*pi*f*n/(Number of samples))
where n ranges from 1 to the number of samples.
There’s one last thing. When generating signals with fast transitions, we have to apply a little bit of filtering during the construction of the wavetable. The Gibbs Phenomenon is caused by the sharp cutoff at the Nyquist Rate of the summation of the partial factors, the individual frequency components, of the wavetable we are building. The fact that we stop abruptly at the Nyquist Rate causes ripples in the pass band. This is noticeable when you build a wavetable that you’ll see ripples in the wavetable near sharp transitions. It is very audible in the signal. So, each sample must be multiplied by a factor to low-pass filter the array, reducing the amplitude of the higher partials as the summation approaches the Nyquist Rate. Each sample should be multiplied by:
m = cos^2((current_partial)-1)*(pi/(2*total_number_of_partials)
One Wavetable is Never Enough
Now, the final piece of the puzzle is that we will need more than one wavetable for our sawtooth synthesis. Let’s say I made a wavetable with sixteen harmonics in it, that is in the formula above, I summed it out to the sixteenth factor. If my sample rate is 32000Hz, my Nyquist rate is 16000Hz. So if I played it back at 1000Hz, the highest harmonic in the signal would be 16 times 1000Hz, or 16000Hz, not higher than the Nyquist Rate. But if I played it back at 2000Hz, the highest harmonic would be 16 times 2000Hz, or 32000Hz, and I would get aliasing because my signal contains frequencies higher than the Nyquist Rate. So, I have to make a series of wavetables, one for each note frequency in the scale, or for a range of frequencies. If I make a wavetable that will not cause aliasing if played back at a certain frequency, then I can play that wavetable at any lower frequency but not at any higher frequency. In reality there is some leeway because of the exponential decrease in the amplitude of higher harmonics, but to be safe, a wavetable should be played only at lower frequencies. In my synthesizer, I’ve played with one wavetable for two notes, four notes, and eight notes. The problem can be that when you cross over to a lower wavetable, the sudden increase in the number of harmonics is noticeable. This becomes a problem particularly during a pitch bend. One solution is taking values from more than one wavetable and averaging them together during playback. If computation time is short, then one table for two notes works. You’ll have to experiment. To some extent it depends on the sound you’re trying to generate.
I’ve written a C++ program which calculates a wavetable based on this formula. It generates a sawtooth wavetable that has 64 tables, one for two notes, with each wavetable containing 128 samples.
It is calculated based on a synthesizer sample rate of 32768Hz. This rate works well because it’s a power of 2, making calculations based on it easy in a binary world where shifting left and shifting right is the same as multiplying and dividing by 2.
So, how do you play back your wavetable? It’s actually very simple. Start with an accumulator, a place to store a number which you are going to increment. Every time a sample is required, add the frequency of the waveform you are trying to generate to the accumulator. Now reference the accumulator value to the wavetable by division. In my case, I have a 128 point wavetable, and a sample frequency of 32768Hz. So, I divide my accumulator by 32768/128 = 256. In binary math, that’s a shift right by 8. Then you just output the sample that this number references. For example, I want to make a 1000Hz wave. The accumulator starts at 0. That’s easy. The first sample is 0. When the next sample is needed, after 1/32768 seconds, I add 1000 to the accumulator. Now I have 1000 in there. I divide by 256 by shifting right by 8. I get 3.9 which in this case is getting truncated to 3. I look for the third value in my wavetable et voila, my second sample. When I need the next sample after another 1/32768 seconds, I add 1000 again. Now, I have 2000…divide by 256…equals 7…look in the wavetable and get the seventh value. And on and on. When my accumulator gets over 32768, I simply subtract 32768 from the accumulator and keep going. And that’s wavetable synthesis in a nutshell. Give it a try!