DSP Instruments
Quick Start
Section titled “Quick Start”dsp instrument MySynth { 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 = MySynth();let lead = AudioTrack("lead");lead.load_instrument(synth);lead << [c4 e4 g4 c5];Syntax
Section titled “Syntax”dsp instrument Name { param name: default range(min, max); voice state name: initial; state name: initial; buffer name(size);
fn render(note, velocity, gate) -> (out_l, out_r) { // DSP code }}
let inst = Name(); // Instantiate the instrumentdsp instrument is a top-level definition statement. The identifier after dsp instrument names the instrument type. Instantiate it with call syntax (Name()) to get a value you can load onto tracks. The name also serves as the slot name. You can rename after creation with .set_name("NewName").
All declarations are optional except the render function.
Render Function
Section titled “Render Function”The render function is called once per sample for each active voice.
fn render(note, velocity, gate) -> (out_l, out_r) { // ...}| Parameter | Type | Description |
|---|---|---|
note | f64 | MIDI note number — supports fractional values for microtonal pitches (e.g. 60.5) |
velocity | f64 | Note velocity, normalized to 0.0-1.0 |
gate | f64 | 1.0 while note is held, 0.0 after note-off |
The function must return a stereo tuple (out_l, out_r). Use gate for envelopes — it drops to 0.0 on note-off, allowing smooth release tails.
Voice State
Section titled “Voice State”voice state phase: 0.0;voice state env: 0.0;Per-voice mutable variables, reset to their initial value on each note-on. Use for anything that should be independent per voice: oscillator phase, envelope state, filter memory.
state master_vol: 1.0;Parameters
Section titled “Parameters”param cutoff: 4000 range(20, 20000);param attack: 0.01 range(0.001, 1.0);Parameters are shared across all voices and controllable from outside the DSP block. The range clause is optional. Inside the render function, parameters are accessed as plain identifiers.
Buffers
Section titled “Buffers”buffer delay_buf(4800);Declares a fixed-size array of f64 values in each voice’s state, initialized to zero. Access elements with indexing:
buf[write_pos] = sample; // writelet delayed = buf[read_pos]; // readIndices wrap with modulo — buf[size] is equivalent to buf[0]. Each voice gets its own copy of the buffer.
Helper Functions
Section titled “Helper Functions”DSP instruments can define local helper functions alongside the render function:
dsp instrument PluckSynth { param decay: 0.995 range(0.9, 0.9999); voice state phase: 0.0; voice state env: 1.0;
fn oscillator(note) -> out { let freq = mtof(note); phase = fract(phase + freq * INV_SR); return sin(phase * TWOPI); }
fn render(note, velocity, gate) -> (out_l, out_r) { let osc = oscillator(note); env = env * decay; let sample = osc * env * velocity; return (sample, sample); }}Local helpers can access the instrument’s state, voice state, buffer, and param variables. Top-level dsp fn and dsp object declarations let you share helpers across multiple instruments. See DSP Functions & Objects for the full reference.
Loading Instruments
Section titled “Loading Instruments”let track = AudioTrack("synth");track.load_instrument(synth);load_instrument replaces any previously loaded instrument on the track. The track receives MIDI note events from patterns and forwards them to the instrument.
Methods
Section titled “Methods”| Method | Description |
|---|---|
.name() | Returns the instrument name |
.param(path) | Get parameter value by name |
.set_param(path, value) | Set parameter value (chainable) |
.set_param_norm(path, value) | Set parameter using normalized 0–1 value (chainable) |
.params() | Print all parameters as a table (range, default, value) |
synth.params();synth.set_param("cutoff", 2000).set_param("attack", 0.1);synth.set_param_norm("cutoff", 0.5); // midpoint of cutoff rangeVoice Management
Section titled “Voice Management”DSP instruments are polyphonic with 16 voices. Voice management is automatic:
- Allocation: The first inactive voice is used for each new note.
- Voice stealing: When all 16 voices are active, the quietest voice is stolen.
- Release: When a note-off is received,
gatedrops to 0.0. The voice remains active until its output falls below the silence threshold, then it is freed for reuse.
There is no manual voice management API.
Complete Example
Section titled “Complete Example”A synthesizer with oscillator, envelope, and lowpass filter:
dsp instrument FilterSynth { 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) { // Oscillator let freq = mtof(note); phase = fract(phase + freq * INV_SR); let osc = sin(phase * TWOPI);
// Envelope let target = gate * velocity; if gate > 0.0 { env = env + (target - env) * attack; } else { env = env + (target - env) * release; }
// Filter let filtered = svf_lp(osc * env, cutoff, 1.5);
return (filtered, filtered); }}
let synth = FilterSynth();let lead = AudioTrack("lead");lead.load_instrument(synth);lead << [c4 _ e4 _ g4 _ c5 _];
synth.set_param("cutoff", 2000);DSP Builtins
Section titled “DSP Builtins”All DSP builtin functions and constants are available inside instrument render blocks. See DSP Builtins Reference for the complete list.
See Also
Section titled “See Also”- Effects — Custom DSP effects
- Routing — Audio routing
- DSP Builtins Reference
- Effects Reference