The same song renders the same samples every time — on first play, on replay, after a reload and on another machine — because every random value is computed from where it happens in the music, not drawn from a running stream.
How randomness is addressed#
Every random draw in the engine — an oscillator's start phase, randomPerNote, sample-and-hold LFOs, granular jitter, indexed noise streams, tol and drift — is a pure function of the module seed and a coordinate:
(seed, synth, track, cycle, event, node id, purpose)
engine/src/rng.rs folds each coordinate component through a hash into [0, 1). There is no mutable generator state, so the order in which voices are rendered cannot change what they draw, and a voice rebuilt for the same position in the song draws exactly the values it drew before. The Rust hash is a bit-for-bit port of the browser runtime's rng.ts, pinned by the vectors in engine/src/rng_vectors.in and by runtime/scripts/determinism-test.mjs.
The module seed is a fixed compile-time constant (0x5EED, ir/src/ir.rs default_seed). Live gestures with no song coordinate — a keyboard hit, a one-shot audition — are deliberately outside the deterministic timeline.
Random draws in a pattern#
A pattern's random choices — mini ? and [a | b], degradeBy, sometimesBy, someCyclesBy, shuffle, scramble — are computed from where they happen too, never from a running stream: each is a hash of a salt (the site, the binding, the placement's seed) and a key (the cycle or repetition), in ir/src/guard.rs. The compiler either unrolls them into plain events or leaves them on the events as guards the scheduler evaluates per cycle; both read the same function. Seeking to any cycle recomputes the same draws. Under randomCycles = N a guarded event keeps the per-voice coordinate it has in the unrolled list (VoiceCoords), so both forms render the same samples.
What that means for a song#
- Two identical sources are two different signals. The node id is part of every coordinate, so two separately written
noiseexpressions in one patch get different streams. To feed one excitation to both a dry and a wet path, bind it once with a locallet src = … in …and readsrctwice (see Strings, bells and resonating objects § Voice dry/wet). - Consecutive cycles differ. The cycle number is part of the coordinate, so cycle 3 of a playing pattern draws different per-note values from cycle 1 — even a plain oscillator pattern, because each note's start phase is a seeded draw (
engine/src/voice/eval_osc.rs). What repeats exactly is the same span rendered twice, not two spans of one render. A looped print (a frozen track, a bounce) replays its own cycles' draws on every pass. - Editing one part does not reshuffle the rest. Nodes the compiler inserts to adapt a clock rate get their own
$periodids rather than taking the next ordinary node id, so adding one does not move any existing oscillator's coordinate (ir/src/ir.rsadd_node). Node ids are identities, not storage positions: a direct-index lookup verifies the id before it falls back to a scan.
For DSP authors#
A new source or effect that needs randomness draws it through engine/src/rng.rs with its own purpose string, never from a fixed or time-based seed — a fixed seed anywhere breaks replay. When an external algorithm is brought in, its arithmetic is kept verbatim and only its seeding and parameter units are adapted, so the port can be checked against the original and remains deterministic.
Related#
- Change music while it plays — the reproducibility substrate this rests on.
- Analog character —
driftandtol, the per-note variation built on these draws.