Anatomy of a self-contained patch
This chapter walks through examples/tracker_three_voices.patches,
a piece for three voices — bass, two leads — driven entirely by the
tracker sequencer. No external MIDI; the patch is the composition.
If Anatomy of a synth voice shows how a voice produces sound, this chapter shows how the sequencer drives it.
The patch is over 100 lines and full of detail; we’ll read it in
chunks. The full file is at examples/tracker_three_voices.patches.
Three things at once
A self-contained piece typically has three layers:
- The composition.
songandpatternblocks declaring what notes play when. Top of the file, before the patch block. - The voice machinery.
templateblocks that wrap synth-voice wiring so the patch can stamp out one per voice. Also top-of-file. - The patch. A
MasterSequencer, onePatternPlayerper voice, instances of the voice templates, mixer, effects, output.
Reading order matters: skim top to bottom for the lay of the land (songs → templates → patch), then re-read each layer in detail.
The song
song demo(bass, lead1, lead2) {
pattern bass_verse {
note: C2 . . ~ Eb2 . G1 . C2 . . ~ Ab1 . . .
vel: 1.0 . . ~ 0.8 . 0.9 . 1.0 . . ~ 0.7 . . .
}
pattern bass_chorus { ... }
pattern lead1_verse { ... }
pattern lead1_chorus { ... }
pattern lead2_verse { ... }
pattern lead2_chorus { ... }
pattern lead2_fill { ... }
play {
(bass_verse, lead1_verse, lead2_verse
bass_verse, lead1_verse, lead2_verse) * 2
(bass_chorus, lead1_chorus, lead2_chorus
bass_chorus, lead2_chorus, lead2_fill) * 2
}
}
A song declares its voices (demo(bass, lead1, lead2)). The
patterns inside reference those voice names. The play { ... }
block lists which patterns are active simultaneously for each row
of the song, with (...) * N for repetition.
Pattern syntax in one breath
note:andvel:are channel rows. The names are conventions; any identifier works. Each step on a row is one slot.C2,Eb2,G1,Ab1— note names with octave..— rest. Silence on this step; the previous gate ends.~— tie. Hold the previous note’s gate across this step without re-triggering.1.0,0.8— bare floats. Used onvel:for velocity.
Step counts must match across rows in a pattern. Here every row has
16 steps; the sequencer treats them as 16 sixteenth-notes (with
rows_per_beat: 4, four steps per beat = sixteenth notes).
Slides (C4>Eb4), continues (x), and repeats (*N) extend the
basic syntax — see the DSL reference under
Patterns and Songs for the full set.
The play block
play {
(bass_verse, lead1_verse, lead2_verse
bass_verse, lead1_verse, lead2_verse) * 2
(bass_chorus, ...) * 2
}
A row names which pattern plays on each voice for the next chunk
of song time. Commas separate columns within a row; newlines
separate rows. Parenthesised groups can be repeated with * N.
Reading the first group: row 1 plays bass_verse on bass,
lead1_verse on lead1, lead2_verse on lead2; row 2 plays the
same three again; the group repeats twice — so four iterations of
“verse” before the chorus block starts.
The voice template
template voice(attack: float = 0.01, decay: float = 0.1,
sustain: float = 0.7, release: float = 0.001,
cutoff: float = 6.0, q: float = 0.0,
glide_ms: float = 0.0) {
in: voct, gate, trigger, velocity
out: audio
module osc : Osc
module env : Adsr { attack: <attack>, decay: <decay>,
sustain: <sustain>, release: <release> }
module vca : Vca
module filt : Svf { cutoff: <cutoff>, q: <q> }
module vel_vca: Vca
module glide : Glide { glide_ms: <glide_ms> }
$.voct -> glide.in
glide.out -> osc.voct
$.gate -> env.gate
$.trigger -> env.trigger
osc.sawtooth -> filt.in
filt.lowpass -> vca.in
env.out -> vca.cv
vca.out -> vel_vca.in
$.velocity -> vel_vca.cv
vel_vca.out -> $.audio
}
A monophonic voice (not poly — see below) with the standard oscillator → filter → VCA → velocity-VCA chain. Differences from the previous chapter’s lead voice:
- Monophonic. No
Poly* prefix anywhere. The tracker plays one note per voice per step; there is no polyphony required. - Glide.
Glidesmooths the pitch CV — when a step plays a different note from the previous step, the pitch slews to it instead of jumping.glide_ms: 0.0disables it. - Velocity VCA. The pattern’s
vel:row drives a second VCA after the amp envelope, so velocity scales the dynamic shape. Svffilter. State-variable filter withcutoffin V/oct units. Cheaper than the lead voice’sPolyLowpass.
The template’s interface is voct, gate, trigger, velocity in and
audio out — the same shape PolyMidiToCv would feed, but mono.
A second template (sq_voice) is a square-wave variant for lead2
— same skeleton, swapped oscillator output and different defaults.
The patch
The actual graph. Read it top-down:
Sequencer + pattern players
module seq: MasterSequencer([bass, lead1, lead2]) {
song: demo,
bpm: 120,
rows_per_beat: 4,
loop: true,
autostart: true,
swing: 0.75
}
module bass_pp: PatternPlayer([note, vel])
module lead1_pp: PatternPlayer([note, vel])
module lead2_pp: PatternPlayer([note, vel])
seq.clock[bass] -> bass_pp.clock
seq.clock[lead1] -> lead1_pp.clock
seq.clock[lead2] -> lead2_pp.clock
MasterSequencer consumes the song (song: demo), runs at
bpm: 120 with 4 rows per beat (sixteenth notes), loops the play
block, autostarts on load, and applies 0.75 swing.
For each voice it emits a clock[<voice>] output: a stream of
“row events” that name which pattern is active, which step is
current, and the step’s data.
PatternPlayer([note, vel]) listens to a clock and unpacks the two
named channels — note and vel. It exposes per-channel outputs:
pp.cv1[note]— the pitch CV from thenote:row.pp.gate[note]— gate held high during notes (low during.).pp.trigger[note]— single-sample pulse at step start.pp.cv1[vel]— the velocity CV from thevel:row.
The names cv1, gate, trigger are the PatternPlayer’s
fixed port set; the [name] index is the channel from the
pattern.
Voices
module bass_v: voice(
attack: 0.005, decay: 0.15, sustain: 0.6, release: 0.1,
cutoff: 3.5, q: 0.3, glide_ms: 40.0
)
bass_pp.cv1[note] -> bass_v.voct
bass_pp.gate[note] -> bass_v.gate
bass_pp.trigger[note] -> bass_v.trigger
bass_pp.cv1[vel] -> bass_v.velocity
Bass uses the voice template with 40ms glide for portamento
between notes, a tight envelope, and a moderately low filter
cutoff. lead1_v and lead2_v instantiate the same shape with
different parameters; lead2_v uses sq_voice for a different
oscillator waveform.
Mixer with sends
module mix: StereoMixer([bass, lead1, lead2]) {
@bass: { level: 0.7, pan: 0.0 },
@lead1: { level: 0.5, pan: -0.4, send_a: 0.3 },
@lead2: { level: 0.5, pan: 0.4, send_a: 0.3 }
}
bass_v.audio -> mix.in[bass], ~meter+osc+spectrum(bass)
lead1_v.audio -> mix.in[lead1]
lead2_v.audio -> mix.in[lead2]
Three-channel stereo mixer. Bass is centred and loudest; the two leads are softer, panned left and right, with a 30% send to bus A.
The ~meter+osc+spectrum(bass) at the end of bass_v.audio ->
attaches a tap to the cable, exposing live meter / scope /
spectrum data to the GUI. See Visualising patches
for taps.
Effects bus
module delay: StereoDelay(2) {
delay_ms: 750, feedback: 0.4, dry_wet: 1.0
}
mix.send_a -> delay.in
delay.out -> mix.return_a
module reverb: FdnReverb {
size: 0.45, brightness: 0.6, mix: 0.2, character: plate
}
module lim: StereoLimiter { threshold: 0.85 }
mix.out -> reverb.in
reverb.out -> lim.in
module out: AudioOut
lim.out -> out.in
Two effects layers:
- Send-return.
mix.send_acarries the delay-bound signal from the leads (no bass). The delay’s wet-only output (dry_wet: 1.0) returns tomix.return_a, which the mixer sums into its main output. Standard send-return topology. - Master bus.
mix.out(everything: voices + return) → reverb → limiter →AudioOut. The reverb is plate-flavoured with moderate mix.
Design notes
A few decisions worth explaining:
Monophonic voices over poly. The sequencer plays one note per voice per step. There’s no polyphony to allocate, so each voice is a mono chain — cheaper, simpler, and the template’s parameter list controls per-voice character without any shared poly cost.
Svf over Lowpass. State-variable filter is cheaper than a
biquad cascade and the leads don’t need the steeper rolloff. Bass
might benefit from a steeper filter; an exercise for the reader.
Pattern length 16. With rows_per_beat: 4 that’s exactly four
beats per pattern. Different pattern lengths within a song are
fine (some voices can sustain across multiple steps via ~), but
keeping them aligned to common multiples simplifies the timing.
Swing on the sequencer, not per-voice. swing: 0.75 shifts
off-beats slightly later for groove. Applying it at the sequencer
means every voice swings in sync.
Autostart in the player; transport-driven in a DAW. With
autostart: true, the piece plays as soon as the patch loads in
patch_player. If you load it as a CLAP plugin in a DAW, you may
prefer autostart: false and wire seq.start to the host’s
transport — but that’s a host-integration decision, not part of the
patch itself.
Mixing the modes
There’s no rule preventing a sequenced backing layer from coexisting
with an externally-driven lead. A MasterSequencer driving a few
voices and a PolyMidiToCv driving a lead voice can live in the
same patch — the engine doesn’t care where the gate edges come from.
Self-contained vs. externally-driven is a property of the wiring, not a setting on the player.