Custom DSP
Resonon lets you write audio effects and instruments directly in the language. Custom DSP code runs at the audio sample rate with zero overhead beyond the math you write — no plugins, no external tools.
Custom Effects
Section titled “Custom Effects”A dsp effect processes audio in real time. The simplest effect is a gain control:
dsp effect Gain { param level: 1.0 range(0, 2);
fn process(left, right) -> (out_l, out_r) { return (left * level, right * level); }}The process function runs once per audio sample. It receives the current left and right input and returns the processed output.
Load it onto a track like any built-in effect:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.load_instrument(Sampler(Kit("cr78")));drums << [bd sd bd sd];drums.load_effect(Gain());PLAY;Parameters
Section titled “Parameters”param declares a controllable value with a default and an optional range:
dsp effect Tremolo { param speed: 4.0 range(0.1, 20); param depth: 0.5 range(0, 1); state phase: 0.0;
fn process(left, right) -> (out_l, out_r) { phase = fract(phase + speed * INV_SR); let mod = 1.0 - depth * (0.5 + 0.5 * sin(phase * TWOPI)); return (left * mod, right * mod); }}
let trem = Tremolo();drums.load_effect(trem);Parameters are accessible as plain identifiers inside process. From outside, modulate them with signals:
use "std/signals" { Sine };
trem.Speed << Sine(0.25).range(2, 8); // Vary speed with an LFOtrem.Depth << 0.7; // Static valuestate variables persist across samples — use them for anything that accumulates over time:
dsp effect EnvFollower { param attack: 0.01 range(0.001, 0.5); param release: 0.1 range(0.01, 2.0); state env: 0.0;
fn process(left, right) -> (out_l, out_r) { let input_level = abs(left) + abs(right); if input_level > env { env = env + (input_level - env) * attack; } else { env = env + (input_level - env) * release; } let gain = 1.0 / (1.0 + env * 4.0); return (left * gain, right * gain); }}Buffers
Section titled “Buffers”buffer declares a fixed-size array for delay lines, wavetables, or any sample-indexed storage. Indices wrap automatically with modulo:
dsp effect Echo { param time: 0.25 range(0.001, 1.0); param feedback: 0.5 range(0, 0.99); param mix: 0.4 range(0, 1); buffer buf_l(48000); buffer buf_r(48000); state wp: 0;
fn process(left, right) -> (out_l, out_r) { let delay_samples = time * SR; let rp = wp + 48000.0 - delay_samples; let delayed_l = buf_l[rp]; let delayed_r = buf_r[rp]; buf_l[wp] = left + delayed_l * feedback; buf_r[wp] = right + delayed_r * feedback; wp = (wp + 1.0) % 48000.0; return (left + delayed_l * mix, right + delayed_r * mix); }}SR is the sample rate (e.g. 48000). INV_SR is 1.0 / SR.
Helper Functions
Section titled “Helper Functions”Define local helpers inside the effect block. They can read and write the parent’s state and buffers:
dsp effect SoftClipper { param drive: 2.0 range(1, 10); param mix: 0.5 range(0, 1);
fn soft_clip(x) -> out { return x / (1.0 + abs(x)); }
fn process(left, right) -> (out_l, out_r) { let clipped_l = soft_clip(left * drive); let clipped_r = soft_clip(right * drive); return (left * (1.0 - mix) + clipped_l * mix, right * (1.0 - mix) + clipped_r * mix); }}Built-in Filters
Section titled “Built-in Filters”SVF (state variable filter) functions are available in any DSP block:
| Function | Description |
|---|---|
svf_lp(input, cutoff, resonance) | Lowpass |
svf_hp(input, cutoff, resonance) | Highpass |
svf_bp(input, cutoff, resonance) | Bandpass |
svf_notch(input, cutoff, resonance) | Notch |
svf_allpass(input, cutoff, resonance) | Allpass |
svf_peak(input, cutoff, resonance) | Peak/bell |
dsp effect AutoFilter { param cutoff: 2000 range(20, 20000); param resonance: 1.5 range(0.5, 10); state phase: 0.0;
fn process(left, right) -> (out_l, out_r) { phase = fract(phase + 0.5 * INV_SR); let lfo = 0.5 + 0.5 * sin(phase * TWOPI); let freq = 200.0 + lfo * cutoff; return (svf_lp(left, freq, resonance), svf_lp(right, freq, resonance)); }}Custom Instruments
Section titled “Custom Instruments”A dsp instrument is a polyphonic synthesizer. The render function runs once per sample per active voice:
dsp instrument SineOsc { voice state phase: 0.0;
fn render(note, velocity, gate) -> (out_l, out_r) { let freq = mtof(note); phase = fract(phase + freq * INV_SR); let sample = sin(phase * TWOPI) * velocity * gate; return (sample, sample); }}
let synth = AudioTrack("synth");synth.load_instrument(SineOsc());synth << [C4 E4 G4 C5];PLAY;| Parameter | Type | Description |
|---|---|---|
note | f64 | MIDI note number (supports fractional values for microtonal) |
velocity | f64 | Note velocity, normalized 0.0–1.0 |
gate | f64 | 1.0 while note held, 0.0 after note-off |
mtof(note) converts a MIDI note number to frequency in Hz. voice state variables are independent per note and reset on each note-on.
Envelope and Filter
Section titled “Envelope and Filter”Combine oscillator, envelope, and filter for a more complete synth:
dsp instrument FilteredSynth { param attack: 0.01 range(0.001, 1.0); param release: 0.2 range(0.01, 2.0); param cutoff: 4000 range(20, 20000);
voice state phase: 0.0; voice state env: 0.0;
fn render(note, velocity, gate) -> (out_l, out_r) { let freq = mtof(note); phase = fract(phase + freq * INV_SR); let osc = sin(phase * TWOPI);
let target = gate * velocity; if gate > 0.0 { env = env + (target - env) * attack; } else { env = env + (target - env) * release; }
let filtered = svf_lp(osc * env, cutoff, 1.5); return (filtered, filtered); }}
let lead = AudioTrack("lead");lead.load_instrument(FilteredSynth());lead << [C4 _ E4 _ G4 _ C5 _];Parameter Control
Section titled “Parameter Control”Set instrument parameters from outside the DSP block:
let synth = FilteredSynth();synth.params(); // Print all parameters
synth.set_param("cutoff", 2000); // Set by namesynth.set_param("attack", 0.1) .set_param("release", 0.5); // ChainablePulse Oscillator with Helper
Section titled “Pulse Oscillator with Helper”dsp instrument PulseOsc { param duty: 0.5 range(0.01, 0.99); voice state phase: 0.0;
fn pulse(p, d) -> out { if p < d { return 1.0; } else { return 0.0 - 1.0; } }
fn render(note, velocity, gate) -> (out_l, out_r) { let freq = mtof(note); phase = fract(phase + freq * INV_SR); let sample = pulse(phase, duty) * velocity * gate; return (sample, sample); }}
let pulse = AudioTrack("pulse");pulse.load_instrument(PulseOsc());pulse << [C4 E4 G4 C5];DSP Builtins Reference
Section titled “DSP Builtins Reference”These functions are available inside any dsp effect, dsp instrument, or dsp signal block:
| Category | Functions |
|---|---|
| Math | sin, cos, tanh, abs, sqrt, pow, clamp, min, max, floor, fract |
| Filters | svf_lp, svf_hp, svf_bp, svf_notch, svf_allpass, svf_peak |
| Conversion | mtof (MIDI to frequency) |
| Constants | SR (sample rate), INV_SR (1/SR), TWOPI (2π) |
| Random | noise (white noise sample) |
Next Steps
Section titled “Next Steps”- MIDI I/O — Connect to external synths and controllers
- DSP Effects Reference — Full effect API, multi-input effects, sidechain
- DSP Instruments Reference — Voice management, buffers, advanced synthesis
- DSP Functions & Objects — Reusable DSP components