Tzopilotl
Docs
GitHub

Writing SynthDefs

9. Delays and Feedback

A DelayVar is the graph's memory primitive: a ring buffer with one writer and any number of readers. Everything stateful — one-sample feedback, filters, envelopes, comb delays, phase accumulators, counters — is built on it.

API

FunctionDescription
delayVar()Declare a delay line. Its length is inferred from the largest fixed read index.
delayVar(maxDelay)Declare a delay line sized for variable reads: maxDelay is the maximum delay in samples (an AsSignal; usually maxSeconds * fs()).
d <- x  /  x -> d  /  d write(x)Write x into the line (once per sample). Returns x, so a write can sit inside an expression. Each delay has exactly one writer.
d read(n)  /  d at(n)  /  d(n)Read the value written n samples ago (fixed integer n). d(1) is the previous sample's write — a unit delay. d(0) (or d()) reads the value written this sample.
d vread(index, interp = Interpolation.cubic)  /  d(indexSignal)Variable (modulated) read at a signal-valued delay in samples, with interpolation. Requires delayVar(maxDelay).
d init(n, x)Set the initial content at offset n (evaluated at init rate), so the first reads see x instead of 0.

Interpolation for variable reads: Interpolation.none (nearest), linear (2-point), cubic (4-point), lagrange (8-point), sinc (8-point).

Feedback

Reads at index ≥ 1 see previous samples, so a delay can be read and written in the same graph — that is how feedback works. The canonical one-pole filter:

fn onepole(x S, a AsSignal) S {
    let y = delayVar();
    y <- x + a * (y(1) - x)     -- y[n] = x + a*(y[n-1] - x); the write is the result
}

Since write returns the written value, the whole function body is both "update the state" and "the output". The unit-delay ugens z1 / z2 (§15.11) package the common cases: x z1 is x delayed one sample.

A delay declared with a multichannel writer widens per channel — each channel gets its own line. Delay times in seconds must be converted: delayTime * fs() is the read index in samples (see comb in §15.14 for the full pattern).

-- variable-delay chorus voice: 0..10 ms modulated cubic read
fn chorusTap(x S, rate AsSignal) S {
    let maxSamps = 0.01 * fs();
    let d = delayVar(maxSamps);
    d <- x;
    d(rate sinosc uni * maxSamps)      -- d(S) = vread, cubic by default
}
↑ Back to top