Band limiting

My synthesizer uses wavetables to generate sound. These are nothing more than arrays containing amplitude values that represent the cycle of a single waveform. It's a computationally cheap and theoretically simple way to create oscillators. All one has to do to pitch these waves is increase the speed at which they are read. To get a note an octave higher, read every other index of the table, two octaves: every fourth index, and so on. It's simple enough to interpolate between indices to represent non-integer increments like 22.3254 too.

This is good enough for sine waves, which contain no harmonic content, but naive implementations of other common waveforms like squares and saws will run into a problem when they are read back at higher speeds: aliasing.

A harmonic is an integer multiple of a fundamental pitch. A harmonically complex waveform played at 440hz might also contain sinusoidal components at 880hz, 1320hz, 1760hz, 2200hz, stretching out to infinity at diminishing amplitudes. A saw wave contains harmonics of every multiple, while a square wave just features the oddly-numbered ones.

This means that a table representing a mathematically perfect square wave

-1, -1, -1, -1, 1, 1, 1, 1 ...

actually contains a multitude of sine frequencies, far beyond the desired pitch, that are also pitched up when the wavetable is read faster. The problem occurs when these harmonics are pitched at or above the Nyquist frequency, which is half the sample rate of the digital synth. These high pitched sinusoids can no longer be accurately tracked and are reflected back into the signal at improper frequencies, resulting in nasty distortion known as aliasing.

Highly pitched wavetables therefore must contain less harmonic content. By replacing one wavetable with a series of progressively simpler ones, perhaps octave by octave, we can avoid aliasing.

Band limited tables for square waves

I decided to use this first wavetable for MIDI notes 31 and below. You can see how it resembles a fairly typical square wave.

square wave 1

I then generated a new table for each successive octave, containing progressively less harmonics.

square wave 2 square wave 3 square wave 4 square wave 5 square wave 6 square wave 7 square wave 8

The highest octave actually degenerates into a pure sine.

square wave 9

Band limited tables for sawtooth waves

The saw wave charts a similar course.

saw wave 1 saw wave 2 saw wave 3 saw wave 4 saw wave 5 saw wave 6 saw wave 7 saw wave 8 saw wave 9

Band limits per note

Assuming a sample rate of 48,000hz and a generous wavetable size of 4096 samples, here are the pitches, wavetable increments, and harmonic limits of every note.

┌─────────┬─────────┬─────────┬────────────┐
│MIDI note│frequency│increment│max harmonic│
├─────────┼─────────┼─────────┼────────────┤
│  0      │ 8.1758  │0.697668 │2935        │
│  1      │8.66196  │0.739154 │2770        │
│  2      │9.17702  │0.783106 │2615        │
│  3      │9.72272  │0.829672 │2468        │
│  4      │10.3009  │0.879007 │2329        │
│  5      │10.9134  │0.931275 │2199        │
│  6      │11.5623  │0.986652 │2075        │
│  7      │12.2499  │ 1.04532 │1959        │
│  8      │12.9783  │ 1.10748 │1849        │
│  9      │  13.75  │ 1.17333 │1745        │
│ 10      │14.5676  │  1.2431 │1647        │
│ 11      │15.4339  │ 1.31702 │1555        │
│ 12      │16.3516  │ 1.39534 │1467        │
│ 13      │17.3239  │ 1.47831 │1385        │
│ 14      │ 18.354  │ 1.56621 │1307        │
│ 15      │19.4454  │ 1.65934 │1234        │
│ 16      │20.6017  │ 1.75801 │1164        │
│ 17      │21.8268  │ 1.86255 │1099        │
│ 18      │23.1247  │  1.9733 │1037        │
│ 19      │24.4997  │ 2.09064 │ 979        │
│ 20      │25.9565  │ 2.21496 │ 924        │
│ 21      │   27.5  │ 2.34667 │ 872        │
│ 22      │29.1352  │ 2.48621 │ 823        │
│ 23      │30.8677  │ 2.63404 │ 777        │
│ 24      │32.7032  │ 2.79067 │ 733        │
│ 25      │34.6478  │ 2.95661 │ 692        │
│ 26      │36.7081  │ 3.13242 │ 653        │
│ 27      │38.8909  │ 3.31869 │ 617        │
│ 28      │41.2034  │ 3.51603 │ 582        │
│ 29      │43.6535  │  3.7251 │ 549        │
│ 30      │46.2493  │ 3.94661 │ 518        │
│ 31      │48.9994  │ 4.18128 │ 489        │
│ 32      │51.9131  │ 4.42992 │ 462        │
│ 33      │     55  │ 4.69333 │ 436        │
│ 34      │58.2705  │ 4.97241 │ 411        │
│ 35      │61.7354  │ 5.26809 │ 388        │
│ 36      │65.4064  │ 5.58135 │ 366        │
│ 37      │69.2957  │ 5.91323 │ 346        │
│ 38      │73.4162  │ 6.26485 │ 326        │
│ 39      │77.7817  │ 6.63738 │ 308        │
│ 40      │82.4069  │ 7.03205 │ 291        │
│ 41      │87.3071  │  7.4502 │ 274        │
│ 42      │92.4986  │ 7.89321 │ 259        │
│ 43      │97.9989  │ 8.36257 │ 244        │
│ 44      │103.826  │ 8.85983 │ 231        │
│ 45      │    110  │ 9.38667 │ 218        │
│ 46      │116.541  │ 9.94483 │ 205        │
│ 47      │123.471  │ 10.5362 │ 194        │
│ 48      │130.813  │ 11.1627 │ 183        │
│ 49      │138.591  │ 11.8265 │ 173        │
│ 50      │146.832  │ 12.5297 │ 163        │
│ 51      │155.563  │ 13.2748 │ 154        │
│ 52      │164.814  │ 14.0641 │ 145        │
│ 53      │174.614  │ 14.9004 │ 137        │
│ 54      │184.997  │ 15.7864 │ 129        │
│ 55      │195.998  │ 16.7251 │ 122        │
│ 56      │207.652  │ 17.7197 │ 115        │
│ 57      │    220  │ 18.7733 │ 109        │
│ 58      │233.082  │ 19.8897 │ 102        │
│ 59      │246.942  │ 21.0724 │  97        │
│ 60      │261.626  │ 22.3254 │  91        │
│ 61      │277.183  │ 23.6529 │  86        │
│ 62      │293.665  │ 25.0594 │  81        │
│ 63      │311.127  │ 26.5495 │  77        │
│ 64      │329.628  │ 28.1282 │  72        │
│ 65      │349.228  │ 29.8008 │  68        │
│ 66      │369.994  │ 31.5729 │  64        │
│ 67      │391.995  │ 33.4503 │  61        │
│ 68      │415.305  │ 35.4393 │  57        │
│ 69      │    440  │ 37.5467 │  54        │
│ 70      │466.164  │ 39.7793 │  51        │
│ 71      │493.883  │ 42.1447 │  48        │
│ 72      │523.251  │ 44.6508 │  45        │
│ 73      │554.365  │ 47.3058 │  43        │
│ 74      │ 587.33  │ 50.1188 │  40        │
│ 75      │622.254  │  53.099 │  38        │
│ 76      │659.255  │ 56.2564 │  36        │
│ 77      │698.456  │ 59.6016 │  34        │
│ 78      │739.989  │ 63.1457 │  32        │
│ 79      │783.991  │ 66.9006 │  30        │
│ 80      │830.609  │ 70.8787 │  28        │
│ 81      │    880  │ 75.0933 │  27        │
│ 82      │932.328  │ 79.5586 │  25        │
│ 83      │987.767  │ 84.2894 │  24        │
│ 84      │ 1046.5  │ 89.3015 │  22        │
│ 85      │1108.73  │ 94.6117 │  21        │
│ 86      │1174.66  │ 100.238 │  20        │
│ 87      │1244.51  │ 106.198 │  19        │
│ 88      │1318.51  │ 112.513 │  18        │
│ 89      │1396.91  │ 119.203 │  17        │
│ 90      │1479.98  │ 126.291 │  16        │
│ 91      │1567.98  │ 133.801 │  15        │
│ 92      │1661.22  │ 141.757 │  14        │
│ 93      │   1760  │ 150.187 │  13        │
│ 94      │1864.66  │ 159.117 │  12        │
│ 95      │1975.53  │ 168.579 │  12        │
│ 96      │   2093  │ 178.603 │  11        │
│ 97      │2217.46  │ 189.223 │  10        │
│ 98      │2349.32  │ 200.475 │  10        │
│ 99      │2489.02  │ 212.396 │   9        │
│100      │2637.02  │ 225.026 │   9        │
│101      │2793.83  │ 238.406 │   8        │
│102      │2959.96  │ 252.583 │   8        │
│103      │3135.96  │ 267.602 │   7        │
│104      │3322.44  │ 283.515 │   7        │
│105      │   3520  │ 300.373 │   6        │
│106      │3729.31  │ 318.234 │   6        │
│107      │3951.07  │ 337.158 │   6        │
│108      │4186.01  │ 357.206 │   5        │
│109      │4434.92  │ 378.447 │   5        │
│110      │4698.64  │  400.95 │   5        │
│111      │4978.03  │ 424.792 │   4        │
│112      │5274.04  │ 450.051 │   4        │
│113      │5587.65  │ 476.813 │   4        │
│114      │5919.91  │ 505.166 │   4        │
│115      │6271.93  │ 535.204 │   3        │
│116      │6644.88  │ 567.029 │   3        │
│117      │   7040  │ 600.747 │   3        │
│118      │7458.62  │ 636.469 │   3        │
│119      │7902.13  │ 674.315 │   3        │
│120      │8372.02  │ 714.412 │   2        │
│121      │8869.84  │ 756.893 │   2        │
│122      │9397.27  │ 801.901 │   2        │
│123      │9956.06  │ 849.584 │   2        │
│124      │10548.1  │ 900.103 │   2        │
│125      │11175.3  │ 953.626 │   2        │
│126      │11839.8  │ 1010.33 │   2        │
│127      │12543.9  │ 1070.41 │   1        │
└─────────┴─────────┴─────────┴────────────┘

Calculating the harmonic limit

The table above was rendered using the following J functions.

lowestFrequency =: 8.1757989156
pitch =: lowestFrequency * 2^%&12
wavetableIncrement =: (4096 % 48000)&*@pitch
maxHarmonic =: <.@(0.5&% @ %&48000)@pitch

The max harmonic is found with a simple equation.

band limit equation

Where fs is the sample rate (48,000hz) and f is the pitch of a note. This means that MIDI note 69, pitched at 440hz, should not contain more than 54 harmonics.

   maxHarmonic 69

54

Additive synthesis

Actually creating band limited waveforms is also fairly simple. The equation for a square wave is as follows.

square wave equation

A saw wave is generated in a similar manner.

saw wave equation

All complex waves are composed of sines. Just render a sine wave array for each harmonic frequency in the wave's series, up to the desired limit. The amplitude in each of these sines diminishes by its own frequency; every value in the third harmonic's sine is divided by 3 for instance. The sum of these weighted harmonic arrays will yield a complex wave like the ones in the graphs above.

I wrote some J code to do this.

sine =: 1 o. 2p1&*@% * (]i.)
harmonic =: %~ sine&4096
odds =: $&1 0 # 1 + i.
all =: 1 + i.
squareScale =: (3.35 % (o.1))&*
sawScale =: (1.7 % (o.1))&*

Running harmonic on the 0 rank of every odd number up to 7 yields a 4x4096 matrix: the individual sinusoid components in a square wave with four harmonics.

   $ harmonic "0 odds 7

4 4096

Summing this matrix up and applying squareScale results in an actual square wave's table. Please note that my scaling is a bit different from the equations above. I wanted all amplitudes to fit between [-1,1].

   squareScale +/ harmonic "0 odds 7

0.00654291 0.0130855 0.0196275 ...

Running this procedure against odds n, where n is the harmonic limit, will result in an appropriately band limited wave table. The same procedure can be performed against all for a saw wave. I scaled my wavetables along the following band limits.

octaves =: 31 43 55 67 79 91 103 115 127
bandLimits =: maxHarmonic octaves

Conclusion

There are many other ways to avoid aliasing, such as using a Fourier transform, but I found this to be easiest to understand. Now I just have to integrate it with my synthesizer.