synth-datagen
Wavetables module
The wavetables module generates waveform lookup tables for oscillator implementations. It produces both naive (mathematically ideal) waveforms and band-limited variants suitable for alias-free synthesis.
Selectors
| Selector | Output array | Type | Description |
|---|---|---|---|
sine |
{id}_sine[N] |
1-D | One cycle of a sine wave |
square |
{id}_square[N] |
1-D | One cycle of a naive square wave |
triangle |
{id}_triangle[N] |
1-D | One cycle of a naive triangle wave |
sawtooth |
{id}_sawtooth[N] |
1-D | One cycle of a naive sawtooth wave |
blsquare |
{id}_blsquare[R][C] |
2-D | Band-limited square wave, one row per octave |
bltriangle |
{id}_bltriangle[R][C] |
2-D | Band-limited triangle wave, one row per octave |
blsawtooth |
{id}_blsawtooth[R][C] |
2-D | Band-limited sawtooth wave, one row per octave |
Where {id} is the identifier from the configuration, N is samples_per_cycle, R is the number of octaves, and C is samples_per_cycle.
Parameters
| Parameter | Required by | Type | Description |
|---|---|---|---|
samples_per_cycle |
all | int |
Number of samples in one waveform cycle |
wavetables_sample_amplitude |
all | float64 |
Peak amplitude of the waveform |
wavetables_sample_scalar_type |
all | string |
C type for sample values (e.g., int16_t) |
data_attributes |
-- | []string |
Optional C attributes (e.g., PROGMEM) |
sample_rate |
blsquare, bltriangle, blsawtooth |
float64 |
Sample rate in Hz, used to compute harmonics |
a4_frequency |
-- | float64 |
Reference frequency for A4 (defaults to 440.0 Hz) |
wavetables_bandlimited_omit_high_octaves |
-- | int |
Number of highest octaves to exclude from band-limited tables |
Parameters are resolved from global_parameters or per-module parameters overrides. The parameter resolver checks for wavetables_-prefixed keys first, then falls back to unprefixed keys.
Naive waveforms
The naive waveforms compute one cycle of samples_per_cycle samples. Each sample is computed mathematically and scaled to the range [-sample_amplitude, +sample_amplitude].
Sine computes amplitude * sin(2 * pi * i / samples_per_cycle) for each sample index i.
Square computes amplitude * (1 - 2 * floor(2 * i / samples_per_cycle)), producing a waveform that transitions between +amplitude and -amplitude at the midpoint.
Triangle computes amplitude * (2/pi) * asin(sin(2 * pi * i / samples_per_cycle)), producing a linear ramp between peaks.
Sawtooth computes amplitude * (1 - 2 * i / samples_per_cycle), producing a linear ramp from +amplitude to -amplitude.
Example: naive waveform usage
Given a configuration producing oscillator_sine with samples_per_cycle = 0x0200 (512) and int16_t scalar type:
#include "oscillator-data.h"
// oscillator_sine is:
// static const int16_t oscillator_sine[512] PROGMEM = { ... };
// #define oscillator_sine_len 512
int16_t read_sine_sample(uint16_t phase) {
uint16_t index = phase >> 7; // map 16-bit phase to 9-bit index
return oscillator_sine[index % oscillator_sine_len];
}
Band-limited waveforms
The band-limited waveforms use BLIT (Band-Limited Impulse Train) synthesis to produce alias-free waveform tables. A separate table is generated for each MIDI octave (128 MIDI notes / 12 notes per octave = 11 octaves, ceiling), minus any octaves excluded by bandlimited_omit_high_octaves.
For each octave, the algorithm:
- Computes a representative frequency for the octave as the geometric mean of the lowest and highest note frequencies in that octave
- Determines the number of harmonics that fit below the Nyquist frequency (
sample_rate / 2) - Generates a BLIT kernel:
sin(pi * x * harmonics) / (harmonics * sin(pi * x))over one cycle - Integrates the BLIT to produce a square wave (for
blsquareand as an intermediate forbltriangle) - Integrates the square wave to produce a triangle wave (for
bltriangle), with a quarter-cycle phase shift - Integrates an offset BLIT to produce a sawtooth wave (for
blsawtooth) - Normalizes each resulting waveform to the full
[-sample_amplitude, +sample_amplitude]range
The result is a 2-D array where each row contains the waveform for one octave. At higher octaves, fewer harmonics are included, progressively smoothing the waveform to avoid aliasing.
Example: band-limited waveform usage
Given a configuration producing oscillator_blsquare with 10 octaves and 512 samples per cycle:
#include "oscillator-data.h"
// oscillator_blsquare is:
// static const int16_t oscillator_blsquare[10][512] PROGMEM = { ... };
// #define oscillator_blsquare_rows 10
// #define oscillator_blsquare_cols 512
int16_t read_blsquare_sample(uint8_t midi_note, uint16_t phase) {
uint8_t octave = midi_note / 12;
if (octave >= oscillator_blsquare_rows)
octave = oscillator_blsquare_rows - 1;
uint16_t index = phase >> 7; // map phase to table index
return oscillator_blsquare[octave][index % oscillator_blsquare_cols];
}
Example configuration
global_parameters:
sample_rate: 48000
samples_per_cycle: 0x0200
wavetables_sample_amplitude: 0x01ff
wavetables_sample_scalar_type: int16_t
wavetables_bandlimited_omit_high_octaves: 1
output:
firmware/oscillator-data.h:
includes:
avr/pgmspace.h: true
stdint.h: true
modules:
oscillator:
name: wavetables
selectors:
- sine
- blsquare
- bltriangle
- blsawtooth
parameters:
data_attributes:
- PROGMEM
This configuration generates oscillator_sine as a 1-D array of 512 int16_t values with amplitude range [-511, +511], and oscillator_blsquare, oscillator_bltriangle, and oscillator_blsawtooth as 2-D arrays with 10 octave rows (11 minus 1 omitted) of 512 samples each. All arrays carry the PROGMEM attribute for AVR flash storage.