synth-datagen
Notes module
The notes module generates MIDI note-related lookup tables: phase step values for oscillator frequency control, note name strings, and octave number mappings. All tables cover the full 128-note MIDI range (notes 0 through 127).
Selectors
| Selector | Output array | Type | Description |
|---|---|---|---|
phase_steps |
{id}_phase_steps[128] |
1-D | Phase increment per audio sample for each MIDI note |
names |
{id}_names[128] |
1-D (string) | Note name strings (e.g., "C4", "A#5") |
octaves |
{id}_octaves[128] |
1-D (uint8_t) |
Octave number for each MIDI note (0 through 10) |
Where {id} is the identifier from the configuration.
Parameters
| Parameter | Required by | Type | Description |
|---|---|---|---|
a4_frequency |
-- | float64 |
Reference frequency for A4 (defaults to 440.0 Hz) |
sample_rate |
phase_steps |
float64 |
Sample rate in Hz |
samples_per_cycle |
phase_steps |
int |
Number of samples in one waveform cycle |
notes_phase_steps_scalar_type |
phase_steps |
string |
C type for phase step values (e.g., uint32_t) |
notes_phase_steps_fractional_bit_width |
-- | uint8 |
Fractional bits for fixed-point phase steps |
data_attributes |
-- | []string |
Optional C attributes |
Phase steps
The phase_steps selector generates a 128-element array where each entry is the phase increment to add to a phase accumulator for each audio sample to produce the corresponding MIDI note's frequency.
For each MIDI note number n, the frequency is computed using equal temperament:
freq[n] = a4_frequency * 2^((n - 69) / 12)
Where 69 is the MIDI note number for A4. The phase step is then:
step[n] = samples_per_cycle * freq[n] / sample_rate
This value represents how many wavetable samples to advance per audio sample. For a 512-sample wavetable at 48000 Hz sample rate, A4 (440 Hz) produces a phase step of approximately 512 * 440 / 48000 ≈ 4.693.
When notes_phase_steps_fractional_bit_width is set, the values are multiplied by 2^fractional_bit_width to produce fixed-point integers. With a 16-bit fractional width, the A4 phase step becomes approximately 4.693 * 65536 ≈ 307,626.
Example: phase step usage
#include "oscillator-data.h"
// notes_phase_steps is:
// static const uint32_t notes_phase_steps[128] = { ... };
// #define notes_phase_steps_len 128
//
// oscillator_sine is:
// static const int16_t oscillator_sine[512] PROGMEM = { ... };
// #define oscillator_sine_len 512
// Phase accumulator for the oscillator.
// High bits index into the wavetable, low bits are the fractional part.
static uint32_t phase_accumulator = 0;
// Advance the oscillator and return the current sample.
// midi_note: MIDI note number (0 to 127)
// Assumes 16-bit fractional phase steps.
int16_t oscillator_tick(uint8_t midi_note) {
phase_accumulator += notes_phase_steps[midi_note];
uint16_t index = (phase_accumulator >> 16) % oscillator_sine_len;
return oscillator_sine[index];
}
Note names
The names selector generates a 128-element array of note name strings following the standard naming convention: C, C#, D, D#, E, F, F#, G, G#, A, A#, B, combined with an octave number starting at -1.
| MIDI note | Name |
|---|---|
| 0 | C-1 |
| 12 | C0 |
| 60 | C4 |
| 69 | A4 |
| 127 | G9 |
The names are emitted as a char* string array:
static const char* notes_names[128] = {
"C-1", "C#-1", "D-1", ..., "G9",
};
#define notes_names_len 128
Octave numbers
The octaves selector generates a 128-element uint8_t array where each entry is the octave number for the corresponding MIDI note, computed as note / 12. This produces values from 0 (MIDI notes 0--11) to 10 (MIDI note 120--127).
Note
The octave numbering here starts at 0 (for MIDI notes 0--11), not at -1 as in the note names. This is because the octave values map directly to band-limited wavetable rows, where octave 0 corresponds to the first row.
static const uint8_t notes_octaves[128] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // MIDI 0-11
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // MIDI 12-23
...
};
#define notes_octaves_len 128
Example: octave lookup for band-limited wavetables
#include "oscillator-data.h"
// notes_octaves is:
// static const uint8_t notes_octaves[128] = { ... };
// #define notes_octaves_len 128
//
// 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(uint8_t midi_note, uint16_t phase) {
uint8_t octave = notes_octaves[midi_note];
if (octave >= oscillator_blsquare_rows)
octave = oscillator_blsquare_rows - 1;
uint16_t index = (phase >> 7) % oscillator_blsquare_cols;
return oscillator_blsquare[octave][index];
}
Example configuration
global_parameters:
sample_rate: 48000
samples_per_cycle: 0x0200
notes_phase_steps_scalar_type: uint32_t
notes_phase_steps_fractional_bit_width: 16
output:
firmware/oscillator-data.h:
includes:
stdint.h: true
modules:
notes:
name: notes
selectors:
- phase_steps
- octaves