Tzopilotl
Docs
GitHub

Writing SynthDefs

15. common_ugens Reference

import common_ugens.*; (it re-imports synthdef's graph API). Everything here is built from the primitives above and inlines into your graph. Most functions are written with untyped parameters and accept any AsSignal; where a parameter must be a signal it is typed S. The module also defines the constants pi and twopi.

Conventions used below: unipolar means the 0..1 range, bipolar means -1..+1. A phase argument is a unipolar ramp position (as produced by phasor), measured in cycles, not radians. A trigger is a signal whose transition from ≤0 to >0 marks an event.

15.1 Math helpers

FunctionDescription
divz(numer, denom, otherwise)Safe division: otherwise where denom == 0.
sign(x) / cmp(a, b)-1/0/+1 sign of x; sign of a - b.
ustep(x)Unit step: 1 where x > 0, else 0.
cmpl(x)Complement, 1 - x.
frac(x)Fractional part, x - floor(x) — also the unipolar wrap.
round(x) / round(x, q) / floor(x, q) / ceil(x, q)Round to nearest; round/floor/ceil to a multiple of quantum q (q == 0 passes through).
princ1(x)Principal value in [-0.5, 0.5): x - round(x).
sq(x) / cb(x) / qu(x)Square, cube, fourth power.
ssq(x) / ssqrt(x) / spow(x, y)Sign-preserving square, square root, and power.
sin2pi(x) / cos2pi(x) / tan2pi(x)Trig with the argument in cycles: sin(2πx) etc. The natural form for phases.
usin(x) / usinpi(x) / usin2pi(x)Unipolar sine (0..1) of radians / half-cycles / cycles.
sinc(x) / sincpi(x)sin(x)/x (value 1 at 0); cycle-argument variant.
fsin(x) / fsinx(x)Fast parabolic sine approximations of a cycles argument; fsinx adds a correction term for extra precision.
fcos(x) / fcosx(x)Fast cosine approximations (phase-shifted fsin/fsinx).
smoothStep(x) / smoothStep2(x)Hermite smoothstep 3t²-2t³ (and the 5th-order variant), input clipped to 0..1.
bsmoothStep(x)Bipolar-in, bipolar-out smoothstep.
smoothMin(a, b, k) / smoothMax(a, b, k)Min/max with a smooth blend of width k around the crossover.
chebyv(x, n)Variable-order Chebyshev polynomial cos(n·acos(x)) — waveshaping to the nth harmonic.
invert(x, a) / invert(x, a, b)Reflect about y = a; reflect within the range [a, b].
isuni(x) / isbi(x) / isint(x)Range/integrality predicates (0/1 signals).
decayCoeff(n, amp) / decay40dB(n) / decay60dB(n)One-pole feedback coefficient that decays to amp (or -40 dB / -60 dB) in n samples. The building block of the lag/decay/comb family.

15.2 Clipping, wrapping, folding

Three families for confining a signal to a range: clip saturates at the edges, wrap jumps back around (modulo), fold reflects off the edges (triangle-style). Each has range, zero-based, symmetric, unipolar, and bipolar forms.

FunctionDescription
max0(x)max(x, 0) — a.k.a. ReLU.
clip(x, a, b) / clip0(x, a) / clip2(x, a)Clip to [a, b] / [0, a] / [-a, a].
uclip(x) / bclip(x)Clip to 0..1 / -1..1.
wrap(x, a, b) / wrap0(x, a) / wrap2(x, a)Wrap into [a, b) / [0, a) / [-a, a).
uwrap(x) / bwrap(x)Wrap to 0..1 (= frac) / -1..1.
fold(x, a, b) / fold0(x, a) / fold2(x, a)Fold (reflect) into [a, b] / [0, a] / [-a, a].
ufold(x) / bfold(x)Fold to 0..1 / -1..1.
bfold_cheap(x) / bfold_cheaper(x)Cheaper bipolar folds valid for inputs bounded in [-3, 5] / [-2, 2].
excess(x, b)What clipping removed: x - clip2(x, b).

15.3 Sigmoids and soft clipping

FunctionDescription
distort(x)x / (1 + |x|) — gentle saturation, never quite reaches ±1.
softclip(x)Linear below |x| < 0.5, then smoothly saturating.
sigmoid0(x)sigmoid8(x)A palette of sigmoid waveshapers with different knees and costs: cubic-clipped (0), erf (1), rational (2, 3, 5, 8), tanh (4), x/√(x²+1) (6), atan (7). Try them as distortion curves.

15.4 Range mapping

The naming scheme is from-to: uni = unipolar 0..1, bi = bipolar -1..1, lin = a linear range [a, b], exp = an exponential range [a, b]. So linexp maps a linear input range to an exponential output range. The oscillator-to-parameter workhorses are bilin and biexp (bipolar oscillator output → parameter range).

FunctionDescription
uni(x) / bi(x)Bipolar → unipolar (0.5 + 0.5x); unipolar → bipolar (2x - 1).
lin(x, m, b)The line formula m·x + b.
axb(x, a, b)The power formula a·xᵇ.
unilin(x, a, b) / lerp(x, a, b)Unipolar → linear range [a, b].
uniexp(x, a, b)Unipolar → exponential range [a, b] (both same sign, nonzero).
linuni(x, a, b) / expuni(x, a, b)Linear / exponential range → unipolar (the inverses).
bilin(x, a, b) / biexp(x, a, b)Bipolar → linear / exponential range. E.g. sinosc(0.2) biexp(200, 2000) is a smooth frequency sweep.
linbi(x, a, b) / expbi(x, a, b)Linear / exponential range → bipolar.
linlin(x, a, b, c, d) / linexp / explin / expexpMap range [a, b] to range [c, d], each side linear or exponential.

15.5 Warp functions

Unipolar-to-unipolar (and bipolar-to-bipolar) curve shapers, useful for envelope curves, crossfade laws, and control response. The _r variants are the same curve rotated 180° about (0.5, 0.5); the s-prefixed variants are S-curves (symmetric about the midpoint).

FunctionDescription
warp_pow(x, p) / warp_pow_r(x, p)Power curve xᵖ and its reflection; 1/p inverts the curve about y = x.
warp_sin(x) / warp_asin(x) (and _r)Quarter-sine ease-out and its inverse (ease-in).
swarp_pow(x, p), swarp_sin(x), swarp_asin(x) (and _r)S-curve versions: ease-in-out (or the reflected out-in).
warp(x, w) / swarp(x, w)General-purpose rational warp / S-warp with a continuously variable curvature parameter w.
bwarp(x, w) / bswarp(x, w)Bipolar-to-bipolar warp and S-warp.

15.6 Musical unit conversions

All pure math, so they run at whatever rate their input has — use them freely on controls. The naming is from-to: nnhz converts note number to Hertz. Units: nn = MIDI-style note number (69 = A440), oct = octaves (5.75 = A440), cents, hz, sec = period in seconds, bpm, ratio = frequency ratio, st = semitones, rad/deg/cyc = angle units, db/amp = decibels / linear amplitude, w = radians per sample.

GroupFunctions
Pitch ↔ frequencynnhz / hznn, octhz / hzoct, centshz / hzcents
Pitch unit conversionsoctnn / nnoct, nncents / centsnn, octcents / centsoct
Intervals ↔ ratioscentsratio / ratiocents, stratio / ratiost, octratio / ratiooct
Amplitudedbamp / ampdb
Tempo and periodbpmhz / hzbpm, bpmsec / secbpm, hzsec / sechz
Anglesdegrad / raddeg, cycrad / radcyc, cycdeg / degcyc
Radian frequencyhzw / whz — Hertz ↔ radians per sample (these two involve fs())
Composedsecnn, secoct, seccents, nnsec, octsec, centssec, bpmnn, bpmoct, bpmcents, nnbpm, octbpm, centsbpm
-- LFO-modulated pitch in note-number space, converted once at the end
(60 + 0.3 * 5 sinosc) nnhz sinosc

15.7 Ring modulation and signal combining

FunctionDescription
ring1(a, b)ring4(a, b)Ring-modulation variants: a·b + a, a·b + a + b, a²b, a·b·(a-b).
sumsq / sqsum / difsq / sqdifa²+b², (a+b)², a²-b², (a-b)².
absdif(a, b)|a - b|.
vca(x, a)Amplitude control that ignores negative gain: x * max(0, a).
scaleneg(x, a) / scalepos(x, a) / scalenegpos(x, a, b)Scale only the negative / positive part / both independently — asymmetric waveshaping.
above(x, a) / below(x, a)Pass x only where above/below the threshold, else 0.
absabove(x, a) / absbelow(x, a)Threshold on |x| — gating / center clipping.
zapgremlins(x)Flush denormals, NaNs, and infinities to 0 — hygiene for feedback paths.

15.8 Noise generators

FunctionDescription
white(chans = 1)White noise (= birand(chans)).
pink(chans = 1)Pink noise: the SuperCollider PinkNoise algorithm (Voss-McCartney, 16 dice rerolled by the trailing zeros of a random integer), plus an auxiliary 3/4-probability die and per-die weights that flatten the ladder. Channels are independent generators. Measured: 1/f within ±0.16 dB over 20 Hz–20 kHz at 48 kHz, holding to below 10 Hz (slowest die corner ~0.2 Hz). The residual shape scales with the sample rate: predicted ±0.25 dB at 44.1 kHz, ±0.07 dB at 96 kHz.
pinkf(chans = 1)Pink noise via the Paul Kellett 7-stage filter. Measured: 1/f within ±0.05 dB from 100 Hz to Nyquist at 48 kHz (±0.2 dB from 20 Hz); flattens to white below ~10 Hz.
pinkfe(chans = 1)Pink noise via the Paul Kellett 3-stage economy filter. Measured: 1/f within ±0.6 dB over 20 Hz–20 kHz at 48 kHz; flattens to white below ~20 Hz.
violet(chans = 1)Violet noise (differentiated white, +6 dB/8ve).
blue(chans = 1)Blue noise (differentiated pink, +3 dB/8ve).
red(chans = 1, a = 0.05)Red/brown noise: a folded random walk with step size a.
gray(chans = 1)Gray noise: random single-bit flips of a 64-bit word.
coin(prob, chans = 1)1 with probability prob each sample, else 0.
velvet(density, chans = 1)Velvet noise: on average density ones per second, else zero.
dust(density, chans = 1) / dust2(density, chans = 1)Random-amplitude impulses at density per second; unipolar / bipolar amplitudes.
pandust(density, chans = 1)chans dust generators, each randomly panned to a stereo pair. Output is 2·chans wide, grouped by side [all L, all R] — transpose(chans) sum(2) mixes it to stereo (see the example in §5).
dustep(freq, chans = 1)Stepped noise: white noise sampled-and-held at velvet-noise times, freq steps per second on average.
exprand(a, b, chans = 1, rate = Rate.audio)Exponentially distributed random values in [a, b] (see §8).

15.9 Triggers, logic, and sequencing

A trigger is the transition of a signal from ≤0 to >0. These ugens turn signals into triggers, count them, divide them, and drive sequences from them.

FunctionDescription
tr(x)"Triggerization": 1 for exactly one sample when x crosses from ≤0 to >0.
eoc(x)End of cycle: emits a trigger when a phasor wraps.
init()A single impulse on the first sample, then 0 forever.
sampleAndHold(x, t)Hold the value of x, updating where t > 0.
once(x)Latches to 1 the first time x > 0.
toggle(x)Flip-flop: output inverts on each nonzero x. (The signal function; distinct from the toggle control constructor, which takes a String name.)
setReset(s, r)SR flip-flop: set to 1 by s, cleared by r (reset wins).
setResetToggle(s, r, t)SR flip-flop with an additional toggle input.
trDiv(x, n, offset = 0)Trigger divider: passes every nth trigger (phase offset).
trCount(x) / trCount(x, reset)Count triggers, optionally reset to 0 by reset.
oneshot1(trig, dur)On trigger, a line from 1 to 0 over dur seconds (then holds at 0) — a timer.
oneshot(trig, dur)On trigger, a line from 0 to 1 over dur seconds — a one-shot phasor for envelopes and grain windows.
timedGate(trig, dur)A gate that stays high for dur seconds after each trigger.
burst(trig, dur, n) / burst(trig, dur, n, w)A burst of n impulses over dur seconds per trigger; w warps the impulse timing.
seq(trigger, pattern, length)Step sequencer: on each trigger, advance through the channels of pattern (cyclic, length steps) and hold the current value.
iseq(trigger, pattern, length)Impulse sequencer: like seq but outputs the value only during the trigger sample, 0 otherwise.
rising(x) / falling(x) / changing(x) / nochange(x)Sample-to-sample movement predicates (0/1).
localmax(x) / localmin(x)1 at local peaks / troughs of the signal.
minfollow(x, r) / maxfollow(x, r)Running min/max since the last reset trigger r.
-- classic step-sequenced melody: an impulse train clocks a pitch pattern
let pattern = [1/1, 6/5, 3/2, 9/5, 2/1, 12/5, 3, 18/5] * 256;
(4 lfimp seq(pattern, 8) + [-0.04, 0.04]) smoothSaw(4)

15.10 Envelopes and smoothing

FunctionDescription
susrel(gate, s, r)Sustain-release envelope: jumps straight to sustain level s at gate-on (no attack segment — for sources that carry their own attack, e.g. samples); exponential release over r seconds when the gate falls.
linen(gate, rise, dec)Csound-style linear envelope driven by a gate: rises from 0 to 1 over rise seconds while the gate is high, holds at 1, then decays to 0 over dec seconds when the gate falls. Segments are linear (not exponential), so the times are true segment lengths; dec is the full-scale (1 → 0) time. The slopes are constant, so a gate that falls mid-rise decays immediately from the current amplitude and reaches silence proportionally sooner.
asr(gate, a, s, r)Attack-sustain-release envelope driven by a gate: exponential attack over a seconds toward sustain level s; exponential release over r seconds when the gate falls. (A segment time is the time to close 99% of the distance to its goal; see decay40dB.)
adsr(gate, a, d, s, r)Attack-decay-sustain-release: full-scale attack over a seconds, decay to sustain s over d seconds, release over r seconds on gate-off.
lag(x, t) / lag2 / lag3Exponential smoothing toward x with time constant t seconds (one, two, or three cascaded stages — higher orders are smoother). The standard control de-zipper.
lag(x, u, d) / lag2(x, u, d) / lag3(x, u, d)Lag with separate up/down time constants — envelope followers, attack/release ballistics.
onepole(x, a)One-pole lowpass with raw coefficient a (0..1; higher = slower).
onezero(x, a)One-zero filter x + a·(z1(x) - x).
leaky(x, a)Leaky integrator y = x + a·y(1).
leakdc(x, k)DC blocker (differentiator + leaky integrator, leak k ≈ 0.995).
decay(x, t)Exponential decay of impulses over t seconds — feed it triggers to get percussive envelopes.
decay2(x, atk, dcy)Attack-decay envelope from impulses: difference of two decays.
fadein(x, fadeinTime)Fade the signal in from silence over fadeinTime seconds at synth start (cubic curve).
fadeout(x, sustainTime, fadeoutTime)Pass the signal at full level for sustainTime seconds, then fade it to silence over fadeoutTime seconds (the same cubic curve as fadein).
-- percussive: no gate needed, just triggers
dust(4) decay2(0.005, 0.3) * 800 fsinxosc

-- sustained: gate-driven ADSR inside a voicer
adsr(gate(), 0.02, 0.1, 0.9, 0.25)

-- linear rise/hold/decay with true segment times
linen(gate(), 0.05, 0.5)

15.11 Differentiation and integration

FunctionDescription
z1(x) / z1(x, i)One-sample delay (optionally with initial value i).
z2(x)Two-sample delay.
diff(x)Unscaled first difference x - z1(x).
slope(x)Derivative in units per second: diff(x) * fs().
accel(x) / jerk(x)Second / third derivative.
unscaledIntegrator(x)Running sum.
backwardIntegrator(x) / forwardIntegrator(x) / trapezoidalIntegrator(x)Time-scaled integrators (backward / forward Euler, trapezoidal rule) — each also has a 2-argument form with a reset trigger.

15.12 Phase shapers and window functions

These map a unipolar phase (0..1, from phasor or oneshot) to a waveform or window. Numbered variants differ in initial phase: 0 / 90 / 180 / 270 degrees. u-prefixed outputs are unipolar, b-prefixed bipolar.

FunctionDescription
sawshift(x, shift) / quadrature(x)Phase-shift a unipolar ramp; shift by a quarter cycle.
btri(x), btri0..btri3(x)Bipolar triangle from a ramp, at the four phases.
utri0..utri3(x)Unipolar triangles at the four phases (utri1 = ∨, utri3 = ∧).
trapez0..trapez3(x)Bipolar trapezoids at the four phases.
upulse(x, pwm) / bpulse(x, pwm) / zpulse(x, pwm)Pulse waves with width pwm: unipolar / bipolar / zero-DC.
upulse1 / bpulse1 / zpulse1Pulse variants guaranteed to emit at least one opposite-polarity sample per cycle (as in SuperCollider), so extreme widths still oscillate.
izigzag(x) / ozigzag(x)Zigzag shapes moving inward / outward (after the Intellijel Rubicon).
bbtri(x)Bipolar ramp → bipolar triangle.
par(x)Unipolar ramp → parabola.
vartri(x, pwm) / varsaw(x, pwm)Variable-symmetry triangle/saw: pwm morphs saw-up ↔ triangle ↔ saw-down.
usquare(x) / bsquare(x)50% square waves, unipolar / bipolar.
Windows: han, ham, sinwin, sincwin, triwin, welwin, quadwin, octwin, trapezwinHanning, Hamming, sine (cosine), sinc, triangle, Welch, quartic, octic, and trapezoid windows over a 0..1 phase — grain envelopes, spectral windows.

15.13 Phasor and oscillators

phasor is the core of nearly every oscillator: a wrap-around phase accumulator producing a unipolar ramp at frequency fm Hz (accumulated in f64 for precision). The lf* oscillators shape that ramp directly — they are not band-limited, so they alias at high frequencies; they shine as LFOs and for deliberately raw tones. The band-limited/smooth options are blip, smoothSaw, smoothSquare, and the sine oscillators. In all of these, fm is the frequency in Hz (a modulatable signal) and pm is a phase offset in cycles.

FunctionDescription
phasor(fm) / phasor(fm, pm)Unipolar ramp 0..1 at fm Hz; pm is a signal phase modulation, or a constant initial phase.
sinosc(fm, pm = 0)Sine oscillator. Phase modulation via pm gives classic PM/FM synthesis: f sinosc(modulator).
fsinosc / fsinxosc(fm, pm = 0)Fast approximate sine oscillators (parabolic; fsinx more precise). Cheap for big banks.
lfsaw(fm, pm = 0)Bipolar sawtooth.
lfimp(fm, pm = 0.999999)Impulse train (single-sample 1s). The default initial phase makes it fire immediately at t = 0.
lftri / lfpar / lfupar / lftrap / lfzig / lfzag(fm, pm = 0)Triangle, parabola (bipolar/unipolar), trapezoid, and zigzag LFOs.
lfusqr / lfsqr(fm, pm = 0)Unipolar / bipolar square.
lfvsaw(fm, pwm, pm = 0)Variable-symmetry saw/tri (see varsaw).
lfupulse / lfbpulse / lfzpulse(fm, pwm, pm = 0)Pulse-width-modulated pulse waves (unipolar / bipolar / zero-DC).
blip(fm, pm, numHarmonics)Band-limited impulse oscillator with a modulatable number of harmonics (capped at Nyquist; smooth harmonic-count changes).
smoothSaw(fm, sharpness) / smoothSaw(fm, pm, sharpness)Variable-sharpness sawtooth: sharpness (in octaves of the corner exponent) morphs sine-like → saw.
smoothSquare(fm, sharpness) / smoothSquare(fm, pm, sharpness)Variable-sharpness square.
sawWinSin / sawWinUsin / usinWinSin / usinWinUsin(fm, freqScale)Windowed-sine oscillators (VOSIM/formant-flavored): a sine at fm * freqScale windowed by a saw or unipolar-sine window each cycle of fm.
-- FM: modulator phase-modulates the carrier
let modulator = (f * 3.5) sinosc * idx;
let car = f sinosc(modulator);

-- morphing saw pad, stereo detune
[110, 110.3] smoothSaw(1/5 sinosc bilin(1, 8))

Band-limited wavetable oscillator: osc

osc(b, fm, pm = 0, tableLen = 16384, numTables = 30, interp = linear) is a band-limited wavetable oscillator in the tradition of SAPF's Osc family and SuperCollider's Osc. The BufferVar b holds a bank of numTables wavetables of tableLen samples, one per 1/3 octave, each holding a progressively band-limited copy of the waveform; the oscillator picks the table from its playback frequency so the top partial always stays below Nyquist — a full-spectrum saw with no aliasing at any pitch. Banks are generated by the wavetables module (from a partial list — oscTables(amps, phases, smooth, ...) — or the presets sawTables(), squareTables(), triTables()) and pushed into the buffer with fillBuffer. Table generation runs through the ifft builtin, so it is NRT — build banks at patch-build time.

The smooth parameter mitigates the Gibbs phenomenon. Each band-limited table is a truncated Fourier series, and with a brickwall cutoff (smooth = 0) a truncated series overshoots by about 9% at any discontinuity in the ideal waveform and rings with ripple beside it; adding more partials squeezes the ripple closer to the edge but never shrinks the overshoot. On a saw or square table this appears as a spike and ringing at each jump — and since tables are normalized to their peak, the overshoot also costs headroom. smooth > 0 replaces the brickwall with a gradual spectral rolloff (partial j of a table with band limit h is scaled by cos(π/2 · j/h)^smooth, as in SAPF), fading the series out instead of chopping it: the overshoot and ripple are suppressed at the cost of slightly duller top partials. Values around 1–2 work well for edgy waveforms; 0 keeps the full brightness of an exact truncation.

Like SAPF, one osc front end dispatches on its inputs at graph-build time: with a constant frequency the table-selection math is constant-rate (evaluated once at init) and a single table is read; with a moving frequency the table is re-selected every sample and the two adjacent tables are crossfaded through a smooth curve so table transitions don't tick. A constant pm is the initial phase; a signal pm is per-sample phase modulation in cycles. interp may be none, linear, or cubic (the bank's guard samples don't cover the wider lagrange/sinc kernels).

-- an alias-free saw at any pitch
fn sawOsc() S {
    let b = bufferVar();
    let f = control("freq", ControlSpec { lo: 20.0, hi: 10000.0, init: 110.0, warp: ControlWarp.exponential });
    b osc(f) * 0.2 |> outlet
}
sawOsc defSynthX("sawOsc") await;

-- engine side, after newNode("sawOsc", 100):
import wavetables.*;
fillBuffer(100, 0, 1, sawTables());

15.14 Comb and allpass delays

Recirculating delays. delayTime and maxDelayTime are in seconds; decayTime is the -60 dB decay time in seconds. The suffix picks the interpolation: none (fixed delay only, cheapest), linear, cubic; the base functions take an explicit Interpolation (default lagrange).

FunctionDescription
comb(x, delayTime, maxDelayTime, decayTime, interp = lagrange)Feedback comb filter — echoes / Karplus-Strong-style resonance.
combn(x, delayTime, decayTime)Non-interpolated comb (fixed delay).
combl / combc(x, delayTime, maxDelayTime, decayTime)Linear / cubic interpolated combs (modulatable delay).
alpas(x, delayTime, maxDelayTime, decayTime, interp = lagrange)Schroeder allpass delay — flat magnitude response, dense echoes; the reverb building block.
alpasn(x, delayTime, decayTime), alpasl, alpasc(...)Interpolation variants, as with comb.
apverb(x, delayTime, decayTime, n = 6)A quick random reverb: n chained allpass delays with random times up to delayTime.

15.15 Panning

FunctionDescription
pan(x, pos)Equal-power pan of x to a stereo pair; pos is bipolar (-1 left … +1 right). Returns [S] — apply join to get a 2-channel signal.
panfun(x) / panfuns(x)The underlying cubic equal-power gain law for a unipolar position (max error ±0.0006 dB from true constant power); panfuns gives the [left, right] gain pair.

15.16 Gated subgraphs and chains

FunctionDescription
pull(gate, initVal, gatedFun)Evaluate the subgraph gatedFun only while gate > 0, holding its last value (initially initVal) while off — a compute-saving sample-and-hold around a whole subgraph.
pause(gate, gatedFun)Evaluate the subgraph only while gate > 0, outputting 0 while off.
chain(x, n, f)Apply f to x n times (unrolled at build time): x chain(6, stage) builds a 6-stage cascade.
↑ Back to top