For readers comparing hosts: the shared audio engine contract and the historical compatibility differences it replaced.
The policy (historical)#
The native engine (engine/) is the reference sound of Volta. When a DSP decision trades audio quality against matching the browser runtime, the native engine takes the quality. The browser runtime (runtime/src/voice.ts + the worklets) is the documented compatibility mode: it remains fully playable and musically equivalent, but where it has not yet adopted a native upgrade its output is the legacy sound, not the reference.
What this changes in practice:
- "Verbatim port of the worklet" is no longer a design constraint on
engine/src/. Parity comments in the engine describe history, not an invariant to preserve. - New DSP lands native-first. Each native-only behavior gets a row in the table below when it ships, with a note on what porting it to the browser would take. Removing a row = the port landed.
- Determinism guarantees are unchanged: both backends remain bit-deterministic with themselves. Cross-backend bit-equality was never promised and is now explicitly out of scope; cross-backend musical equivalence (same notes, same envelope times, same mix topology) is still required.
- Golden IR snapshots are unaffected — the IR is shared. Divergence lives strictly in how each backend renders a node.
- A/B against the legacy sound = play the same song in the browser build.
Native-only behaviors (the porting ledger) — HISTORICAL#
Historical as of 2026-07-24. These rows were written as a porting ledger against the deleted Web Audio graph; their "browser (compat)", "to port" and "effort" columns were dropped 2026-09-06 because there is one backend. What remains is the DSP rationale each row records — the native implementation is simply the implementation.
| # | Behavior | Native implementation | ||
|---|---|---|---|---|
| 1 | Exponential ADSR segments | dsp::adsr_value shapes attack as a normalized concave RC rise and decay/release as convex RC falls (K = 4.6, exact arrival), replacing all-linear segments. The release is floored at dsp::MIN_RELEASE = 2 ms (2026-08-04, DSP audit E5): r = 0 used to return exactly 0 at note-off — a 0.107 step on a 0.175-peak sustain, 17.8× the sustain's own p99 |Δx|, which the ~10 Hz voice DC blocker turned into a thump. A sub-floor release now renders bit-identically to a 2ms one; anything ≥ 2 ms is untouched | ||
| 2 | Retrigger-from-current-value envelopes | the Adsr node is stateful per voice; a glide-voice retrigger ramps the attack from the envelope's current value (no click, analog behavior). Envelopes anchor on Voice.env_anchor, never start_sample — that one gates lo | ||
| 3 | True periodic wavefolder with ADAA | fold is an unclamped periodic triangle fold with SECOND-order MinusX antiderivative anti-aliasing (stateful, per channel; un-folded signal passes bit-exactly one sample late — 2026-08 audit #1) | ||
| 4 | Unclamped, ADAA drive & clip | drive applies its (optionally biased) tanh(k·(x+b))/tanh(k) to the raw input (no pre-clamp), and both drive and clip use SECOND-order MinusX antiderivative anti-aliasing (stateful, per channel — the same machinery as #3; linear part passes undrooped at one sample of latency, output clamped to the transfer's true range; per-sample feedback-region arms stay first-order Direct so loops don't detune). A nonzero drive bias adds even harmonics | ||
| 5 | Per-voice DC blocker | a 10 Hz one-pole high-pass per voice channel on the stereo render path (kills pulse-duty / folder DC thumps at note edges) | ||
| 6 | Zero-delay-feedback ladder | the Moog ladder resonance loop is solved zero-delay (linearized TPT solve, tanh on the input path, full 1+k passband compensation; the opt-in ladder … { makeup } rescales it to 1+m·k, so m = 0 exposes the bare ladder's resonance loss). With drive > 0 the native core additionally runs the FULL nonlinear four-transistor-stage ladder on the DAFx-21 paper-37 port-Hamiltonian non-iterative solve (ladder_tick_ph; provably zero-input-stable discrete-gradient step, 2026-08 audit A7) | ||
| 7 | Per-sample biquad design under modulation | lpf/hpf/bpf keep one coefficient set for a block whose cutoff and Q are static; a moving cutoff/Q redesigns the RBJ section at every sample (2026-08 audit F6 — the earlier start/end coefficient lerp left fast envelopes with a −16 dB null and block-grid FM sidebands) | ||
| 8 | FDN reverb | |> reverb decay mix renders through an 8-line feedback delay network (Householder feedback + zita-style in-loop allpasses, mutually-prime delays ≈25–63 ms with a doubled table past 2.5 s decays, per-line THREE-band decay — bass blooms 1.5×, mids 0.45×, top octaves ≈0.29× — split L/R input diffusion feeding even/odd lines — the input image survives into the tank — slow fractional-read modulation on 3 lines against metallic long decays, decorrelated L/R taps; decay = RT60) (engine/src/fdn.rs) | ||
| 9 | Osc sync + corner rounding (|> sync master ["hard"|"soft"], |> soften a) | Osc carries an optional sync master input (hard reset, polyBLEP-corrected; triangle reset also BLAMP-corrected — 6.3a; the SINE reset instead gets the closed-form all-orders La Pastina/D'Angelo residual on a cubic B-spline kernel, dsp::sine_sync_residual — no added latency), a sync_mode ("soft" = window-gated reset, 6.3b), and soften (0..1 Juno-DCO reset-RC corner rounding, 6.5 — also available as a standalone Soften { input, amount } node when the piped source is not a bare Osc, e.g. as3340 … |> soften a; the two spellings render bit-identically) | ||
| 10 | FM operator phase-mod input (fmOp … |> phaseMod m d, and sine … |> phaseMod m d which promotes to the same operator) | FmOperator carries an optional phase_mod signal (cycles), enabling true PM operator chains (DX-style), not just freq-summing FM | ||
| 11 | Wavetable mip crossfade | the oscillator lerps the two mips bracketing the continuous level log2(fs·hz/sr) — no brightness step at octave boundaries on a glide | ||
| 12 | Band-limited sample reads | the plain sample player, stretch streaming, chop and granular grain reads all go through the rate-tracking Kaiser-sinc kernel (resample::sinc_read_for); only a NaN rate falls back to 4-point Catmull-Rom (dsp::hermite4). Since 2026-08-06 (audit X1/X7 and X1's D1 review) that covers sub-unity rates — the 44.1 kHz-bank-at-48 kHz case, i.e. most playback — and unity, whose fractional-phase reads the 4-point kernel notched | ||
| 13 | PolyBLAMP triangle | the triangle's corner discontinuities at phase 0/0.5 get 2-sample polyBLAMP residuals (B(s)=(1−|s|)³/6 scaled by the ±8·dt slope change) | ||
| 14 | Macro smoothing | live SetMacro (knob/shred) chases a ~15 ms one-pole, rendered as a within-block linear ramp by MacroRef; the control-side set_macro convenience snaps (offline renders start on target) | ||
| 15 | Voice stealing | voices = N is enforced per synth before the full 64-voice arena ceiling; either tier steals released-nearest-end, else oldest (glide voices last). A steal settles through a ~2 ms exponential declick and every voice fades its last ~5 ms before the lifetime gate (no end-of-tail step) | ||
| 16 | Additive Nyquist fade | partials fade linearly over the top 15% below Nyquist instead of switching off (break) — no pop/timbre step when a glide crosses a partial out | ||
| 17 | True-stereo per-voice granular | each grain keeps its equal-power pan into a real L/R pair (the granular Sample node originates stereo in compute_stereo) | ||
| 18 | Oversampled nonlinear filter loop (2× / 4×, per model) | when filter drive > 0, the native tick runs oversampled with a per-model factor selected by measurement (circuit-fidelity Stage G): ladder/diode/korg35 at 4× (upsample → four inner ticks → two-stage allpass-IIR half-band decimate; the ladder upsamples through a halfband anti-imaging interpolator, diode/korg35 through cubic-Hermite; korg35 was promoted from 2× on 2026-08-05, audit F2), svf at 2× (linear-interp upsample → two inner ticks → one-stage half-band) — the *_OS_FACTOR consts in engine/src/voice/filters.rs. Cutoff g warps for factor·sr. All four clear the −60 dB alias budget on the §4.3 isolated-nonlinearity probe (see Filters: shape brightness and resonance § Oversampling & latency). A clean filter (drive = 0) stays single-rate and bit-identical — except that a ladder/diode/korg35 at normalized resonance ≥ 0.8 oversamples its loop even at drive = 0 (2026-08 DSP audit #6: near self-oscillation the loop nonlinearities limit-cycle) | ||
| 19 | PatternMod anti-pop glide | numeric-pattern modulators glide ~5 ms at each step boundary (a pure function of transport time — block-size independent, identical across voices) | ||
| 20 | Plate decay is a true RT60 | plate.rs maps decay via the closed form g = 10^(−3·T_loop/(4·rt)) over the tank's loop delay (0.9 safety cap ≈ 12 s max); the old (1 − 0.3/rt).clamp(0.4, 0.9) made every decay ≤ 0.43 s identical and rang ~4× nominal at the top | ||
| 21 | Tape echo first repeat through the loop path | TapeLine::tick returns the damped+saturated read (playhead-is-output): echo #1 already sounds like tape | ||
| 22 | Dynamics de-zipper + two-stage optical release | compressor/optical/FET gains slew per-sample toward the control-rate target, each with τ = exactly its own hold (16 samples / 16 / 4 — the FET recomputes 4× finer, so it slews 4× faster); the optical release blends fast (0.1 s) + slow (1.5 s) cells in dB — real two-stage recovery | ||
| 23 | De-zippered k-rate parameters | modulated per-voice delay time/feedback, comb time (fractional-sample read — comb is now in TUNE as a resonator), waveshaper amounts, formant morph and eq3 gains all ramp across the block; haas slews its delay (τ 5 ms) | ||
| 24 | diode — TB-303 diode ladder | four unbuffered one-pole stages loading each other (Pirkle/mystran closed-form ZDF solve, staggered G1..G4 poles), an input tanh clipper on the solved loop, a ~6 Hz AC-coupling HPF in the feedback (the 303 series cap), self-osc at k≈17 pre-warped (DIODE_TUNE) so the resonant peak tracks the nominal cutoff, drive-gated 4× oversample (engine/src/voice/filters.rs::diode_tick) | ||
| 25 | korg35 — MS-20 Sallen-Key | TPT LP+HP one-poles in a positive-feedback loop, K 0..2, a fixed-pivot feedback self-limiter (Kₑ = K·tanh(K·yₚ)/(K·yₚ)) → bounded self-osc near K≈2; "lp" 12 dB/oct, "hp" the 6 dB/oct hybrid; drive-gated 4× oversample (engine/src/voice/filters.rs::korg35_tick) | ||
| 26 | bbd amount "i"|"ii"|"i+ii" — Juno chorus modes | the mode-set bbd runs the Juno recipe (engine/src/voice/modfx.rs::bbd_juno_sample): a triangle LFO on the clock period (square-wave pitch deviation, delay ~1.5–5.5 ms), no compander, two cascaded ~8 kHz LPs + a ~10 Hz HP, a seeded-noise hiss floor scaled by wet depth, and a stereo output (right LFO inverted in i/ii, in-phase in i+ii; measured L/R correlation ~0.05). The generic mode-less bbd is unchanged | ||
| 27 | adsr … punch — analog attack overshoot | the optional trailing punch (0..1) aims the RC attack past the target (overshoot 1 + punch·0.3, output clamped at 1) so the front edge is steeper (dsp::adsr_value); punch = 0 is bit-identical to the plain RC attack | ||
| 28 | Through-zero FM (signed oscillator frequency) | an fm modulator deep enough to drive Osc/Pulse freq below 0 runs the phase accumulator backwards through the crossing (p -= p.floor() wraps both directions; the PolyBLEP step uses |dt| so it stays correct under reverse motion) — the TZFM clang. Positive-freq renders bit-identical (engine/src/voice/eval.rs Osc/Pulse arms) | ||
| 29 | Tape hysteresis (|> tape feedback-path magnetization) | the tape echo's feedback saturation is a one-state hysteretic shaper (TapeHysteresis, engine/src/effect.rs, analog-fidelity 6.6): a magnetization state relaxes toward tanh(k·x)/k, the relaxation widening + turning asymmetric with the drive direction as level rises (genuine memory — measured loop area at moderate drive, collapsing to ≈memoryless at low level), a trailing DC blocker, driven-alias floor ≈−54 dB | ||
| 30 | Spring reverb (|> spring tension decay [mix]) | a phenomenological spring tank (engine/src/spring.rs, analog-fidelity 6.7): two slightly-detuned tanks (L 100 / R 88 first-order allpasses) in feedback delay loops + in-loop LP — the descending dispersion chirp (HF-faster, measured ≈38 ms spread) with a decorrelated stereo field | ||
| 31 | as3340 "tri"|"saw"|"pulse" freq [pw] [character] — AS3340/CEM3340 circuit model | a full tri-core VCO model (engine/src/as3340.rs, analog-fidelity addendum): direction-switched triangle integration, a derived saw with the mid-ramp tri-core glitch/spike, a comparator pulse with hysteresis + PWM latch, causal 3-tap four-point cubic-B-spline BLEP/BLAMP (existing 1-sample alignment retained; 2-point high-rate fallback; clean ~2 kHz alias floors saw −37.7 / pulse −42.3 / triangle −64.2 dB), an analog expo converter (scale/curve trim error + HF flattening) driven at block rate, and per-sample drift/wobble/jitter. character (0..1) scales the imperfections; the per-voice chip identity + noise seed are drawn from rng::draw (deterministic, replay bit-identical) | ||
| 32 | BBD circuit layer (circuit-fidelity Stage B) | all native BBD paths integrate the CLOCK for their tap positions (voice/modfx.rs::bbd_clock_step — pitch shift ∝ clock derivative, transit-time asymmetry); generic bbd + bbdDelay run an NE570-style syllabic compander (ne570_compress/expand, τ=20 ms, feedback-sensed comp / feedforward expand); bbdDelay filters track clock/3 (MN3005-class N=4096) and leak a clock tone when the clock is in-band (−54 dB source × the reconstruction bank's | H(f_clk) | ≈ −38 dB — the hardware's leak passes through the output filter); BbdChorus hiss is coordinate-seeded (module seed + track) |
| 33 | Tape circuit pass (circuit-fidelity Stage C) | TapeLine runs the physical chain: record-amp tanh → true Jiles-Atherton magnetization (JaHysteresis: trapezoidal + fixed-cap Newton, f64 state, 2× + halfband, build-time unity calibration, engine-side bias) → 30 Hz coupling-cap HP → tape; playback gap-loss LP + 60 Hz/+2.5 dB head bump on the wet tap; wow & flutter is coordinate-seeded capstan+pinch (shared L/R) + independent per-channel 40 Hz cubic-interpolated scrape motion | ||
| 34 | vca control [character] — modeled gain cell (circuit-fidelity Stage D) | the native Vca arm (voice/eval.rs, §5.3) renders input · control at character = 0 and, as character rises (blend min(1, character·2.5)), an OTA/Blackmer cell: tanh at a shrinking headroom (6→1, = 4.0 at c=0.4), seeded control feedthrough (the high-passed control dg/dt, at a per-voice "vca-feed" draw over −50…−70 dBFS — fast envelopes thump, slow ones don't), and a gain-tracking noise floor (white·character·DEPTH·(0.25+0.75·g)). Stereo-aware; deterministic (coordinate-seeded) | ||
| 35 | svf … [drive] [character] — OTA character mode (circuit-fidelity Stage I) | the native Svf arm (voice/filters.rs::svf_tick_ota, §5.2) runs the linear ZDF SVF at character = 0 and, at character > 0, the SEM/Prophet OTA cell: an input-attenuator OTA (fast_tanh(x/s_in)·s_in, s_in = 0.7/c — distorts earlier/creamier, odd/H3-dominant), a resonance-path OTA on the bandpass integrator (s = 0.3/c) that bounds resonance to a clean on-tune sine, resonance loss + a self-osc bias folded into k (k·(1+c·0.30·k) − c·0.02), and a seeded LM13700 noise floor (white·c·0.0012 at the OTA input, so it rides the cell); OTA engages 2× oversampling (alias floor −79 dB lp / −66 dB mode @ c=1/drive=1, clears the −60 budget). character = 0 is bit-identical to the shipped SVF | ||
| 36 | Band-limited, Nyquist-clamped LFOs (2026-08-04, DSP audit E8) | CompiledNode::Lfo renders lfoSine/lfoSaw/lfoSquare/lfoTri through dsp::osc_sample_bl — the same polyBLEP/BLAMP kernels the audio oscillators use — and clamps the rate at 0.45·sr like Osc/Pulse/FM (audit B6). The shapes accept any Hz or signal rate, so an audio-rate AM/ring-mod LFO is a supported idiom; it used to alias like a naive oscillator (lfoSaw −29.1 dB at 50 Hz, −12.7 at 2 kHz) and a rate past Nyquist silently WRAPPED (30 kHz played at 18 kHz). Now −54.9 / −36.5 dB, +21.7…+26.5 dB at every rate. lfoSine is bit-identical, and the residual is zero away from a corner, so ordinary LFO rates are untouched but for the sample straddling the wrap | ||
| 37 | Plate diffuses the input PER CHANNEL (2026-08-06, DSP audit R1) | plate.rs runs one copy of Dattorro's input chain (bandwidth one-pole + four series diffusers) per channel and feeds each tank half from one of them, figure-8 crossfeed unchanged — so a stereo image survives into the tank, as it already did in the FDN (#8). It used to mono-sum: measured wet level −5.9 dB at interchannel correlation ρ=+1 but −9.0 at ρ=0 (ordinary wide stereo), −16.0 at ρ=−0.8 and exactly zero at ρ=−1, and hard-left/hard-right sources produced bit-identical reverb. Now −5.92 dB at every ρ; a mono source renders BIT-IDENTICAL to the old path (both chains are the same filter), so only stereo input behaviour moved. The L chain drives the r branch because Dattorro's output accumulators cross — that is what puts a hard-left source's reverb on the left (+3.0 dB same-side) |
Pre-existing divergences (documented before this policy)#
- Shred/one-shot default-bus voices: native routes them through master+limiter; the browser sends them to
ctx.destinationpast both. - Glide of
pitch: native glides every use ofpitch(a keytracked filter follows the slide); the browser glides only the freq port. - Effect tails across recompile: converged (2026-07-10). Both backends now preserve an unchanged chain's state across a recompile — the browser via the effects-JSON rebuild key, native via whole-chain reuse in the hot-swap (
FxReuse, Hot Swap §4.5). Chains binding sample-bank buffers (convolve/spectralMorph/sampledgranularize) still rebuild on native so late-loading samples and re-baked bounces re-resolve — and every rebuild (both backends) crossfades out over 80 ms instead of cutting the tail. - PingPong/Delay channel topology: small documented differences in down-mix points (see
effect.rscomments).
Native gaps (browser-only features — the reverse direction)#
Both are closed. Input (live mic/line) is a real CompiledNode::Input fed by the engine's LiveInput hub (Live audio input), and warp source-BPM lock landed 2026-07-24 (SampleBank::bpm, set by set_sample_bpm; the Sample/Stretch path warps by project_bpm / source_bpm).
Workflow#
There is nothing to port. A native DSP change belongs in the feature's own docs/reference/<feature>.md, with ARCHITECTURE.md updated when the system-level behavior changed. This page is closed to new rows.