Contents

Polyphony

Voice allocation for patterns. poly() runs an instrument function per voice and sums the outputs. mono() and legato() are single-voice variants with different retrigger behavior. spread() distributes an array across N voices for unison stacks.

poly

Polyphonic Voice Manager - Allocates voices for a pattern and runs an instrument function per voice.

ParamTypeDefaultDescription
inputpattern-Pattern or chord(...) producing events (notes or chords)
instrumentfunction-A function ({freq, gate, vel}) -> signal run per voice
voicesnumber64Voice count (1-128, must be a literal)
release:number0.0Seconds of voice-mix tail past note-off. Holds the gate high for the window so the instrument’s ADSR finishes its release stage instead of being multiplied to zero.

poly() reads pattern events at runtime and assigns each note to its own voice slot. The instrument runs once per voice and the outputs of all active voices are summed. When the same note appears in consecutive events, the voice slot is reused (preserving phase continuity); when all voices are busy, the oldest is stolen.

The instrument destructures the event record — name the fields you want and they bind per voice: ({freq, gate, vel}) -> signal. Any pattern event field works (freq, gate, vel, trig, note, dur, phase, …) plus custom record-suffix fields like c4{cutoff:0.8}. Missing fields bind to 0, or to an explicit {cutoff = 0.5} default. The instrument may return a mono or stereo signal — poly always produces stereo output, broadcasting mono bodies to both channels.

// Polyphonic chord progression with the default 64 voices
stab = ({freq, gate, vel}) ->
    saw(freq) * ar(gate, 0.05, 0.4) * vel
    |> lp(@, 1100)

chord("C Em Am G") |> poly(@, stab) |> out(@)
// Lower voice count if you want predictable stealing
n"c4 e4 g4 b4"
    |> poly(@, ({freq, vel}) -> sine(freq) * vel, 8)
    |> out(@)
// Per-note filter from a custom record-suffix field
n"c4{cutoff:0.9} e4{cutoff:0.3} g4{cutoff:0.6}"
    |> poly(@, ({freq, gate, cutoff}) ->
        saw(freq) |> lp(@, cutoff * 4000) |> @ * ar(gate, 0.01, 0.3))
    |> out(@)
// Stereo instrument: each voice returns L/R, poly sums per-channel.
n"c4 e4 g4"
    |> poly(@, ({freq, vel}) -> stereo(saw(freq), saw(freq * 1.01)) * vel, 4)
    |> out(@)

The instrument may also take a (...e) rest param to bind the whole event record (e.freq, e.vel, …). The legacy positional form (freq, gate, vel) -> — fields by position, not name — stays valid for back-compat, but the record form is canonical.

For unison per voice — fanning each chord note into a stereo-spread, detuned cluster — wrap unison inside the poly instrument.

release: — click-free note-off

By default poly() accumulates voice * gate per sample, so when a note-off drops the gate to zero the instrument’s ADSR release tail is multiplied out instantly — audible as a click on every key-up with live midi() input. Cycle-aligned pattern note-offs hide this; live keyboards expose it on every note.

release: 0.3 holds the gate high for 300 ms past note-off so the instrument’s own ADSR runs its release to completion. The voice slot isn’t reusable until the window expires, so set the window roughly equal to your longest ADSR release.

fn lead({freq, gate, vel}) ->
    saw(freq) * adsr(gate, 0.01, 0.2, 0.7, 0.3) * vel

midi() |> poly(@, lead, 8, release: 0.3) |> out(@)

Same option is available on mono and legato.

mono

Monophonic Voice Manager - Single-voice manager with retrigger on every new note.

ParamTypeDefaultDescription
inputpattern-Pattern producing events
instrumentfunction-A function ({freq, gate, vel}) -> signal
release:number0.0Seconds of mix tail past note-off (voice-manager mode). See poly for mechanics.

mono() is poly() with one voice and last-note priority. Every new note retriggers the gate so envelopes restart cleanly, the classic hardware-mono behavior. The instrument destructures the event record exactly like poly.

mono(stereo_signal) is a different builtin (stereo-to-mono downmix). The compiler routes based on argument type: a function instrument gets the voice manager; a stereo signal gets the downmix.

// Mono lead with retrigger on every note
n"c4 e4 g4 c5"
    |> mono(({freq, gate}) -> saw(freq) * adsr(gate, 0.01, 0.1, 0.6, 0.3))
    |> out(@)

legato

Legato Voice Manager - Single-voice with no retrigger between connected notes.

ParamTypeDefaultDescription
inputpattern-Pattern producing events
instrumentfunction-A function ({freq, gate, vel}) -> signal
release:number0.0Seconds of mix tail past note-off. See poly for mechanics.

Like mono(), but the gate stays high while notes overlap, so envelopes don’t restart on every note. Frequency and velocity update but the AR/ADSR keeps decaying through the phrase. Best for legato leads and bass lines. The instrument destructures the event record exactly like poly.

// Smooth bassline, gate stays high across notes
n"c2 e2 g2 c3"
    |> legato(({freq, gate}) -> saw(freq) * adsr(gate, 0.01, 0.2, 0.8, 0.4))
    |> out(@)

spread

Spread - Distribute an array across N voices evenly.

ParamTypeDefaultDescription
nnumber-Number of voices to spread across
sourcearray-Source array of values

spread() is a compile-time helper that takes an array and distributes its values across n slots, repeating or reducing as needed. Useful for unison voices and detuned stacks.

// Detuned saw stack, 5 oscillators across the array
saw(spread(5, [220, 220.7, 219.3, 221.4, 218.6])) |> out(@)

voice

A voice is one independent instance of the instrument function. poly() runs N voices in parallel; mono() and legato() use one. Voice slots are pre-allocated at compile time; runtime allocation is just a slot lookup.

voices

The voice count parameter to poly() (default 64). It must be a literal so the compiler can statically allocate. Lower voice counts give predictable voice stealing; higher counts handle complex patterns without dropouts.

Related: unison, sequencing, chord, pat