VOLTAreference

Reference / Start here

Bring a sound to life

Start here · playable examples

Open in the workbench →
On this page · 13 sections

Modulation means changing one part of a sound with another signal. An envelope can close a filter after each note, an LFO can create a repeated wobble, and a macro can connect several changes to one knob. Start with one moving parameter so you can hear what it does.

Choose a kind of movement#

MovementUseMusical example
EnvelopeA shape that follows a note's lifeA pluck that starts bright and softens
LFOA repeating wave, often slow enough to hear as movementVibrato, tremolo, a filter sweep
PatternValues chosen at event positionsA different cutoff for each note
MacroA named control you can adjustOne knob that opens a filter and adds resonance

An LFO is a low-frequency oscillator. lfoSine 2Hz repeats twice per second. Its raw values range from −1 to 1. A filter needs positive frequencies, so map or offset that wave into an appropriate range.

Hear a filter breathe#

Listen for: brightness rising and falling within one held note. slow 4 makes the note last four cycles, so there is time to hear the movement.

Listen to this example · four bars, then the tail
tempo = 120bpm
timingVersion = 2
play "saw" ("c3" |> slow 4)
  |> cutoff (lfoSine 0.5Hz * 900Hz + 1300Hz)
  |> resonance 2
  |> attack 30ms
  |> release 200ms

Make it yours: change 0.5Hz to 3Hz. The range remains 400–2200 Hz, but the movement becomes a wobble. Changing 900Hz changes the depth; changing 1300Hz shifts the center. Ordinary voice LFOs restart with each note, so short notes may reveal only the start of a slow sweep.

Move once per note#

A control pattern chooses values at note onsets. This is useful when every hit should have its own character, rather than continuously changing while held.

Listen for: a bright note followed by three darker ones.

Listen to this example · four bars, then the tail
tempo = 120bpm
timingVersion = 2
play "saw" "c3 c3 c3 c3"
  |> cutoff "2600 600 900 600"Hz
  |> resonance 2
  |> release 80ms

Make it yours: rearrange the four cutoff values. The same pitches can suggest a new accent pattern through brightness alone. Keep units next to the closing quotation mark: "…"Hz is a frequency pattern.

Put several changes on one knob#

A macro is a named value. Studio presents it as a control you can adjust. Use expressions to give that one control several related jobs.

Listen for: a resonant, rounded bass. Copy this program to Studio and turn the bright macro to hear the range of the instrument. The short preview uses the written starting value.

Listen to this example · four bars, then the tail
tempo = 120bpm
timingVersion = 2
bright = macro 0.3
play "saw" "c3 ~ [g3 c4] ~"
  |> cutoff (400Hz + bright * 3200Hz)
  |> resonance (1 + bright * 3)
  |> release 180ms

Make it yours: change 3200Hz to 1200Hz for a gentler knob. Design its range so the low and high positions are both useful, then give it a name that describes what you hear.

Make movement follow the music#

Use lfoSine @ 2c for a two-cycle period. @ describes how long a sweep takes, so a larger cycle value makes a slower sweep. Use a frequency such as 0.5Hz when you want a fixed speed independent of tempo. Envelopes in these examples use seconds or milliseconds.

Movement can also come from another track. A follower measures its level, onset detection turns attacks into events, and MIDI and other controls connect instruments and controllers. Continue to Macros and automation for scene ramps and richer control, or The modulation matrix to inspect connections visually.

Move delay time and timbre#

delayTrack, pingpong, tape and bbdDelay accept live seconds. They reserve ten seconds of delay before playback and clamp live values to one sample–10 seconds (tape retains a three-sample minimum read). Clean delays default to a 5 ms Doppler glide; their optional trailing "crossfade" transition blends stationary taps over 20 ms and coalesces rapid changes. Tape/BBD keep their physical pitch motion and running histories. Tape bias is live too: a prepared calibration table and 5 ms smoothing retain magnetization and filter state. Scene-address ramps use the destination's physical units and range, such as 100ms–2s for delay time.

SVF character, AS3340 character/details, noise tilt/width, soften, and string stiffness/sympathy accept per-sample controls. Moving them preserves phase, seeded identity and histories. Live noise keeps its independent right stream running at zero width. SVF oversampling engages at the actual required sample and stays latched for the voice. Live four-pole SVF character keeps both clean and OTA histories warm and crossfades topology changes at zero over 20 ms; parked controls retain the corresponding literal sound. Live string coupling computes bounded bridge gain per sample and budgets the full possible tail.

Simulation forces and rates are live: bounce integrates phase; flock/orbit update retained worlds on 60/120 Hz grids. A live world begins at its voice/control onset and avoids missed-history replay. Use @shared for a persistent transport-scoped world. Literal legacy worlds retain their old absolute-time loop; counts, agent selection and CA rule/seed/layout define structure. See Motion from physical simulations.

Move tails or choose a prepared space#

reverb and plate amount, spring tension/decay and limiter ceiling/release accept live controls. Tail controls smooth over 5 ms while retaining delay and diffusion state. Reverb density and spring tension also glide reserved delay geometry over 20 ms; that motion changes pitch. Omitted reverb/plate wet follows the applied amount; explicit wet overrides it. Limiter ceiling applies to its input frame after fixed 2 ms lookahead, so requested and applied values can differ. See Mastering and delivery.

A structural choice can use preparedFx choice alternatives. Declare two to four fixed track functions, each containing one or two reverb, plate, spring, shimmer, convolve, gainTrack or saturate inserts. The bank prepares all states before playback. Its zero-based choice rounds/clamps to the bank, then crossfades over 20 ms; rapid requests coalesce until that fade finishes. At most two alternatives process audio. Inactive states are suspended and retained. This is discrete prepared selection, not an IR/model rebuild at control rate.

Listen to this example · four bars, then the tail
space = lfoSine 0.25Hz |> scale [0, 1]
p = patch { source = sine 220Hz, ampEnv = adsr 2ms 60ms 0 40ms, gain = 0.3 }
t = play p !"c4*4" |> preparedFx space
  [(\t -> t |> shimmer 0.4 1200Cents), (\t -> t |> shimmer 0.4 (-1200Cents))]

An unavailable IR cannot replace active audio. Prepared convolution limits are 65,536 device-rate frames per IR and 262,144 channel-frames per bank; mono IRs count twice for stereo processing. Larger resources use an ordinary prepared convolve insert. Studio bank edits prepare on the control thread (a separate worker in the browser) while the current audio continues. Newer browser requests supersede older candidates; resources and module install together. The Routes view distinguishes requested and active bank choices. Already-ready selection is allocation-free; a whole-module edit retains the usual module-swap costs. Embedded players use the same DSP in a single-owner engine when their parent page cannot support an isolated worker.

Choose when a timing control takes effect#

Follower attack/release and smooth/withSlew times accept composed controls in seconds or milliseconds. They update each sample while retaining the running envelope or smoothing state. Live times clamp to 0–60 seconds; the legacy minimum time constants remain 1 ms for follower and 0.1 ms for slew.

ADSR attack, decay, sustain, release and punch latch together at note-on or retrigger. withDecay latches duration at the same boundary; withDelay latches once at voice onset. Held and legato notes retain their shape. The voice stays alive for its actual latched release. Live sustain/punch clamp to 0–1. Constant cycle durations in these envelope slots use the compiled tempo; live beat/sample units currently require an explicit clock conversion.

Track/return/master controls now include duck timing, dynamic-EQ frequency/Q, de-esser and mono-bass crossovers, width's low anchor, OTT depth, and delay damping/wet. Filter and damping controls use a 5 ms smoother and a persistent 16-sample coefficient clock (width updates every sample), retaining DSP history. Wet and depth read sample curves. Retune speed keeps the existing block-end analysis cadence. A live zero OTT depth keeps its crossover running; literal zero retains the historical bypass. Dense curves are callback-independent; ordinary knob moves retain the existing block-interpolated 15 ms smoothing.

Choose a clock and compose events#

lfoSine 1c integrates the live beat clock; beats and bars track tempo/meter, while Hz and seconds remain absolute. A sample period uses the device rate. Changing a live musical/sample period preserves phase; an invalid/nonpositive musical/sample period freezes it. lfoSH integrates live rate changes without redrawing its seed. wander keeps its smoothing time at half the live hold period.

All six clock sources (lfoSine, lfoSaw, lfoSquare, lfoTri, lfoSH, wander) accept fixed frequencies/periods, live numeric rates and numeric pattern lanes. Allowed units are Hz, seconds, milliseconds, cycles, beats, bars and samples, plus bare or normalized numbers. A bare scalar means Hz; a bare lane means cycle periods. Hz-valued audio modulation is supported. Boolean, pitch, dB and aggregate rates fail checking, including through aliases, factories, imports and higher-order calls. The result is always a bipolar control signal. See Control patterns for individual events for lane timing.

Fixed absolute rates must be positive and finite. Live periodic Hz can run backwards; live sample-and-hold Hz clamps negative values to zero. Live seconds and milliseconds use reciprocal conversion. These reader policies are distinct from musical/sample period validation.

Clock preparation rejects unsupported shapes or period units, non-finite clock context, non-finite phase seeds and fixed frequencies that overflow engine precision. Finite phase seeds wrap modulo one before narrowing, matching authored phase. Fixed musical/sample periods must be positive and finite. Invalid replacements leave the installed performance running. Live period signals retain the freeze policy above.

control |> syncTo "note" creates a separate clock per voice. "transport" creates persistent state that resets on stop/seek; "section" also resets at section entry. "free" continues through stop/seek and advances while stopped, retaining its last tempo. Global scopes reject implicit note inputs. Ordinary voice LFOs remain note-local; ordinary constant signal/stepSeq retain their legacy absolute cycle clock. Explicit scopes apply to their whole operand; shared references retain the referenced instance's ownership.

stepSeq [a, b * 0.7] accepts compatible live controls and latches at step boundaries. interpolate "hold" holds each sample, "linear" also latches the next endpoint and interpolates toward it, and "glide" moves from the current value over 5 ms. Numeric rests retain the preceding value; boolean patterns supply explicit zero windows. segment 8 control "live" samples a numeric control eight times per beat-clock cycle, preserving its unit and holding each value until the next boundary. Count and mode are fixed; live counts are dimensionless integers from 1 to 4096. Audio requires an explicit follower/conversion first. The default segment prepares an affine cycle-rate LFO as a pattern instead. Aliases, returned functions and partial calls retain the same choice; mode names are case-insensitive. Gain-as-velocity lanes keep their existing note events.

patternTrig pattern turns nonzero/non-rest events into sample pulses. mergeTrig [a,b] coalesces coincident hits; filterTrig trigger gate passes hits while the gate is at least 0.5; quantizeTrig 1b trigger waits for the next grid boundary, coalescing pending hits in one cell. playOn, hold, count, toggle and onCount consume these graphs, including shared triggers. Adjacent-sample events remain distinct. adsr ... |> gateOn gate latches its shape on gate rise and releases on fall. resetOn trigger resets an LFO before that sample, or retriggers an open gated ADSR from its current level. phase accepts a live LFO phase offset in cycles.

lastVelocity lead 0.2 and lastPitch lead ~C3 provide an explicit last-scheduled-note policy, with a prepared initial value. Name the source track by its binding (bare, or qualified through an import); a quoted name such as "lead" is still accepted as the legacy spelling, and a name that matches no pitched track is refused at the argument. The initial value can come from ordinary bindings, records or functions; a runtime pitch cannot serve as the initial pitch. Use freq on the pitch. Values arrive exactly 128 samples after note-on; simultaneous pitched notes resolve in scheduler order, with the final nonzero-velocity note winning. Unpitched samples leave the value held. Stop/seek restores the initial value. Initial values must fit finite engine precision; velocity must lie within [0, 1]. Source compilation and module loading share those requirements.

Where an expression can go#

Inside a voice, live signal inputs can combine several macros, LFOs and named followers. For example, two macros can multiply to control a patch's gain. Units still matter: a gain expects a linear multiplier, while cutoff expects Hz. Some controls sample their input at a particular moment: ADSR stages latch at note-on, and grain settings such as size and position latch at grain spawn. The word “live” alone does not mean a held note or an existing grain will change.

Track, bus and master effect parameters can also use persistent control expressions: several macros, nonlinear arithmetic, LFOs, control patterns, curves, slew, delayed followers, inline follow conversion, simulations and chaos. Named expressions, local bindings, imported helpers and record fields use the same evaluator as inline expressions. These controls continue across notes and do not create extra macro knobs.

Listen for: a reverb blend that moves during a held note. Turn space or depth in Studio to change the bottom of the sweep.

Listen to this example · four bars, then the tail
tempo = 120bpm
timingVersion = 2
space = macro 0.3
depth = macro 0.7
movement = lfoSine 0.5Hz |> scale [space * depth, space * depth + 0.2]
play "saw" ("c3" |> slow 4)
  |> release 200ms
  |> reverb 0.6 movement

Use the same expression in a bus section or master insert. Reverb's tail amount and explicit wet argument can both move. This does not make compile-time-only parameters live. Audio must pass through follow, inline onsets or inline pitchOf conversion; note sources such as vel still need a voice context. Persistent controls check rate and dependencies: a control-rate vel still needs a note, while a delayed follower drums 5ms 150ms can run at module level. Macro, shared/parameter-control, plugin parameter/CC and audio-insert source and native/JSON graph loaders share context requirements: note-scoped clocks, note envelopes, per-note values/randomness and note-pitched samples require a voice. Explicitly gated ADSR remains valid without a note. Names do not grant an exception; notebook curves use an explicit offline note probe. Persistent outputs must be control-rate; audio inserts must return audio, and only insert graphs may read FxInput. Every stored node is checked, even an unused one. Each audio path in a persistent graph needs a detector conversion; an unrelated detector cannot justify an audio operand in control arithmetic. Analysis-backed audio converted by a detector receives the same preparation in macro/shared/parameter graphs and plugin signal lanes as in a voice or audio insert: harmonic and modal fits, extracted exciters, partial/residual analyses, morph correspondences and onset-slice tables. Scratch-capacity discovery uses the same complete template set. With a neural-enabled engine and a loaded model, decoded audio can also drive these controls through a detector, for example neuralOsc "model" [0.2, 0] |> follow { attack = 5ms, release = 100ms }. Macro modulation, shared controls and parameter lanes retain separate decoder instances. Their worker output is ready before control evaluation; explicitly free controls continue decoding while transport is stopped or paused. Missing follower sources diagnose before playback. Live-input followers need a captured take for deterministic replay.

A graph carries the parameter's surface units: Hz for frequency, dB for an EQ gain, a linear multiplier for level, and −1…1 for pan position. Intermediate values are not clipped to macro travel. Existing bare-macro and single-macro affine spellings retain their legacy destination mappings. At insert slots such as width, that legacy mapping can differ from surface units; see Mixing tracks and buses.

The destination still decides when to read. A written automation lane (Draw a change, and record one) aims at these same destinations and does not change any row of this table: a shared timing model does not upgrade a DSP destination to audio rate, and the lane records which cadence it got so the inspector can say when the next update will be heard.

DestinationRead policy
Reverb wet, continuous gain inserts, strip/master level and sendsPer-sample curve, retaining the destination's smoothing
Track-effect coefficient controls (delay feedback, dynamics curves, color knobs)Block end when settled; while moving, the end of each 16-sample slice, with existing processor smoothing
Voice delay/comb time and feedback; chorus/flanger/phaser/bbd/ensemble amount, rate, depthPer sample (the modulator's own curve, not a block-endpoint line)
EQ band frequency/gain/QBlock target; existing 32-sample coefficient grid
Strip pan/widthA knob: block endpoints interpolated as a stereo matrix; a modulation graph: per-sample curve, matrix per sample
Track gainTrackScheduled notes sample graph controls at their exact onset; held-note gain stays latched
Bus/master gainTrackContinuous gain insert
chooseByCycle decision
Arrangement whenHost walk decision using the latest published graph value
A region's control = { … } curveLatched at each onset the region admits; a sounding note keeps the value it took

Legacy inline pan/gain LFO lanes retain their phase and anti-pop smoothing. Hosted plugin parameters and MIDI CC lanes use the same persistent evaluator, but send only the first sample of each block, clamped to 0…1. The general control evaluator runs on desktop and wasm; plugin endpoints require native hosting. See Host instrument and effect plugins.

A graph-valued group level shares one engine VCA across its members; the workbench group fader remains an additional trim. Existing bare-macro/affine group levels retain their initial-only behavior and warning for compatibility. Graph varispeed starts at unity until its first control block. On recompile, persistent control state survives only when its graph and reachable macro unit/range contracts remain compatible. Macro reads through shared controls and other macro modulators participate in this check. Unrelated macro additions or declaration reordering do not restart a compatible control.

Share one control instance#

Ordinary signal bindings instantiate independently at each destination. To share one phase, detector or random stream across destinations, declare a control with an explicit stable identity:

@shared "room-motion-v1"
motion = lfoSine 0.5Hz |> scale [0, 0.4]
play "saw" "c3@4" |> reverb 0.6 motion |> delayTrack 200ms motion

The annotation belongs above a control-rate signal binding. Its string is the instance identity, independent of the editable binding name; imported identities are scoped to their module. References in helpers and voice/effect expressions read the same prepared instance. No extra macro knob is created. Shared controls and macro modulation run in dependency order; a cycle reports the offending path. Unshared random and simulation instances keep independent streams.

Compatible edits retain state, including when an unrelated graph moves storage indices. A changed graph or seed resets that instance. For unshared destinations, identity follows the owning declaration and control occurrence; @identity can pin that declaration. Explicit shared identity survives a binding rename.

Graph and insert compatibility uses the shared typed semantic projection in ir/src/identity.rs. Source locations (including pattern-event locations), annotations, display groups and cost estimates do not reset state. Output maps, saved knobs, node operations/references, units/rates, effect order, bypass and prepared alternatives remain significant. A user field or knob named cost or annotations remains ordinary data. Keys retain resolved instance/node names and node order; they do not promise continuity across arbitrary rewrites. Seed, resource and source-table checks still apply separately. Key encoding is internal, not a persistent identity format.

OperationPersistent control state
New note, note release, track or scene stop/startContinues while the transport runs, including insert tails
Insert bypassControls stay warm; the insert retains its existing bypass/reset policy
Compatible recompileState carries into the new prepared template
Hard stop/restartReset; the same seed and inputs reproduce the same control stream
Seek or transport re-originDestination/shared graphs reset; newly admitted stateful macro sources reset; legacy macro-only phase behavior remains compatible
Pattern or arrangement loop without a seekContinues; pattern readers keep their declared looping clock

New persistent simulation instances start local simulation time at their reset point. A seek resets them at the destination; it does not replay the skipped history on the audio thread. This is a reset policy, not reconstruction of a previous performance. Literal voice simulation timing retains its legacy behavior; live worlds start at voice onset. Macro base ownership and explicit manual takeover/release are described in Macros and automation.

Use one audio function on a whole track#

An audio function can listen to its own input and change that sound. audioFx runs such a function as an insert, keeping its filter and detector state between notes. It also works in an aux return or the mixer's master inserts.

Listen for: the brighter attack of each bass note closing into a rounder tail.

Listen to this example · four bars, then the tail
tempo = 120bpm
timingVersion = 2
autoFilter audio =
  let level = audio |> follow { attack = 5ms, release = 100ms }
  in audio |> lpf (200Hz + level * 4000Hz) 0.7
bass = patch { source = saw 110Hz, ampEnv = adsr 2ms 160ms 0.2 100ms }
t = play bass "c3 ~ c3 ~" |> audioFx autoFilter

Make it yours: raise 100ms to 300ms for a slower closing filter, or add { wet = 0.5 } after audioFx autoFilter to blend the original brightness back. A macro or control expression can move wet continuously. Inside a patch, call autoFilter directly on the audio expression. In a return, use bus movement = audioFx autoFilter; in master inserts, use [audioFx autoFilter].

The function receives stereo audio at its actual insert level. An identity function (\x -> x) preserves both channels at unity. DSP operations retain their usual channel policy: a mono processor can still collapse stereo. Filtering the branch that feeds follow changes only the detector's input; it leaves the audible branch unchanged unless you process that branch too.

Insert state runs through silence after notes stop, so delays can ring out. Exports use the selected tail window and its final fade. Bypass clears the processor's memory and passes dry audio; re-engaging starts with cleared state. An unchanged graph retains state on recompile. Changed graphs or source tables use the existing short effect-chain crossfade. Transport jumps reset state. The dry side of the wet blend follows the graph's reported latency.

An insert has no individual note's pitch, velocity or envelope. Pass explicit controls into your function instead. Control update cadence and finite memory limits remain those of the DSP operations you compose.

Listen at a precise point in the mix#

tapLive drums { stage = "preFader" } |> follow follows the drum source independently of its fader. Choose "postStrip" to follow the strip or "postFx" to follow the complete chain. insertPoint "dry" creates a named boundary read with stage = "insert:dry". These sources work in shared controls, macro graphs and audio inserts, including returns. The compiler orders all current reads and diagnoses instantaneous loops. Explicit tap provides a fixed 128-sample feedback delay. See Route audio between tracks for stage, latency, scene and export behavior.

Stage 6B verification: Expressive Modulation Stage6b 2026 09 11.