17.6. Audio synthesis: oscillators, drums, filters, and effects
Module strudel_synth
17.6.1. Constants
- SAMPLE_RATE = 48000
SAMPLE_RATE:int const
- TWO_PI = 6.2831855f
TWO_PI:float const
- OSC_TURN_DOWN = 0.3f
OSC_TURN_DOWN:float const
- VOWEL_NUM_FORMANTS = 5
VOWEL_NUM_FORMANTS:int const
- VOWEL_MAKEUP_GAIN = 8f
VOWEL_MAKEUP_GAIN:float const
17.6.2. Enumerations
- OscType
- Values:
osc_sine = 0 - Pure sine wave.
osc_sawtooth = 1 - Band-limited sawtooth (poly-BLEP anti-aliased).
osc_square = 2 - Band-limited square wave (poly-BLEP anti-aliased).
osc_triangle = 3 - Triangle wave derived from the phase.
osc_supersaw = 4 - Unison stack of detuned band-limited sawtooths, stereo-spread.
osc_pink_noise = 5 - Pink noise via Paul Kellet’s 7-stage cascaded IIR.
osc_white_noise = 6 - Uniform white noise.
osc_unknown = 7 - Unrecognised sound name — used as a sentinel by to_osc_type.
17.6.3. Structures
- PinkNoiseState
struct PinkNoiseState
- FormantBiquad
- Fields:
a0 : double = 0lf - Feed-forward coefficient 0.
a1 : double = 0lf - Feed-forward coefficient 1 (0 for BPF).
a2 : double = 0lf - Feed-forward coefficient 2 (-a0 for BPF).
b1 : double = 0lf - Feedback coefficient 1.
b2 : double = 0lf - Feedback coefficient 2.
z1 : double = 0lf - Internal delay-line state 1.
z2 : double = 0lf - Internal delay-line state 2.
gain : float = 1f - Per-formant amplitude scaling applied to the filter output by the vowel filter.
active : int = 0 - Non-zero when the filter has valid coefficients and should process samples.
- VowelFilter
- Fields:
formants : FormantBiquad[5] - Five formant bandpass biquads processed in parallel and summed.
active : bool = false - True once the filter has been set up for a known vowel; otherwise
tickis bypassed by callers.
- FormantData
- Fields:
freqs : float[5] - Centre frequency of each formant in Hz.
gains : float[5] - Linear gain of each formant (formant 0 is usually the loudest).
qs : float[5] - Q (resonance) of each formant bandpass.
- VoiceFX
- Fields:
bc : ma_bitcrush - Bitcrusher state (bit-depth + sample-rate reduction).
ws : ma_waveshaper - Waveshaper state (drive-based soft distortion).
dj : ma_djfilter - DJ filter state (single-knob low-pass/high-pass blend).
bp : ma_bandpass - Bandpass filter state (centre + Q).
ph : ma_phaser - Phaser state (rate/depth/centre/sweep).
trem : ma_tremolo - Tremolo state (amplitude modulation rate/depth).
comp : ma_compressor - Compressor state (threshold/ratio/knee/attack/release).
has_crush : bool = false - Bitcrush is enabled and will be applied to the voice.
has_shape : bool = false - Waveshaper is enabled.
has_djf : bool = false - DJ filter is enabled (position != 0.5).
has_bpf : bool = false - Bandpass filter is enabled.
has_phaser : bool = false - Phaser is enabled.
has_tremolo : bool = false - Tremolo is enabled.
has_compressor : bool = false - Compressor is enabled.
active : bool = false - True if any of the above effects is enabled; skip fx_apply entirely when false.
- OscVoice
- Fields:
osc_type : OscType = strudel_synth::OscType.osc_sine - Which waveform this voice produces.
freq : float = 440f - Base frequency in Hz (before FM and supersaw detune).
phase : float = 0f - Main oscillator phase in [0, 1).
duration : float = 1f - Total note duration in seconds (release stage extends beyond this).
attack : float = 0.001f - ADSR attack time in seconds.
decay : float = 0.05f - ADSR decay time in seconds.
sustain : float = 0f - ADSR sustain level in [0, 1].
release_sec : float = 0.01f - ADSR release time in seconds.
samples_elapsed : int = 0 - Number of samples rendered so far; used to compute the envelope time.
finished : bool = false - Set to true when the ADSR has fully decayed; scheduler then retires the voice.
gain : float = 1f - Effective voice gain (event.gain * event.velocity).
pan : float = 0f - Stereo pan: -1 = full left, 0 = centre, +1 = full right.
cut : int = 0 - Cut group — non-zero values cut off any previous voice with the same cut value.
offset_frames : int = 0 - Sub-chunk onset delay in frames, consumed at the start of the first render chunk.
lpf_biquad : ma_sf2_biquad - Low-pass biquad filter state (initialised by osc_voice_init_filters).
hpf_biquad : ma_sf2_biquad - High-pass biquad filter state.
fm_depth : float = 0f - FM modulation index (0 disables FM).
fm_harmonicity : float = 1f - Ratio of modulator frequency to carrier frequency.
fm_mod_phase : float = 0f - FM modulator oscillator phase in [0, 1).
supersaw_phases : float[7] - Independent phases for each detuned sawtooth voice in the supersaw stack.
pink : PinkNoiseState - Pink-noise generator state (Paul Kellet’s 7-stage IIR).
white_seed : uint = 0x3039 - LCG seed used by the white-noise generator.
reverb_send : float = 0f - Send amount into the reverb bus (driven by Event.room).
chorus_send : float = 0f - Send amount into the chorus bus (driven by Event.chorus).
delay_send : float = 0f - Send amount into the delay bus (driven by Event.delay_amount).
orbit : int = 0 - Orbit index used by the scheduler to route this voice into a shared effect bus.
vowel_filter : VowelFilter - Optional vowel formant filter applied after the biquad filters.
fx : VoiceFX - Per-voice FX chain (bitcrush/waveshaper/DJ filter/bandpass/phaser/tremolo/compressor).
17.6.4. Pitch conversion
- note_to_freq(note: float): float
Convert a MIDI note number to frequency in Hz (A4 = 69 = 440 Hz, middle C = 60).
- Arguments:
note : float
17.6.5. Noise generators
- noise(seed: uint&): float
Linear congruential pseudo-random noise in [-1.0, 1.0]. Advances the seed in place.
- Arguments:
seed : uint&
17.6.6. Drum renderers
- render_bd(duration: float; gain: float): array<float>
Render an 808-style kick drum as a mono buffer at SAMPLE_RATE. Combines a pitched sine body with a fast pitch sweep, a bandpass beater click and a rimshot-like snap, then applies a short room.
- Arguments:
duration : float
gain : float
- render_cowbell(duration: float; gain: float): array<float>
Render a cowbell: two detuned square-wave tones through a narrow bandpass, with a second quieter strike 8 ms later.
- Arguments:
duration : float
gain : float
- render_cp(duration: float; gain: float): array<float>
Render a hand-clap: a sharp bandpassed noise burst (~1.1 kHz) with a metallic bright edge and a long room tail.
- Arguments:
duration : float
gain : float
- render_crash(duration: float; gain: float): array<float>
Render a crash cymbal: lower-pitched bell partials plus a broadband metallic wash with a medium-fast decay.
- Arguments:
duration : float
gain : float
- render_hh(duration: float; gain: float): array<float>
Render a closed hi-hat: metallic oscillator bank layered with a short tonal bell (~180 Hz), plus room.
- Arguments:
duration : float
gain : float
- render_oh(duration: float; gain: float): array<float>
Render an open hi-hat: the same metallic oscillator bank as hh but with a much slower decay.
- Arguments:
duration : float
gain : float
- render_ride(duration: float; gain: float): array<float>
Render a ride cymbal: two bell partials (~340/387 Hz) plus a metallic shimmer, with a long sustain.
- Arguments:
duration : float
gain : float
- render_rimshot(duration: float; gain: float): array<float>
Render a rimshot/side-stick: a short woody body (~200 Hz) plus a bandpassed noise snap and a high transient click.
- Arguments:
duration : float
gain : float
- render_sd(duration: float; gain: float): array<float>
Render a snare drum as a mono buffer: tonal body (~220/330 Hz) plus high-passed noise for the wires, with a short room tail.
- Arguments:
duration : float
gain : float
- render_tambourine(duration: float; gain: float): array<float>
Render a tambourine: high-passed noise with two narrow bandpass jingle peaks (~3.8 kHz and ~8.8 kHz) and a delayed second hit.
- Arguments:
duration : float
gain : float
- render_tom(duration: float; gain: float; base_freq: float): array<float>
Render a tom drum at base_freq with a BD-style body + beater click + impulse + resonant-head overtones and a short room. Used for tom_low (~80 Hz) and tom_high (~175 Hz).
- Arguments:
duration : float
gain : float
base_freq : float
17.6.7. Oscillator type
- to_osc_type(sound: string): OscType
Map a strudel sound name (“sine”, “sawtooth”, …) to an OscType; unknown names return osc_unknown.
- Arguments:
sound : string
17.6.8. Voice FX chain
- fx_apply(fx: VoiceFX; buf: array<float>; n_frames: int)
Apply the enabled effects to an interleaved stereo buffer in place. Order: crush -> shape -> djfilter -> bandpass -> phaser -> tremolo -> compressor (phaser/tremolo modulate the finished tone, compressor is last so it sees the post-FX signal).
- Arguments:
fx : VoiceFX
buf : array<float>
n_frames : int
- fx_apply_to_samples(event: Event; samples: array<float>; sample_rate: float)
Apply Event-driven effects to a pre-rendered stereo buffer as a one-shot. Effect state is local to this call, so effects reset every render; used for drum/sample voices that don’t stream.
- Arguments:
event : Event
samples : array<float>
sample_rate : float
- fx_init_from_event(fx: VoiceFX; event: Event; sample_rate: float)
Configure a VoiceFX chain from the relevant Event fields (crush/coarse/shape/djf/bpf/phaser/tremolo/compressor). Leaves everything disabled when no Event field is active, so the chain can stay at fx.active == false.
17.6.9. Oscillator voice
- osc_render_chunk(voice: OscVoice; output: array<float>; reverb_send_buf: array<float>; delay_send_buf: array<float>; chorus_send_buf: array<float>; chunkFrames: int)
Render one chunk of the oscillator voice additively into the output buffer and the reverb/delay/chorus send buffers. Hoists invariants out of the inner loop and dispatches to the per-oscillator-type render function.
- Arguments:
voice : OscVoice
output : array<float>
reverb_send_buf : array<float>
delay_send_buf : array<float>
chorus_send_buf : array<float>
chunkFrames : int
- osc_voice_init_filters(voice: OscVoice; lpf: float; hpf: float; lpq: float; hpq: float)
Configure the voice’s low-pass and high-pass biquads from Event lpf/hpf cutoffs and lpq/hpq resonances. Cutoffs <= 0 or >= Nyquist leave the corresponding filter inactive.
- Arguments:
voice : OscVoice
lpf : float
hpf : float
lpq : float
hpq : float
- osc_voice_init_supersaw(voice: OscVoice)
Seed the supersaw unison phases with randomised offsets so the detuned voices don’t start in phase.
- Arguments:
voice : OscVoice
17.6.10. Vowel filter
- formant_biquad_setup_bpf(bq: FormantBiquad; freq: float; q: float; sample_rate: float)
Compute bandpass coefficients for the given centre frequency and Q. Deactivates the filter if the cutoff is outside the usable range (near 0 or Nyquist).
- Arguments:
bq : FormantBiquad
freq : float
q : float
sample_rate : float
- formant_biquad_tick(bq: FormantBiquad; input: float): float
Process one sample through the biquad and return the output.
- Arguments:
bq : FormantBiquad
input : float
- vowel_filter_init(vf: VowelFilter; vowel: string)
Configure a VowelFilter for the named vowel, deactivating it if the vowel is unknown.
- Arguments:
vf : VowelFilter
vowel : string
- vowel_filter_tick(vf: VowelFilter; input: float): float
Process one sample through the vowel filter: sum the 5 parallel formant bandpass outputs and apply the makeup gain.
- Arguments:
vf : VowelFilter
input : float
- vowel_get_formant_data(vowel: string; data: FormantData): bool
Look up formant parameters for a vowel name (“a”, “e”, “i”, “o”, “u”, “ae”, “aa”, “oe”, “ue”, “y”, “uh”, “un”, “en”, “an”, “on”). Returns false if the vowel is unknown.
- Arguments:
vowel : string
data : FormantData
17.6.11. Voice rendering
- mono_to_stereo(mono: array<float>): array<float>
Upmix a mono buffer to interleaved stereo by duplicating each sample to both channels. Consumes mono (moves and frees it).
- Arguments:
mono : array<float>
17.6.11.1. render_event_stereo
- render_event_stereo(event: Event; duration_sec: float; bank: SampleBank): array<float>
Render an Event into stereo PCM, preferring a sample from the bank when present and falling back to synthesis. Returns raw stereo — gain and pan are applied later by the scheduler.
- Arguments:
event : Event
duration_sec : float
bank : SampleBank
- render_event_stereo(event: Event; duration_sec: float): array<float>