Skip to content

DSP Instruments

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];
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 instrument

dsp 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.

The render function is called once per sample for each active voice.

fn render(note, velocity, gate) -> (out_l, out_r) {
// ...
}
ParameterTypeDescription
notef64MIDI note number — supports fractional values for microtonal pitches (e.g. 60.5)
velocityf64Note velocity, normalized to 0.0-1.0
gatef641.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 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;
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.

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; // write
let delayed = buf[read_pos]; // read

Indices wrap with modulo — buf[size] is equivalent to buf[0]. Each voice gets its own copy of the buffer.

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.

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.

MethodDescription
.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 range

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, gate drops 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.

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);

All DSP builtin functions and constants are available inside instrument render blocks. See DSP Builtins Reference for the complete list.