Bandlimited Wavetable Synthesis

Sin Wavetable
I’ve discussed the phenomenon of aliasing in digital synthesis in several previous posts.  I described the phenomena, it’s source and what it sounds like.  There are many, many solutions to the problem of aliasing in digital synthesis.  A lot of them rely on performing sequences of calculations, multiplications and additions to implement various filtering methods or methods of synthesis which theoretically do not create aliasing, like additive synthesis. In my Rockit 8 Bit Synth, I don’t have the luxury of loads of extra cycles to throw at calculations, so I need something that can reduce the aliasing without requiring boatloads of clock cycles.  I settled on bandlimited wavetable synthesis.  With it, I have reduced the aliasing to a point that I can tolerate and probably further optimize.  Let’s discuss how it works.  Also, click through for source code for generating a wavetable and a sample wavetable from my synthesizer.

 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[0] = 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.

Aliasing Example

 

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

 

Fourier Spectra of Three Signals

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).

Sine Wave

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.

Sawtooth Wave

Sawtooth Wave

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.

Gibbs Phenomenon

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.

Sample Code

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.

C++ Program

C++ Program for Generating Sawtooth Wavetable 64×128

Wavetable

SawtoothWavetable

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.

Wavetable Playback

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!

Posted in General Electronics, Rockit and tagged , , , , , , , , , .

7 Comments

  1. Pingback: 8 Bit Synth: MATLAB Bandlimited Wavetable Simulation and Aliasing « Hackme Electronics

  2. Great, clear explanation, thanks. Far and away the most understandable explanation I’ve found.

    Quick question: is there some relationship between the number of partials (16,000 in your example) and either the sample rate and/or the number of samples in the table? Put another way, how did you arrive at 16,000 for the number of partials – is it just Nyquist?

    Thanks

    • Thanks.

      Yes. The Nyquist has to be the upper bound for your partials. Any summing of partials has to stop before the Nyquist frequency. More partials sounds better though, so the more the merrier. For me, my sample rate is 32kHz, so my Nyquist frequency was 16kHz. I made a 32 wavetables for each sound, so each wavetable works for 4 notes. The highest note in the wavetable sets the limit for the number of partials. 1Hz = 16000 partials, 2Hz = 8000 partials, 4Hz = 4000 partials….

      • Cheers, I got it working very nicely. I’m working on a computer, not embedded, so I am using 44.1k sample rate, 22.05k max partials and 4410 length tables with one table per note. It sounds absolutely fantastic and it’s super fast. The luxury of all that RAM! Thanks again.

        • Awesome. It’s it great when it all comes together. I’m looking at ways to get that much memory in hardware. A lot of software stuff, from what I understand, calculates wavetables on the fly as they’re called up to avoid being a massive memory hog. I don’t spend much time up in that world, but it seems to be common.

  3. Pingback: AUD210 Blog #3 – Synthesis | Gareth Griffiths | SAE Student

Leave a Reply