VOLTAreference

Reference / Language and libraries

Language reference

Language and libraries · playable examples

Open in the workbench →
On this page · 18 sections

The complete guide to Volta syntax, values and commands, with links to musical explanations and detailed feature pages.

New to Volta? Start with Make sound with Volta and Your first sound. You can play a phrase with one line:

Listen to this example · four bars, then the tail
play "keys" "c4 e4 g4 e4"

Listen for: a short rising-and-falling melody. Make it yours: change e4 to eb4 to alter its color. For a longer path, try Build a synth sound, Rhythm and groove, Melody and harmony by ear, Modulation, Sampling, Effects, and From loop to finished track. The sound recipe book, glossary and troubleshooting guide are useful companions.

The shape of a song: a patch defines an instrument, a pattern describes its events, a track joins them, a mixer balances the parts, and an arrangement chooses when they play. You can begin with built-in voices and add these layers as your idea grows. The production handbook connects the complete language to practical production decisions.

About this reference#

A detailed reference for Volta’s language. Use it to look up an exact rule or explore a capability after hearing it in a guide. Volta is a pattern-first, typed language for sound design and live coding: a Rust compiler lowers your source to a JSON IR, and one Rust audio engine renders that IR — natively on the desktop, and in the browser inside an AudioWorklet. The workbench you are reading this in is the same Rust program in both places (native on the desktop, WebAssembly in the browser), so a song sounds the same wherever you open it.

Authority & caveats. When this file and the compiler disagree, the compiler wins: the primitive set is defined by the builtin registry (compiler/src/builtins.rs, ~450 builtins) and the reference catalogue beside it (compiler/assets/docs-data.json, which feeds hover, completion and this manual's per-symbol cards). A test suite checks that every registry name is at least mentioned here, so a new builtin cannot ship undocumented. docs/spec.md is the aspirational design (a fuller dependent-type story than is shipped) — treat it as intent, not behaviour. Per-feature canonical docs live in docs/reference/<feature>.md; this file maps the whole surface and points at them (§16 is the index).

Signatures use name arg arg — note. Sig R U = a signal at rate R (Audio/Control/Event) carrying unit U. Most builtins are unit/rate-polymorphic (Ty::Hole), so signatures show intent, not rigid types. Optional args are [bracketed] — and every [bracketed] knob can also be given by name, in braces after the positional arguments: galactic 0.4 { bigness = 0.8, mix = 0.35 } skips brightness/detune at their defaults (Named options and parameter values).

The core vocabulary (Your first sound §7) — the ~45 names a first session needs; everything else is the producer tier (the body of this reference) or the lab (measurement, research models, bit-exact ports). Hover and the reference panel show the tier; completion ranks these first.

names
program shapepatch song import
tracksplay sound knob
sourcessaw square sine triangle noise sample freq pitch vel
shapingadsr lpf hpf gain drive chorus unison
modulationlfoSine macro scale const
patternsfast slow rev every stack silence transpose chord arp degrade off superimpose chop struct ply
the trackpan speed swing reverb delay compress tape

The first sound, with nothing declared (Your first sound):

play "saw" "c3 e3 g3" |> cutoff 800Hz |> release 200ms |> reverb 0.3
sound "bd sn hh*2"

1. Program shape#

A program is a flat list of top-level name = expr bindings. Values may refer to later bindings: dependencies are realized before their consumers. Static queries use the same dependency order and the program's key, tuning and timing settings. Cycles between value definitions are errors; dependency analysis includes conditional paths. Recursive functions use rec and bounded compile-time evaluation. No statements outside do blocks.

tempo = 122bpm                      -- module tempo (a bpm literal)
key   = "e minor"                   -- ambient key for roman-numeral / scale-degree patterns
import "drums" (clap, rim)          -- pull public bindings from drums.volta
import "kits" as k                  -- whole-module namespace alias → k.drumKit
priv body = patch { source = sine (freq pitch) }   -- declared but NOT importable

type Quality = Maj | Min | Maj7     -- a user-defined sum type (see §2)
bright = macro 0.5                  -- a top-level binding (a knob named after it)
pad    = patch { source = saw (freq pitch), ampEnv = adsr 300ms 500ms 0.8 800ms,
                 cutoff = 800Hz, gain = vel * 0.4 }               -- a synth
pat    = !"c3 eb3 ~ g3"             -- a pattern (mini-notation)
padTr  = play pad pat               -- a track
  • Comments: -- to end of line.
  • Strings: "double quoted". Patterns: !"…" (mini-notation, §8).
  • Annotations: name : TyExpr = … (types) and @key "value" decl annotations (e.g. @doc "…"), written above the binding. @shared "stable-id" makes a control-rate binding one module instance across its consumers; see Bring a sound to life.
  • Proof block: patch { … } by { decide } — discharge refinement obligations (currently decide only; advisory, see §2). Optional — the refinements are checked either way, so ordinary songs never write it (volta fmt --modernize removes it).
  • Callable names: lambda parameters retain their lexical binding while their callable type is still unknown. Named and anonymous signal lambdas both apply. Scalar settings shadow builtins too; use fx.drive when a module also declares a scalar drive setting.
  • Privacy: priv on any binding, type, or module keeps it module-local.
  • Namespaces: binding identity is (module, name) — two modules may define the same name; import … as m exposes members only as m.name; builtins also have qualified spellings (track.gain ≡ gainTrack). See Names and library imports.

Surface forms#

FormExample
Binding / typed bindingx = 1 + 2 · lead : Sig Audio Bipolar = saw (freq pitch)
Import / import-as / privateimport "lib" (a, b) · import "lib" as ns · priv x = …
Type declarationtype Beats = Int · type Chord = { root: Pitch, … } · type Q = Maj | Min (§2)
Parameterized modulemodule kit(punch: Normalized) = { kick = patch {…}, … } (§2)
Patch literalp = patch { source = saw (freq pitch), … } (an optional by { decide } proof block may follow)
Patch templatesoft = pluck with { cutoff = 300Hz } · patch from soft { glide = 40ms }
Top-level audio bindinglead = saw (freq pitch) |> lpf 2kHz 4 → a one-out synth
Lambda (patch transform / audio fn)warm = \s -> s |> lpf 3kHz 1
Function-def sugarmkPad b = patch { … } ≡ mkPad = \b -> patch { … } (the head must start its line — see §"Declaration boundaries")
let (one or many binders)let a = saw (freq pitch) b = a*2 in a + b
Record construct / access / updatec = { root = ~C4 } · c.root · c with { root = ~D4 }
Pipenoise |> hpf 1500Hz 1 ≡ hpf noise … (lhs → first arg)
Named optionst |> galactic 0.4 { bigness = 0.8, mix = 0.35 } · adsr { attack = 2s, release = 3s } · galactic 0.4 hall where hall = { … } — a builtin's optional knobs by name, in braces, last; skipped slots take their defaults (Named options and parameter values)
Record-field defaulttype Cfg = { gain: Db = -6dB, pan: Bipolar = 0 } · module comp(gain: Db = -6dB) = … — a record TYPE field may declare a literal default, and a call that omits the field gets it (below)
Rate operator @lfoSine @ 1/4c (sugar for application; 1/4c is a Cycles value — a period of a quarter cycle — and means the same in lfoSine (1/4c))
Operators+ - * / · % (Euclidean remainder, binds like *) · ++ (str/list concat) · == (compile-time), < <= > >= (static or live) · # <| |+ +| |* *| (pattern merges, §9)
Conditionalsif cond then a else b (values or signals) · case x of { Maj -> …, Accent n -> …, _ -> … } (compile-time)
Unit literals400Hz 2kHz 180ms 0.5s 8Cents 12Semitones 4Beats 1/4c 120bpm -3Db (kHz = scaled Hz)
Pitch literals~C3 ~Eb4 ~F#-1 (tilde-prefixed; octave mandatory)
Lists[-4, 4] · [[8, intro], [16, drop]]

Declaration boundaries. Newlines are trivia to the lexer, so the parser decides where one declaration ends and the next begins by shape: an identifier followed by = or : opens a binding, and an identifier followed by one or more parameter identifiers and then = opens a function definition. The second rule additionally requires the name to start its line, with its parameters and = on that same line — without it t1 = play lead pat followed by t2 = … would be indistinguishable from t1 = play lead followed by pat t2 = … (the same token run). Write declarations one per line, starting at the line's first column, and both readings agree.

Canonical spelling for new code#

Volta accepts more spellings than it teaches. New code — and everything the reference, the course and the copilot put in front of a reader — uses one of them. The others stay accepted — there is no language version switch and no legacy lowering adapter, so a spelling the compiler once took, it still takes. They are legacy, not wrong.

RuleCanonicalAlso accepted
Lexical names. A thing is named by the binding that declares it, not by a string repeated beside it.bright = macro 0.5 — the knob takes its binding's namebright = macro "bright" 0.5 (write the string only when the knob's public name must differ from the binding)
Subject-first pipes. A chain starts with its subject and reads left to right, one stage per |>.riff |> every 4 rev |> fast 2fast 2 (every 4 rev riff)
Explicit musical units where confusion is costly. Write the unit wherever a bare number could be read in a second domain — time, gain, pitch, rate.|> duck kick 0.7 5ms 80ms|> duck kick 0.7 5 80 — the bare numbers adopt the port's domain (seconds here), so this asks for a 5-second attack, which the engine clamps; the compiler warns (BareNumberUnit) and @strict refuses it
Named options for long calls. Past the first two or three positional arguments, name the knob you mean instead of counting slots.|> galactic 0.4 { bigness = 0.5, mix = 0.5 } — brightness and detune keep their defaults|> galactic 0.4 0.5 0.5 0.5 0.5 — the same call, with all five slots counted rather than named

Written out, the four rules together:

warmth = macro 0.35
riff   = !"c3 eb3 g3 bb3" |> every 4 rev |> fast 2
lead   = patch { source = saw (freq pitch), cutoff = 800Hz + warmth * 2000Hz,
                 ampEnv = adsr 5ms 180ms 0.3 240ms }
leadT  = play lead riff |> galactic 0.4 { bigness = 0.5, mix = 0.5 }

volta fmt --modernize rewrites the legacy spellings it can prove equivalent — const on a bare literal, by { decide }, a parenthesised literal argument, and macro "name" v where the name repeats the binding — and proposes an explicit pattern subject for a pattern-last call (every 4 rev p → (p |> every 4 rev)). It writes a file only when the program compiles to the same IR, so a file whose meaning would move is reported and left alone. It is not the formatter and does not touch layout: see Format and modernize source § --modernize, and Read and write pattern pipes for the pipe family's exact receivers and limits. The unit rule is the one rule --modernize cannot apply for you: a bare number that adopted the wrong domain already means something, so only a reader can say what was intended (§2, [[#The unit policy, in one table]]). The compiler says when the reading is implausible for its port — warning[BareNumberUnit]: "duck's attack reads 5 as 5 seconds — did you mean 5ms?" — and a @strict binding refuses every bare duration and frequency.

What this section does not claim is a guarantee. Types, stage checks and range analysis enforce what §2 describes; the rules above are house style on top of that, and a legacy spelling is refused by nothing.


2. Types, units, rates#

The checker keeps a deliberately narrow type representation (compiler/src/types.rs):

Int Float Bool BoolSignal(Rate) Str Pitch Trigger
Const(Unit)          -- compile-time value with a unit
Sig(Rate, Unit)      -- a signal at a rate, carrying a unit  (Modulator u ≡ Sig Control u)
Adsr Range(Unit) Distribution Curve(Unit)
Pattern(T) List(T) Union([T]) Tuple([T]) Arrow([T], T) Record([(name, T)]) Effect(in, out)
Named(name, [T]) Var(index) -- user types and their generic variables
Track Unit Hole

Which of the rules below is a refusal, which is a warning and which is neither is the subject of [[#What is enforced, and by what]] later in this section.

  • Numeric precision: compile-time floating-point values use f64. Kernel literals and initial state obey the same finite f32 boundary as graph constants. A numeric constant materialized in a signal graph must round to a finite f32, after unit conversion (for example, milliseconds become seconds). Normal rounding and underflow to zero are allowed. Large intermediate values can still reduce to a representable result before graph construction; unused generic code and discarded static branches do not materialize their constants. Source, native IR and JSON preparation apply this same rule to every graph constant.
  • Rate Audio ≻ Control ≻ Event; a mixed expression joins to the higher rate.
  • Unit ∈ Hz Cents Semitones Seconds Milliseconds Beats Cycles Bars BeatsPerMinute Samples Linear Db Normalized Bipolar Q Ratio Dimensionless. A type shows Dimensionless as Number — hover, the REPL and diagnostics say h * 2 : Number, Signal Control Number — while the IR keeps its wire spelling (). Arithmetic on plain numbers checks as that Number even when both operands are Int or Float; a binding's inference pass then narrows arithmetic over literals alone (2 + 1 : Int, 3.0 * 2 : Float) but not through a name (h = 3.0 … h * 2 : Number). Same value, two spellings of its type.
  • Unit algebra (the complete policy is the table in [[#The unit policy, in one table]]; what follows is the musical reading of it): Hz + Cents → Hz, Hz + Semitones → Hz, Cents + Semitones → Cents; seconds and milliseconds convert to a shared time scale. Normalized × T = T (also Bipolar/Linear/Ratio/Dimensionless). Products of physical quantities need a supported result domain: Hz * Hz, Seconds * Seconds, Cents * Cents, Q * Q and dB * dB are rejected. A unit label never makes a squared quantity look like its original dimension. Dividing compatible quantities produces a unitless ratio (1000Hz / 500Hz = 2); 1 / 2s = 0.5Hz, and 1 / 2Hz = 0.5s. The musical fraction 1 / 8c retains its eighth-cycle meaning. This is a finite musical unit algebra, not a general system of dimension exponents. The same reciprocal rule applies to scalar controls (Normalized, Bipolar, Linear, Ratio): n / 250ms is Hz, while 250ms / n is a duration. Other inverse domains are unsupported: 1 / 2st, 1 / 2dB, 1 / 2Q, 1 / 2beats, 1 / 2bars, 1 / 2Samples and 1 / 120bpm are errors. dB + dB and dB - dB operate on logarithmic magnitudes; multiplying or dividing dB by a scalar scales that magnitude. dB / dB is a unitless quotient of the written numbers, not a linear gain ratio. dB + Linear and dB * dB are invalid; no implicit logarithmic conversion occurs. A LEVEL port declares its conversion instead: gain (on a track, a bus, the master or a voice) and a mixer level read dB or a linear gain and convert 10^(dB/20) explicitly (the port table below). Products of scalar representations use Linear, then Ratio, then Bipolar, then Normalized precedence; a nominal range label never proves bounds or clamps the result. Division of matching scalar representations gives a unitless ratio.
  • Duration arithmetic uses the same unit plan before conversion at its consumer. Bare numbers in a sum adopt the other operand's unit: 500ms + 500 is 1 second; 0.125c + 0.125 is a quarter cycle (0.5 seconds at 120 BPM in 4/4). Inline and named expressions agree. A unitless result uses the consumer's ordinary convention: 1000ms / 500ms is 2, read as 2 seconds by a delay and 2 beats by wait. Mixed cycles and absolute time still require an explicit conversion; arithmetic does not infer a tempo.
  • Pitch arithmetic: Pitch ± Const(Cents|Semitones) → Pitch (lowered as PitchAdd).
  • Frequency intervals: Hz ± Cents/Semitones multiplies the base frequency by the signed interval's ratio, for constants and live signals. For example, freq pitch - 8Cents detunes down eight cents; freq pitch - 8Hz subtracts eight hertz. 440Hz + 12st is 880Hz; 100Cents + 1st is 200Cents. Frequency-plus-interval is symmetric; interval-minus-frequency is rejected. Parentheses retain their meaning: (440Hz + 12st) + 440Hz is 1320Hz. The compiler shares this operation plan across checking, folding and IR; live transposition emits an explicit Detune node.
  • Negative literals: a - glued to a digit with whitespace, an opener, a separator or an operator before it is part of the number — limiter -3dB 60ms, scale [-7, 7], x = -0.5, 2 * -3, { cents = -1200 }. A - after a value is subtraction: a - 3, a-3, a - -3. Application therefore takes a negative argument without parentheses ((-3dB) still works, and volta fmt --modernize drops the parentheses).
  • Application binds tighter than arithmetic. t |> pan y * b reads as ((pan y) * b) t: the call pan y is formed first, the product is taken of that function, and the pipe then tries to call the result. The checker names this shape and the two repairs — parenthesise what the call takes (|> pan (y * b)) or close the pipe first ((t |> pan y) |> …) (compiler/src/check.rs::not_callable_error). Tracks have no arithmetic at all: + between tracks is not a mixer and * on a track is not a fader — use a bus/mixer and gainTrack or a strip level.
  • Division / on two integers is the number it names: 1/8 is 0.125, 8/2 stays the integer 4. In a rate or duration slot a bare fraction is Hz / seconds, and the compiler advises the cycle spelling ((1/8c)).
  • Modulo %: a % b is the Euclidean remainder — always in [0, |b|), so (-1) % 4 is 3, not -1 (Rust's truncated %). Chosen for the dominant use, table indexing (nth (i % length xs) xs, §14), where a negative result would be an out-of-range error instead of the wrap the author meant. It binds at the * / / level (a + b % c ≡ a + (b % c)), requires comparable quantities and converts their scales (150Cents % 1st = 50Cents; Hz and intervals cannot be remainders of each other), keeps Int operands integral, and is compile-time only — live operands are rejected during type checking; a % whose operands don't fold is an error, and a zero divisor is a spanned diagnostic. The [0, |b|) bound is exact for integers; on floats the usual rounding caveat applies — a tiny negative left operand can land on |b| itself (-1e-18 % 1 is 1, not 0), because the correction that lifts the remainder into range is a single addition. Integer indices, the intended use, are unaffected.
  • and short-circuits: if the left operand folds to false, the right is never evaluated — so a bounds guard is writable, if i < length xs and nth i xs == p then …. Pruning is left-to-right only, so a guard written after the access guards nothing. or mirrors it (short-circuits on a folded-true left operand) and binds looser — a or b and c is a or (b and c). Both are contextual keywords, still usable as names.
  • Signal comparisons are values (Runtime Control Flow Stage 1): <, <=, > and >= compare the operands' actual numeric domain. m > 0.5 is false at exactly 0.5; an Hz control is compared in Hz. Live comparisons emit Compare, an exact 0/1 operation without input clamping or smoothing. Musical intervals compare after conversion to cents. The explicit gate audio effect keeps its own waveshaping behavior. and over live comparisons is their product; or is a + b − a·b. An if over a live condition lowers to then·g + else·(1−g): both arms always run, and the result joins the rates of the condition and both arms. Naming or returning this expression preserves its live type. Branches must be scalar numbers/signals or booleans with compatible types and units; compatible seconds/milliseconds and cents/semitones are converted before blending. Lists, records, patterns, functions and other structural choices require a compile-time condition and fail during checking when the condition is live. case stays compile-time.
  • Control range tags: mixing dimensionless Normalized, Bipolar, Linear or Ratio controls with +/- produces a dimensionless result. This proves no output range. An explicitly required Normalized slot still rejects a raw Bipolar signal; use scale [0, 1] to supply that range.
  • Static operation boundaries: == cannot compare live values, including live payloads inside tuples or sum values. % also rejects numeric patterns consumed as live controls. These errors are checked through functions and containers, including unused bindings. Numeric patterns retain operand order: 1 / !"500 1000"ms is an Hz control; !"500 1000"ms / 2 is a duration control.
  • Const → Sig lifting: a Const u is promoted wherever a Sig _ u is expected (the lowerer folds it to a constant node). A bare number lifts the same way, adopting the slot's unit — lpf 2kHz 4 and svf "lp" c 7 need no const and no suffix (so do additive amplitude lists). A Pattern Number lifts to a control signal in arithmetic (or explicitly via signal). Q also accepts Normalized, so a live resonance (const 2 + grit * 8) drives a q port directly. The reverse is refused: the Normalized 0..1 resonance of ladder/diode/korg35 reads an amount, not a quality factor, so ladder c 0.7Q d is an error that points at svf/svfMorph.
  • Aggregate types: List Hz checks the elements, record updates carry their new field types, and known generic constructor payloads instantiate type arguments (Some "hello" cannot satisfy Option Int). Heterogeneous lists retain their possible element types for structural consumers such as weighted pattern pairs. Scalar and aggregate function results specialize at calls, including callbacks stored in records/lists or returned by functions. map, filter, foldl, scan, iterate and unfold check callback types before evaluating elements, including zero-term generators. Branch joins retain rates inside records, tuples and lists. Known constructor tags may carry live payloads; matching a payload literal still needs a static value.
  • Annotations reject unknown names (including misspellings). Str is the string type; Sample retains its string-name spelling. Three readable spellings name the types a module parameter most often takes: Patch ≡ SynthPatch (an instrument handle — the annotation is nameable, and a wrong argument is still caught when the instrument is resolved), Number ≡ Float, and Note ≡ Pitch, so module lane(inst: Patch, pat: Pattern Note) = { … } reads. Unlike the older prelude names, these three yield to a user declaration: a file with its own type Note = … means its own type everywhere. When inference leaves a value unknown, its annotation remains an obligation checked when elaboration exposes the value, even for an unused annotated local or a function alias. A list operation or callback cannot erase a known unit error. Forward type aliases retain their eventual types; alias cycles are errors. Frequency annotations distinguish frequencies from intervals and periods: a : Hz = 12Cents and a : Hz = 2c are errors, including in function arguments/results, numeric patterns, aggregates and constructor payloads. Branches must agree on their physical domain even when one is not selected. Seconds/milliseconds and cents/semitones remain compatible scales; an annotation preserves the value's scale rather than performing a cast. Bare Normalized annotations do not prove a value lies between 0 and 1. Int cannot erase physical units or accept fractional values, including fractional ratios such as 1Hz / 2Hz. Whole ratios work as integer counts. An authored Signal Control contract rejects audio-rate values; Signal Audio accepts control-rate values. Builtin ports retain their documented modulation rules.
  • Equality converts comparable numeric units (100Cents == 1st), and rejects unrelated domains in either operand order. Pattern equality includes timing, payload, velocity, sample variants, glide, tuning and note controls; source positions do not affect it. @test uses this same equality.
  • Lexical names: a binding shadows a builtin in value and call positions. sine = 1 makes sine 440Hz an error, and the error names the shadowing binding. Qualify a builtin to select it explicitly: src.sine 440Hz, mods.gain, pattern.arp, form.markers. Patch fields are bindings too: use source = saw 440Hz |> mods.gain 0.5 inside a patch that also declares a gain field. A qualified builtin keeps its identity through resolution and cannot be captured by that field. Sibling field types resolve in dependency order, independent of their written order. A field's own name still reads an outer binding (cutoff = cutoff). Dependency inference uses the patch's defining scope; a local at the reference cannot capture a sibling's free names. Duplicate fields and dependency cycles are errors. Substituting a patch argument or overriding a field rechecks affected sibling uses without executing or reordering field computations.
  • Calls and cases: parentheses preserve calls to returned functions; user functions accept partial application and subsequent arguments apply to a returned function. Cases reject unknown constructors, wrong payload arity, repeated binders and patterns incompatible with the scrutinee. Known sum types and booleans must be exhaustive, including constructor payloads. Add _ -> … when the remaining cases intentionally share a result. Types and constructors resolve through imports (lib.Color, lib.Red).
  • Inference and recovery: declaration progress is separate from value types. A failed declaration and its transitive consumers have no checked type in editor queries, regardless of declaration order; independent bindings remain available, including after an unknown annotation. Forward-inference depth exhaustion is an error. Ordinary lambda parameters have distinct inference identities; id = \x -> x preserves one shared parameter/result. Let and top-level functions instantiate independently at each use. Captured parameters stay monomorphic: \f -> (f 440Hz, f 2s) is a type error. Deferred operations and projections follow the same substitution as the function's type. Records, lists, partial/returned calls and constructor payloads retain those relationships. Occurs checks reject infinite types such as \x -> x x, even in an unused function; failed equations roll back. Deliberate ?name holes retain their syntax and contextual goals, separately from ordinary inference variables and declaration failure state. Legacy open builtin slots still project to Ty::Hole until their operation is specialized. Checking callbacks never executes them or draws randomness. Scalar graph operators require concrete operand evidence before emitting nodes; unresolved generic operands are errors at that boundary. An unapplied generic function may remain open. Direct named holes acquire the operator's operand domain (500Hz + ?depth gives ?depth an Hz goal; multiplication uses a dimensionless neutral hole). Pattern transforms retain their payload type; classifying a literal does not render its events. Control constructors such as const, hold, signal, stepSeq and smooth retain unresolved input obligations until concrete values arrive. They reject nonnumeric payloads during checking, including inside unused functions. Numeric sequence/held-pattern inputs retain their physical units; signal and stepSeq also accept boolean patterns as 0/1 windows. Scheme parameters provide constraints rather than replacing inferred callback results; see Define your own value types.
  • Arithmetic errors: evaluated division by zero and signed 64-bit integer overflow are spanned compile errors. Pitch decoding and transposition also diagnose values outside their supported range. Unselected static branches and short-circuited operands are not evaluated.
  • Refinements T | pred parse but are dropped by the checker; enforcement is post-lowering via interval arithmetic in refine.rs, emitting advisory diagnostics (e.g. "filtEnv.decay ≥ ampEnv.decay — not a pluck"). by { decide } is the proof slot. An annotation is a claim about the annotated binding's own value: it is checked against that binding's graph even when the binding is only consumed as an operand and not published, and a derived b = a with {…} does not inherit it.
  • Constraint holes ?name — a refinement run backwards: the compiler proposes scalar values (interval inversion) or patterns (seeded search) that satisfy the predicate, pinned to literal source. An open hole plays a neutral default (fails only under @strict). See Explore choices with constraints.
  • Counterpoint & theory predicates — pattern refinements grown relational: melodic-shape rules (stepwise, singlePeak, …), rules against a sibling voice (intervalsWith, noParallels, contrary, …), soft goals via and prefer <pred>, and species N cantus bundles. Forward they lint (warn + witness); backward a seeded CSP and a roman functional walk solve them. See Interweaving melodies.
  • Opaque value types carried between builtins: Curve (linear → keytrack), Distribution (uniform → randomPerNote), Scale (tonality → inScale), Spectrum (the …Spectrum generators → additive), Wavetable (wavetableOf → wavetable), plus Track/Synth/Song produced by play/patch/song.

linear slope constructs a prepared dimensionless response curve for keytrack curve referencePitch. The reference pitch may be computed, but must be fixed before voice execution. Key tracking returns the slope times the MIDI-semitone distance from that reference.

uniform [lo, hi] constructs a prepared distribution with exactly two finite dimensionless bounds. randomPerNote distribution uses the existing seeded draw for each note; constructing or checking a distribution draws nothing. Curves, distributions and these callables work through aliases, functions, imports and containers. Named calls use slope, bounds, curve/refPitch, and dist. Live controls cannot choose prepared slopes, bounds or reference pitches.

What is enforced, and by what#

Four layers carry the guarantees below, at different times, against different evidence. Reading them as one body of rules is the usual mistake: an annotation is not a bound, a range finding is not a refusal, and only strict mode turns a refinement into a failed compile. Each layer states what it guarantees, what it does not — with the counter-example that is pinned by a test — and the test that holds the line.

(a) Types — inference, units as types, Ty::declared. Guarantees. Every expression has a type before anything is lowered (compiler/src/check.rs, compiler/src/check/judgement.rs, compiler/src/check/inference.rs). A unit is part of the type rather than a comment on it — Const Hz and Const Seconds are different types, the algebra above is the whole law (compiler/src/semantics/units.rs), and a port's declared unit is the law for every spelling of its argument. A record field's declared default rides its type, so a field's type is read through Ty::declared() and never matched directly. Checking runs no DSP, executes no callback and draws no randomness. Does NOT guarantee. A type proves a DOMAIN — never a range, never liveness. Normalized is a nominal label: the annotation does not prove the value lies in [0, 1], and mixing scalar tags with + gives a dimensionless result that proves no output range. A Sig type is not evidence that a value is live, because const 0.5 has it. And a type is not inherited from a failed neighbour: a broken declaration has no type in editor queries rather than a permissive one. Proved by. compiler/tests/unit_policy.rs and compiler/tests/language_conformance.rs (one numeric law under one label, across the four evaluation paths), compiler/tests/type_integrity.rs, compiler/tests/generic_inference.rs, exit_matrix::checking_draws_no_random_and_evaluates_no_callback, and compiler/tests/inference_recovery.rs for the failure side.

(b) Stage checks — what must be known at compile time. Guarantees. Exactly one port declaration carries a closedness obligation: Live::Const, whose declaration is "folded at compile time". compiler/src/params/liveness.rs discharges it at CHECKING, in every spelling — a literal, a name, an imported name, a record field, a list element — with the caller's span, so it beats the lowering arm that used to notice first. refresh_application (compiler/src/core/calls.rs) is the single boundary every call passes through after its family, which is why a shared gate belongs there and cannot pre-empt a family's own sentence. Does NOT guarantee. Live::Structural declares a SHAPE, not closedness, whatever its slot: a structural number port takes a signal by design (p |> degradeBy (lfoSine @ 1c)), and a structural text port naming a sample accepts a sampler source. The boundary also refuses only what it can prove unsatisfiable — an unsettled value stays unknown and passes, and the lowerer's evaluator is strictly stronger, so "unproven" is never a refusal. Proved by. compiler/tests/exit_matrix.rs, which walks every port against six spellings: exit_matrix::a_live_port_accepts_its_value_through_every_abstraction, exit_matrix::a_const_or_structural_port_refuses_a_name_in_lowering, and exit_matrix::the_unproven_remainder_is_named — the remainder is listed, not hidden.

(c) Range analysis — advisory unless a contract asks for proof. Guarantees. After lowering, compiler/src/refine.rs runs interval arithmetic over the graph and reports what it can prove about a value's range, carrying the witness that made it. Run backwards (compiler/src/solve.rs), the same analysis proposes: interval inversion for a scalar ?name hole, a seeded search for a pattern one, and species N cantus expands into the hole goals a counterpoint walk then satisfies. Does NOT guarantee. Nothing in this layer refuses a program. A refined annotation is a SOFT diagnostic — cutoff: Modulator Hz | x <= 5000Hz on a patch that reaches 15 kHz still compiles, with an error-*level* diagnostic and a witness — so never read a violating song as one that "won't compile". An unpinned hole bakes to a neutral default and plays. Proved by. refine_diags::violation_without_strict_still_compiles (the pinned counter-example), refine_diags::pluck_advisory_anchors_at_the_filt_env, constraint_holes::scalar_hole_lowers_to_neutral_default_and_warns, counterpoint::species_one_solves_the_fux_cantus.

(d) Strict mode — the opt-in that makes (c) bite. Guarantees. @strict "on", per module or per binding, makes the user's refinement obligations load-bearing: enforce_strict in compiler/src/lower.rs fails the compile on the first strict obligation that is refuted or merely undischarged — under strict, "not refuted" is not enough. Because that is a compile error out of lower, the live-coding contract holds for free: a failed compile does not swap in and the previous audio keeps playing. An open ?name hole fails a strict build. Does NOT guarantee. Only user obligations bite. Convention checks and heuristic lints — the pluck advisory, the headroom lint, the Stage 5 bounds — never fail a strict build, and @strict "off" on a binding exempts it from a module-wide @strict "on". Proved by. refine_diags::strict_on_fails_a_violation_with_witness, refine_diags::strict_rejects_an_undischarged_obligation, refine_diags::convention_warnings_do_not_fail_strict, refine_diags::strict_off_exempts_a_binding_from_module_strict, constraint_holes::strict_rejects_an_open_hole.

Across all four: the editor sees a partial answer, the engine never does. The checker has a second, recovering mode used only by the IDE and by diagnose — per declaration, per ambient mixer/bus/group/submix block, and per type declaration inside the type environment — so one broken piece costs itself and its own references, not the file's other answers. The executable doors (volta <song>, --diagnostics-json, the MCP and copilot compile_volta) print that recovery as one error per independent mistake: a use site's "cannot use x" folds into x's own error as (bdepends onxand was not checked), and the declarations with no path to a broken one are lowered so their lowering errors show too — each such lowering is thrown away, and the sweep stops after six (diagnostics::failure_errors, failure_report::*). Any recovered diagnostic skips the product phase, so a partial program can never become a plan (inference_recovery::the_query_recovery_never_reaches_the_executable_compiler, inference_recovery::a_broken_ambient_block_leaves_every_other_block_typed, inference_recovery::an_alias_cycle_costs_one_type_declaration_and_a_reference_is_its_own_diagnostic).

The unit policy, in one table#

Everything above is one law, and compiler/src/semantics/units.rs is where it lives. Every site that converts, accepts or refuses a unit — the checker, the constant evaluator, timed code, the control planner, the graph adapters, the track/score lowerers and the shared builtin-port gate — asks that module. A particular port may still declare a narrower domain of its own (see the end of this section); none may declare a wider one.

A unit belongs to exactly one dimension. A dimension may have more than one representation, and those convert by a fixed scale. Two scalar representations carry an advisory range, which describes intent and is never a proof or a clamp.

DimensionRepresentations (canonical first, conversion scale)Advisory range
ScalarDimensionless, Linear, Ratio, Normalized, Bipolar — all ×1, and no two of them convert into each otherNormalized 0…1, Bipolar −1…1
FrequencyHz—
IntervalCents; Semitones ×100 → cents—
TimeSeconds; Milliseconds ×0.001 → seconds—
BeatsBeats—
CyclesCycles—
BarsBars—
SamplesSamples—
TempoBeatsPerMinute—
DecibelsDb—
QualityQ—

Beats, cycles and bars are separate dimensions, not representations of one another and not representations of time: converting between them needs a tempo map or a meter, which arithmetic never infers.

With that, the whole operation policy. D is any non-scalar dimension — the first row covers scalar-with-scalar — and tagged scalar means Normalized, Bipolar, Linear or Ratio (a bare number is Dimensionless). Cells marked refused are check-time type errors naming both operand units.

LeftRight+ −*/%
ScalarScalarequal tags, or a tag with Dimensionless, keep the tag; mixed tags → DimensionlessLinear ≻ Ratio ≻ Bipolar ≻ Normalized ≻ Dimensionlessthe left tag survives a Dimensionless divisor; Linear also survives Ratio/Normalized/Bipolar, Ratio survives Normalized/Bipolar, Normalized and Bipolar survive Ratio; every other pair (a tag over itself included) → Dimensionlessas +
DimensionlessDD — the bare number adopts the authored domainDsee reciprocalsD
tagged scalarDrefused — a range label is not a magnitude in DDsee reciprocalsrefused
DDimensionlessDDDD
Dtagged scalarrefusedDDrefused
HzCents/SemitonesHz, by ratio: the interval converts to signed cents and multiplies the frequency by 2^(cents/1200)refusedrefusedrefused
Cents/SemitonesHz+ → Hz (symmetric); − refused — an interval minus a frequency has no meaningrefusedrefusedrefused
DD (same dimension)the canonical representation, both sides converted by their scalesrefused — no supported squared domainDimensionless, both sides convertedcanonical D
D₁D₂ (different)refusedrefusedrefused except the reciprocals belowrefused

Reciprocals are the only inverse domains, and only a scalar numerator reaches them: scalar / Seconds|Milliseconds → Hz, scalar / Hz → Seconds, and scalar / Cycles → Cycles (the musical fraction 1 / 8c keeps its eighth-cycle meaning). Every other denominator dimension is refused — 1 / 2st, 1 / 2dB, 1 / 2Q, 1 / 2beats, 1 / 2bars, 1 / 2Samples, 1 / 120bpm.

Ports — a builtin parameter declared with a unit (params::Slot::Unit(target)) reads the same policy through units::port_fit, and answers one of four ways:

The call suppliesThe port declaresOutcome
a bare number or any tagged scalaranythingadopts the port's domain, unscaled — lpf 2kHz 4, gain 0.5, a Normalized macro in a Q port
the same dimension, another representatione.g. Secondsconverts once by the representation scale — limiter -1dB 60ms
Beats/Cycles/BarsSeconds/Millisecondstempo-folded at the duration consumer, never in the unit algebra — delay 1beat 0.3
Seconds/Milliseconds/Beats/BarsCyclesclock-folded at the consumer against the track's own cycle — nudge 10ms
dBa LEVEL: gain (track, bus, master, voice), a mixer levelconverts by the port's declared law, 10^(dB/20) — the one logarithmic conversion, and a port's, never the algebra's: |> gain -3dB is ×0.708
anything elseanythingrefused at check time, naming both: `nudge`'s `offset` reads Cycles, not dB

Adoption is legal, and judged. A bare number in a Seconds, Milliseconds or Hz port adopts that unit whatever it was meant to be, so the compiler judges the reading against the port's declared clamp, or, for a port without one, a narrow per-unit default: more than 10 seconds, less than one millisecond, an audio-band frequency (a cutoff, a pitch) under 20 Hz. Outside it is warning[BareNumberUnit], naming the reading and the suffix that lands the same digits in range (`comb`'s `time` reads 6.1 as 6.1 seconds — did you mean `6.1ms`?). Rate ports (a chorus or grain rate, an FM depth, a frequency shift) and long-by-nature ones (a ramp's over, a song offset) have no default. A binding under @strict refuses every bare number in these ports, plausible or not; zero is exempt everywhere (compiler/src/params/plausibility.rs).

A level converts dB, by declaration. A level port reads dB or a linear gain: a dB value that folds converts once, in dB (gain ((-3dB) + (-3dB)) is −6 dB), and a dB literal inside a live level is that level's trim (gain (fade * -6dB) is 0.501 · fade, as on a mixer strip). A live signal that is itself in dB — a macro -6dB, a linram (-6dB) 0dB 4s ramp — is refused: neither level reader (the note-on latch, the continuous gain) has a per-sample dB law, and reading −6 as a linear factor flips the polarity 15.6 dB hot.

A Cycles port counts the track's EXPANDED cycle, so a bare nudge 0.01 after slow 8 is 0.32 beats; nudge 10ms, (1beat/4) and (1bar/16) are absolute, converted against that cycle and the track's clock (ms at the song tempo; beats exact under a tempoMap). A hair-sized cycle nudge a long cycle turns into ≥ 1/16 beat is warning[NudgeCycleSpan], naming the realized offset.

That table is the ceiling, not the floor: a descriptor may declare a narrower domain for one of its ports, and several do — a filter cutoff reads Hz or a bare/Normalized number and refuses the other range labels, a Const port refuses a live value whatever its unit. The ladder/diode/ korg35 resonance declares Normalized, so a Q value there falls to the last row: refused, with a sentence naming svf/svfMorph for a real Q. Those narrowings are per-port and are listed with the port in the parameter schema. What the table forbids, no port may allow.

Two behaviors are pinned as they read today rather than covered by the table. comb's time port accepts a musical period and then has no duration fold behind it, so the engine reads the node as seconds (comb (1/64c) is 1/64 of a second, not of a cycle) — one corpus song depends on it. And a value annotated Linear still accepts a dB magnitude through check::judgement, which is the one place the "no implicit logarithmic conversion" rule above is not enforced.

User-defined types#

Name your own types, introduce constructors, and case on them — type aliases, nominal records, sum types (enums + tagged unions with payloads), and generics. All are compile-time CtValues that erase before lowering (no runtime tags). This is the floor under first-class Chord values and typed module configs. See Define your own value types.

type Beats   = Int                                  -- alias
type Chord   = { root: Pitch, quality: Quality }    -- nominal record
type Quality = Maj | Min | Dom7 | Maj7              -- enum sum
type Interval = Chromatic Semitones | Diatonic Int  -- payload-carrying constructors
type Option a = Some a | None                       -- parameterized (generic)

c  = { root = ~C4, quality = Maj7 }                 -- construct a record
c2 = c with { root = ~D4 }                          -- functional update
depth = \i -> case i of { Chromatic s -> s, _ -> 2Semitones }
                                                    -- a constructor pattern binds `s`

Parameterized modules (functors)#

module Name(params) = { … } is a module parameterized over a typed config — ML's functor, framed as a re-skinnable kit. It is an ordinary function taking one record: configurations can be named, returned by helpers, locally bound, imported, projected or updated with with. An empty module takes {}. Aliases and higher-order helpers preserve this calling rule. Reach result fields by qualified access. Each application flattens into its own independent synths/patterns/tracks, and inline macro knobs are namespaced per instance — all at compile time. A track-valued field is a track of the instance's own: out = r.line re-binds it (one track, named out), and song { main = [r.line] } plays it under r.line. See Configurable library modules. Positional module arguments must be written as fields of the config record. Ordinary functions receive record arguments intact, even if a field shares the parameter's name.

module drumKit(punch: Normalized, tone: Normalized) = {
  kick = patch { source = sine (freq pitch), ampEnv = adsr 0ms (60ms + punch*90ms) 0 80ms,
                 cutoff = 180Hz + (macro "drive" 0.5) * 3200Hz, gain = vel },
  hat  = patch { source = noise |> hpf (5000Hz + tone*4000Hz) 1,
                 ampEnv = adsr 0ms (20ms + punch*40ms) 0 20ms, gain = vel * 0.5 }
}
k909 = drumKit { punch = 0.85, tone = 0.6 }   -- one instance
kick9 = play k909.kick !"g1 ~ g1 ~"           -- k909.kick is a synth; k909.drive a per-instance knob

Default arguments (record-field defaults)#

A record type's field may declare a default — a literal, written after the field's type. A call that omits the field gets that literal, spelled at the call site. It is the one way a user function gets optional arguments, and it works wherever a record type is written: a module parameter list (which is its configuration record's field list), an anonymous record type in an annotation, and a named type.

module comp(drive: Normalized = 0.5, tilt: Bipolar = 0) = {
  lead = patch { source = saw (freq pitch), gain = vel * drive }
}
hot  = comp { drive = 0.9 }   -- tilt is 0
flat = comp {}                -- drive is 0.5, tilt is 0

type Cfg = { lvl: Normalized = 0.4, wide: Bipolar = 0 }
shape : Track -> Cfg -> Track = \t cfg -> t |> gain cfg.lvl |> pan cfg.wide
out = play hot.lead !"c3 e3" |> shape { lvl = 0.9 }

The rules, in one paragraph. Naming an argument is naming a field of the record parameter — a user function has no builtin-style "options" reading, so f { … } is always that parameter's operand and never has to be disambiguated against a schema. Partial application is unchanged: a record fills one parameter like any argument, so shape { lvl = 0.9 } on a config-first function is still a Track -> Track value. A pipe binds its subject to the first positional, so a pipeable function declares its subject first. The default must be a literal (a number with an optional unit suffix, a string or a boolean) so that it means the same thing at every call site, in every file that imports the declaration; an expression is refused where it is written. Aliases, imports, returned functions and functions held in a record or list all fill the same defaults, because the defaults ride the declared type. Diagnostics stay the caller's: a field with no default that is left out is record is missing \x\, an unknown field is `unknown record field \`x\, and a field written twice is duplicate record field \x\`. What is **not** supported: optional *positional* parameters (there is no way to tell a missing trailing argument from a partial application) and keyword call syntax (f a = 1`). The staged rationale is User Function Arguments; builtin ports have their own per-port defaults (Named options and parameter values).


3. The synth (patch) model#

A patch { … } is a record of fields. Two shapes:

Classic subtractive — give the parts and the lowerer wires the voice graph (oscillator → synthesized filter from cutoff/resonance → amp), the standard fields:

pluck = patch {
  source    = saw (freq pitch + pitchMod),       -- the audio source DAG
  ampEnv    = adsr 0ms 180ms 0 80ms,             -- amplitude envelope
  filtEnv   = adsr 0ms 120ms 0 60ms,             -- modulation envelope (feeds cutoff)
  cutoff    = 500Hz + filtEnv * vel * 4500Hz,
  resonance = 4,
  pitchMod  = 0,                                  -- summed into the source's freq
  gain      = vel * 0.55 + 0.25,                  -- output level (Control Normalized)
  glide     = 70ms,                               -- optional: mono portamento (TB-303); default time, `(glide=…)` per note
  voices    = 8                                   -- optional: per-synth polyphony cap
}

Explicit out / audio binding — build the whole audio graph yourself (filters written inline as lpf/hpf/bpf); a top-level Sig Audio binding becomes a synth with one out. See Voice signal flow.

Every classic field but source (or out) is optional — omit cutoff for no filter, ampEnv for no envelope; resonance defaults to 1 when cutoff is present.

Field projection: somePatch.field on a top-level patch declaration lowers by pure substitution of that field's own expression — kickP.ampEnv * 4000 means exactly what the patch wrote there, re-lowered at the new site (note-scoped inputs like vel/pitch take the new site's context; the bench prompt's curve rung uses it, Try an idea in the notebook). Projection on anything else reports the v0 limit. Templates build a patch by overriding a base: soft = pluck with { cutoff = const 300Hz, gain = pluck.gain * 0.5 } or the constructor spelling patch from soft { glide = 40ms }. Templates chain, may add fields/directives the base never declared, and may reference base fields by name (strings = cinematicStrings with { cutoff = const 700Hz + filtEnv * 2000Hz } — the base's fields are in scope for the whole override list, exactly as a patch literal's fields see each other) as well as through the base.field projection. The lambda form \p -> p with { … } uses the same additive update: a function can add a previously absent cutoff or glide, and existing fields are replaced. Named, imported, returned and locally bound transforms share this rule. Patch values keep their saved @knobs settings when updated. Referencing a missing base field is an error; updates are never conditional on its existence. See Patch Templates.

A field name is not a binding. Sibling fields see each other, but a field's own name does NOT shadow the outer scope inside its own value: in a patch { … } literal cutoff = 300Hz + cutoff * 4000Hz reads the top-level cutoff macro, so a knob may be named after the field it drives. A field cannot depend on itself through a sibling ring, and in an UPDATE the base's old value is read as base.cutoff — x with { cutoff = cutoff * 2 } is a cycle, x with { cutoff = x.cutoff * 2 } is the doubling.

Voice knobs on the track (Your first sound §2): any patch field name applied to a play track sets that field — play p pat |> cutoff 800Hz |> resonance 4 is play (p with { cutoff = 800Hz, resonance = 4 }) pat (a lifted template, p$knob1); attack / decay / sustain / release address the stages of the patch's adsr ampEnv; a pattern argument (|> cutoff "<400 800>"Hz) is a per-event lane the lifted patch reads through trackParam "cutoff", sampled per hit on the lane's own cycle like gain / speed. The explicit spelling is |> knob "cutoff" 800Hz. Names the track already knows (gain, pan, reverb, …) keep their track meaning. Built-in voices: play "saw" pat (saw square sine triangle pluck pad bass keys) is a compiler-owned patch, lifted under the hood to saw$voice — no declaration needed for the first note.

Per-voice note params (read inside a patch): pitch (Sig Control Pitch), vel (Normalized), noteTrig (a voice-onset Trigger, held high for the voice; edge readers such as hold latch it once), inside a processO source kernel only noteTime (seconds since the note started) and noteGate (1 held, 0 released), and trackParam "name" [default] (Sig Control Normalized — a named per-note parameter a track sets, so one patch can be played differently per track). freq : Pitch → Hz. const x lifts a literal into a control signal keeping its unit (const 30Semitones is a Sig Control Semitones) — a bare literal lifts the same way, so const is never needed on a literal and volta fmt --modernize removes it (see Format and modernize source).


4. Sound sources#

  • Oscillators saw·sine·square·triangle (freqHz) → Sig Audio Bipolar (bandlimited).
  • Pulse / PWM pulse freq width — variable-duty; width (0..1) may be modulated at control or audio rate. These five oscillators accept Hz-valued audio frequency modulation, such as sine (440Hz + sine 10Hz * 110Hz). Their frequency and width contracts remain the same through aliases, containers and returned functions.
  • Circuit-model VCO as3340 "tri"|"saw"|"pulse" freq [pw] [character] — an AS3340/CEM3340 tri-core VCO model; character 0 (clean DCO) → 1 (worn chip). engine/src/as3340.rs, rendered on every target. Named options include quality = "high", audio-rate linearFm, and independent character amounts; |> sync masterHz ["soft"] resets or reverses the core. High quality includes automatic voice/track timing alignment. See The AS3340 oscillator. Frequency requires Hz or bare numbers; pulse width, character, linear FM and the character amounts require dimensionless values. These contracts survive aliases, containers and returned functions. Supplying only the mode creates a partial function; supplying mode and frequency completes the call with its optional defaults. Fixed character and detail values must be finite; they clamp to 0–1 and 0–2 respectively before engine precision conversion.
  • Oscillator character sig |> soften amount (rounds the saw/DCO reset corner, one-pole 10–300 µs; live amount 0–1; soften 0 is bit-identical; takes any audio source, so as3340 … |> soften 0.5 is one voice), osc |> sub octaves [level] (a square sub-oscillator tracking note pitch, octaves 1..4, level default 0.5).
  • Noise noise (white), pink (−3 dB/oct), coloredNoise tilt [width] (continuous −1 dark … +1 bright colour; optional 0..1 true-stereo width — the tilt and width accept live controls, read per sample; both seeded streams and their filters keep running even at zero live width), and crackle density (Poisson surface ticks and rare louder pops through energy-scaled short resonant decays; density may be a live macro or control signal — the rate is held from the first active sample of each rendered voice slice, including scheduling splits). Tilt and width are dimensionless; crackle density accepts Hz or bare clicks per second. All are per-voice seeded and deterministic; sustained white/pink/coloured streams do not loop. Fixed tilt and width must be finite and clamp to their stated ranges before engine precision conversion. Fixed crackle density must be non-negative and finite in engine precision (zero is silent). These same preparation rules apply to native and JSON plans, including live-port fallback fields; invalid replacements preserve the installed performance.
  • Additive additive (freq) [level, …] — partial levels are constants or control signals (one per harmonic; ≤32 in an explicit list, 512 from a generator). Variants: additiveAt freq [[ratio, amp, phase?], …] (inharmonic bank — bells/tines/gongs), additiveOf (sample "x") (freq) (additive resynthesis from a sample's STFT). Post-ops: inharmonic B (Railsback piano stretch), ensemble depth (per-partial detune-drift, string-machine chorus), tuned [amount] (snap every partial's pitch class onto the ambient tuning's cents lattice — timbre matched to intonation; see Match timbre to tuning). Procedural spectra feed additive's second arg: sawSpectrum n, squareSpectrum n, triangleSpectrum n, pulseSpectrum n width, formantSpectrum n [f1, …], gaussSpectrum n center bandwidth (Shepard source). See Additive synthesis.
  • Partials (SMS analysis-resynthesis) — a compile-time analysis value cello = partialsOf "cello-a2" [maxPartials n] analyses a recording at load into McAulay–Quatieri tracks (per-frame frequency/amplitude trajectories) plus a 24-band noise residual. PartialsAnalysis values pass through functions and containers; PartialsConfig holds the static count setting. partials cello (freq pitch) plays it back, repitched by hz / f0. Modifiers fold into the one node: pTime r (stretch by moving the cursor — no smear, no phasiness, and cost independent of r), pPos sig (scrub analysis time with a signal; mutually exclusive with pTime), pNoise x (live residual level; 0 = sines only), pStretch x (live partial-frequency ratio exponent; 1 = unchanged), pTilt x (live numeric dB/octave partial slope; 0 = unchanged), pMorph target amount (live morph of partial pitch ratios, amplitudes and residual; primary duration, normalized destination timeline, constant or control amount 0–1), residualOf other (A's tracks over B's breath), pFormants (hold the analysed spectral envelope under repitch) and tuned [amount] (snap each track's mean ratio onto the ambient tuning). Renders silence until the bake lands. Supersedes additiveOf for anything but its cheap harmonic case, and the phase vocoder for stretching pitched material. See Analyze and rebuild a sound.
  • Wavetable wavetable "name" (freq) (pos) — pos (0..1) sweeps the table; wavetableOf builds a table in-language from frames (or a sample). See Wavetable synthesis.
  • Physical models modal (freq pitch) [[ratio, decay, amp], …] [exciter] (a modal resonator bank — T60-exact two-pole modes; inharmonic ratios = bells, near-integer = marimbas/tines), string freq damping [brightness] [pickPos] [exciter] [stiffness] [sympathy] (Karplus-Strong waveguide, cents-accurate fractional delay; the two trailing knobs are 0..1, default 0 — stiffness stretches the partials like a stiff string, ideal → piano ≈ 0.27 → sitar, and sympathy hangs every voice of that node on one bridge so notes ring each other and each decay breaks in two; { bridge = "passive" } makes that bridge a lossy scattering junction for a far harder split, with weight, bank and a cross-patch/cross-track group = "name" — Strings, bells and resonating objects §1.10), bowed freq pressure [position] [sympathy] (friction-table bowed waveguide; pressure is live — envelopes/macros bow it; a sympathy puts it on the same passive bridge; { body = "violin"|"viola"|"cello"|"bass" } radiates it through an authored acoustic body), sig |> stringBody body [mix] (the same violin/viola/cello/bass bodies as a voice effect for any source — a plucked string, strike, noise; "none" and mix = 0 are exact passthroughs — Strings, bells and resonating objects §1.12; bowedEvo and stringBodyEvo take the same arguments and render the evolved string core and body, a one-word swap to A/B by ear — §1.14; a track insert |> stage room x depth { listener, radiator } seats the track in a hall and a shared bus h = hallTail room { listener } carries its late tail — § "The hall" there), blown freq pressure ["reed"|"jet"] [noise] (a clarinet or flute pipe; pressure is live blowing pressure — put the envelope there; blownEvo takes the same arguments and plays them on the physical woodwind core, whose kind also names "flute", "recorder", "panflute", "shakuhachi", "clarinet", "sax", "oboe" and "bassoon" — Strings, bells and resonating objects §1.15), bowedModal freq pressure modes { speed, rosin, friction, blade } (the same stick–slip bow — or, with friction = "finger", a wet fingertip — on a mode table: a bowed vibraphone, cymbal or bell, a glass harmonica, and with blade = 0.66 a musical saw whose bend follows the pitch — Strings, bells and resonating objects §1.13; bowedModalEvo takes the same arguments and renders the evolved engine bank, so one word A/Bs them). Exciters strike s / pick c / scrape r are seeded note-locked bursts; ANY audio signal also works as an exciter (modal … (noise * env)). The mode table can also be fitted from a recording — modalOf (sample "bowl") [modeCount] fills the same slot and the engine bakes the ratios/T60s/amps out of the audio at load time — and the bank composes as a chain element: src |> body (freq pitch) modes hangs a modal body on any signal (its exciter is the piped source). The modal library (import "modal" (bell, marimba, gong)) ships measured tables. Coupled bodies: drive |> couple freq a b { coupling = 0.4 } connects two independent literal/fitted mode tables with reciprocal internal energy exchange. Live ratio, decay, stretch, excitation position and pickup balance shape a held note; see Coupled bodies. Hybrids: exciterOf "tabla" [len] extracts a deterministic 40 ms hand (integer 2–500 ms) for a modal/string exciter or the input of body. native in the frequency slot uses a modalOf fit's recorded root and is rejected for literal tables. modes |> sustainBy k |> stretchBy s |> tiltBy dB reshapes literal or fitted tables with folded constants; identity emits no IR keys. track |> bodyTrack native (modalOf "bowl") 0.35 adds the body's resonance to untouched dry stereo audio, with a live amount and exact zero null (constant Hz also works). Fitted amplitudes are √mode-count normalized; literal levels are unchanged. See Strings, bells and resonating objects §1.9. Clouds: track |> modalCloud 55Hz 32768 6s { detune = 12, stretch = 0.05, bright = 0.5, width = 0.8, seed = 3, amount = 0.6 } rings up to 32 768 tuned resonators with the track's mid and adds their stereo sum; every knob but amount (a macro-rideable 0–1, zero a true null) is constant. From 4 096 modes GPU offload renders it on the GPU's synchronous lane, bit-identically (Run DSP on the GPU).
  • Circuit-modeled drums kick808 tune decay [drive] (a true bridged-T resonator — the ring's own amplitude shifts its operating point, so the pitch dip is intrinsic and grows with drive), kick909 tune decay [click] (real VCO-style pitch-envelope sweep + a highpassed click layer), snare808 tone [snappy] (two bridged-T shells at 1:1.83 + the snappy noise path), hat606 decay [tone] (six squares through the metallic bandpass; decay closed→open — drive it from a per-note trackParam to play both from one patch), clap909 spread (three retriggered bursts + a body tail), and the rest of the box — tom808 tune decay [noise] (a pitched bridged-T pair + the noise puff), rim808 [tone] / cowbell808 [decay] (knobless one-shots: bare rim808 is the stock hit, like noise), cymbal606 decay [tone] (the hat bank through the two-band swing VCA — ride and crash). Knob-tuned like the hardware (no pitch tracking); every knob is a control signal latched per hit, seeded + deterministic. mesh freq tension damping [size] [strike] is the one PITCH-TRACKED drum — a 2-D waveguide mesh membrane (toms, timpani, gongs) whose clock freq pitch sets; size is baked (it sizes the junction grid — the cost guard). import "machines" (tr808, tr909, skins) wraps them as parameterized kits. See Design a drum kit.
  • FM / RM / PM / sync fm modSig index (audio-rate freq mod, sums into a freq port), fmOp freq feedback (Hz or bare frequency; dimensionless feedback in radians), ringMod a b [mix] (≡ a * b, with an optional 0..1 dry/wet crossfade against a), phaseMod sig mod depth (true PM over a wavetable, an fmOp, or a plain sine — which the compiler promotes to the equivalent fmOp f (const 0)), osc |> sync masterFreq ["hard"|"soft"] (oscillator sync, polyBLEP/BLAMP; default hard). See FM, phase and ring modulation.
  • Phase distortion osc |> phaseDist amount ["curve"] — the Casio CZ's filterless sweep: the carrier's own phase accumulator is warped (or a synced cosine is windowed) before the lookup, so one oscillator runs from a pure cosine to a bright saw/pulse or a marching resonant formant with no filter in the patch. Curves: "saw" (default) and "square" (the two DCW phase warps), "sawReso" / "triReso" / "trapReso" (the resonant windows). amount is a live 0..1 signal, so a filtEnv in the slot is the DCW envelope; it replaces the carrier node, so only an oscillator or fmOp can receive it. Band-limited (4× oversampled, no added latency); see phaseDist in The effects reference. See The effects reference.
  • Unison sig |> unison count spread [blend] [width] [drift] — detuned stack (spread in cents; defaults: blend 0.85, width 0.6, drift 2.5Cents); supersaw … is the same with the JP-8000 non-linear detune curve; jp8000 detune mix [width] [drift] is the thesis-exact Szabo model (7 fixed voices, default width 0.6, published offsets, measured mix law, and the hardware's keytracked highpass at the played note on the summed stack; knobs 0..1 — a literal folds at compile time, a macro/LFO/envelope goes live through per-voice LUTs of the exact knob→cents law, so one knob sweeps lush pad → rave swarm). unisonWith count law [width] [drift] writes the per-voice law yourself: law is \i n -> cents or \i n -> (cents, gain) (voice index i in 0..n-1), folded at compile time once per voice into unison's own stack. See Unison and layered voices.
  • Magenta instrument keys = magenta "soft electric piano", then play keys "c4 eb4 g4 bb4". Optional { variation = 1.3, drums = false }. Chord patterns steer one continuous local model per track; no audio input is needed. Track effects and the mixer process its output. Native Studio on Apple Silicon; Sounds → Generate freezes heard phrases to portable samples. See Generate samples with Magenta RealTime 2.
  • Sampler sample "name" + chainable shapers: frames startAt pitched speed loop warp trim reverse, chop n / chopAt, slice n "name" idx / sliceAt (onset detection), warpLock. See The sampler, Slice a recording at its attacks, Keep a loop in time, Time-stretching and pitch-shifting (stretch, pitchShift).
  • Bounce (resampling as a value) gen1 = bounceOf 4c (play growl bassline |> mbcomp 0.6) renders a track offline at load into the sample bank; the binding IS the bank handle (sample gen1 — every sample verb above works on it), and generations compound by reference (a bounce may sample an earlier one; the compiler orders the bakes and rejects feedback). bouncesOf n dur f seed chains n generations by reference (gen k renders f applied to gen k-1's handle) into one list-valued binding; sampleAt names idx plays back a bank name chosen from a list (any bouncesOf chain, or a hand-written list of distinct handles) by a live control signal, read once per note — the "resample-and-mangle" workflow's passes knob, made real. bounceOf 8c master prints the whole mix instead of one track — every ordinary track through the mixer, buses and master section into a single handle, so one print respools the entire arrangement's material (sample mixPrint |> speed 0.7). A track that samples the print is left out of it — you print a mix in order to play it back, and a mix cannot contain a print of itself. See Resample your own sounds. A print you want to keep exactly can be pinned beside the song from the editor — the source does not change; see Pin a print: keep a bounce as a song-local sample.
  • Remote samples hook = remoteSample "spotify:track:<id>" 64s 8s names a span of a Spotify track as a sample source — every sample verb composes (hook |> pitched ~C3 |> chop 8). The song carries only the reference; each desktop resolves it locally (the ~/.volta/captures cache, else a muted capture from the Spotify app on macOS 14.4+). Until then the slot is silent; exports refuse an unresolved reference (volta capture <song> prefetches); the browser lists it as desktop-only. Spans ≤ 60 s. See Remote samples: a streaming track as a sample.
  • Sample synthesis kickBuf = sampleOf 0.6s (\t -> sin (t*6.283*55.0) * exp (0.0 - t*9.0)) bakes a buffer from a pure function of time at load; the binding IS the bank handle (every sample verb works on it, a bounce may sample it, and it composes with sampleAt lists). The lambda is process's compile-time envelope minus macros — a baked buffer is static; duration is a constant (≤ 30 s). Completes the bake family: bounceOf renders tracks, wavetableOf builds tables, sampleOf renders pure functions. See Create samples from synthesis.
  • Mastering dynamics & stereo track |> ott amount [depth] — three-band upward and downward compression (mbcomp is downward-only), pulling each band's range in from both ends; depth scales each band's gain travel toward unity (not a wet/dry blend — the crossovers stay flat at every depth). |> monoBelow freqHz — frequency-dependent mono (the elliptical EQ): bass sums to the centre over an LR4 split, the top is untouched. |> decorrelate amount — an allpass widener that works on near-mono sources and leaves the mono sum exactly intact. See Mastering and delivery.
  • Whole-mix varispeed mixer { master = { inserts = [varispeed 0.75] } } — the tape-deck move: at ratio k the entire render advances k song samples per device sample, so tempo, drum transients, tails, formants, groove and tuning all slow and drop together. That is what makes a slowed mix sound printed rather than transposed. Master-only (a track effect is a same-rate box; re-timing is not) and one per mix; the ratio rides a macro (varispeed (drag * 2)) or a scene address (master.varispeed.ratio = ramp 1.0 0.6), clamped to 0.05–4.0; every spelling is the ratio itself, so 1 is normal speed. Offline renders and bounceOf follow the ratio on the song clock, so a slowed export still captures the whole song. See Mastering and delivery §5.
  • Sympathetic strings track |> sympathetic [~c3, ~g3, ~c4] amount — a bank of lightly-damped waveguide strings (≤ 12) hung across the bus and summed back with the dry signal. Pitch-*selective* resonance, which no send reverb can make: material near a string's fundamental rings for seconds, everything else passes through. The tanpura / sitar-*tarab* / una-corda halo; amount is macro-rideable and 0 is a true null. See Strings, bells and resonating objects.
  • Parametric EQ track |> eqTrack [highpass 30Hz, peak 200Hz -3dB 1.4, highShelf 8kHz 2.5dB] — the console EQ (up to 12 RBJ biquad bands; the per-voice eq stays the fixed 3-band tone knob). Band constructors: peak f g [q], lowShelf f g, highShelf f g (fixed-slope shelves), lowpass f [q], highpass f [q], notch f [q]. Every slot accepts surface constants or persistent controls. A bare dimensionless macro maps its [0, 1] travel across the slot's range (freq/Q log, gain linear); Hz/dB macros and affine controls retain surface units. A bell can sweep live, click-free. eqMid [ … ] and eqSide [ … ] run the SAME cascade on one leg of a mid/side encode — tone on the centre without narrowing the image, or frequency-selective width (eqSide [highShelf 9kHz 3dB]) that a width knob cannot make; the encode/decode pair is exact, so a flat M/S band list is a bit-accurate bypass. Buses and master inserts take the same list as eq [ … ]. A bare negative gain (-3dB) is a negative literal (§2, Negative literals); parens also work. See EQ and frequency balance. Band constructors return EqBand, preserving their type through helpers, containers and annotations. A named band can be placed in the list just like an inline constructor; a numeric signal input rejects it with a band-list hint.
  • Air band track |> airBand air [ground] [slew] [limit] — the EQ that isn't a filter: a four-sample Kalman-style predictor runs over the bus and its prediction residual is the air band, the remainder the ground. air and ground are the gains that recombine them — both unity at 0.5, so airBand 0.5 0.5 is an exact null, above 0.5 boosts and below cuts — and because the split is an estimation error rather than a filter output, lifting the top end neither rings nor pre-echoes the way a steep highShelf does. slew (default 0.52) is a built-in high-frequency slew ceiling, limit (default 0, off) how much of that limited copy is blended in: past air 0.6 the boost gets steep, and 0.2–0.5 of limit keeps it from going spiky. All four knobs are [0, 1] and macro-rideable; track, bus or master insert. A port of Airwindows Air4 (MIT). See The effects reference.
  • Tone by density track |> stonefire air [fire] [stone] [range] and track |> fatEq high [highMid] [lowMid] [bass] [out] — a category of their own: tone controls with no band gain anywhere. Both split the bus into bands and change how hard each band is driven into a saturation curve, so a band gets denser and more present without a filter being boosted — nothing can ring or pre-echo, and the "boost" survives a limiter because harmonic content is not peak level. Every knob is [0, 1] and macro-rideable, and the band knobs are unity at 0.5 — both null there, to the f64 floor rather than bit-exactly (stonefire 5.6e-17, fatEq 3.9e-16 and one sample late). stonefire (Airwindows Stonefire, built for its ConsoleX bus) splits with predictors rather than filters — the Air3 residual is the treble (air), a deeper Kalman stage the bass (stone), the remainder the mid (fire), and range moves that mid/bass split (at 0 the bass band is empty and stone does nothing; at 1 the whole spectrum is "bass"). It has no output stage — all three bands full is +13.5 dB of peak, so follow it with gain. fatEq (Airwindows FatEQ) is the pure case: four bands from three cascaded "pear" filters, the whole structure run six times per sample, with saturation depth as the only control. Its lift self-levels with input — measured +23 dB at −30 dBFS down to +0.6 dB at 0 dBFS while THD climbs 1 % → 42 % — so quiet parts get much louder and hot parts just get denser. out is an output trim; costs one sample of PDC-compensated latency. Reach for eqTrack instead whenever you want a measurable dB. Both are track, bus or master inserts, both MIT ports. See The effects reference.
  • Bus saturation track |> saturate amount ["shape"] [mix] — the gain-compensated track-rate saturator ("drive"/"tube"/"clip"/"fold" keep their ADAA kernels; "tape" runs the tape machine's 2× hysteretic record/play electronics at effectively zero transport delay). mix < 1 is parallel distortion in one insert. track |> crush amount [mix] is the bus bitcrusher (the voice bitcrush map; aliasing intentional). Both legal on tracks, buses and master inserts. See EQ and frequency balance.
  • Safety clipper track |> clipOnly [ceiling] — a transparent peak clipper (a port of Airwindows ClipOnly3). Below ceiling the output is the input bit for bit, one PDC-compensated sample late; at the ceiling only the clip's entry/exit corners are shaped, so a flat top never squares off into buzz. ceiling reads positive as a linear ratio and zero-or-negative as dB (clipOnly 0.9 ≡ clipOnly (-0.9151)), default 0.94 (≈ −0.54 dBFS, Airwindows' own); it rides a macro live. No gain reduction, no release, no program dependence — so unlike limiter it cannot pump or dull transients, and unlike limiter it makes no true-peak guarantee. The two stack: clip the spikes off, then limit. Track, bus or master insert. |> clipOnly -1dB reads as a negative literal (§2). See The effects reference.
  • Bus density track |> inflate drive [curve] [mix] — a port of Airwindows Inflamer: an Oxford-Inflator-style loudness curve that adds harmonics at low and mid level and bends back toward linear near full scale, with no dynamics reduction anywhere. drive is a stepped input trim on a power-of-two ladder (2× above 0.9, unity over (0.567, 0.9], 0.5× at the default 0.5, down to silence below ~0.005) and curve (default 0.5) tilts between the even-harmonic +50 transfer at 1.0 and the odd-harmonic −50 at 0.0, with a neutral dead zone over [0.375, 0.625]. Both ladders are the original's quantisation, kept verbatim — the steps are the sound. Unlike saturate it is neither gain-compensated nor anti-aliased. mix (default 1) nulls exactly at 0; all three knobs ride macros live. Track, bus or master insert. See The effects reference.
  • Noise-reduction colour track |> dubly input [tilt] [shape] [output] — a port of Airwindows Dubly3: a µ-law compander in series with its own decoder, which is what a Dolby-B/C tape chain physically was. Encode boosts a level-dependent high band, sin(clamp(x, ±π/2)) stands in for the medium, the decoder subtracts the band back out, and what you hear is the round trip's error — sheen on quiet material, a soft ceiling on loud. Measured at the defaults on a 1 kHz sine: unity below −20 dBFS, −0.24 dB at −6, −1.05 dB at 0 dBFS, with THD 0.06 % → 4.18 % over the same span. input is (x·2)² (0…4×, unity at 0.5, and an exact mute at 0), output is x·2. tilt (0.5) is the encode/decode balance and is asymmetric — the decoder's depth saturates at full for every tilt ≤ 0.5, so below 0.5 it only fades the encoder out (darker) while above it drives the encoder to double (brighter and louder). shape (0.5) splits one coefficient budget between the two band-split one-poles (both ≈4.9 kHz at 0.5), and its outer quarter is an effect, not an EQ: a zero coefficient makes that stage wideband and freezes its compander wide open, worth +6.6 dB at shape 0 and +11.3 dB at
    1. No wet/dry — it is a colour, always in-line. All four ∈ [0, 1] and ride a macro. Track, bus or master insert. See The effects reference.
  • Transient compressor track |> pyewacket [input] [release] [output] — a port of Airwindows Pyewacket, the one compressor here that adds no fatness. Where compress/optical/fet pull the peak down and push the body up, this leaves the transient's full height alone and reshapes only the decay behind it, so crest factor goes up: measured on a drum burst at the defaults, −0.04 dB off the peak while the 50–150 ms tail comes down 2.9 dB. There is no gain-reduction multiplier in it at all — a linear envelope that is structurally one sample late crossfades each sample between the dry line and a starved 1 − cos|x| curve, which is exactly why the transient escapes. input/output are dB trims in [−12, 12] (default 0); release is [0, 1], default 0.5, and higher is faster — it moves where the decay is removed, holding the tail ~11 dB down out to 400 ms at 0 versus back within 0.7 dB by 150 ms at 1. Stereo-linked; all three ride a macro (|> pyewacket 6dB 0.3 -4dB). The input clamps at ±π/2 (≈ +3.9 dBFS), so a trim that pushes peaks past that clips rather than compresses. Track, bus or master insert. See The effects reference.
  • Bézier compressor track |> beziComp [comp] [speed] [mix] — a port of Airwindows BeziComp, and the one compressor here with no envelope follower. The rectified programme is undersampled into buckets and a quadratic Bézier curve is drawn through the last three, so the gain signal is C¹-continuous — its value and its slope carry across every bucket boundary. A one-pole detector is only C⁰, and those slope corners are what a compressor's intermodulation grit is made of; with no corner there is nothing to alias, so the detector can run far below audio rate and still sound smooth. comp (default 0.5) is the threshold on a squared law with a fixed sqrt(threshold + 1) makeup, so the knob is both amount and makeup at once; speed (default 0.5) is the bucket rate on a sixth-power law — the bottom of the knob is one bucket per ~10 000 samples and the top one per sample; mix (default 1) is a linear dry/wet. Detector stereo-linked on max(|L|, |R|); one sample of PDC-compensated latency at every rate (its ClipOnly2 output stage — upstream's descending copy loop cascades, so the buffer is a broadcast rather than a spacing-long delay line, exactly as clipOnly documents). All three ride a macro. Track, bus or master insert. See The effects reference.
  • Sub weight track |> weight amount [freq] — a port of Airwindows Weight, and deliberately not a shelf. Eight cascaded Holt's-method double-exponential smoothers extract a sub band which is then added to the dry signal. Holt's method blends toward the last output plus its extrapolated slope rather than toward the last output alone, so the extraction has no steady-state lag and its upper edge falls away fast — which is exactly what a lowShelf cannot do, because a shelf's single transition slope lifts the 100–250 Hz mud along with the 30–60 Hz weight. amount is required ([0, 1]; upstream's own default is 0, a no-op) and also sharpens the extraction — it feeds the resonance term. freq is the corner in Hz, [20, 120], default 70. Both ride a macro (the macro's [0, 1] travel spans the full 20–120 Hz). Fully independent stereo. Track, bus or master insert. See The effects reference.
  • Air texture track |> silken amount [freq] [window] — a port of Airwindows Silken: a linear-phase windowed-sinc FIR whose taps sit on the composite integers (1, 2, 4, 6, 8, 9, … — the primes are skipped). A regularly-spaced tap grid is a comb, and a comb puts its stopband ripple on exact harmonic multiples, which is what makes a cheap FIR sound grainy; spacing that is irregular but never resonant smears that error into texture. The lowpass residual is added, not crossfaded — out = dry·(amount+1) − filtered·amount — so amount 1 leaves the body at unity and doubles the air. amount is required ([0, 1]; upstream's default is 0). freq (default 0.5) is the corner, squared so the knob is fine at the bottom; window (default 0.5) is the FIR length, up to 998 taps, and is the one knob here with a real CPU cost. All three ride a macro. Fully independent stereo. Note it reports no PDC latency: its group delay moves with freq/window, so there is no single number that would be right. Track, bus or master insert. See The effects reference.
  • Console input grit track |> mackity [trim] [pad] — a port of Airwindows Mackity, a model of the Mackie 1202's input stage: subsonic loss, a square-law input trim, a 19160 Hz lowpass at Q 0.43, the quintic x − 0.1768·x⁵, a second 19160 Hz lowpass at Q 1.16 (two lowpasses at the same corner with different Qs, one on each side of the shaper — that pair is the voicing), a slower subsonic one-pole and the output pad. The quintic leaves the transfer's slope at the origin untouched, so quiet material stays genuinely clean and only the top bends — the console-input signature rather than the everywhere-compression of a tanh stage. trim defaults to 0.1 because that is UNITY on the square law ((trim·10)²): 0.2 is +12 dB, 0.5 is +28 dB, 1.0 is +40 dB, so small moves at the bottom of the knob are large moves in drive. pad (default 1) is a plain linear output attenuator so you can slam the stage and still land at level. Both ride a macro. Fully independent stereo. Track, bus or master insert. See The effects reference.
  • Clipper flavour track |> cornerClip [drive] [posCeiling] [negCeiling] [voicing] [mix] — a port of Airwindows OneCornerClip, and the counterweight to clipOnly. clipOnly softens both the entry into and the exit out of a clip, which is why it is transparent; this softens the entry only — a clipped sample is replaced by last · voicing + threshold · (1 − voicing), a lag toward the ceiling seeded from the previous output, and the clip then snaps out in a single sample. That asymmetry puts real odd-harmonic energy in without a hard corner's buzz, and below threshold at unity drive it is bit-transparent with no latency at all. drive is a dB trim in [−12, 24], default 0 (and any non-zero value forces the unit out of its bypass). posCeiling and negCeiling are independent thresholds reading positive as a linear ratio and zero-or-negative as dB, exactly as clipOnly's does, both default 0.966 (≈ −0.3 dBFS) — setting them apart is asymmetric clipping and brings the even harmonics up. voicing (default 0.618) is how round the corner is: 0 is a hard clip, 1 is a repeat of the last sample. mix (default 1). All five ride a macro (|> cornerClip -3dB reads as a negative literal). Fully independent stereo. One upstream quirk to know: its clip targets are block-local, so the output depends very slightly on the host block size — that is the plugin's own behaviour and is reproduced deliberately. Track, bus or master insert. See The effects reference.
  • Mastering limiter track |> limiter — a lookahead, true-peak brick-wall limiter that GUARANTEES its ceiling (default −1 dBTP / 100 ms). The detector runs 4× oversampled (BS.1770 Annex 2) so it catches inter-sample peaks, and the audio is delayed by the lookahead so the gain is already down when the peak lands. An explicit ceiling is a negative literal — |> limiter -3dB 60ms (§2; parens still work). Track, bus or master insert; reports latency to PDC and gain reduction to the meter. Distinct from the fixed safety limiter at the device hop. See Mastering and delivery.
  • Comping lead = comp [take "rec.t1" 0c 4c, take "rec.t3" 4c 8c] |> fades 12ms assembles recorded takes into one buffer at load; the binding IS the bank handle, like bounceOf/sampleOf. Slices lay end to end in list order, their bounds measured into the SOURCE (any duration unit, compile-time constant); a slice's source is a string literal or a bounceOf/sampleOf/comp handle, so comps compose. |> fades ms is the crossfade policy — one declared equal-power fade at every seam, centred on it and drawing on handle material outside the bounds, so changing it never changes the comp's length. Because a comp is source it diffs, semantically and audibly — the thing no DAW's comp can do. Takes come from the transport's take recorder. See Recording and takes.
  • Granular: sample "name" |> granular, or note-pitched grains "name" ~C3. Independent pools honor trim, sample selection, reverse/speed and stereo. Live source controls: grainSize, grainRate, grainPos, grainPitch, grainSpray, grainPan, grainJitter; source morphing grainMorph [samples] amount, per-grain grainGlide (semitones), grainDrift (source seconds), grainOrbit (pan travel); compile-time grainWindow and grainPitchQuant. Exact clocks: grainEvery (1c/16) / grainTrigger pattern. Track timing and quantization use grainJitterT, grainEveryT, grainTriggerT and grainPitchQuantT. granularize ["name"] reads a sample, the track bus when bare, or actual host audio with "input". The T counterparts expose live insert controls; grainWetT, grainDryT, grainHistoryT, grainFreezeT add mixing/capture. The shared Grains inspector shows real onsets, pools and history, with source edits and frozen-capture keep. See Granular synthesis.
  • Bare control sources (nullary, Sig Control Bipolar) — for modulation, not audio: sine saw tri isaw square rand perlin lorenz henon (e.g. pitchMod = perlin * 4Cents). sine/saw/square are overloaded: nullary = the control source, with a frequency argument = the oscillator. Binding f = sine keeps both uses: f 440Hz makes audio, while f * 110Hz uses its default one-cycle control signal. phase f 0.25 consumes that control form and returns a signal. triangle requires frequency; tri is control-only. tri/isaw/rand/perlin are control-only. perlin also takes a rate (perlin 0.3Hz, perlin (1/8c), or a live control), which selects real 1-D gradient noise — smooth, aperiodic, band-limited around the rate; BARE perlin keeps its original 2 Hz sample-and-hold-through-a-slew sound unchanged. Aliases, containers and returned values retain both forms. Fixed gradient rates must be positive and finite in engine precision. Live rates integrate per sample, clamp to ±0.45 times sample rate and may reverse; NaN holds position. lorenz/henon are chaotic attractors — continuous, genuinely aperiodic modulators (pitchMod = lorenz * 8Cents). They take an optional const rate scaling the integration step (lorenz [rate], henon [rate]; bare = 1 Hz) — slow it heavily for sub-Hz drone motion, e.g. lorenz 0.05. A bare number is Hz; a single c literal is cycle-relative (lorenz 0.125c); a signal-valued rate is rejected.
  • Live input input ["device"] (marks the module non-deterministic). See Live audio input.
  • Cross-track audio tap track reads another track's mixer bus as an audio source (1-block latency) — the basis for ring mod (sine (freq pitch) * tap speaker) and vocoding (§11). tapLive track is the zero-latency sibling: it reads the source track's current block (sample- aligned, for un-smeared dry/wet), so the compiler topologically orders the tracks and a tapLive cycle is a compile error (use tap to feed back). The follow-on family (all reduce to tap + existing DSP): resonate exciter tune [damping] excites a tuned Karplus resonator with a tapped signal (rhythm → pitch); convLive carrier track is live convolution (convolve a carrier with a track's current block as a live IR); specMorph a b amount is spectral cross-synthesis (keep b's phase and level, morph its magnitude toward a's shape — a silent excitation stays silent); specMask signal track amount is a per-frequency spectral sidechain (keep signal where the track is quiet). Cross-track feedback (A → B → A) is legal — the 1-block tap delay tames it, the engine soft-clips (only) those tap reads that close a cycle, so a runaway network saturates instead of blowing up while an acyclic tap and the bus itself stay verbatim, and check_causal warns when the estimated loop gain ≥ 1. See Route audio between tracks, Patterns that respond to playback.
  • Audio → events onsets track [sensitivity] — a live onset-detected Trigger stream from a track's bus ("input" = the mic; marks the module non-deterministic). Block-rate energy-flux detection, 50 ms refractory, ~5 ms trigger quantization; trigger-shaped like noteTrig, so hold (onsets drums) m latches a modulator per hit. Feed it to playOn (§12) to fire notes. See Turn audio into musical events.
  • Audio → pitch pitchOf track [smooth] — a live pitch tracker: the detected fundamental of another track's bus ("input" = the mic; marks the module non-deterministic) as a control signal in Hz, so it drives freq ports and arithmetic directly (saw (pitchOf lead) shadows the lead; pitchOf lead * 0.5 = an octave-down shadow). Per-block normalized- autocorrelation (McLeod) detection over a ~2048-sample window; a confidence gate HOLDs the last pitch on unvoiced/quiet frames; smooth (ms, default 20) slews the output. See Follow the pitch of live audio.

hear it — open Breaks in SONGS: the sound drum machine (mini-notation tokens are sample names) and chop breakbeat slicing, commented line by line. Then Rinse for the same ideas at 170 — the Amen break warped to project tempo, chopped, and speed-stuttered.


5. Filters#

  • Biquad lpf·hpf·bpf source cutoff q (or leave cutoff/resonance as patch fields → synthesized).
  • ZDF / character svf "mode" cutoff q [drive] [character] ("lp"/"hp"/"bp"/"notch", …24 for 24 dB/oct; character 0..1 morphs into the SEM/Prophet OTA cell — circuit-fidelity Stage I), svfMorph morph cutoff q [drive] [character] (mode sweep LP→BP→HP→notch + tanh self-oscillation), ladder cutoff resonance [drive] [makeup] (Moog 4-pole, self-oscillating; makeup = 0 exposes the authentic resonance passband loss), diode cutoff resonance [drive] (TB-303 diode ladder), korg35 "lp"|"hp" cutoff resonance [drive] (MS-20 Sallen-Key), formant "ae→oo" morph (vowel bank). See Filters: shape brightness and resonance.
  • Comb / phaser / flanger comb time feedback (Karplus-Strong-ish; the feedback may be a live macro / affine-macro expression / control signal, ramped across the block), phaser, flanger amount [feedback] (optional live signed regeneration, ±0.95). Audio-rate endomorphisms — pipe them: sig |> drive 0.4 |> comb ….
  • Frequency shifter sig |> freqShift shiftHz [mix] — single-sideband shifting through an IIR allpass quadrature (Hilbert) pair: every partial moves by the same number of Hz, so harmonic ratios are destroyed (inharmonic, bell-like) — categorically unlike pitchShift/shimmer/glitchShift, which preserve ratios, and unlike ringMod, which keeps both sidebands at once. shift is signed; down is freqShift -150Hz (a negative literal, §2). Both slots are live signals and the modulator phase is an accumulator, so a swept shift is continuous — wrap it in feedback for barberpole phasing. See The effects reference.

6. Envelopes, shaping, modulation#

  • Envelopes adsr attack decay sustain release [punch] (punch 0..1 adds an analog attack overshoot, default 0). Attack/decay/release take seconds, milliseconds or bare seconds; fixed beats/bars/cycles use the song clock. Live times use seconds or milliseconds; sustain and punch are dimensionless. Stage values and modifier durations must remain finite at engine precision; punch clamps to 0..1 before conversion. These checks also apply when a host loads an execution plan directly. Live timing ports carry canonical seconds. All five live shape values latch together at note-on or retrigger. Named calls default attack/decay/release to zero and sustain to one, so adsr {attack = 10ms} is complete. Positional prefixes remain functions until all four stages are supplied, including through aliases and imports. Modifiers withDelay t, withSlew t (alias smooth), withDecay t (one-shot linear decay to zero). withSlew is signal-first (m |> withSlew 120ms) and smooth is seconds-first (smooth 120ms m) — the same one-pole either way, and a piped receiver (m |> smooth 120ms) is read as the signal, not as the time. The engine floors the effective release at 2 ms, so the idiomatic tight gate adsr 2ms 0ms 1 0ms stops dead without clicking; anything at or above 2 ms is untouched. There is no such floor on the attack: a 0ms attack is a literal step from silence, so a sine starting mid-cycle clicks (measured on a 1 kHz tone: an HF burst −9 dB re peak at 0ms, −29.5 dB at 1ms, −43 dB at 5ms). Write 1–5 ms unless the click is the point.
  • LFOs lfoSine lfoSaw lfoSquare lfoTri @ rate, lfoSH @ rate (sample-and-hold). Rate via @: lfoSine @ 1/4c (per-cycle) or @ 5Hz. The rate may be a signal, and audio-rate LFOs are a supported idiom (AM / ring-mod / tremolo): the shapes are band-limited (the same polyBLEP/BLAMP kernels saw/square/tri use — ~+22…+26 dB of alias suppression at audio rates), and the rate is clamped at 0.45·sr so a modulated rate can never wrap the phase step into a folded pitch.

    At LFO rates the correction is confined to the one or two samples that straddle a corner — it is not "nothing below 1 Hz". At 0.1 Hz, lfoSaw bends 6 and lfoSquare 10 of the 480 000 samples in a cycle away from the naive shape, by up to full scale; lfoTri moves 7 samples by <1e-4; lfoSine is exact at every rate. For continuous modulation (a cutoff, a gain) those samples are the mid-transition values a hard edge skips over.

    It does matter in one place: a square used as a binary selector. The sample that lands exactly on an edge reads 0.0, not ±1 (lfoSaw likewise reads 0.0 at phase 0 rather than −1). Code that thresholds an LFO square — lfoSquare @ 4c |> scale [0, 1] gating a gain, say — will see that one sample at the halfway value. Threshold with > 0 rather than >= 0, or drive the selector from a pattern instead.

    There is no slow limit, and the rate you ask for is the rate you get. The phase accumulator is f64, so a lfoSine @ 0.001Hz (a 16-minute swell) completes its cycle, and the sum keeps time to 1e-12 at every rate.

  • Rate spellings — one meaning in every position. A Cycles number in a rate slot is a period in cycles, so 8c is one LFO period every eight cycles and 1/8c is a period of an eighth of a cycle (eight LFO periods per cycle — aurora.volta's "1/4c = one beat"). The @ form and the ordinary argument form are the same value: lfoSine @ 1/8c, lfoSine (1/8c) and r = 1/8c … lfoSine r all lower to the identical rate, as do compounds ((3c/8), (2 * 4c)) — a tempo-aware PeriodRate node that keeps the Cycles unit, so a tempo change keeps the musical meaning. Because the number is a period, lfoSine @ 1/16c is sixteen sweeps per cycle and a sixteen-cycle sweep is @ 16c. A bare integer fraction is a plain number, not a cycle count: (1/8) is 0.125 (an Int / Int names the float it is), so in a rate slot it reads as 0.125 Hz and the compiler advises the cycle spellings (IntegerFractionRate: "write (1/8c) for an eighth of a cycle, (8c) for a period of eight cycles"). A rate that folds to 0 or a non-finite period is refused. Durations read the same unit list the other way round — see §11's Duration spellings.
  • Cycle-rate lanes — @ also takes a pattern, so a cycle-locked rate can change by cycle instead of folding to one constant: lfoSine @ !"<0.5 0.25 0.1875>"c is the dubstep wobble vocabulary (half, quarter, dotted eighth) from one patch. Each step is a period in cycles (0.5 ≡ 1/2c); an unsuffixed lane reads as cycles too. An explicit Hz / ms / s suffix reads as the scalar form does, and survives a pattern combinator — !"2 4"Hz |> fast 2 really is 2 and 4 Hz (Patterns with units). The one shape that still erases it is a fold whose operands disagree (stack [!"2 4"Hz, !"0.5"c] has no coherent unit to carry); the lane rejects that rather than guessing, since a lost suffix here is an 8×-to-2000× silent rate error. Rests hold the previous rate; the LFO's phase accumulator free-runs across a change, so the hand-off preserves phase. The four periodic shapes, lfoSH and wander accept lanes; live sample-and-hold retains its seed and integrates the new rate until the next phase wrap. drift retains an absolute-time hash grid and rejects lanes. All six clock sources reject incompatible units and nonnumeric inputs through ordinary callables. See Control patterns for individual events.
  • Analog character drift depth [rate] (free-running per-oscillator pitch wander, cents — the "analog fatness"), tol amount (per-voice component tolerance — a tiny per-note random offset so no two voices are identical), hiss amount (a pink-noise living-circuit floor), accent amount [threshold] [reso] (the TB-303 accent-sweep CV: velocity-gated RC charge that carries between accents in a mono glide voice — sum into cutoff, multiply into gain; opt-in { reso = r } couples the sweep's RC to the filter resonance), flicker rate (1/f parameter noise, bipolar — scale it into a cutoff or level for slow analog jitter). Unit-polymorphic — pipe into any port. See Analog character. Drift depth accepts cents, semitones or bare numbers (cents); semitones convert on fixed and live paths. Dimensionless control depths also mean cents; their range tags impose no depth bound. Live depth reads each sample; live rate holds from the first active sample of each rendered voice slice and re-derives its absolute-time grid. Fixed depth and rate must fit engine precision. Accent amount is dimensionless and read each sample; its threshold is a finite prepared dimensionless value, clamped 0–1. These contracts survive aliases, imports, containers and returned constructors.
  • Seeded "drunk" motion wander rate — a continuous seeded Sig Control Bipolar (a sample-and-hold smoothed by a slew); deterministic + replayable. See Patterns that respond to playback.
  • Simulation sources bounceSim gravity elasticity [rate] (note-anchored accelerating bounce envelope, [0,1]), flock n cohesion [rate] / flockAgent n cohesion i [rate] (1-D boids centroid / one agent — same-param flocks are the SAME flock, so agents correlate across synths), ca rule seed [cells] (elementary cellular automaton scanned as an evolving 0/1 gate), orbit [rate] (chaotic three-body x — bare orbit works like bare sine). Coefficients and rates can be live; dimensionless counts, agent selection and cellular layout are fixed at preparation. Named options, defaults and these unit/stage rules survive aliases, imports, factories and containers. Bare orbit retains its callable rate form through the same value. The rhythm sibling bouncePat gravity elasticity [pitch] writes the ball's impacts as a compile-time pattern (accelerating fill, decaying velocities). See Motion from physical simulations.
  • Pitch tools keytrack (linear k) refPitch; curve constructor linear.
  • Per-note randomness randomPerNote (uniform [lo, hi]) — latched once per note (deterministic from seed + note coordinate).
  • Modulator algebra scale sig [lo,hi] (affine map bipolar→range; either bound may be a live expression — scale [0.1, (0.3 + mouth * 0.6)] — since the map is pure Const/Product/Sum sugar, two constant bounds still fold to the same two constants. The result carries the range's unit: lfo |> scale [0Hz, 2600Hz] is a Signal Control Hz, so a later * someNormalizedMacro stays Hz rather than collapsing to Normalized. A bare range ([0, 1]) stays dimensionless. Exactly two compatible bounds are required; mixed seconds/milliseconds and cents/semitones convert, and unrelated quantities reject. A bare bound adopts its physical peer's unit. The INPUT, though, must be dimensionless — Normalized, Bipolar, Linear, Ratio or plain numbers. lfoSine 2Hz |> scale [0Hz, 2600Hz] is the everyday form and is fine; const 440Hz |> scale [0, 1] is rejected ("scale requires a dimensionless input; divide by a reference quantity first"), because an affine map of a physical quantity onto an unrelated range has no meaning the unit algebra can defend. Divide first: cutoff / 1kHz. The result joins the input and both bounds' rates, including audio), gain sig n (a TRIM, and type-transparent: the result is the PRODUCT of its operands, so an audio chain stays Audio Bipolar through one trim, two chained trims, or a duck product, and a unit-carrying amount multiplies through — filtEnv |> gain 600Hz is an Hz FM index, not a bare envelope; an adsr on either side reads as the [0, 1] control it becomes once triggered. gain is the * of §2, so it inherits §2's rule that a product needs a supported result domain: one side must be dimensionless. const 440Hz |> gain 2Hz is rejected — "incompatible operands for *: Signal Control Hz and Hz" — because Hz * Hz is not a quantity Volta names), multiply (≡ *), gate threshold m (compatible numeric units; inclusive m >= threshold, antialiased transitions, no input clamping; a piped m |> gate threshold gates m, like m |> smooth t), inverse m (1-m, dimensionless inputs only, no clamping). Gate joins both operand rates; inverse preserves its input rate and retains a Normalized input's unit. These constraints survive aliases, imports, returned functions, containers and partial calls; scale 0 and gate 500ms are reusable functions. Other modulation tools include crossfade a b t (compatible branches convert into a common unit, as do min/max), curve "exp|log|^2|sqrt|sCurve", mkCurve a b c d (a 4-point spline consumed by curve; input is dimensionless and clamped to 0–1, named outputs are Normalized, spline outputs may overshoot and are dimensionless), log2/exp2 (log-space interpolation over a dimensionless ratio only: log2 440Hz is rejected — "log2 requires a dimensionless ratio, got Hz; divide by a reference quantity first" — while log2 (cutoff / 1Hz) is fine, and 1Hz * exp2 (log2 (cutoff / 1Hz)) restores a frequency), stepSeq [v…] (or stepSeq !"<[…] […]>" — a pattern of step SHAPES, so a trancegate can swap to its fill per bar), quantizeTo [steps] m (snap a modulator to listed steps, converting compatible units first), hold trig m (sample-and-hold: latch m on each trigger), linram from to over (one-shot note-on linear ramp; derives its unit from compatible endpoints and converts them before interpolation), signal (pattern→control bridge). Tape physics: brake, spinUp, wow, track-level brakeTrack (Tape stops and spin-ups).
  • The trigger algebra (Runtime Control Flow Stage 2; see Turn audio into musical events): count trig [n] (rising-edge counter, modulo n when given, else saturating at 2^24; n is a fixed dimensionless integer from 1 through 2^24). Both arities survive aliases and higher-order calls. count (onsets kick) 4 is a live bar-position-by-kicks. toggle trig (0/1 flip per edge), onCount n trig (clock divider — passes every n-th edge, still trigger-shaped), and choose [v…] sel (stepwise runtime table read, round + clamp — the runtime nth; choose [0.0, 0.3, 0.7, 1.0] (count (onsets kick) 4) steps a modulator through a table one kick at a time).
  • Boundary latching & seeded runtime randomness (the runtime-control-flow quick wins; see Turn audio into musical events): latchCycle cycles m (cycle-boundary sample-and-hold — commit a modulator once per cycle, every n-th with the count; one cycle is one bar on Volta's grid), cycleRand "id" (one uniform [0,1) draw per cycle — position-hashed off the module seed, so replay is bit-exact and a seek lands on the same value; every reader of the same id shares the draw), noteRand "id" (one draw per note, constant for the voice's life, keyed on the deterministic voice coordinate — replayable per-note jitter/round-robin), schmitt lo hi m (hysteresis comparator: 1 above hi, 0 below lo, held in between — the chatter-free decision head; thresholds may be live like gate's), and holdFor dur g (release debounce — stays 1 for dur after the gate was last high; accepts ms/s/1c).

7. Macros & the modulation algebra#

bright = macro 0.5                                   -- a live, [0,1] knob named `bright`
air    = macro 0.4 (lfoSine @ 1/4c |> scale [0,0.3]) -- + a modulation graph
cut    = macro "cutoff" 800Hz                        -- an explicit knob name, a unit
orb    = macro 0.5 (lfoSine @ 1/4c |> scale [0,1]) { blend = "offset" }

A macro init is a global, MIDI/UI/shred-writable knob (Sig Control Normalized) whose registry name is the binding's (bright = macro 0.5 is the knob "bright"); macro "name" init names it explicitly — write the string only when the knob's public name must differ from the binding. An optional third arg is a modulation graph: any note-context-free control expression (LFOs, numeric patterns, drift, curves, other macros — cycles rejected), instantiated once per module, combined with the base and clamped to the macro's range. { blend = … } says how: "sum" (the default, base + mod), "offset" (base + mod − mid — the knob moves the modulation's centre, so a unipolar orbit keeps its whole travel), or "scale" (travel × mod — the knob is a depth control). See Macros and automation §1.0.

modulate <macro> (<graph>) [{ blend = … }] is a top-level statement that adds a modulation graph to a macro from outside its declaration — the only spelling that reaches a macro of an imported module:

cut = macro 0.3
acidVoice = patch { source = saw (freq pitch), cutoff = 200Hz + cut * 2000Hz, gain = vel }
acidLine = play acidVoice !"c2*8"
modulate cut (lfoSine @ 1/4c |> scale [0, 0.4])

The target is the macro binding (cut, or acid.cut for one flattened out of an alias-imported module) or its registry name as a string. Every graph aimed at one macro — the declaration's own first, then each modulate in source order — is summed and lowered into ONE graph, so one macro is still one modulation voice. A macro has ONE blend, so a modulate may name one only when the declaration named none or named the same one. Entry-only: a library's modulate does not travel. See Macros and automation §1.3.

Macro initial values and range endpoints must fit finite engine precision. Reversed ranges and nonzero ranges that collapse at that precision are errors; explicitly flat ranges remain valid. See Macros and automation for the native/JSON unit contract.

Song-time modulators songPos, sectionPos (Sig Control Normalized): songPos ramps 0→1 across the whole arrangement; sectionPos 0→1 within the current section. Driven by the song play-head; read 0 when no song plays (§12). harmonyTension (Sig Control Normalized) is the song's harmonic tension curve, computed at compile time — one step per chord span on song beat time; see Chords, scales and harmony § Tension as a modulator.

Visuals live in the qualified-only vis. namespace (vis.osc, vis.feedback, vis.layer, vis.output, vis.emit, vis.scan, media sources vis.image, vis.text, vis.video, vis.camera, vis.spectrum, …): typed image fields of space and tick that ordinary controls and patterns drive. None of those names is reachable bare, so osc, noise and scale keep their audio meaning. A visual library function annotates with Image (any field) and VisualLayer and takes its controls as an options record with declared defaults (look : Image -> LookCfg -> Image, called img |> look {}); vis-materials and vis-instruments are the shipped ones. The whole family, its spelling rules and the standalone volta-vis host are in visual-synthesis.

Scan rasters rasterX n, rasterY n (Sig Audio Bipolar): visual scan mode's raster pair — rasterX sweeps the canvas n times per simulation tick, rasterY steps through the n line heights once per tick, both locked to the tick clock (see visual-synthesis).

Modulation is just typed control-rate data: anything that produces a Sig Control routes anywhere a modulator is accepted, composing through the unit calculus.

Signal arithmetic. + - * / are per-sample on signals, and the divisor may itself be a signal — 1Hz / (0.001 + len * 0.5) tracks a knob exactly. A divisor within 1e-12 of zero yields 0, so a knob parked at its floor is silence, never a NaN. max a b and min a b are signal-rate too when either argument is live: they lower to an exact per-sample Math min/max over the operands in their common unit, so lpf (max 200Hz (knob * 4000Hz)) is a real clamp and not a crossfade, and its range (for the peak bound) is the operands' own. Their two arguments must be comparable quantities (the same unit, or Seconds/Milliseconds, or Cents/Semitones); anything else is a compile error. Each argument is evaluated ONCE, so max noise 0.2 compares the noise it passes through. Every other math builtin (abs, tanh, sin, cos, floor, sqrt, exp, ln, pow, atan2, ilog2, exp2i) runs per sample on a live signal too, as one Math node computing exactly what the kernel op of that name computes (tanh (saw (freq pitch) * 3.0) is a waveshaper, exp (-(y * y)) a Gaussian of a macro). Its operands are dimensionless samples (abs (freq pitch) is refused; divide by a reference first), and its result is NaN-flushed but not soft-limited, unlike a kernel's output.

Control constructors retain their numeric, unit and prepared-input constraints through aliases, returned functions, records/lists and partial calls. For example, settle = smooth 10ms and everyOther = latchCycle 2 are reusable functions. latchCycle requires its cycle count: write latchCycle 1 m for each cycle. Its count, quantizeTo's step values, and all linram inputs must be compile-time numbers. Named forms include latchCycle { cycles = 2, m = depth }. Each port's update contract is separate: hold reads its trigger each sample and latches its value on qualifying edges; live stepSeq entries latch at step boundaries, while constant lists and patterns are prepared before rendering.

The twelve math functions share their constraints across direct calls, aliases, returned functions, partial applications and collection callbacks. abs and floor preserve the input unit in constant expressions. Constant min/max convert compatible representations just like their signal forms: min 500ms 1s is 500ms, and max 1st 300Cents is 3st. The transcendental functions (tanh sin cos sqrt exp ln pow atan2) require bare numbers or Normalized, Bipolar, Linear or Ratio values. Divide a physical quantity by a reference first, for example sin (phase / 1s); sin 440Hz is a type error.

Named arguments use x for unary math and a, b for binary math: min { b = 1s, a = 500ms }. Compile-time calls have no runtime instance; signal math (the Math node) and kernel math run per sample in their consuming graph. That cadence does not change when the operand type says Control, and naming a function does not create shared runtime state.

hear it — open Aurora in SONGS: its lane macro is a single knob that slides the whole engine from uplifting trance to driving tech, and nine more macros ride cutoff, saturation, LFO depth and gate depth from one place.


8. Patterns & mini-notation#

A pattern is a first-class value. The mini-notation string !"…" (compiler/src/mini.rs):

SyntaxMeaning
c3 eb3 g3a sequence (one cycle, evenly divided)
~ or .a rest
[a b] cnesting — [a b] subdivides its slot
<a b c>alternation — one element per cycle (a multi-cycle pattern); a nested element advances only when picked (<a <b c>> = a b a c)
<a@2 b>weighted alternation — an element holds weight cycles (a for 2, then b; Strudel's [a@2 b]/3)
a b , c d epolyrhythm — comma-separated layers share the cycle
a*2 / a/2speed up / slow down a term
a*<2 4> / a/<2 3>patterned factor — one value per cycle (Pattern Applicative)
a!3 (or bare !)replicate
a@3weight — uneven slot widths
a(3,8) / a(3,8,2)euclidean rhythm (k,n[,rotation])
a(<3 5>,8,<0 2>)euclid args may alternate per cycle
0 .. 7 / 0..7inclusive integer range sugar (descending counts down)
hh? / [a b]?per-event coin — kept with p 0.5, fresh each cycle over an 8-cycle unroll
[a | b | c]per-cycle random choice among alternatives (same salted unroll)
bd:3sample variant index (also :? random, :* round-robin)
c4=0.7per-event velocity
c2_glide/slide marker — the FOLLOWING note slides in (no amp retrigger)
_c3 / -c3 / 'c3per-note glide override on any glide synth, mono or poly — glide in / hold (pitch step, no re-attack) / attack (re-attack, pitch step)
d4_(expression=0.5)per-note params — slide marker first, then (key=value) (d4(…)_ diagnoses)
_a4(glide=180ms)per-note glide TIME on a glide synth (a time unit is required); |> glide !"40 180"ms sets it per note from the track
d4(bow=down)the per-note bow key also takes the words down / up (stored as 2 / −2): bowstrings' absolute stroke direction
c4:maj7chord token (expands to stacked tones; also chord "c4" "maj7")
[db3,f3]_=0.9chord-token suffix — _ and/or =v after the ] distribute to every member
i VI iv, ^0 ^2 ^4roman numerals / scale degrees — resolved against key (§10)

A token's span is its note length. Each token owns its slot of the cycle, and a rest is its own silent event rather than an extension of the note before it: in !"g1 ~ ~ ~" the g1 lasts a quarter cycle and the three rests are three pitchless events. To hold a note across the bar, weight it (!"g1@8 ~") or give it a whole cycle and slow the pattern (!"b1" |> slow 2). The slide suffix (d1_) changes how the next note starts, not how long this one lasts — it is not a legato tie.

The chord-token suffix saves spelling a marker on every member of a wide voicing: [a2,a3]_ is exactly [a2_,a3_], [db3,f3,ab3]=0.9 is exactly [db3=0.9,f3=0.9,ab3=0.9], and a chord-quality token inherits too ([c4:maj]_). A member that carries its own marker keeps it — only bare members inherit ([db3=0.5,f3]=0.9 leaves db3 at 0.5). The order matches an atom's own spelling — slide first, then velocity (c3_=0.9 → [a2,a3]_=0.9); the reverse diagnoses. A slide on a non-pitch token (a chop slice index, !"7 2_ 5") is an error at that token: there is no pitch to ramp. Mind the space after a _ suffix — [a2,a3]_ c3 slides the chord, [a2,a3]_c3 does not: with no space the _ binds forward as the glide-*in* sigil of _c3 (Glide between notes).

A string is a pattern where the slot wants one (Your first sound §3): sound "bd sn hh*2", play p "c3 e3", "c3 e3" |> every 4 rev, "c3 e3" |> fast 2 and the track lanes (|> gain "1 0.7") read a plain string as the !"…" literal; !"…" stays the explicit form, and a Str slot (sample "kick", arp "up", chord "c3" "maj7") is untouched. The unit suffix works on both spellings ("400 800"Hz). A top-level string binding used as a pattern reads as its pattern (melody = "d4 f4 a4" … play "sine" melody); the binding itself stays a Str.

Pattern values & units: tokens take the unit of their slot — all-numeric → Pattern Normalized, pitched → Pattern Pitch (Patterns with units). Constructors: patternOf [v…], pure v (one-event), silence (empty), run n (the ramp 0 1 … n−1), segment n sig (discretize a cycle-rate signal — segment 16 (lfoSine (4c)) — the dual of stepSeq), ctrl "name" p (tag a numeric pattern as a control pattern for a named destination — the value side of a # merge, Combine patterns and controls).

segment n input "live" returns a held control in the input's unit. Its fixed count is a dimensionless integer from 1 to 4096. The default "pattern" mode prepares an affine cycle-denominated LFO expression; its count rounds and floors at one, subject to the pattern event budget. Mode names are case-insensitive. Aliases, factories, record fields and partial calls such as sample = segment 4 retain the selected result and default. Checking records static string evidence without sampling the input; the execution adapter checks the realized mode and source shape before emitting a pattern or graph. Prepared affine expressions use the same unit conversions as graphs, including milliseconds and mixed semitone/cents values.

hear it — open Bjorklund in SONGS: its header is a Strudel/Tidal → Volta vocabulary table, and every rhythm below it is written in the notation above rather than typed out.


9. Pattern combinators (the algebra)#

All resolve to a flat MiniPattern at compile time. The transform arg f is a bare combinator (rev), a section (fast 2), a lambda, or a named/imported callable: up = transpose 12, then p |> every 4 up. Fully supplied sections leave one pattern open; up p and p |> up apply it. Forward callable aliases, local helpers and factories returning captured lambdas are supported. Known receiver and numeric pattern units specialize at the call, including inline rate-lane calls. Named pattern captures retain their baked events, while random operations in transforms belong to the calling binding. See Read and write pattern pipes for the supported section family and remaining factory/local-callable limits.

A transform in a port sees the whole pattern. every, firstOf, lastOf, someCyclesBy, chunk, within, superimpose, off, jux and sometimesBy apply f to the complete subject, exactly as f applies outside the port — so every 1 f is f, every 1 (slow 2) is slow 2, and every 2 (iter 4) rotates cycle 2. every n f p plays cycle k of f p on the selected cycles and cycle k of p on the others (Tidal's whenT); the result's period is the least common multiple of n and both periods.

Argument order — put the subject before the pipe. Write p |> every 4 rev |> transpose 12, just as you write p |> chunk 4 rev or p |> iter 4. In a transform slot omit the subject: p |> every 4 (fast 2). The one-argument operations also use this rule (p |> rev, p |> degrade, p |> palindrome, p |> voiceLead).

Legacy positional calls such as every 4 rev p remain accepted with their existing meaning. Numeric parameters can themselves be patterns, so the compiler uses explicit pipe syntax to identify the subject. A full direct call can also put the subject first (every p 4 rev, transpose p 12) when the fixed slots rule out the legacy order. If both readings remain possible, the old positional meaning wins; string/pattern contents never decide. transpose !"0 12" p and p |> transpose !"0 12" agree when p is known to be a pitch pattern. Two potentially numeric patterns in a transpose/chop pipe diagnose the old/new subject ambiguity; name the numeric slot to select their roles: p |> transpose { n = amounts }. Fixed arguments can be named throughout this family: p |> every { n = 4, f = transpose { n = 12 } }. Multi-pattern operations (sew bp p1 p2, stitch bp p1 p2, wedge t p1 p2) retain their positional signatures.

The full pipe family, sample-voice overloads, current receiver/alias limits, and the IR-checked fmt --modernize --check preview are documented in Read and write pattern pipes. Existing errors for malformed calls such as chunk 4 rev p still point to the valid pipe spelling, and a transform piped onto a whole track (play "saw" "c3 e3" |> struct "t ~ t t ~") is refused with the spelling inside play: play "saw" ("c3 e3" |> struct "t ~ t t ~"). struct bools p, mask bools p and squeeze bools p take Tidal's order, booleans first. Their two slots are both patterns, so the subject-first prefix (struct p bools) swaps the roles: it is refused when literals prove the swap, and through bindings the booleans play as the notes in silence — pipe those.

|> supplies the first parameter of a user function too. A helper you write is piped by the same law, so its track goes first: pass = \t az -> t |> delayTrack az 0 0 1, called track |> pass 0.5. Write the track last and the pipe hands the helper's first parameter a track and its last parameter a number; the compiler then names the helper and the parameter at the call site — "pass takes its track as parameter 2 (t, the one delayTrack receives), but |> supplies the FIRST parameter" — instead of blaming delayTrack from inside the helper's body. The diagnostic is raised only when the application has already failed, so a program that compiles never sees it; when the evidence walk finds no such parameter the original error is printed unchanged.

Timing version. Unversioned entries use composable timing (version 2); timingVersion = 2 is optional. The default applies to imported patterns too: fast/slow preserve their timing through arrange, stack, par, voice lines and merges; slow retains the full period. Arrangement weights give cells whole-cycle durations, while seq explicitly fits materialized children into equal slots of one cycle. timingVersion = 1 preserves legacy behavior. Unsupported versions are errors. Fractional factors, window boundaries and the strict migration preview are documented in Patterns: events over time §Timing versions and Format and modernize source.

  • Time fast n, slow n, rev, palindrome, iter n (Tidal's direction: cycle k opens k/n into the pattern), iterBack n (the other way, Tidal's iter'), linger n, swing, nudge, push !"0 8 -4 0" (these three are TRACK pipes — play p pat |> swing 0.6; swing's amount may be live — a macro, macro * 0.45, or a control signal — sampled once per cycle, so a knob move lands on the next downbeat; swing moves the odd eighths of the track's CYCLE, not of each bar, so on a multi-bar cycle (an arrange lane, slow 2, <a b>) it shifts whole half-bars — swing a one-bar pattern, or write a per-bar push lane; push is a directed per-event micro-timing lane in signed milliseconds, positive = late — the directed sibling of humanize; additive with swing/nudge). swing/nudge/push offsets are signed and absolute: a negative one moves the event genuinely earlier, across the cycle seam if it has to, and the scheduler re-sorts each cycle so nothing is dropped or stacked (Control patterns for individual events §Notes & limits). Only an event of the FIRST source cycle pushed before the transport's own zero is clamped to it; a later cycle that lands before zero is dropped rather than stacked on the downbeat. nudge is capped at ±120 cycles (inside the scheduler's 128-cycle scan reach) — beyond that it is a compile error, not silence. A push lane can be extracted rather than written: push (grooveOf "breaks125" 16) runs the onset detector over the named break and pushes each step by the milliseconds that hit misses the grid by (Control patterns for individual events §Extracted groove).
  • Structure stack [p…] (layer), arrange [[n, p]…] (weighted concat: each cell gets n whole cycles and REPEATS p from the cell's own start — it does not stretch it; [n, p |> slow n] spans the cell), chunk n f, bite n i, every n f (every n-th cycle comes from f p), ply n (repeat each event n times inside its own span — the build-up intensifier: each eighth becomes n even subdivisions carrying the same pitch/sample/velocity, and the cycle span is unchanged). ply 1 is the identity, rests pass through untouched, and n may itself be a pattern (p |> ply !"<2 4>"), sampled stepwise at each event's onset. A slide marker (c2_) rides only the LAST copy — it means "slide out into the next step", so the intermediate repeats retrigger like a drum roll while the roll as a whole still slides into whatever follows. Degenerate counts are coerced, not rejected (the humanize precedent): ply 0 and any negative count are ply 1, and a fractional count rounds (ply 2.4 ≡ ply 2). In version 1, fast/slow do not survive an arrange cell. A cell is defined to fill its own n cycles, so normalising it discards whatever rate the cell's pattern carried: arrange [[1, !"a3 c4" |> fast 2]] lowers byte-identically to arrange [[1, !"a3 c4"]]. The compiler warns (ArrangeCellScale) at the offending cell, including through a computed schedule (arrange (map (\p -> [2, p]) rolls) where rolls came from an iterate ladder of fasts — every stage would otherwise come out identical). To double a cell's density, double it structurally — seq [p, p], which survives; to stretch one, widen the cell ([2, p]). What does survive is a transform that builds multi-cycle structure — every 4 (fast 2) p takes one cycle in four from fast 2 p, so it is preserved and is not warned about. fast applied to the whole arrange also works, but compresses the entire progression. The same erasure applies to every fold that lays patterns onto a common span — stack, voiceLines, wchoose, and the merge operators (#, <|, |+, +|, |*, *|) all drop an operand's fast/slow, and all warn with the same ArrangeCellScale class (the message names the fold). Legacy seq / par have a separate materializer, with restricted fractional rates and inconsistent multi-cycle spans. Version 2 replaces these paths with one checked normalization seam and preserves rates in all these folds.
  • Structure/merge operators (infix, Combine patterns and controls) a # b (structure-left, value-right override), a <| b (structure-right), a |+ b / a +| b (one-sided add), a |* b / a *| b (one-sided multiply), a |- b / a -| b and a |/ b / a /| b (subtract/divide — the combined value is always LEFT ∘ RIGHT regardless of which side supplies the structure), and the BOTH-structure forms a |+| b / a |*| b (onsets from both sides, union-deduped; the left-structured event wins an exact-onset collision) — the bar marks the structure side. Both operands are patterns; sample the value side at the structure side's onsets. Unit-checked: cutoffHz |* gainNorm is fine (Normalized scales any unit) but cutoffHz |+ gainNorm is a compile error (can't add Hz and Normalized).
  • Patterned arguments (Pattern Applicative — the applicative seam): every numeric argument slot above also takes a numeric pattern: p |> fast !"<1 2>" (per-window specialize-and-restrict, Tidal's innerJoin rendered eagerly), p |> degradeBy !"0 .5" / transpose !"<0 12>" p (per-event sampling over the LCM-expanded lattice), every !"<2 3>" rev p (the factor sampled at each cycle start; span = the full LCM of every specialization). A single-constant pattern normalizes to the constant (fast !"2" IS fast 2); a varying factor always takes the structural reading and advises at top level (the constant spelling scales the bar instead); a pitch/sample argument pattern is rejected; every span-widening fold shares one 65 536-event budget (so every 100000 now errors instead of materializing 100000 cycles). degradeBy/sometimesBy also take an inline cycle-rate LFO (sometimesBy (lfoSine (8c)) (fast 2) — density breathes over 8 bars); an Hz rate is rejected with the (Nc) hint, and a signal on a re-timing combinator defers to segment (p |> fast (segment 8 (lfoSine (8c)))).
  • Window & feel backfill zoom lo hi (play just a window, stretched), within lo hi f (transform only the window), inside n f / outside n f (apply f at n× / 1/n× resolution), stut n fb t (decaying repeats), press / pressBy x (slot-relative swing), hurry n (fast n + sample speed n), firstOf n f / lastOf n f (the first/last of every n cycles — lastOf is the turnaround position), rolled (chord-stack strum stagger), brak (every second cycle squashed into half a cycle and shifted a quarter), wedge t p1 p2 (one cycle of p1 squeezed into [0, t), one of p2 into [t, 1)).
  • Structural (boolean) patterns — the positional rest law: a rest means "hold the previous value" in VALUED positions (merge value side, argument slots) and false in STRUCTURAL positions, where Tidal's spellings hold too: t, true, x, 1 are true, and f, false and a numeric 0 are false like ~. mask bp / struct bp read their argument that way; joining them: p |> squeeze bp (each true window receives one full cycle of p, time-squeezed into it), bp |> inv (complement the window set — true ↔ gap or false step), sew bp p1 p2 (hear p1 in true windows, p2 in false ones, each keeping its own rhythm), and stitch bp p1 p2 (the bool pattern's rhythm — true windows sample p1, false ones p2). Any combinator output works as bp: degradeBy .3 gate is a random selector, x(3,8) a Euclidean one. All of them LCM-expand both spans, so a boolean longer than its input (or the reverse) advances across cycles instead of freezing on cycle 0; struct stamps every co-onset member, so a chord lane stays a chord; and signal bp / stepSeq bp read a boolean as 1/0-valued windows, so one !"t ~ t t" can be the structure, the mask, the complement AND the gate; !"1 0 1 1" and "t f t t" spell the same gate. The prefix takes Tidal's order, booleans first (struct "t ~ t t ~" "c3 e3", like sew bp p1 p2); the pipe "c3 e3" |> struct "t ~ t t ~" is the canonical spelling, and the subject-first prefix (struct "c3 e3" "t ~ t t ~") is a check-time error that names it. Full rules: Patterns: events over time § "Structural (boolean) patterns".
  • Density / feel degrade, degradeBy x, undegradeBy x (the exact complement — keep what degradeBy x drops), mask boolPat, struct boolPat, humanize amount (deterministic seeded onset/velocity jitter), sometimesBy prob f (apply f to a seeded random subset of the events, leave the rest) with the sugar sometimes f = 0.5, often f = 0.75, rarely f = 0.25. sometimesBy shares degradeBy's coin: each cycle p |> sometimesBy 0.3 f transforms exactly the events p |> degradeBy 0.3 drops, so the two name the same 30 % of the bar. Rests are never chosen; prob = 0 is the identity and prob = 1 is plain f; a probability outside [0, 1] clamps into it rather than erroring (the humanize precedent). f sees the chosen events as their own sub-pattern, not as holes in the original: a structural transform works within the subset's timeline, so p |> sometimesBy 0.5 rev reverses the chosen events among themselves (the first chosen event lands where the last one was) rather than reversing the bar. Value transforms (transpose n, fast n) have no such subtlety. A transform that widens the span (iter n) widens the merged result to the LCM, exactly as superimpose does.
  • Layering superimpose f, off t f (offset copy), jux f (stereo-split copy), wchoose [[w, p], …] (weighted cycle share: each pattern plays for round(w) consecutive cycles, in the order written — nothing is drawn).
  • Fresh-per-cycle randomness (Pattern Semantics §4): every random draw is fresh each cycle, as in Tidal — the coins of degrade/degradeBy/sometimesBy, someCyclesBy x f (whole-cycle coin), shuffle n (per-cycle permutation of the cycle's n slots), scramble n (with-replacement draw), mini-notation hh? and [a | b] — deterministic, seekable, and never repeating in a track's note pattern. A top-level randomCycles = N binding (whole number, 1–64 — the tempo recognition pattern) makes every draw repeat every N cycles: randomCycles = 1 draws the same events every cycle, the stable groove. A draw that cannot stay on the events (a lane, an argument, arrange) is unrolled and repeats (Patterns: events over time). Choice made while audio runs is the runtime choosers' job, below; wchoose is a fixed rotation.
  • Runtime choosers (Runtime Control Flow Stage 3): chooseBy sel [p1, p2, …] — a pattern whose content is one of N compile-time-flattened alternatives, selected by a macro-affine signal sampled once at each cycle boundary (floor(sel · N), clamped; re-read at hot-swap/seek; mid-cycle knob moves never chop the running cycle). whenSig cond f p (usually p |> whenSig cond f) is the two-alternative sugar — p vs f p by cond ≥ 0.5. Mixed-length alternatives tile to a common span. Top-level only (the pattern of play/sound, directly or through a binding); cost counts the max across alternatives.
  • Sample sampleAdd src [lo,hi], chop n, slice n "name" idx.
  • Generative rwalk steps maxStep [cycles] — a seeded random walk over scale degrees, baked to fixed pitch events (Patterns that respond to playback).
  • Patterns as data events p returns List (Event a): complete musical events supporting typed field access and with updates. patternData p returns { period, events }; patternFromData data retains both expression and phrase length, including trailing silence. patternOfEvents xs accepts events or plain { start, dur, value } records (optionally with velocity, pan, slide, params, controls) and infers an integer period. seq xs / par xs sequence / overlay lists of patterns. See Generate and transform musical structures for event fields and validation.
  • Generative determinism: random draws (degrade, degradeBy, sometimesBy, humanize, rand, randomPerNote, rwalk) are a pure function of the module seed + the performance coordinate — different per seed, bit-identical on replay (Change music while it plays). degradeBy / sometimesBy draw from one unsalted per-event coin keyed on the cycle and the event's position in the pattern, which is what makes the complement identity above hold.

See Patterns: events over time, Control patterns for individual events.

hear it — open Jitter in SONGS: a track built almost entirely out of this algebra — euclidean fills at coprime lengths drifting against each other, a rwalk melody, degrade holes, and every n (jux rev) reshuffling the music every cycle.


10. Harmony#

key = "e minor" sets the ambient root + scale. Then:

  • transpose n p — semitone shift; voiceLead p — smooth chord voicing; arp "up|down|updown|thumb" p — arpeggiate; inScale scale p — snap to a scale; chord "c4" "maj7" / tonality "c dorian" — chord & scale constructors.
  • voiceLines [p…] — like stack, but each line becomes a distinct glide-voice identity on polyphonic glide synths. voiceLines n p is the solved form: the voicing CSP turns p's chords into n voice-led lines (no crossing, upper-voice spacing ≤ an octave, no parallel perfects, least motion), each stamped with the same kind of identity — see Interweaving melodies.
  • Patterns can use roman numerals (!"i VI iv VII") and scale degrees (^0 ^2), resolved against key. Chords/scales are compile-time sugar that erase before lowering. The harmony stdlib (examples/songs/harmony.volta) exposes a first-class Chord record type (§2) transformable by ordinary Chord -> Chord functions.
  • A chord track other parts follow. A top-level progression = !"<I vi IV V>" declares the song's chords once. The follower tokens root/third/fifth/ seventh/ninth (an optional octave digit names the root's octave: root2, fifth2) and ^^N (degree N from the current chord's root) take the chord sounding at their onset, and p |> followChords re-maps a pattern written over the first chord onto each chord in turn — see Chords, scales and harmony § Following a progression.

The numeral grammar. [b|#]<numeral>[quality]:

I–VIIthe degree; UPPER case is a major triad, lower case a minor one
bVII, #iva chromatic degree (♭/♯ spell the same thing)
V7, iimaj7, Isus4any chord quality from the c4:maj7 table
vii°, vii°7, iiø7, V+the figured-bass markers — ° dim, ø half-dim (m7♭5), + aug

A bare numeral is diatonic: it names a degree of the declared scale, so VII is B in C major and B♭ in C minor. An accidental-prefixed numeral is chromatic and counts from the tonic's major scale instead, so bVII is B♭ and #IV is F♯ in every key and mode — the usual reading of bVII in a minor key too. Chord roots sit an octave below the degree pitch. A token that is shaped like a numeral but is not one (VIII) is a parse error naming these forms; it never degrades into a sample name (and so never into silence).

Tuning systems#

tuning = … is a second ambient binding beside key — a compile-time re-mapping of every sounding pitch (patterns, chords, trigger, freq ~C4) from nominal 12-TET to fractional MIDI; no declaration = 12-TET.

``volta norun four alternative spellings of onetuning` binding tuning = edo 19 -- n equal divisions of the octave (nearest-degree note names) tuning = just ~C2 -- fixed 5-limit just intonation on a root tuning = tuningOf [0, 90, 193, 294, 386, 498, 590, 702, 794, 884, 996, 1088] -- explicit cents-from-root table (12 entries) tuning = adaptive -- adaptive JI: each CHORD retunes to just ratios of its -- own root (maj = 4:5:6, dom7 = 4:5:6:7); melodies and -- hand-spelled stacks stay 12-TET


Combinators stay tuning-agnostic (`transpose` is nominal semitones, retuned
after); per-note cent offsets compose on top; glide interpolates the retuned
pitches. See [[reference/tuning]].

### Timbre matched to intonation

A tuning is only half of consonance — the other half is the *spectrum*. Two
surfaces snap partials onto the tuning's cents lattice, so microtonal and
inharmonic material is consonant by construction (Sethares):

```volta
tuning = tuningOf [0, 120, 190, 258, 400, 539, 600, 675, 785, 860, 943, 1060]

bar   = additiveAt (freq pitch) [[1, 1], [2.76, 0.6], [5.4, 0.4]] |> tuned
soft  = additive (freq pitch) (sawSpectrum 24) |> tuned 0.6   -- part-way, in cents
drone = play bass bassline |> spectralTune 0.8                -- live, any audio
  • tuned [amount] — a compile-time rewrite of an additive bank's ratio table (additive / additiveAt / spectrum generators / additiveOf); zero runtime cost. amount 0..1 lerps in cents (default 1).
  • spectralTune amount — the live sibling: an STFT track effect (and master insert) that shifts each spectral peak, with its region of influence, to the nearest absolute lattice frequency. Macro-ridable, identity at 0.

No tuning = … declared ⇒ tuned is an explicit no-op (with a warning) and spectralTune falls back to a 12-TET lattice on C. See Match timbre to tuning.

See Chords, scales and harmony.


11. Effects#

Per-voice (audio endomorphisms in the voice out DAG, signal-first / pipeable): drive amount [bias] (tanh sat; bias adds even harmonics; the optional { keepLows = 200Hz, mix, trim = -3dB } drive only the band above a crossover, blend it with its dry and set its level — Voice signal flow), vca control [character] (modeled gain cell — tanh headroom, seeded control feedthrough, gain-tracking noise; character = 0 is a plain sig · control product; see Analog character), delay time feedback, bitcrush amount, chorus amount [rate] [depth] (optional live LFO rate in Hz and normalized delay-swing depth; defaults 0.6 / 0.35), eq lowDb midDb highDb (shelves 200Hz/4kHz, peak 1kHz), fold, clip, comb, phaser, flanger amount [feedback], pan pos (per-voice placement), bbd amount ["i"|"ii"|"i+ii"] (bucket-brigade chorus; the mode strings are the Juno-faithful variants, absent = generic BBD; piped onto a track it is bbdTrack, the shared-bus free-running Juno chorus insert, see below), ensembleChorus amount (Solina triple-BBD string-ensemble, stereo; on a track it is ensembleChorusTrack, the free-running bus ensemble; 1.0 = the authentic 100%-wet wash), tube (tube/tape saturation with hysteresis), discontinuity topDb (the nonlinearity of air itself under intense loudness — amplitude-driven wave steepening that makes a sound read as viscerally loud and close without gaining a dB; a port of Airwindows Discontinuity. Its knob is a loudness in dB, not a 0..1 amount: 70 is near-transparent, 105 the centre, 120 obvious, 140 the pinned top. Per-sound by design — the kick's reading and the pad's reading are what create the depth cue). The slew-shaping family — slew amount (slope limiter: de-glares by clamping the sample-to-sample step, no filter band and no phase smear), slewNoise dust [wet] (worn-media dust: sample-rate random gain scatter that rides the signal and vanishes in silence), bloom amount [output] [wet] (five cascaded slew-driven VCAs — rounds pointy sounds at low settings, makes drums bloom/explode at high ones). All three are faithful ports of Chris Johnson's MIT-licensed Airwindows Slew2 / TapeDust / Smooth, mono inside the voice; see The effects reference. The voice flavors are four more Airwindows ports in the same seat. sweeten amount is an isolated second harmonic — the signal is band-limited, squared and band-limited again before being subtracted from the dry, so H3 and above measure below −190 dBc; the knob is stepped in 6 dB increments (H2 runs −78 dBc at 0 to −18 dBc at 1 on a −12 dBFS sine) and it emits a signal-power-proportional DC offset that the master DC blocker eats. hypersoft drive [depth] [bright] [level] is an extreme soft clipper made of a stack of sin(n·x)/n³ terms, each slew-gated so it switches itself off where it would alias — odd harmonics only, +6.8 dB on small signals and +0.5 dB at 0 dBFS, output ceiling ~4.0. tremolo speed [depth] is a tremolo whose LFO sweeps a saturation curve rather than a fader: measured against the identical LFO with the curve removed, it makes H3 at −34.5 dBc where a gain LFO gives −105 (depth 0.5, −6 dBFS). Rate runs 1.40 Hz at speed 0 to 15.4 Hz at 1, sample-rate independent. gatelope threshold [trebleSustain] [bassSustain] [snap] [mix] is a gate that closes filters, not gain: below threshold a low-pass corner walks down while a high-pass corner walks up, and the output goes hard zero when they meet — so a drum tail filters shut from both ends and the gate never clicks. snap is an attack speed (higher is faster). All four are mono inside the voice; see The effects reference. track |> gatelopeTrack threshold [trebleSustain] [bassSustain] [snap] [mix] is the same gate on a mixer bus, one core per channel — the spelling that reaches a sound track, which has no patch to hold a voice-level insert (sound !"amen:0 …" |> transient 6dB |> gatelopeTrack 0.3).

zFilter "lp"|"hp"|"bp"|"notch" cutoff q [drive] [level] is the character filter next to the clean svf / ladder / korg35 — a port of the Airwindows Z-filter family (ZLowpass2 / ZHighpass2 / ZBandpass2 / ZNotch2), the Emu e6400 sampler filters. Every biquad stage clips at ±1 inside its own feedback path, so resonance and distortion are one mechanism. q is Airwindows' Poles, not a resonance: it progressively engages up to four cascaded stages and q = 0 is exact dry bypass. Each mode reaches a different cutoff range and a cutoff outside it pins to that mode's rail; drive (default 0) is 0 → +30 dB of pre-clip gain and level (default 0.3 ≈ unity) is the output trim. Coefficients are latched and crossfaded once per 128-sample control block — the original's per-buffer smoothing, which is why a fast sweep has its sampler-ish zipper. See See The effects reference. glitchShift semitones [trim] [looseness] [feedback] [mix] — the gnarly pitch shifter (a port of Airwindows GlitchShifter), the deliberate opposite of the clean pitchShift: a variable-length buffer whose read tap walks against the write head and splices to the best-matching recent zero crossing every time it laps, so the artifacts are the instrument. looseness sets how far the tap may wander — 0 is a 6-sample buffer splicing thousands of times a second (a ring-mod-ish tone), 1 is a 1.5-second buffer the tap drifts across (tape unravelling) — and feedback re-shifts its own output. Its semitones axis is the original's: linear in speed, not in cents, so only 0 and ±12 are equal tempered (the tritone lands +102 cents sharp); trim rides the same axis if you want exact ET back. Voice-level and mono, output hard-clipped to ±4; see The effects reference. A named chain is a lambda: warmth = \s -> s |> drive 0.3 |> chorus 0.4.

User feedback loops — feedback \fb -> body closes a signal loop (the binder fb is the body's own output, one sample late); feedbackAt time \fb -> body is the tuned variant, a fractional-sample loop delay — Karplus-Strong when time = 1 / freq pitch. The fed-back path may carry arithmetic, the filters, the waveshapers, and delay/comb; a built-in soft limiter (identity below ±1, capped at ±2) keeps hot loops bounded. Dub trick: feedback \fb -> stab + fb * 0.6 |> lpf 1.2kHz 2 |> drive 0.3 |> delay (3/8c) 0 darkens and thickens every echo. feedbackN [t1, …, tk] (\a b … -> [outA, outB, …]) closes up to 8 COUPLED loops over one region (one binder and one returned signal per loop; the value is their sum) — an FDN with a mixing matrix you write. v |> stickSlip pressure { speed } inside a loop is a bow on whatever the loop body is ("bow anything"): it reads the body's velocity and returns the friction force, so feedback \v -> 5 * (v |> stickSlip 0.6 |> bpf (freq pitch) 200) bows one mode (port = "wave" carries velocity waves instead, for a delay-line string). See Build a feedback loop, Strings, bells and resonating objects §1.13 and Write a custom audio processor.

User effects — effect name (p: Normalized = 0.4, …) on track [latency N] = \bus -> … (or on voice = \x -> …) declares a named effect whose parameters are a configuration record with defaults: t |> name { p = 0.6 }, name {} for all defaults, a macro rides live inside its kernels. A track effect is a persistent insert usable in pipes, bus declarations and master inserts. See Write a custom audio processor § Named user effects. User oscillators — oscillator name (p: T = lit, …) = \hz -> … declares a named SOURCE whose subject is the frequency in Hz: source = name (freq pitch) { p = 0.6 } or freq pitch |> name {}, its parameters live and addressable like an effect's (lead.name.p). examples/songs/osclib.volta ships polySaw polyPulse polyTri syncLead fmPair folder ksPluck noiseDrum. See Write a custom audio processor § Source kernels.

  • User-defined waveshaper shape f — f is a scalar lambda (\x -> x - (4.0/3.0)*x*x*x) evaluated at compile time at 2049 points over [-1, 1] and baked into a lookup table in the IR; the engine just plays a waveshaper (a LUT + first-order ADAA on the exact trapezoid antiderivative — antialiased for free), so any transfer law you can write as arithmetic + if/case costs nothing extra live. The lambda must be compile-time evaluable (§14's evaluator envelope) — referencing a signal, macro, or note param is a compile error. See Draw a waveform with a function.
  • User DSP kernels sig |> process f / sig |> process2 f [init] — shape's big sibling: the lambda compiles to per-sample bytecode (no LUT), so piecewise laws are exact, free macro references become live kernel parameters (≤8 — process (\x -> tanh (x * (1.0 + grit * 8.0))) puts the knob inside the law), and process2 (\x s1 … sN -> [out, [n1, …]]) threads up to 8 state cells per channel — one per binder after the input; [out, next] for one cell — so one-poles, DF2T biquads, SVFs, and chaos maps are written from scratch (optional init: scalar or list). sampleRate (kernel-only) feeds coefficient math; a parameterized recipe partially applies (process2 (biquadLp (200.0 + cut*3800.0) 0.9)). examples/songs/kernels.volta ships the classics: softclip diode crossover wavefold crush cheb2 cheb3 · onepoleLp onepoleHp biquadLp biquadHp biquadBp · dcblock slewLim · logistic (demo: kernellab). Envelope: numbers, arithmetic, comparisons, eager if (branchless select), let, scalar-lambda calls (inlined), and the math builtins abs min max tanh sin cos floor sqrt exp ln pow atan2 ilog2 exp2i (atan2 y x, the angle of (x, y), −π … π; ilog2/exp2i, a binary exponent and an exact power of two) (which now also fold in any compile-time expression, shape laws included). Outputs NaN-flush to 0 and soft-limit to ±2; division is guarded. Works inside feedback loops. Delay lines: an init-list slot written dline 50ms is a line — read it with dtap buf d (Hermite) / dtapi buf d (integer), push into it through its next-state element; foldl over a constant range compiles to one loop (FIR taps). Up to 4 signal ports follow the init (process2 duck [0.0] (follower kickT 5ms 80ms)) as extra binders; processS runs one program over both channels (\l r … -> [outL, outR, next]); processC runs once per block and yields a control signal. Source kernels hz |> processO f [init] [ports…] are oscillators: the law's first binder is the frequency in Hz (the one physical unit a kernel reads — its ports may carry Hz too), its state a phase, and inside it vel, noteTime (seconds since the note started) and noteGate (1 held, 0 released) read the voice. The kernel stdlib's band-limited laws blSaw blSquare blPulse blTri blPhasor syncSaw (polyBLEP/BLAMP helpers polyBlep polyBlep4 polyBlamp) alias no worse than the builtin oscillators. sig |> oversample N stage (N = 2, 4, 8) applies an Audio -> Audio stage and runs every user kernel it builds at N·sr between linear-phase halfband resamplers (latency 31/39/42 samples, compensated on parallel paths and reported to PDC); adaaTanh/adaaClip in kernels.volta are first-order ADAA laws for 1×. Execution: a register-machine interpreter run a block at a time, with sample-independent work vectorized across the block (engine/src/kernel/regs.rs; the decode-validated stack tiers in engine/src/kernel.rs remain its bit-exact reference and serve feedback regions) on every target. See Write a custom audio processor.
  • Spectral frame kernels sig |> spectralMap f [init] [ports…] / sig |> spectralPolar f [init] [ports…] — the kernel law runs once per STFT bin per frame (Hann 1024, hop 256 — the freeze/blur frame) over the bin's magnitude m (amplitude units: a sine of amplitude A reads ≈ A), or magnitude and phase (\m ph -> [m', ph']); the phase passes through a spectralMap. State binders are cells per bin that persist frame to frame; captured macros are read once per frame; a signal port is STFT-analysed and its binder reads the port's magnitude at the same bin (cross-synthesis in user space). Inside the law, and only there: binIndex, binHz, trueHz (phase-vocoder frequency), framePeak (stereo-linked), frameIndex, magAt d / phaseAt d (bin k + d of the input frame; foldl over a range of offsets is one loop; magAt d side / phaseAt d side read a port binder's spectrum), stateAt s d (state binder s of bin k + d as the previous frame left it), frameSize, and frameFold i. Options: { frame = 2048 } picks the frame (256 … 4096, hop a quarter of it, latency one frame); { fold = \m e -> e + m * m } (seeds foldInit) is a frame-level prologue run over every bin before the law, whose accumulators the law reads as frameFold 0 …. One frame (1024 samples by default) of latency, reported to graph alignment and PDC; voice and track seats (effect … on track, audioFx), not inside feedback. examples/songs/spectrallib.volta ports spectralGate/tilt/freeze/blur and adds robotize, whisperize, bin shift, a per-bin compressor, spectral sustain and sidechain imprint (demo: spectrallab). See Write a spectral effect.

Per-track (one instance on the mixer bus, so tails overlap — attached with |> to a track). The receiver decides: a name applied to a track takes its track-rate meaning — track |> delay 0.25 0.4 is delayTrack, track |> eq [ … ] is eqTrack, track |> gain 0.5 / pan -0.3 (a constant or a macro) are gainTrack / panTrack while gain !"1 0.7" and gain (lfoSine 4) stay the per-event / continuous lanes, gatelope → gatelopeTrack, bbd → bbdTrack, ensembleChorus → ensembleChorusTrack, duckBy → duckByTrack, bitcrush → crush, grainSize → grainSizeT, and so on; the …Track / …T spellings remain as the explicit form. A voice-only effect on a track (drive, chorus, phaser, …) is a check-time error naming the bus sibling. A track piped into an effect that is still missing a required argument (t = play p x |> reverb) is a function, not a track: the binding is legal but nothing plays, so it carries an UnderAppliedTrackEffect warning naming the effect's accepted form. For gain, the selected reader also survives aliases, factories, function arguments and container fields. Constants and macro-affine levels scale event velocity at note-on; arbitrary control graphs install continuous gain. Explicit gainTrack reads at note-on on source tracks and continuously on bus/master receivers. A track level is a linear number or dB (|> gain -3dB is ×0.708, read exactly as a mixer level, § 2 "The unit policy, in one table"); a gain PATTERN is linear. A diagnostic names the call as it was written — `delay`'s `time` reads Seconds, not Hz — never the twin it dispatched to. See Control patterns for individual events. play / sound / instrument / midi / playOn now type as Track.

  • Reverb / convolution reverb amount [wet] (FDN), plate amount [wet] (Dattorro figure-8), spring tension decay [mix] (dispersive two-tank spring reverb), shimmer amount [cents] [feedback] (a pitch shifter inside the reverb's feedback loop, so each pass adds a transposed halo — cents defaults to 1200, an octave up, and below ~100 becomes a chorus-y in-tank detune; amount and feedback ride a macro, see The effects reference), galactic replace [brightness] [detune] [bigness] [mix] (Airwindows Galactic — the enormous, slow-onset pad/space reverb. Three cascaded four-line blocks whose mixing matrix is exactly twice an orthogonal one, so nothing inside the loop damps and replace alone sets the decay — and it runs backwards: 1 is the shortest tail, 0 a lossless freeze that admits no new signal. A crossed L/R feedback path swaps the tail between channels once per round trip, and a quadrature pre-delay drifts at a rate that re-randomises itself forever, so the tail never loops. Every knob ∈ [0, 1] and live; see The effects reference), crunchCoat room [regen] [derez] [mix] (Airwindows CrunchCoat — the cursed retro digital reverb, and the one lo-fi flavour the rest of the family cannot make. Four cascaded lossless four-line blocks clocked by an accumulator whose step is derez + lastVerb, where lastVerb comes from the tank's own last output — so the reverb's internal sample rate jitters with the material, and between ticks the output is a backwards linear ramp rather than a resample. room picks one of seventeen tuned delay sets, 96-seat room → 1541-seat hall, and clears the tank when it moves; regen is also the exact loop gain (1 − (1 − regen)²), so the 0 default is a bare 16-tap cascade and 1 is a lossless freeze that still accepts input; derez is the tank clock, sample-rate independent, and 0 mutes the wet; mix is a submix blend, not a crossfade — at or below 0.5 the dry stays at full level. Every knob ∈ [0, 1] and live; see The effects reference), kPlate mode [input] [regen] [derez] [predelay] [mix] (Airwindows kPlate140 / kPlate240 — modelled EMT plates, and the character alternative to the Dattorro plate. mode is a compile-time string literal: "140" the large steel sheet, "240" the small gold foil. Five stages of five delay lines per channel read ONE twenty-five-entry table — the left tank walking a 5 × 5 grid's columns, the right its rows — with the two tanks' feedback paths crossed, so the stereo comes from a transpose rather than a detune. regen is the decay and runs forwards (1 = longest, the opposite of galactic's replace): RT60 2.6–8.0 s on "140", 2.4–3.9 s on "240". derez clocks the undersampled tank, and 1 is not "off" — even the maximum runs the tank at half the sample rate, which is why the result is ~6× denser at 50 ms and ~20 dB darker at 8 kHz than plate. mix is additive, not a crossfade: 0.5 is unity on both legs. Every knob ∈ [0, 1] and live; see The effects reference), convolve "ir" mix (Convolution: recorded spaces and colors).
  • Delays pingpong time feedback [damp] (stereo), delayTrack time feedback [damp] (mono; damp ∈ [0, 1], default 0, is a one-pole low-pass inside the feedback loop so each repeat is darker than the last — 0 is the clean, spectrally static line, bit-for-bit; see The effects reference), bbdDelay time feedback [wet] (analog bucket-brigade), tape time feedback [wet] [bias] (tape echo with wow/flutter; bias ∈ [0, 1], nominal 0.5, sets tape-head bias — under-bias grittier, over-bias cleaner/duller).
  • Duration spellings — one meaning in every slot. A Cycles number in a duration slot is a musical length in cycles (the mirror of §6's rate reading, where the same tag is a period to invert), and dividing it by a plain number scales it. So (1/8c), 0.125c and (1c/8) are one length — an eighth of a cycle, 0.25 s at 120 BPM — and (3/8c) ≡ (3c/8) ≡ 0.375c ≡ (3 * 0.125c). It holds in every duration slot: the voice delay / feedbackAt / grainSize, the track delayTrack / pingpong / tape / bbdDelay / grainSizeT, holdFor, and the shred wait / trigger / record. A bare integer fraction is a plain number of seconds: (1/8) is 0.125 s (it names the float), and the compiler advises the cycle spelling ((1/8c)) since that is what such a fraction means everywhere else in a song. A duration that folds to zero, a negative or a non-finite is refused in the slots where a zero length is degenerate (the delay/grain family) instead of being clamped silently. Absolute durations — 250ms, 0.25s, a bare 0.25 (seconds) — are unaffected, as are + / - / % over durations. A duration given a name (tank = 3/8c … delayTrack tank fb) reads like the literal it is bound to.
  • Vinyl (the medium's physics, not added noise — contrast crackle / hiss / wow, which add what a record makes) toVinyl [midHz] [sideHz] [hfLimit] [wear] (Airwindows ToVinyl4 — the cutting chain: an elliptical EQ that high-passes the mid sum and the side difference at separate corners, 10–300 Hz, defaults 22/44; plus cutter-head HF limiting and mid/side-asymmetric groove wear. Every arg optional, all four live. Unlike monoBelow it narrows rather than collapses — measured 1.00 → 0.18 side/mid at 22Hz 120Hz, never 0 — and the defaults are a rumble filter, only 1.00 → 0.83), grooveWear wear [mix] (Airwindows GrooveWear — a played-in record's top end. Four cascaded 20-tap slew-average stages. Linear, but not a lowpass: it lifts the presence region up to +4.8 dB and then drops the last octave into a hard Nyquist null (−172 dB at 22 kHz at wear 1). mix engages stages, it is not a dry/wet). See The effects reference.
  • Modulation bbd amount ["i"|"ii"|"i+ii"] (on a track: bbdTrack, the shared-bus Juno chorus, one free-running clock after the voice mix; mode defaults to "i"), ensembleChorus amount (on a track: ensembleChorusTrack, the shared-bus Solina triple chorus, free-running 3-phase clocks), rotary amount [speed] (dual horn/drum Leslie cabinet; speed 0 chorale…1 tremolo, default 1, with separate motor inertia).
  • Dynamics compress amount, optical amount (LA-2A, gentle), fet amount (1176, aggressive), mbcomp amount (3-band Linkwitz-Riley), transient attackDb [sustainDb] (differential-envelope transient shaper — ±24 dB on the onsets and the tails independently, threshold-free so the shaping is level-independent; both dials macro-live: |> transient 6dB -4dB; see The effects reference), dynEq freqHz amount [q] (program-relative dynamic bell CUT — the deess mechanism at an arbitrary centre: ducks a resonance only while it spikes above the rest of the spectrum, q default 2.5; see EQ and frequency balance).
  • Stereo / placement width amount [lowAnchorHz] (mid-side, 0 mono…2 wide; the optional anchor highpasses the SIDE channel so the bottom stays centred — 100–250 Hz, absent = no filter), haas amount (precedence widener), srsly center space [level] [q] [nonlin] [mix] (Airwindows Srsly3 — a psychoacoustic Hughes-SRS bank of nine fixed mid/side and isolated-L/R bandpasses, summed back partly phase-inverted so the image reads outside the speakers; every knob ∈ [0, 1] and live. It enhances an existing image rather than inventing one — a mono source comes out exactly mono — and it adds real gain, so pull level), tripleSpread spread [tighten] [mix] (Airwindows TripleSpread — the exact complement of srsly: a stereo tripler that manufactures width from a centred source and leaves an already-decorrelated one at ×1.00. The dry stays in the middle while two glitchShift-style granular taps, detuned sharp and flat, are hard-wired one per side. spread ∈ [−1, 1] (0 = off; the sign only mirrors which side goes sharp; ±1 is +70.67/−73.68 cents, 144 cents apart — a chorused triad, not a unison). tighten ∈ [0, 1] (default 0.5) is the granular buffer length and the splice bias: 0 is a 65536-sample buffer that drifts, 1 is a 6-sample buffer that is all fizz and at which the width collapses. mix (default 0.5) keeps the centre copy — at mix 1 it is a duo, not a tripler. The mono fold-down never cancels (×0.60…×0.94), but one side is silent for ~0.33 s at tighten 0.5 while its buffer fills, so use tighten 0.85+ if the spread has to land on a downbeat), panTrack amount (constant / pattern / macro auto-pan — one constant-power law for all three forms, and for a hosted plugin's bus: centre is unity and the total level never moves, see Mixing tracks and buses §3.5), place az el [dist] (binaural spatialization on the sphere — degrees, wraps; macro slots orbit; headphones. See Position sounds in space).
  • Spectral (STFT) freeze amount (capture & sustain the current spectrum — ride a macro to catch a chord as an infinite shimmer), blur amount (spectral smear), spectralGate threshold (keep only bins ≥ threshold × frame peak), tilt amount (±6 dB/oct dark…bright about 1 kHz, amount ∈ [−1, 1]), spectralMorph "sample" t (cross-synthesis — the sample's spectrum imposed on the track's excitation), spectralTune amount (snap each spectral peak onto the module tuning's absolute lattice — hard-tune for partials; Match timbre to tuning). All knobs macro-live; identity at 0 (fixed ~21 ms bus latency). See Spectral effects.
  • Vocal / pitch tune strength [speedMs] (monophonic autotune toward the module key, formant-preserved; strength macro-live, small speeds = the zipper), formantShift semis (±12 — move the formants, keep the pitch), harmonize [degrees] [mix] (up to 4 diatonic voices stacked on the tracked f0), deess amount [freqHz] (program-relative de-esser — the threshold tracks the body, the detector is band-limited to the sibilance region above freqHz (default 6 kHz) and the cut is a bell there). All four render on every target. Fixed 768-sample STFT latency on tune/formantShift/harmonize. See Vowels, voice and pitch effects.
  • Level / utility gainTrack x (the per-track fader, |> gain x on a track — a linear level or dB (-3dB), macro-affine like every peer, so |> gainTrack (0.25 + trance * 0.5) and a bare |> gainTrack trance ride live; read at each note-on), granularize ["name"], brakeTrack pat secs, bypassed stage …args (keep a stage in the pipe but skip it; a voice stage must return the signal it takes).

Composed modulation clocks and events (Bring a sound to life) — patternTrig pattern, mergeTrig [triggers], filterTrig trigger gate and quantizeTrig period trigger compose with playOn, hold, counts and shared controls. gateOn gate gives an ADSR an explicit gate; resetOn trigger retriggers an already gated ADSR or resets an LFO. resetOn requires a Trigger; gates accept Booleans, Triggers and dimensionless controls, never audio-rate or physical quantities. LFO phase accepts a live offset; audio oscillators accept only a prepared seed. Phase is dimensionless or in cycles, wrapped modulo one before conversion to engine precision. Negative and multi-cycle seeds retain their fractional phase; omitting the seed keeps the source's usual start. These constraints survive aliases, imports, collections and partial/returned functions. Concrete graph receivers are checked again before execution. syncTo "free"|"transport"|"note"|"section" chooses state ownership. stepSeq accepts live entries; interpolate "hold"|"linear"|"glide" sets its boundary behavior. Mode names are case-insensitive fixed strings. segment n control "live" returns a sampled control. lastVelocity track initial and lastPitch track ~C3 require an explicit last-note policy, published 128 samples later. Inline onsets audio and pitchOf audio accept the same audio source graphs as follow.

Per-event control lanes (Control patterns for individual events) — a numeric pattern zipped onto the track, sampled stepwise at each note's onset, one value per triggered event. Lanes shorter than the track wrap; a lane slower than one cycle (<a b c>) advances between cycles; a rest holds the previous value; the last combinator wins when a param is set twice.

LaneEffect
gain pmultiplies into the voice's effective velocity
speed pmultiplies into a sampler voice's playback rate
pan pstereo placement; overrides the event-level jux pan
push psigned-millisecond micro-timing (positive = late)
push (grooveOf "brk" [n])the same lane EXTRACTED from a real break — onset-detect the donor, quantize to n steps (default 16), push by each hit's own deviation
warp pBoolean {0,1} — beat-match THIS hit (ORs with the warp modifier)
pitch pa pitch pattern that OVERRIDES the trigger pitch
pitchRoot mthe recorded pitch pitch transposes from on a sound track
variantP psample-variant index; an explicit bd:3 token still wins
cut gsong-wide choke group (0 = none): an onset in G fades every voice sounding in G
begin b / end ethe event's sample window, fractions of the trim / slice window; end <= begin is silent
kit = sound !"bd ~ sn bd"
   |> gain     !"1 0.7 1 0.6"      -- accents
   |> variantP !"<0 3 5 7>"        -- a different kick every cycle
   |> warp     !"1 1 0 0"          -- beat-match the first two hits only

Every lane is an ordinary pattern, so the §9 combinators apply to it directly — gain (!"1 0.4 0.8 0.4" |> every 2 rev), variantP (!"0 3" |> fast 2).

A named track chain is a track-transform lambda: hall = \t -> t |> reverb 0.85.

Cross-track (read another track's signal): insertPoint "name" marks a transparent track/return/master insert boundary. tap and tapLive accept { stage = "preFader" }, "postStrip", "postFx" or "insert:name"; explicit delayed reads lag exactly 128 samples, and current reads schedule the complete causal graph. See Route audio between tracks.

preparedFx choice alternatives selects a bounded bank of fixed track functions on tracks, returns or master inserts. Two to four alternatives, one or two supported inserts each, are prepared before playback; selection crossfades over 20 ms and coalesces rapid requests. See Bring a sound to life for supported effects, IR limits and requested/applied state.

audioFx transform { wet = 1 } adapts an ordinary Audio → Audio function into a persistent stereo track, return or master insert. Its input is the actual insert audio at unity; its state continues through note gaps. Wet is a live 0–1 control. Implicit note pitch, velocity, randomness and note-triggered envelopes diagnose. duckBy control depth works on audio or an insert receiver, applying 1 − clamp(depth, 0, 1) × clamp(control, 0, 1) continuously without creating a detector. See Bring a sound to life and Make one sound follow another.

follow converts inline audio into a linear level control with live attack/release and explicit RMS/peak and stereo policies (see Make one sound follow another). tap track pulls a bus in as audio (§4); follower track attack release → Sig Control Normalized RMS envelope for sidechain ducking (also accepts "input"); vocode carrier modulator bands is a channel vocoder, e.g. vocode (saw (freq pitch)) (tap speaker) 20. See Make one sound follow another, Patterns that respond to playback.

MIDI out (both backends — Web MIDI / midir): midi device pat is a MIDI-only track (no Volta voice); track |> midiOut device mirrors a play track out to hardware while still sounding. A device Name = { match, channel, cc, mpe, bendRange, zone, … } declaration is an in-language, importable preset (sugar for midiDevice { … }). Setting mpe = true (with optional bendRange = 48, zone = "lower") switches to MPE output — per-note member channels + pitch bend, so tuning systems, per-note cents, and glide reach external gear intact. See Send MIDI to instruments; inbound controllers / CC-learn live in Connect controllers and other software.

Plugin hosting (native backend): track |> plugin "path", instrument "path" pat, pluginId "com.vendor.Name", pluginParam id value — host CLAP / Audio Unit / VST3. pluginPreset "name" loads a named state snapshot from the workspace preset store (captured via the plugin picker's preset chip — e.g. a Kontakt patch, saved once, reused by name across songs). ccLane n value drives MIDI CC n on an instrument (mod-wheel dynamics CC1, expression CC11; constant, pattern lane, or signal — normalized [0,1]). See Host instrument and effect plugins, engine/src/plugin/.

Neural resynthesis (desktop and browser): track |> neural "model" [mix] — run the track bus through a RAVE-class model via the pure-Rust tract backend. Split .rave.json packages expose full encode/decode and streaming state; legacy .onnx graphs support forward-only inserts. The engine's neural feature is enabled by Studio, CLI and engine-wasm. The wet path has a two-hop prefill; mix is macro-live. The latent is a first-class control surface: track |> neuralMod "model" [bias…] [mix] adds per-dimension offsets, and encode "model" (tap track) → a latent, decode "model" <latent> → audio, with latentSmooth / latentHold / latentQuant / latentAdd / latentXfade between them. Decoder-only generators neuralOsc "model" [lanes…] and neuralPrior "model" temp [seed] play the model as a patch source (no input). The latent surface requires a split package. Direct TorchScript playback was removed; supported archives must be converted first. Browser playback uses a Worker and bounded AudioWorklet transport; late inference can produce silence. See Neural sound transformation for model and browser acceptance limits.

Latent timbre — a stored latent coordinate. latentOf "model" src returns a LatentCapture value, usable inline or through functions, imports, local bindings and containers. At load the engine encodes the source sample and keeps its trajectory and per-lane mean. The value carries its source/model pair independently of binding names; strings cannot substitute for captures, so latentXfade celloZ fluteZ (macro "morph" 0.3) is a preset morph on a knob. On top of it: latentPath [z…] t (vector synthesis along up to 8 waypoints), latentAxis a b amount z (a direction — "30 % more flute-ness"), latentPlay z rate and latentScrub z pos (replay or scrub the captured trajectory — any sample becomes a neural instrument). A coordinate's model must match the enclosing decode's: typed endpoints, checked. Split packages run on desktop and browser; the sample library's similarity index is desktop-only. See Latent Timbre, Neural sound transformation.

Patch morph morph a b t — structural interpolation between two same-shaped patches (modulators crossfade, audio sources crossfade equal-power, Hz fields blend in log space, ADSR lerps per-segment); a compile-time elaborator, not a node. See Morph between patches.

MIRA patches — a track opened in the MIRA window plays <alias>.inst from a song-local mira-<track>-<id>.volta module (MIRA's own program plus a -- @mira header), routed through a submix whose insert is the module's chain. Edit it in the window, not by hand. See Play a track through MIRA.


12. Tracks, songs, arrangement#

Named tracks are reusable values. t = play "saw" "c3 e3 g3" followed by main = t |> delay 250ms 0.4 copies the realized notes and effect chain without changing t. Both names remain separate playback tracks; play only main for the inline-equivalent result. Aliases, forward references, local bindings, qualified imported tracks, and typed Track -> Track transforms are supported. Randomness belongs to the defining binding, so extracting a randomized inline expression into a new name can reseed it. See Name and reuse tracks for identity, safe source-editing behavior, and the remaining reuse limits.

Name a track by its binding. Every seat that names another track takes the track's binding — bare (duck kickT 0.6) or qualified through an import (duck drums.kickT 0.6): duck, tap, tapLive, follower, onsets, pitchOf, convLive, specMask, playOn's onsets trigger, lastPitch, lastVelocity, the shred verbs wait until onsets kickT and record "take" 2c from kickT, and the visual bridges (vis.onset kickT, vis.level, vis.emit, …). The binding is resolved through scope, so a rename, go-to-definition and find-references include it, an import's flattening follows it, and a misspelling is refused at the argument while you type. The string (duck "kickT") is accepted legacy that names exactly what it says; volta fmt --modernize rewrites it to the binding wherever the two compile to the same IR (Format and modernize source). Strings remain the only spelling for what has no binding: "input" (the live audio hub), "master", and a bus, submix or group name. A string is also the spelling of a FEEDBACK reference — a track read inside its own definition chain (two channels that tap each other: one direction stays tap "chA"; follower "snareT" in a macro the snare's own patch reads), where a binding would be a dependency cycle; --modernize holds those (feednet, the iw-* set). A value holding the track (a record field, a list element, a function's result) names it too; a string held in a binding of an imported library does not follow the library's flattening and is refused as a missing source — hold the binding instead (Names and library imports).

import "devices" (minilogue)         -- a MIDI device preset (devices.volta)

kickTrack = play kick kickPat        -- a synth + a pattern
hatTrack  = sound !"hh hh hh*2"      -- drum-machine track (Sample-payload, no patch)
extTrack  = instrument "dexed.clap" mel  -- a hosted plugin instrument (native)
midiTrack = midi minilogue mel       -- a MIDI-only track (no Volta voice)
bassTrig  = playOn bass (onsets kickTrack) !"e1 g1 a1"  -- audio-driven track
leadTrack = play lead mel |> delay 250ms 0.35 |> reverb 0.6   -- track-effect chain

Track constructors: play synth pat accepts a named instrument, a kit field, a patch literal or a computed patch/audio value. A factory works inline, through a local binding, as a returned value, or through a list/record: play (mk 440Hz) pat and let p = mk 440Hz in play p pat both work. Computed definitions are instantiated by the receiver; a reference or alias of an existing named instrument reuses its handle. Built-in voice strings also work: play "saw" pat, play "pluck" pat — see §3 and Your first sound. Anonymous tracks: a top-level line that starts in column 0 with play / sound / instrument / midi / playOn and carries no name = is a track named track1, track2, … in source order — the sketch form (play "keys" "c4 eb4 g4" |> reverb 0.3 on a line of its own). Voice knobs (|> cutoff 800Hz, |> release 200ms, |> knob "field" value) sit on a play track like any stage (§3), sound pat (drum machine), instrument "path" pat (hosted plugin), midi device pat (MIDI-only), playOn synth trig pat (trigger-driven: each onsets trigger fires the pattern's next event in order, cycling — the drums supply the rhythm, the pattern the pitches; Turn audio into musical events). A song is scenes (vertical track-stacks) plus an optional horizontal timeline (order/transitions/motion — Arrange a song):

riserTrack = play lead !"c5"

set = song {
  scenes = {
    intro = [kickTrack],
    build = [kickTrack, bassTrack],
    drop  = [kickTrack, bassTrack, leadTrack] with { air = ramp 0.3 1.0 },  -- scene macro automation
    outro = [kickTrack]                        with { boom = 0.2 }          -- pin a macro for the section
  },
  edges = {
    intro -> drop  : [1, build],          -- came-from-aware transition (overlay)
    *     -> drop  : [1, [riserTrack]],   -- wildcard source; inline anonymous scene
    drop  -> outro : [2, build] replace   -- replace mode (transition tracks alone)
  },
  arrange = arrange [[8, intro], [16, drop], [8, intro], [8, outro]]  -- bars-weighted order; loops by default
              |> rev          -- the deterministic algebra (rev / slow n / fast n / once)
}
  • Bare form song { intro = [t1, t2], … } (no scenes/arrange) stays valid — uniform auto-advance over the record in order.
  • Scene algebra — a scene may be a set expression over earlier scenes: drop = groove + [leadTrack] - [padTrack] (+ ordered union, - difference; removing an absent track is a no-op; outro = intro aliases). A referenced scene contributes only its track list — with { … } automation stays scoped to the declaring scene. Resolved at parse time. See Scene Algebra.
  • arrange — a list of [bars, scene] (a scene name or inline [track…]). Weight is bars (= pattern cycles; a bar ≈ one cycle). Loops by default; |> once stops at the end. rev/slow n/fast n are baked at compile time. The schedule is an ordinary compile-time expression — bars, the slow/fast factors, and the whole list all fold, so §14's list algebra generates form (arrange (map (\n -> [n, "drop"]) widths)). A bare ident in a scene slot stays a scene name; computed slots name scenes with strings. Note this is the song-level arrange — its cells hold scenes, and its own |> fast n / |> slow n do apply (they rescale the schedule). The pattern-level arrange [[n, p]…] in §9 is a different combinator, and there a fast inside a cell is erased; see the note there.
  • edges — from -> to : [bars, scene] [replace], endpoints scene names or *. Resolved by specificity (exact → *->to → from->* → *->*); overlay (default) unions the transition tracks over the outgoing scene, replace swaps them in.
  • with { macro = value | ramp a b } — scene-scoped macro automation; the endpoints are compile-time scalars.
  • with { <mixer path> = value | ramp a b } — the same motion aimed straight at a mixer site, no hand-minted macro in between: padStrip.sends.hall = ramp 0.1 0.6 (a send level), padStrip.level|pan|width, master.plate.mix / hall.reverb.mix / padStrip.tape (a master, bus or track-pipe insert arg; the parameter name may be dropped when the effect has just one modulatable slot). A user effect's own parameters are addressed the same way — padT.warmth.amount, room.warmth.tone, master.fade.wet, and for an on voice effect or an oscillator the patch that applies it (bass.slew2.amount, lead.syncLead.ratio); see Write a custom audio processor § Live effect parameters. Desugars to an internal mix:<path> macro substituted at the site, so it rides the shipped macro path. A level path takes dB or a linear ratio; a site already bound to a user macro is a diagnosed collision, not a silent compose. See Mixing tracks and buses §1.
  • songGraph { a -> b : … } — walk-driven form (edges, no fixed arrange).

Shipped full-stack; details + the timeline UI in Arrange a song.

hear it — open Skyline in SONGS: a full song { scenes, edges, arrange } — six scenes (one built by scene algebra), riser edges into every drop, and a bars-weighted running order you can watch march across the TIMELINE panel.

The written timeline (region, automate, song { timeline })#

region returns Region C, preserving its content type; placements can be named, imported, returned from functions and collected with map. play, sound and automate accept these List Region values as well as inline lists. Region lists are distinct from patterns. Shared receiver contracts check the content kind before lowering; placement values and options are validated during realization. AutomationCurve and AudioContent distinguish placed curves/assets from functions/signals. See Reusable region values.

Arrangement combinators return Timeline. stack and concat select temporal composition from operand types; mixing temporal and non-temporal operands is a type error. Timeline values retain their meaning through functions, imports, records, lists and local bindings. Bare silence is an empty pattern; applying a duration produces a timeline extent, also through ordinary callable values.

A song can be written as placements instead of scenes: each part says where it plays and how long it lasts, and nothing has to be re-listed in a scene for every combination (Write a song as a timeline).

tone = macro 620Hz
opening = automation { points = [[0bars, 620Hz], [24bars, 5600Hz]],
                       interpolation = "linear" }

keysT  = play keys [region 0bars 40bars chords]         -- one uninterrupted part
drumsT = play kick [region 8bars 8bars beat,
                    region 20bars 16bars beat]           -- comes and goes
bassT  = play bass [region 8bars 24bars bassPat { id = "bass-a", offset = 2c }]
opened = automate tone [region 8bars 24bars opening]     -- one 24-bar gesture

main = song { timeline = [keysT, drumsT, bassT, opened],
              length = 40bars, playback = "once" }
  • region at length content { … } — one placement. at/length are unit-bearing (bars, beats, c, s) and must share a domain; the authored coordinate is kept beside the resolved beats. Optional keys: id, offset (content units), phase ("entry" default / "transport"), rate (exact as (3/2)), repeat ("native" / "once" / [start, length] source fold), boundary ("let-ring" default / "clip"), continuity, attach (curves), bind (groups), seed (redraws this placement's own choices), and — on an audio region — fade = [in, out] and warp ("off" default / "resample"). A key that does not belong to this region's content kind is refused by name.
  • play voice [region …] — a region track. The track keeps its instrument, inserts and strip; the regions own placement and timing, so |> at, |> once and phraseOrigin are errors on it (|> clock (p/q) still scales the whole track). Content is stored once however many regions place it.
  • sound [region …] — the same receiver without an instrument: the audio track. sound is already "samples, no patch"; handed a region list instead of a pattern it places takes (audio "vox" below) exactly as play places note content, and a sound region list still accepts note content. There is no separate audioTrack word.
  • automation { points = [[t, v], …], interpolation = "hold"|"linear" } — a finite typed curve; times are content units, values in the destination's own unit.
  • audio "name" — a placed audio asset, on a sound track: takeT = sound [region 8bars 4bars (audio "vox") { offset = 2.5s }]. The argument is a bank name or a binding that is one (bounceOf, sampleOf, comp). An audio region's offset is seconds of the source, it plays once, and its container gates it — an unwarped take keeps its own speed when the tempo changes, and warp = "resample" fills the container instead by changing speed and therefore pitch.
  • automate target [region …] — a lane writing one destination's base (Draw a change, and record one is the whole surface). target is a declared macro, or a dotted parameter address — automate "bassT.reverb.mix", "bassT.level", "bassT.sends.hall" — which the lowerer resolves by name at every level to a stable runtime slot. @gap "hold" on the binding holds the previous region's final value between regions; @compose "crossfade" / "priority" says how two overlapping base curves combine. Without a declared composition, two authored base writers on one destination — two overlapping curves, two lanes, or a scene ramp against a lane — are a compile error.
  • song { timeline, length, playback } — timeline is a list of region tracks and lanes; length is the extent (inferred from the latest region end when omitted); playback is "once" (default) or "loop". scenes may coexist; arrange, edges and songGraph may not — they are a second scheduler. Unknown song fields are rejected with a did-you-mean.
  • Timeline combinators — stack [a, b] (concurrent), concat a b (append by declared extent), repeat n a (structural passes, one shared content) and silence d (declared empty time). stack/concat/silence are the shipped pattern words overloaded by position; repeat is new. A finite timeline placed by region is a group, and bind = { port = track } redirects one of its receivers.
  • A TRACK in a timeline is a declared name. Both receivers that place tracks — a stack over tracks and a song's timeline — resolve each member through its declaration, so [keysT, drumsT] works and a computed member ([make 1], [rack.line], [head parts], [let held = line in held]) is refused at checking with one sentence: a track in a stack or a song's timeline must be a declared top-level name for now. Bind it (part = make 1) and place part. The rule is about the TRACK, not about computation: a computed TIMELINE (makeGroup keysT) still reaches the receiver, as do all the ordinary spellings of a timeline value.

Shipped through the engine and the workbench's Arrange view. What is still staged is listed in Write a song as a timeline § "What is not here yet".

The mixer (buses, groups, strips)#

Entry-owned mixer records, bus effect sections and group lists use ordinary expressions. A section has type Track -> Track and shares checking/lowering with track pipes. Bind/import option records, use typed transform helpers, and override mixer records with with; see Mixing tracks and buses §1.2:

snareTrack = play snare snarePat
padTrack   = play pad padPat

bus hall = reverb 0.85 0.4 |> width 1.3          -- a shared aux/send bus
group drums = [kickTrack, snareTrack, hatTrack]  -- a VCA over strips
mixer {
  kickTrack = { level = -1.5dB, sends = { hall = -18dB } },
  hatTrack  = { level = -10dB, pan = -0.3, width = 1.4 },
  bassTrack = { level = -3dB, polarity = inverted, mute = false, solo = false },
  padTrack  = { presends = { hall = -12dB } },      -- pre-fader send
  drums     = { level = -1dB },                     -- the group VCA
  master    = { level = -0.5dB, mono = false, dim = -20dB, console = false,
                refLevel = -18dB, inserts = [compress 0.12] }
}

Strip fields: level (dB or linear), pan [-1,1], width [0,2], mute/solo, polarity "normal"|"inverted" (bare aliases also work), sends/presends maps into declared buses. Groups take level/mute/solo; master adds mono, dim, console (off-by-default nonlinear channel-encode/bus-decode summing), refLevel (the BS.1770 meter's reference), and an inserts chain.

submix name = [tracks and patches] declares an audio sub-mix: a bus its members output into (a send copies; a sub-mix member is moved, and reaches the master only through the sub-mix). A patch member means the voices a host plays on it by hand — held notes and one-shots. Its mixer entry takes inserts (the master's grammar; place belongs here, not on the master) and level:

lead = play pad !"c4 e4 g4 e4"
az   = macro 0.5
submix flyers = [lead, padTrack, pad]
mixer { flyers = { inserts = [place az 0 1], level = -3dB } }

It is a bus in every other respect — tap "flyers", follower "flyers" (a bus is named by string: bus, submix and group names are not lexical bindings), meters. See Mixing tracks and buses §1.3.

console takes a bool or a flavour name — true (PurestConsole, sin / asin), "9" (Airwindows Console9), "LA" (Airwindows ConsoleLA, the Quad Eight colour), "MC" (Airwindows ConsoleMC, the bright MCI colour), false (plain digital sum, the default). Every flavour is unity for a single in-range channel through the midband and the top, so the choice is texture, not level — "MC" additionally tightens the lows on purpose (−5.3 dB at 20 Hz, −2.0 dB at 100 Hz, flat above 400 Hz). Unknown names are a compile error listing the legal set. See Mixing tracks and buses §3.1 and examples/songs/consoleflavors.volta.

hear it — open Console in SONGS: every field above in one readable mix — channel strips, a shared hall bus fed by sends, a drums VCA group, a bypassed tape insert, bus-keyed sidechain, and a master section with inserts and a loudness reference. Move a fader in the MIXER panel and watch the strip follow.

Tempo maps & polytempo#

tempo is a typed value, not just a number (Tempo changes and independent clocks):

``volta norun four alternative spellings of onetempo` binding tempo = 122bpm -- constant (the common case) tempo = tempoMap [[0, 100], [32, 128]] -- piecewise map over beats (linear ramps) tempo = accel 100 128 16 -- sugar: speed up over 16 bars tempo = ritard 128 96 8 -- sugar: slow down over 8 bars


A per-track clock ratio is a separate knob:

```volta
drift = play bass pat |> clock (13/12)    -- per-track rational clock ratio (polytempo)

The map compiles to a closed-form beats↔seconds bijection, so scheduling stays sample-exact under an accelerando; clock (p/q) runs one track's whole beat timeline at a rational multiple of the module clock.

tempo (and meter) are ordinary compile-time values: a name, an if or a function's result works, and a value that does not settle is an error rather than a silent 120. A breakpoint may be a (beat, bpm) tuple, and a position written in bars is a notational bar placed through the meter, not a beat (Tempo changes and independent clocks § Computed tempo and meter):

bpm   = 128
tempo = tempoMap [(0, bpm), (8bars, bpm + 12)]   -- bar 8's downbeat in any meter

Linear time (markers, absolute placement)#

The absolute bar axis (0-based whole bars; note tempoMap breakpoints are beats unless written in bars; Schedule events on a timeline §3):

markers = form.markers [[0, "intro"], [8, "verse"], [37, "fix the snare"]]
crash   = play cym !"c5" |> at 37bars |> once   -- one hit, at bar 37
enter   = play kick beat |> at 8bars            -- silent until bar 8, loops after

markers is pure metadata — a song-map lane (click = seek), jump-to-marker transport, and WAV cue chunks in exports. at anchors a track's cycle 0 at a bar through the tempo clock (so it holds under a ramp); once plays the first complete pattern period. With default phraseOrigin "transport", a seek or join past that period plays nothing. phraseOrigin "entry" starts a phrase when its track activates, including once per activation for once. Tracks kept across scenes continue; removing and re-entering restarts them. Nonzero at placement and entry origins are incompatible. See Arrange a song §Phrase origins.

Meter (meter = p/q)#

meter = 7/8          -- a bar/cycle is 7 eighths = 3.5 quarter-note beats
tempo = 174bpm       -- bpm keeps meaning quarter notes
grid  = sound !"bd ~ ~ sd ~ bd sd"   -- 7 steps = the 7 eighths

meter = p/q (a literal time signature; denominator a note value 1–32, not a declared builtin) sets the notational bar to p·4/q beats — 7/8 → 3.5, 3/4 → 3, 12/8 → 6. One cycle = one bar, so pattern cycle length, Nc and bars literals, arrange weights, scene quantization, and every ruler follow it; the beat axis stays quarter notes, so tempo, tempoMap breakpoints, and ms/Hz are unaffected. Mini-notation is meter-correct by construction: N top-level steps subdivide the cycle evenly. Default 4/4.

Meter changes are a positional map over the bar axis:

meter = meterMap [[0, 4/4], [32, 7/8], [40, 4/4]]   -- whole-bar breakpoints

Bars become variable-length (the clocks integrate bars↔beats in closed form); markers, at, arrange weights, and scene quantization follow the map, while tempo/tempoMap (the beat axis) are unaffected — except a breakpoint written in bars, which is placed through the map. clock (p/q) tracks keep uniform cycles and drift against the bar grid, exactly as they do against 4/4.

A time signature is also the value (p, q), which survives bindings, lists and functions where a computed 7/8 would be integer division (0):

odd   = (7, 8)
meter = meterMap (map (\b -> (b, if b == 32 then odd else (4, 4))) [0, 32, 40])

A song's own schedule is a value too — set.arrange is its flat (bars, "scene") list — so tempo and meter can be derived from the form; examples/songs/conductor.volta (sectionTempo, sectionMeter) does it (Arrange a song § Schedules as values).


13. Shreds (imperative time)#

A do { … } block is a shred — a straight-line score over musical time, auto-launched with the transport. See Timed live processes.

bell = patch { source = sine (freq pitch), ampEnv = adsr 1ms 400ms 0 300ms,
               gain = vel * 0.4 }

conductor = do {
  bright := 0.2;                      -- set a macro
  loop do {                           -- (or `times N do { … }`)
    wait 4beats;                      -- advance shred-local time
    bright := read air;               -- copy another macro's live value
    trigger bell ~E5 2beats vel: 0.6; -- fire a synth voice (no track → default bus)
    wash := !"0.2 0.8 0.5"            -- a macro from a looping numeric pattern
  }
}

Ops: wait <dur>, <macro> := <value>, := read <macro>, trigger <synth> <pitch> <dur> [vel: n], record "name" <dur> [from track] (the looper — capture the mic, or a track's bus, into the sample bank mid-performance; every sample verb then applies to the take — Record a loop), loop / times N. Durations fold to beats (tempo-relative). A mic record flags the module non-deterministic like input.

Runtime control flow (Runtime Control Flow Stage 5): if read m > x do { … } [else do { … }] branches on live macro state (macro-vs-constant conditions in v1); wait for marker "name" jumps to a marker's bar (Schedule events on a timeline, never rewinds); wait until onsets "track"|"input" PARKS the shred until the source's next transient — the trigger form lives outside the lookahead-horizon model, and a mic source flags non-determinism like record. See Timed live processes.


14. Compile-time meta (lists, recursion, branching)#

Folded away before lowering — for generating patches/patterns, not runtime control:

  • List algebra range a b, map f xs, filter f xs, zip, length, head, tail, nth i xs, concat, mix [sigs] (sum), foldl f z xs. zip yields tuples; the four projections (length/head/tail/nth) open a tuple as well as a list, so a zipped pair is readable either by projection or by destructuring (Generate and transform musical structures). A callback's let folds too.
  • Table-driven generation nth i xs reads the i-th element (0-based) — the computed-index companion to head. There is no xs[i] bracket syntax: an index is an ordinary argument, so nth composes inside map / foldl. Paired with % (§2) it is the "pick the i-th of a vocabulary" step behind Markov rows, L-system rules, and parity formulas: patternOf (map (\i -> nth (i % length notes) notes) (range 0 16)). An index outside [0, length xs) — negative included, there is no wrap-around — is a compile error naming both the index and the length.
  • Bounded recursion / generators iterate f x n ([x, f x, f (f x), …]), scan f z xs (running fold, keeps every intermediate), unfold f seed n (corecursive — the step returns [emit, nextState]). The dual of foldl; for L-systems, Fibonacci, fractal phase sums. See Patterns that respond to playback.
  • Branching if c then a else b; case x of { Ctor n -> e, _ -> e } (constructor patterns destructure user-defined sum types, §2). A compile-time if lowers exactly one arm; an if whose condition is a signal comparison is the runtime exception — it lowers both arms and blends them through a gate (§2, Runtime Control Flow). case is always compile-time (sum types erase).
  • Introspection assert cond "msg" (compile-time assertion → Unit), show x (render a compile-time value to a Str).

See Functions and reusable ideas.


15. Compilation & runtime model#

  • Compiler (compiler/): resolve → check → lower → JSON IR (ir::Module: synths, tracks, macros, songs, scores). Run via volta-cli (cargo run -p volta-cli -- --json file.volta) or the wasm build (./build-wasm.sh stages wasm/pkg, the browser compiler — rebuild it after any compiler change).
  • Lexical binding: local references keep their binding identity through functions, closures, let and case. Reusing a spelling in an inner scope does not retarget a captured outer value. Import aliases point to the same defining declaration; equal names in different modules remain distinct. The compiler verifies local scope and declaration ownership before executable elaboration or static inspection. These IDs are local to a compilation and do not determine random draws or persistent engine state.
  • Compile-time evaluation: invalid computations and exhausted work limits return errors through recursive evaluation; later sibling expressions stop. A value that cannot be evaluated statically is distinct from a failed computation; the REPL reports failures instead of answering with a type alone. Expression and static evaluation check cancellation within the walk.
  • Execution contract (ir/): volta-ir owns the versioned shared schema, typed graph rates/units, graph traversal and validation. Native and JSON preparation are fallible; malformed references and unsupported contracts fail before installation. Validation includes effect parameters (including nested alternatives and vectors), mixer sends/groups, scene/score references and audio-source reads. A declared but stopped source remains valid; a missing source is an error. Oscillator phase seeds must be finite and wrap before conversion to engine precision; unsupported waveform and sync selections reject before replacing the installed performance. The engine has no production compiler dependency. Simulation preparation also validates finite coefficients/layouts, positive engine-precision rates and typed live ports; CA seeds retain all 32 bits after modulo reduction.
  • Engine (engine/): one Rust engine plays that IR everywhere (One audio engine across hosts). The desktop workbench (ui/ + app-native/, the volta-studio binary) drives it in-process; the browser runs the same crate compiled to wasm inside an AudioWorklet (./build-engine-wasm.sh — rebuild it after any engine change). There is no second, browser-only synthesis path: the old Web Audio graph was retired in 2026-07, so what you hear in a tab is what the desktop renders.
  • Workbench (ui/): also one program — native on the desktop, WebAssembly in the browser (./build-ui-wasm.sh). The page around it (runtime/) supplies what only a browser can: the AudioWorklet, sample fetches, storage, the community transport.
  • Host-only features. Only two things can be missing in a tab (docs/ARCHITECTURE.md §5.8): an off-by-default Cargo feature the wasm build omits (plugin hosting — plugin/instrument/pluginPreset — and neural), or platform device I/O (Ableton Link is desktop-only). All DSP is the same Rust code everywhere, tap/onsets/pitchOf/additiveOf included.
  • Determinism: all randomness is seeded and keyed to the performance coordinate, so a performance replays bit-identically; live hand-jumps stay outside the seeded timeline.

16. Where to go deeper#

docs/spec.md (design north star — aspirational) · docs/ARCHITECTURE.md (the system as built; §3 surface/types/builtins, §8 the spec↔impl gap) · docs/reference/<feature>.md canonical per-feature docs:

  • Temporal core (contributors): Temporal core — the exact region-query foundation; current language syntax and playback are unchanged.
  • Audio production: The production handbook — the agent handbook for synthesis, production workflows, capability limits, and genre/song briefs.
  • Creator presets: Ready-made instruments — the Foundations collection, sixty instruments with independent controls, editable demos, twelve Dance Fugues and audio previews.
  • Synthesis — additive, wavetable, fm, unison, filters, analog-character, as3340, voice-topology, physical-modeling.
  • Sampling & time — sampler, slice, warp, stretch, multisample, granular, tapestop, sample-synthesis, bounce, pinned-prints, autosample, sfz-import, looper, tempo, remote-samples, Generate samples with Magenta RealTime 2 (local live generation and sample capture).
  • Patterns & harmony — patterns, pattern-controls, pattern-units, Pattern rolls and phrase editing (written and compiled pattern displays), unified-time (fast/slow/rev on control signals + continuous pan/gain lanes), harmony, harmony-atlas (the corpus-wide progression analysis), tuning, spectral-tuning, generative-feedback, programmability, programmability-2.
  • Modulation & macros — mod-recipes, macro-modulation, Draw a change, and record one (what an automate lane writes — parameter addresses, curve segments, base composition and the Read/Touch/Latch/Write/Off recording modes), velocity, glide, follower, connect, simulation-sources.
  • Effects & I/O — effects, bbd, tape, convolve, feedback, shape, user-kernels, DSP cookbook (tested recipes for writing filters, delays, reverbs, dynamics and saturation as kernels, with the dsplib library behind them), Write a spectral effect (per-bin kernel laws in the STFT domain), spectral, spatial, neural, midi, interchange (volta export midi|stems + import midi — SMF read/write + per-track WAV stems), native-plugins, audio-input, audio-events, pitch-tracking, cross-track-audio.
  • Structure — arrangement, Write a song as a timeline (the written timeline — region placements on play, audio regions on sound, automate lanes, editRegions overrides and song { timeline }), mixing, Freeze a track to save CPU (desktop track prints), shreds, modules, parameterized-modules, user-defined-types, polyphony, versioning, tracking (the recorded-audio lifecycle — transport click / count-in / punch / tap tempo, loop-record into numbered takes, comp as an expression, the waveform clip lane), takes (the cross-cutting map: "take" names three different things — recorded audio passes, source snapshots, and looper captures — with three stores).
  • System & tooling — native-reference-sound, livecoding, lens, patch-inspector, builtin-viewers (per-builtin hover cards + live probes in the bench), signal-overlay, linked-notes (click a Timeline note: its atom and transformation chain in Source, its echoes, when it was accepted, and a one-step edit), kernel-lab (measure a kernel case by case: stimulus and reset, trace an output sample to its state cells and source, sweep a parameter, replay stored cases bit for bit), mod-matrix (the modulation graph as a queryable value + the matrix view + live mod rings), bench-tools (write-back visual editors in the notebook — piano roll, envelope editor), patchbay, live-bench, cycle-wheel (the rhythm as a circle — Timeline ▸ wheel and the bench wheel ▸ dock), now-view (only the sounding parts under a fixed play-head; Stage), pads (the song's sample hits as pads: audition, trim, choke groups, step entry), pro-workbench (the workbench's pro affordances), structural-editing (paredit-style pipe/pattern editing), patch-shelf (pin a patch, quick-add it into any song), multi-song-and-files (many songs open and one live — song tabs, split source, the two cross-song paste verbs, compare, and the Mine / Library / Modules browser), bench-repl (the bench rail + the ⌘J evaluate-anything prompt), command-bar (⌘⇧K — type a mix move, an effect or a tempo change as a sentence), workbench-ui (the workbench's visual contracts — density presets, text-role hierarchy, panel fit, monitor states), workbench-layout (movable panes and saved workspace profiles), produce (the Produce workspace: type only patterns; instruments, mix and arrangement stay visual), arrange-workspace (the Arrange workspace: Timeline, the selected clip's roll, and an agent that writes a part into the selected region), skin-assets (skin artwork: decoration slots, nine-slice and format-2 .voltaskin packs), control-faces (skin-drawn knob and fader faces over the host's protected value layer), customization-authoring (the pack catalog, authoring tools, Appearance inspector and Apply workspace), interaction-preferences (knob drag, wheel, reset and detent preferences; the command strip), instrument-visual-details (instrument presentation in the Mixer, Notebook and Bay), musical-backgrounds (musically reactive scenes and their presentation controls), cost-model (static DSP cost estimate + live per-track profiler), parallel-render (a block's tracks on every core — Studio's dsp threads, the same audio bit for bit), gpu-dsp (long convolutions' tail sums on the GPU, bit-identical), doctor (volta doctor — the "why is this silent?" debugger), fmt, learn-mode, literate, story-notebook (a song file as its own markdown-chaptered introduction: -- prose, -- ▸ chapters, counter cards, the story preset), ai-copilot, copilot-plugin-exploration, mcp-server (Volta's tools for Claude Code, Codex and other MCP agents), patch-morph, stdlib, libvolta, plugin-target, patch-plugin-export (volta export plugin — one patch as a standalone CLAP instrument), lsp (external-editor language intelligence), community, invites (private-beta invitation links), auto-dj (Volta Radio's beatmatched queue), first-run (the intent-routed welcome journey), send-feedback (the in-workbench Help ▸ feedback report path), release-discipline (CI, packaging, signing, and release gates), reference-ui (Symbols/Manual search, exact routes, history, and reading), public-site (the crawlable /reference/ and /learn/ pages + sitemap that search engines index, each deep-linking into the workbench), accessibility (non-visual / screen-reader live coding), adding-a-builtin, audio-gates (the rendered-audio CI regression suite — metric baselines + blessing workflow).

Keep this file honest with compiler/src/builtins.rs + the canonical docs catalogue compiler/assets/docs-data.json when primitives are added or renamed.

<!-- Coupled-body instrument contract: Coupled bodies -->

Pulsar instrument#

pulsar rate formant { cycles, shape, mask, burst, rest, window } repeats independently windowed carrier particles from subaudio rhythms to pitched formant tones. See Pulsar synthesis.

Complete-song playback#

songSource station.film 24s exposes an imported complete finite song as a prepared stereo source, preserving its own timing, arrangement and mixer. See Play a complete imported song for preparation, transport position and limits.

Reusable theory operations#

fitScale scale pattern, transposeDiatonic steps scale pattern, invertVoicing steps pattern, dropVoicing [positions] pattern, imitate delay semitones pattern, and counterLines options pattern operate on materialized patterns. voiceLines also accepts reusable option records with ranges and fixed-note locks. The harmony library supplies chord-value transformations, voicing presets and canon. See Theory tools and composition workflow for examples, species 4–5, local repairs, Studio controls and bounded-search semantics. The harmony atlas is the same harmony analysis run over the whole song corpus.