Sculpt a sound's frequency balance, from a gentle darkening to a resonant sweep, and choose a filter character for the job.
Hear it and change it#
A low-pass filter lets low frequencies through and reduces higher ones. Its cutoff sets where that reduction begins. Raising the cutoff usually makes a harmonically rich sound brighter. Resonance emphasizes frequencies near the cutoff, giving a sweep a more pronounced vowel-like edge.
Listen for: the same saw-wave phrase becoming darker and brighter on alternate bars. The angle brackets choose one cutoff value per cycle.
tempo = 120bpm
timingVersion = 2
play "saw" "c3 eb3 g3 bb3"
|> cutoff "<500 2400>"Hz
|> resonance 2
|> release 100ms
Make it yours: try "<300 4500>"Hz for a larger contrast. A filter cannot reveal high overtones that a pure sine did not contain; use the saw for this comparison. Explore the different colors in Filters.
Reusable filter calls#
lpf, hpf, bpf, svf, svfMorph, ladder, diode, korg35 and formant keep their argument contracts through aliases, functions, imports, records and lists. Named options and omitted trailing defaults use the same binding rules. A partial call keeps its remaining required ports; supplying all required ports produces audio, rather than a function waiting for optional knobs.
Cutoff uses Hz; plain numbers and unsuffixed patterns adopt that port's unit (a bare cutoff under 20 Hz warns — lpf 2 1 reads 2 Hz, and the compiler asks whether 2kHz was meant). Q accepts Q or dimensionless numbers. Resonance accepts dimensionless numbers and refuses Q; drive, character and morph require dimensionless numeric values. These ports retain their existing live DSP readers and clamps. A pattern mixing Hz and time cannot lose its units and be reinterpreted as a cutoff.
The two resonance scales are not interchangeable. lpf/hpf/bpf/svf/ svfMorph take a real quality factor (0.7Q is near Butterworth). ladder/diode/korg35 read resonance as a Normalized 0..1 position and clamp it, so a Q there is refused at check time — ladder c (const 2Q) d would pin the filter at self-oscillation, and 0.7Q would be nearly self-oscillating: "ladder's resonance reads Normalized, not Q — it is a 0..1 amount (near 1 self-oscillates), not a quality factor". A normalized macro still drives a Q port directly. The compiler's ResonanceUnit warning names a plain value always above 1 reaching one of the three; write 0.1–0.6 there instead.
Mode and vowel selectors can be prepared string values, including function results. svf accepts lp/lowpass, bp/bandpass, hp/highpass and notch/br/bandreject, with optional 12 or 24 suffixes. korg35 accepts lp/lowpass and hp/highpass. These spellings are case-insensitive. Invalid literal selectors fail checking; selectors produced by ordinary functions are also validated when the graph is prepared.
Numeric graph constants must be finite in engine precision, across all DSP families. A prepared SVF character setting is the exception to the precision bound: any finite value clamps to 0–1 before conversion. Live coefficients retain their existing DSP clamps. Native and JSON execution plans validate the same port units and bipolar audio output, require SVF poles to be 2 or 4, and reject unknown biquad kinds and empty/unknown vowel paths. Wire biquad kinds and vowel tokens use their canonical lowercase spelling; source selectors accept the aliases above.
What it is#
Three resonant/character filters that live next to the cheap biquad lpf/hpf/bpf, for the sound-design moves the biquad can't make: a filter swept fast without zipper noise, a resonance pushed into clean self-oscillation, the gooey 24 dB/oct Moog character, and vowel formants.
All three are cores in the one Rust engine (engine/src/voice/filters.rs), which renders on the desktop and — through engine-wasm in an AudioWorklet — in the browser, so there is a single sound. Because the cores are zero-delay-feedback (TPT / trapezoidal) designs, their cutoff is stable under audio-rate modulation: no per-block coefficient recompute, so a filter envelope or an LFO on the cutoff doesn't zipper, and high resonance doesn't blow up.
svf/svfMorph— a Cytomic / Andrew-Simper state-variable filter.ladder— a 4-pole Moog-style ladder. The native engine solves the resonance loop zero-delay (linearized TPT solve + full1+kpassband compensation), so the resonant peak / self-oscillation stays on the cutoff all the way up the spectrum; withdrive > 0it runs the full nonlinear four-transistor-stage model on a non-iterative port-Hamiltonian solve (below).formant— a parallel-bandpass vowel filter.
The existing lpf/hpf/bpf stay as the cheap biquad path — a one-pole sweep that never needs to self-oscillate shouldn't pay for the loop. Reach for svf when you want it to resonate.
Drive (or near-self-osc resonance) oversamples. When drive > 0 — or, for ladder/diode/korg35, when normalized resonance reaches 0.8 (their loop nonlinearities are always in the path and limit-cycle near self-oscillation; the linear svf stays exempt at d = 0) — the filter cores run their loop oversampled (and warp the cutoff for it), so the feedback tanh's harmonics don't fold back as aliasing on hot or fast-swept signals — the saturation stays clean. The factor is per-model, selected by measurement (Stage G, § Oversampling & latency): ladder, diode and korg35 at 4×, svf at 2×. A clean filter (drive = 0) stays single-rate and bit-identical, paying nothing.
quality = "eco" halves the driven filters' cost. ladder, diode and korg35 take a quality selector — "standard" (default, the shipped 4×) or "eco" (a 2× loop): saw 55Hz |> ladder 600Hz 0.7 0.8 { quality = "eco" }. It only changes the oversampled path, so a clean low-resonance filter renders identically either way. Reach for it when a song's DspLoad warning names stacked driven filters; see § Eco quality.
The switch is sticky per note (os_latched) — once a note oversamples it keeps oversampling, because flipping re-warps g and moves the decimator's group delay. A moving drive or resonance oversamples from the note's first block: when either input is anything but a literal, live or pitch constant (an LFO, envelope, macro or pattern), the note latches at onset, because a mid-note flip still clicked through the re-seat (measured +53 dB ladder / +28 dB diode HF transient at the crossing; engine/tests/filter_os_onset.rs). A constant setting keeps the value-based latch and renders as before; the price is the oversampling cost on modulated notes that never reach the trigger. A flip can still happen when a constant is EDITED live past it; all four latched models — ladder, diode, korg35, svf — re-seat the decimators and the upsampler seed across that seam, and ladder additionally rebuilds its g-dependent states — see § the latch re-seat.
Ladder nonlinear solve — port-Hamiltonian, non-iterative (native)#
The driven ladder (drive > 0) integrates the real four-transistor-stage ladder ODE with the discrete-gradient scheme of DAFx-21 paper 37 (Danish, Bilbao & Ducceschi, "Applications of Port Hamiltonian Methods to Non-Iterative Stable Simulations of the Korg35 and Moog 4-pole VCF") — ladder_tick_ph, engine/src/voice/filters.rs. The model is their eq. 32,
(1/ω)·ẋ = [ −tanh(x₁) + tanh(u − α⁴x₄), −tanh(x₂) + tanh(x₁),
−tanh(x₃) + tanh(x₂), −tanh(x₄) + tanh(x₃) ], α⁴ = 4·res
and three moves make it solvable in one step without iteration:
- the feedback identity
tanh(u − z) = −tanh z + γ(z,u)splits the input out of the feedback tanh, leaving a zero-input system plus a source term; - a diagonal rescale
w = diag{1,d,d²,d³}·xwithd = max(1,α)and Bilbao's Lyapunov functionH(w) = Σ Hᵢ(wᵢ), whoseS(w)has a negative -semidefinite symmetric part over the whole physical range0 ≤ res/4 ≤ 1— the zero-input stability certificate; - the state change
z = √2·sgn(w)·√H(w), after which the storage function is the plain quadratic½zᵀzand the discrete-gradient (midpoint) rule becomes a linear solveδz = 2g·(I − g·A(z))⁻¹·(A(z)·z + G(z,u)).
A(z) is lower-bidiagonal plus one feedback corner, so the inverse is a forward substitution and a single division — constant cost per sample, no Newton iterations, no tolerance, no variable load. g = tan(π·fc/fs) replaces the paper's k·ω/2: the bilinear prewarp, which puts the poles exactly where the TPT ladder puts them, and the passivity proof holds for any step size.
Because z is defined through (α, d), a resonance move has to re-encode the state, not merely re-read it — otherwise the four capacitor voltages jump when the knob moves. Stages 1–3 rescale exactly by (d₁/d₀)ⁱ⁻¹ (no transcendental needed); the 4th is re-encoded from the physical x₄. Pinned by ph_resonance_move_re_encodes_the_state; without it a full resonance ramp nulls only −62 dB against the correct render.
Why it replaced the Mystran fixed pivot (2026-08 DSP audit A7). The old solve linearized each pole's tanh around last sample's stage state (t_i = tanh(z_i)/z_i). Zavalishin p.202 flags the fixed pivot as worst-case at high cutoff × high feedback, and that is exactly what it did: at resonance = 1, drive = 1 a full-scale impulse made its ring grow into the ±4 makeup clamp at a badly mistuned frequency — a diverging solver, not a filter —
| fc | fixed pivot (old) | port-Hamiltonian (now) |
|---|---|---|
| 500 Hz | −100.6 dB THD, −0.1 ct, amp 0.085 | −99.6 dB THD, −0.2 ct, amp 0.050 |
| 1 kHz | −87.3 dB THD, −0.6 ct, amp 0.19 | −100.3 dB THD, −0.3 ct, amp 0.051 |
| 2 kHz | −22.1 dB THD, −299 ct, amp 4.05 | −106.6 dB THD, −0.1 ct, amp 0.039 |
| 4 kHz | −12.6 dB THD, −604 ct, amp 4.17 | −112.4 dB THD, −0.1 ct, amp 0.028 |
| 8 kHz | −9.1 dB THD, −1178 ct, amp 5.32 | −120.4 dB THD, −0.0 ct, amp 0.018 |
Musically: max resonance + drive used to scream at the wrong pitch because the solver was diverging. It now rings on the cutoff and decays — and it decays at the same rate the drive = 0 reference path does, which is the real check that the new number is right, not merely quieter. Per-second peaks of an impulse-excited ring at res = 1:
| 1 s | 2 s | 4 s | 8 s | |
|---|---|---|---|---|
fc 500 Hz, drive = 0 (reference, unchanged) | 0.045 | 0.037 | 0.029 | 0.022 |
| fc 500 Hz, driven — port-Hamiltonian | 0.064 | 0.046 | 0.032 | 0.023 |
| fc 500 Hz, driven — fixed pivot (old) | 0.085 | 0.085 | 0.086 | 0.087 |
fc 4 kHz, drive = 0 (reference, unchanged) | 0.333 | 0.024 | 0.014 | 0.009 |
| fc 4 kHz, driven — port-Hamiltonian | 0.504 | 0.023 | 0.013 | 0.009 |
| fc 4 kHz, driven — fixed pivot (old) | 4.22 | 4.17 | 4.17 | 4.17 |
res = 1 is α⁴ = 4, i.e. r = 1 — the exact stability boundary of the physical model (the paper's limit cycle only exists for r > 1), so a slow decay is the correct answer and always was: the drive = 0 path has always decayed there. The fixed pivot's sustained/railed ring was the solver adding energy.
What else moved. The drive law (fast_tanh(x·(1 + 3·drive)) into the summing node) and the ×(1 + k) passband makeup are carried over verbatim, and because the ladder core is DC-linear (every stage tanh cancels at equilibrium, so x₄ = u/(1+α⁴)) the DC transfer is the old drive curve to the last ulp — ph_dc_transfer_is_exactly_the_drive_curve settles a DC input and compares against fast_tanh(x·(1+3·drive)) directly: 9.1e-16 relative (4 ulp) at resonance = 0, ≤1.4e-13 at every other resonance (that residual is the conditioning of σ's own 1 − e^(−ξ²) at the equilibrium, amplified by the loop gain — not a bias). What did change is the AC behaviour, and it changed in the intended direction: the model has a real tanh at all four poles plus the input differential pair, where the secant only had a state-pivoted gain, so hot signals with content at or above the cutoff compress more. Nulled against the retired solve, 220 Hz sine into fc = 2 kHz:
| input level | worst null (res 0) | worst null (res ≥ 0.3) | worst RMS delta |
|---|---|---|---|
| −34 dBFS | −71 dB | −71 dB | 0.00 dB |
| −20 dBFS | −45 dB | −56 dB | 0.03 dB |
| −6 dBFS | −18 dB | −29 dB | 0.30 dB |
| 0 dBFS | −15 dB | −15 dB | 0.56 dB |
On harmonically rich hot material the compression is larger: the ladder_driven golden (a saw through ladder 900Hz 0.85 0.6) drops −2.75 dB peak / −2.17 dB RMS, and analog_stack (a supersaw through ladder 700Hz 0.4 0.3) −1.29 / −1.52 dB. Both goldens were re-blessed for this; a driven ladder is now a little quieter and rounder than it was. (The source-average fix below re-blessed the same two a second time, for +0.0017 / −0.0001 dB and −0.0015 / +0.0003 dB — the tilt it removes lives above 5 kHz at the shipped 4×, where a lowpassed saw has nearly nothing.)
The small-signal response is the TPT ladder's, at every rate. The paper's eq. 53a is trapezoidal in the state but rectangular in the source, so as written the input path lacks the (1+z⁻¹)/2 factor an exact bilinear transform carries. That is not a wash — it multiplies the whole transfer by 1/cos(π f/f_s), a broadband HF shelf with a matching half-sample phase lead. At the shipped 4× (f_s_eff = 192 kHz) that is only +0.47 dB at 20 kHz, but the node also runs single-rate — inside a feedback region (a causal loop can't oversample) and on the first block when drive leaves zero mid-block — where the same tilt measures +0.02 dB at 1 k, +0.47 at 5 k, +2.01 at 10 k, +5.11 at 15 k, +11.74 at 20 k. So the source term is averaged, u_eff[n] = (u[n]+u[n−1])/2, at the solver's own rate: (1+z⁻¹)/2 has magnitude exactly cos(π f/f_s) and phase exactly −ω/2, cancelling shelf and lead identically wherever the solver runs. ph_small_signal_response_matches_the_tpt_ladder drives the core with a tiny sine at 1× and correlates the steady state against the analytic ZDF TPT ladder (1+k)·H₁⁴/(1 + k·H₁⁴): worst deviation 0.0012 dB / 0.004° over fc ∈ {300, 2000, 12 000} Hz × k ∈ {0, 1.2, 3.2} (normalized resonance 0, 0.3, 0.8) × f ≤ 20 kHz — and that 0.0012 dB is the PH_A4_FLOOR residual below, not the source term. Resonant peak height and frequency sit inside that same figure, which fixes a claim that used to be narrower than it read: "resonant peak unchanged to 0.01 dB" held only where the tilt was under 0.01 dB, i.e. below ≈2.9 kHz at the 4× rate and below ≈730 Hz at 1×. It now holds across the band, at every rate. DC is untouched by the average (u[n] = u[n−1] ⇒ identity), so the drive curve above is unaffected. Zero-input (self-oscillation, the A7 regime) never had the tilt: with no source term the step is exactly trapezoidal either way, and the self-osc THD/tuning table above is unchanged to 0.1 dB.
One deliberate deviation remains, measured: the resonance floor PH_A4_FLOOR = 1e-4 on α⁴ (−80 dB of feedback) keeps the 4th storage function H₄ = (d²/α⁴)·ln cosh(α⁴w₄/d³) from degenerating as α → 0. The ×(1 + k) makeup reads the same floored α⁴ the core solved with, so resonance = 0 comes out on the drive curve exactly (using the true res there left it at 0.99990·fast_tanh(…), −0.00087 dB). What is left is the floor's own AC contribution: +0.00087 dB of stopband lift at resonance = 0, which is the 0.0012 dB in the response figure above.
drive = 0 is untouched and still bit-identical to the linear ZDF fast path (single fast_tanh on the solved loop input) — pinned by ladder_drive0_is_bit_identical_to_linear_reference. The two paths keep their state in different spaces (TPT trapezoid states vs. the transformed z), and the conversion between them (ph_seed_from_tpt / ph_flush_to_tpt) is exact in the linear limit — the trapezoid relation sᵢ = (1−g)xᵢ + g·xᵢ₋₁ it inverts is the linear TPT structure, so a drive automation crossing zero at a hot level converts the capacitor voltages faithfully but not the saturation state that produced them. ph_tpt_handoff_is_continuous pins the seam in the regime where the claim holds (a −60 dBFS source, where a botched handoff shows as a step orders of magnitude above the signal's own slope).
The oversampling latch re-seats. os_latched is sticky per note and can go false → true mid-note — a drive = 0 patch sweeping resonance up through FILTER_OS_RES does exactly that — at which point g is re-warped against 4 × sr. Three things are stale across that seam: the g-dependent TPT trapezoid states, the 4× anti-imaging upsampler's allpass state, and the halfband decimators (a cold allpass cascade passes ~5 % of its first sample). All three are re-seated now (ladder_reseat_for_rate) — the port-Hamiltonian work is what makes the first one possible, since it keeps the g-free capacitor voltages in plin. Measured on a 220 Hz sine through ladder fc (0.6→0.95) 0, max |Δx| at the flip against the signal's own p99 slope:
| fc | before | after |
|---|---|---|
| 300 Hz | 1.0225 (16.8× p99) | 0.0608 (1.00×) |
| 1.2 kHz | 0.9640 (32.5× p99) | 0.0297 (1.00×) |
| 5 kHz | 0.8764 (30.4× p99) | 0.0290 (1.01×) |
i.e. the largest step in the render is now the signal's own slope, and it is no longer at the flip. Pinned by ladder_oversampling_latch_flip_does_not_click.
diode and korg35 share the latch, and share the fix. The last two of the three stale items need nothing model-specific — only the channel's own last output — so they are hoisted into os_reseat_decimators and called from those two arms as well (2026-08-03 audit F1). The earlier note here claimed the missing plin was what left them clicking; it was not. The click was the cold decimator cascade plus the stale xprev2, and on the same recipe (drive = 0, resonance ramped 0.6→0.95, the seed level being Korg35Chan::yprev and DiodeChan::ylast):
| model | fc | before | after |
|---|---|---|---|
korg35 | 300 Hz | 1.0001 (19.7× p99) | 0.0515 (1.01×) |
korg35 | 1.2 kHz | 0.9066 (11.7× p99) | 0.0815 (1.05×) |
korg35 | 5 kHz | 0.8451 (22.3× p99) | 0.0419 (1.11×) |
diode | 300 Hz | 0.0349 (3.63× p99) | 0.0100 (1.04×) |
diode | 1.2 kHz | 0.2350 (7.19× p99) | 0.0331 (1.01×) |
diode | 5 kHz | 0.1287 (1.01× p99) | 0.1287 (1.01×) |
The korg35 flip was a full-scale discontinuity in a signal whose own p99 slope is 0.05, and it landed exactly on the flip sample every time; after the re-seat the maximum moves off it in all six cases. Pinned by {diode,korg35}_oversampling_latch_flip_does_not_click.
What stays ladder-only is the first item, the g-dependent state rebuild: neither model keeps a plin equivalent and neither can cheaply recover one (the diode cascade is unbuffered, so the capacitor voltages don't factor out of its solve; the korg35's three states are a Sallen-Key loop, not a chain), and recovering them from the trapezoid states means dividing by 1 − g. That remains a documented sub-1 % residual on those two models — the ≤1.11× p99 above is what it costs — not an audible seam.
Cost — the driven path is 2× the fixed pivot. 4× oversampled, one channel, per host sample: 363 ns vs 181 ns (2.01×); the extra is four exp, one ln1p, two tanh and ~8 divisions per inner tick. The three stage-σ's are independent (they overlap), so the cost is the serial chain exp → sqrt → ln1p → div → tanh → solve, not the transcendental count — removing three of the four exps entirely buys only 3 %. On cargo bench -p volta-engine --bench dsp (M-series, release, ms per second of rendered audio): render/filter_static 19.8 → 35.7, render/filter_sweep 21.2 → 36.8 (both scenes are nothing but a driven ladder, 8-note polyphony; the source average added +0.76 % / +0.48 % on top — 35.65 → 35.92 and 36.69 → 36.87 ms, measured back-to-back — which is one extra add and multiply per inner tick, the fast_tanh being cached, not recomputed), render/dense_mix 29.2 → 40.2 — so the worst case is still under 4 % of realtime. The 2026-08-11 post-anti-imaging release benchmark reads 34.9 ms per second for render/filter_static, confirming the extra nine allpass sections per host sample keep the same realtime envelope. The drive = 0 fast path is untouched at 34 ns/sample — it is the default, and it is what every non-driven patch in the corpus pays. Resonance- and cutoff-derived constants are cached in the channel, so the block-constant case pays its two sqrts per block rather than per sample.
Fast driven core and multirate 4× (evolve volta_ladder_fit #40). The driven solve is the same scheme with its expensive parts replaced (engine/src/voice/ladder_ph.rs): σ, φ, e^(−ξ²) and τ come from shared cubic-Hermite tables (h = 1/32, built once at voice preparation; each stage reuses last tick's segment while the state stays within one segment of it, exact closed forms past the tables' range), the step is solved with one reciprocal, and tanh of the averaged drive output is a GPU-fitted minimax rational (max error 2.5e-8 on |u| ≤ 1). Fused multiply-adds are used on aarch64 only (plain multiply-add elsewhere; wasm32's mul_add is a libm call), so aarch64 and x86/wasm renders differ in the last bits. In the 4× wrapper the driven core runs at 2× below 0.04475·sr and at 1× below 0.020625·sr (10 % hysteresis, decided per sample, so callback splits cannot change it); the drive stage stays at 4× and is decimated ahead of the core by the same halfbands the 4× path applies after it, so the total delay is the same in every mode, and an output decimator that sat idle restarts at the DC state of the core's last output. The 1×→2× input halfband is now a 12-coefficient elliptic design (transition band 0.0575) for both the driven and the latched clean path: ~2 host samples more latency (driven small-signal group delay ≈ 4.6 samples, still not compensated), images −53…−83 dBc. Linear (drive = 0) ticks are unchanged and always run at 4× when latched; drive leaving zero seeds the drive-stage decimators at the input's DC level.
Measured (offline, not listened to): the fast core stays within 3e-7 of the exact closed forms on a driven saw with moving cutoff/resonance (ladder_fast_core_matches_exact_reference); the 2×/1× modes keep harmonics within 0.6 dB of the core pinned at 4× (ladder_multirate_core_tracks_the_4x_core) and mode switches in a cutoff sweep add no step (…_mode_switch_does_not_click). measure_os_factors: hot 3.5 kHz sine at 4× −69.7 → −96.8 dB, broadband saw at 4× −66.9 → −79.1 dB. On the evolve harness (32× reference): alias mean 90.5 → 96.0 dB, worst 79.5 → 89.4 dB, fidelity 0.48 → 0.52 dB; bass probes on the 1× core lose ~12 dB of alias suppression (−109 → −97 dB) but stay far inside the budget. Engine cost, whole engine with one driven-ladder saw voice (ns/frame, best of 3 rounds alternating old/new builds on a loaded machine; the bare saw voice is ~48): 1200 Hz/0.5/0.5 (2× core) 286 → 103, 300 Hz/0.9/1.0 (1×) 287 → 80, 5 kHz/0.3/0.8 (4×) 287 → 149, enveloped sweep 279 → 132, clean latched (drive = 0, res 0.9) 138 → 136. The figures in the paragraph above are the pre-#40 core.
The 4× oversampling factor was re-measured, not inherited. On the §4.3 isolated-nonlinearity probe (hot 3.5 kHz sine) the solve reads −61.6 dB at 2× and −69.7 dB at 4×. Before the anti-imaging input fix, the same 4× core read −64.2 dB; the two-stage halfband boundary buys another 5.5 dB there and 14.7 dB on the broadband saw. LADDER_OS_FACTOR stays at 4: the 2× midpoint path only just clears the isolated budget and misses the broadband budget at −52.7 dB, while the shipped path reaches −66.9 dB. At 4× the ~2 kHz budget probe reads −97.0 dB, 37 dB inside the gate.
The solve's algebra is pinned against the paper itself in engine/src/voice/filters.rs unit tests: the closed form vs a dense 4×4 elimination, A₄₄ vs their eq. 51a/51b, the state change round-trip — all four stages including the α-scaled 4th, to 1e-13 — the whole step vs RK4 on eq. 32, the resonance re-encode, the DC drive curve, the small-signal response vs the analytic TPT ladder, and the discrete energy balance (eq. 8) never growing at zero input over a grid that reaches fc = 23.5 kHz, res = 4 and 20× into saturation. End-to-end figures live in engine/tests/dsp_validation.rs.
The scalar helpers the transform is built from (ph_sigma, ph_sigma_phi, ph_tau, ph_lncosh) each carry a small-argument series and a closed form, and where the branch sits is a measured trade, not a guess — the series wins near 0 where the closed form cancels, the closed form wins once the series' truncated term overtakes it. Two crossovers were on the wrong side of that and were moved (2026-08 A7 review): ph_lncosh from |v| < 0.1, where its 4-term truncation is 4.4e-11 relative against ~1.2e-13 for the closed form, to |v| < 0.052 where they cross (worst case ~1.3e-13 either side); and ph_tau — where the closed form tanh(v)/v has no cancellation at all, so the series is needed only to dodge the literal 0/0 — from |v| < 0.1, whose 2.2e-10 truncation showed up in the DC transfer for any settled x₄ just under 0.1, down to |v| < 1e-4.
Precision — f64 recursive filter cores (native, circuit-fidelity Stage H). As of Stage H, all recursive filter-core state and solver math is f64, per the circuit-modeling guide §8 mandate ("f64 for all recursive state and solver math; f32 acceptable for bulk non-recursive audio buffers"). The wasm build compiles the same code — no cfg-gated precision difference — so browser-vs-native diffs stay explainable.
What went f64:
- The four ZDF filter channels (
SvfChan,LadderChan,DiodeChan,Korg35Chaninengine/src/voice/filters.rs): every integrator/stage state (ic1/ic2,z1..z4,s1..s3, the diode feedback-HP memory), the oversampler input seedsxprev/xprev2, and the per-sample tick math. Coefficients (g = tan(π·fc/fs),a1..a3,big_g,k,hp_r) are now derived in f64 from the still-f32param buffers; the inputxis widened once at the tick boundary and the output narrowed once at the store. The in-loop saturator is afast_tanh_f64twin — the same Padé rational as the f32fast_tanh, notf64::tanh(that would change the curve shape and the sound), so only the arithmetic precision differs, never the transfer curve. - The in-loop half-band decimators (
HalfbandDec/HalfbandDec2and thehb_apallpass, plus theHB_A1..3coefficients): they sit inside the filter feedback loops, so their allpass state is f64 too. - The voice-path biquad users: the
Filternode (lpf/hpf/bpf) and theformantbank's parallel-SVF integrators. TheFilternode (engine/src/biquad_matched.rs, evolve volta_biquad #32) is Vicanek's matched design — impulse-invariant poles, numerator matched at DC, Nyquist and the cutoff, so it follows the analog prototype into the top octave (mean error 0.20 dB vs 3.1 dB for the bilinear RBJ section it replaced; anlpf 7.5kHzwas 9.7 dB off at 18 kHz) — realised as a TPT SVF plus an output mix, with f64 integrator state. A static block runs it in state-space form eight samples per step; a moving cutoff designs per sample (polynomial sin/exp; the cutoff still slews at most one octave per sample) and stays bounded under audio-rate FM. Feedback regions tick the same filter one sample at a time. - The mixer
eqTrack/eqcascade runs f64 coefficients and f64 TDF2 state: its f32 state left a −75 dB noise floor on the master's 28 Hz highpass. - The drum bridged-T ring (
BridgedTinengine/src/drum.rs, the same SVF core family): its ring integrators, the amplitude followers that drive the intrinsic 808 pitch dip, and its tick math.
What deliberately stays f32 (non-recursive, or out of the filter-core precision scope, documented per the guide's §9 split rule):
- Bulk audio buffers — the
f32sample buffers themselves; f64 lives only in the recursive inner loop, converted at the boundary. - Track-side biquad users —
EQ3, thephaserallpass chain, thetapehead-bump, and thebinauralnotch keep thef32dsp::biquad_tick. These are shelving/notch/allpass colour stages, not resonant self-oscillating cores; the precision mandate targets the latter. (A bench control confirms the f32phaserpath is unchanged, ±noise, after the migration.) - Effect one-poles and envelope followers (bbd/tape damping, compander detectors), FDN/plate/spring internals, DC blockers, and the drum noise-shaping
Bandpass(hat/clap colour, not a resonant ring) — allf32. The tape'sJaHysteresiscore was already f64 (Stage C); it now feeds the shared f64HalfbandDecat the boundary and narrows back before its f32 DC blocker.
Why (self-osc stability + long-tail cleanliness, not tuning). The Analog Fidelity Stage-5 audit had already established that the high-cutoff self-osc tuning figures are inherent bilinear/topology behaviour, not precision — measured identical in f32 and f64 to the digit — so f64 buys nothing for tuning, and Stage H confirms no tuning regression: SVF real resonance stays exact (ring error −0.05 ct @ 5 kHz, −0.02 ct @ 10 kHz), the ladder self-oscillates on the cutoff to ±0.0–0.5 ct across 50 Hz–10 kHz, and korg35 (+138 ct @ 4 kHz) / diode (±21 ct) are unchanged topology behaviour. What f64 does buy is the two things the recursive precision mandate is for: long-tail cleanliness (a Q=50 SVF's decayed tail now reaches exact zero in f64 where the f32 tail held denormal dust — the old "−71 ct @ 5 kHz" figure was that artifact) and self-oscillation stability in the recursive solve. Retroactive re-bless: every golden clip that runs a filter drifted by precision only (peak/RMS ≤ 0.00 dB, hashes changed) and was re-blessed; non-filter clips are byte-identical.
Surface#
-- stable under audio-rate cutoff modulation; drive pushes it to sing
lead = saw (freq pitch)
|> svf "lp" (filtEnv * 4000Hz + 200Hz) (const 8) 0.4 -- src "mode" cutoff q [drive] [character]
-- character (0..1, native): the SEM/Prophet OTA cell — distorts earlier,
-- resonance loss, purer self-osc, seeded noise floor (0 = the shipped SVF)
ota = saw (freq pitch) |> svf "lp" (const 1200Hz) (const 6) 0.5 0.8
-- 24 dB/oct (the "24" suffix), notch, etc.
hollow = saw (freq pitch) |> svf "notch" cutoff (const 6)
steep = saw (freq pitch) |> svf "lp24" cutoff (const 10)
-- a continuous mode sweep LP→BP→HP→notch (a `macro` is a top-level binding)
morph = macro "morph" 0
morphing = saw (freq pitch) |> svfMorph morph cutoff (const 6)
-- 4-pole Moog ladder: self-oscillates near resonance 1, drive = gooey
bass = saw (freq pitch) |> ladder (env * 3000Hz + 150Hz) (const 0.85) 0.5
-- …with the real ladder's resonance passband loss instead of the full makeup
bare = saw (freq pitch) |> ladder (env * 3000Hz + 150Hz) (const 0.85) 0 { makeup = 0 }
-- TB-303 diode ladder: squelchy, gritty, staggered poles
acid = saw (freq pitch) |> diode (env * 2000Hz + 120Hz) (const 0.85) 0.4
-- MS-20 Korg-35 Sallen-Key: "lp" (12 dB/oct) screams at self-osc, "hp" is 6 dB/oct
scream = saw (freq pitch) |> korg35 "lp" (env * 3000Hz + 300Hz) (const 0.95) 0.3
-- vowel sweep across a formant path
vowel = macro "vowel" 0
vox = saw (freq pitch) |> formant "a→e→i→o→u" vowel
Signatures (all signal-first, so they pipe):
svf : Sig Audio Bipolar -> Str -> Sig _ Hz -> Sig _ Q -> [Normalized] -> [Normalized] -> Sig Audio Bipolar
svfMorph : Sig Audio Bipolar -> Sig _ Normalized -> Sig _ Hz -> Sig _ Q -> [Normalized] -> [Normalized] -> Sig Audio Bipolar
ladder : Sig Audio Bipolar -> Sig _ Hz -> Sig _ Normalized -> [Normalized] -> Sig Audio Bipolar
diode : Sig Audio Bipolar -> Sig _ Hz -> Sig _ Normalized -> [Normalized] -> Sig Audio Bipolar
korg35 : Sig Audio Bipolar -> Str -> Sig _ Hz -> Sig _ Normalized -> [Normalized] -> Sig Audio Bipolar
formant : Sig Audio Bipolar -> Str -> Sig _ Normalized -> Sig Audio Bipolar
cutoff, q, resonance, drive, mode, and morph are all control-rate AudioParams, so any modulator (LFO, envelope, macro, follower, pattern) drives them live — the whole point of moving off the biquad.
Filter signatures at a glance#
The four character filters differ in two independent ways: whether a mode string comes first, and which resonance unit the slot is nominally typed in. This table is the cheat-sheet (the recurring papercut behind production-gaps I2 — writing korg35 (cutoff) … with no mode gives a mode string diagnostic that now names the missing argument):
| filter | mode arg first? | resonance slot unit | drive |
|---|---|---|---|
svf | yes — "lp"/"hp"/"bp"/"notch" (+"24") | Q | optional |
svfMorph | a 0..1 morph signal (not a string) | Q | optional |
ladder | no | Normalized 0..1 | optional |
diode | no | Normalized 0..1 | optional |
korg35 | yes — "lp"/"hp" | Normalized 0..1 | optional |
formant | a vowel-path string ("a→e→o") | — (morph position) | — |
Resonance units, one direction. A Q slot accepts a Normalized value (svf … (const 0.8), a macro in a q port), which passes through as authored — there is no Q↔Normalized remap. The reverse is refused: the ladder/diode/korg35 resonance is declared Normalized, and a Q-tagged value there is a check-time error in every spelling (a literal, const 2Q, a name, a Q macro, a function argument), pointing at svf/svfMorph.
svf modes#
| string | output | string | output |
|---|---|---|---|
"lp" | lowpass | "hp" | highpass |
"bp" | bandpass | "notch" | band-reject |
Any mode may be suffixed 24 (e.g. "lp24") for a 24 dB/oct slope — a second SVF stage in series. The default is 12 dB/oct. q is a Q value (higher = more resonant, matching lpf/hpf/bpf); push it high with a little drive for self-oscillation.
svfMorph replaces the mode string with a 0..1 signal that sweeps the four outputs continuously (0 = LP, ⅓ = BP, ⅔ = HP, 1 = notch).
The literal "lp"/"bp"/"hp"/"lp24" modes run a matched design (engine/src/svf_matched.rs, evolve run volta_svf #81, character 0 only): impulse-invariant poles with the numerator matched to the analog prototype, realised as the same TPT SVF plus an output mix, so the response follows the analog curve into the top octave instead of cramping toward Nyquist (mean error 0.11 dB vs 4.7 dB for the bilinear design; an lp 9kHz is no longer 10 dB low at 18 kHz). Above 0.7·π the upper match point slides to Nyquist and an hp caps its Nyquist gain, so a cutoff near Nyquist never resonates past the analog peak. With drive > 0 the input passes a halfband interpolator and a first-order ADAA drive stage, and the in-loop limiter is rate-consistent (the driven sound no longer depends on the host rate); lightly driven, dark lowpasses (cutoff ≤ 0.09·fs, drive ≤ 0.1) stay single-rate. A cutoff or Q that moves within a block glides the design per sample (designs every 2–78 samples, by how fast the controls move and how sharply they bend); a moving drive or a latched 2× loop runs stretches of 4–16 samples, each latched at its first sample, gliding the filter sections between full designs every 16–60 samples. svfMorph, "notch", "hp24"/"bp24"/"notch24", the OTA character and feedback regions keep the bilinear TPT SVF below.
svf … [drive] [character] — OTA character mode (native, circuit-fidelity Stage I)#
svf (and svfMorph) take an optional 6th argument, character ∈ [0, 1] (a live control, default 0), that morphs the cell from the shipped linear ZDF SVF into the SEM/Prophet OTA state-variable filter — the Circuit Modeling §5.2 lineage. It models the LM13700/CA3080 OTA behaviors a ladder doesn't have: the signal is attenuated to the chip's ~20 mV internal scale, tanh-limited, then re-gained, so an OTA cell distorts earlier and creamier than a ladder at the same level; resonance is lost at high damping; self-oscillation is a purer sine; and the LM13700 noise floor rides the tiny internal signal (comparatively high, and filtered by the cell itself).
character = 0 is bit-identical to the shipped SVF (the null-to-ideal rule — existing songs and goldens do not move; the const folds to 0, is skipped in the IR, and svf_dispatch routes it to the untouched linear tick).
Live character reads each sample without resetting integrators or decimators. Oversampling engages at the actual sample requiring it and stays latched for that voice. The clean four-pole LP and OTA cascade retain their respective topologies; changing character through zero switches between them.
Topology (engine/src/voice/filters.rs::svf_tick_ota). Two OTA nonlinearities on the Cytomic ZDF core, each ota(v) = fast_tanh_f64(v/s)·s (the engine's Padé fast_tanh, not f64::tanh), with the scale shrinking as 1/c so both vanish continuously as c → 0⁺ (the c = 0 branch is for bit-exactness + cost, not a discontinuity):
- Input-attenuator OTA on the through-signal, ceiling
s_in = SVF_OTA_S_IN / c(S_IN = 0.70). This is the "attenuate → tanh → re-gain" front end: soft odd (H3-dominant) harmonics that grow with level — distorts earlier. - Resonance-path OTA on the bandpass-integrator state, ceiling
s = SVF_OTA_S_C1 / c(S_C1 = 0.30, the same node the shippeddrivetanh-limits). It bounds the resonance/self-oscillation to a clean, on-tune, tanh-limited sine and leaves the lowpass DC/passband level intact.
Resonance loss + self-oscillation are folded into the damping k = 1/Q by svf_ota_k: k_eff = k·(1 + c·K_LOSS·k) − c·SELFOSC_BIAS (K_LOSS = 0.30, SELFOSC_BIAS = 0.02). The k² loss term dominates at high damping (low Q, k ≳ 0.26 / Q ≲ 4) so those settings go audibly softer/lossier; the small −c·BIAS dominates only at the top of the Q range (k ≲ 0.02 / Q ≳ 50), tipping it into net gain so the cell genuinely self-oscillates (the linear SVF only rings and decays — its "self-osc tail" is denormal dust). Both stay inside the ZDF stability bound.
Seeded noise floor. white · c · NOISE_DEPTH (NOISE_DEPTH = 0.0012) is injected at the resonance-path OTA input, so it is shaped by the cell (rides the resonance). It reads the shared seeded white ring at the voice-local sample index (the vca/bbd pattern; right channel half a ring away), so the floor is deterministic and replays bit-identically. Measured: −50.7 dBFS on a silent input through a resonant q = 8 cell at c = 1, absent (−125 dBFS) at c = 0.
Measured character (engine/tests/dsp_validation.rs, svf_ota_*; pins from 2026-07-22):
| behavior | measurement |
|---|---|
| distorts earlier | clean SVF THD ≈ −132 dB vs OTA (c=1, drive 0) ≈ −24 dB, H3 ≫ H2, even/odd ≈ −107 dB (creamy odd) |
| self-osc purity | OTA self-osc is a uniformly clean, on-tune (±0 ct) sine at ≈ −63.6 dB THD across 400 Hz–4 kHz. (Historic note: this row used to add "…and purer than the ladder above 2 kHz". That stopped being true in 2026-08 — the ladder's driven self-osc was −12.6 dB THD at 4 kHz only because the fixed-pivot solve was diverging; the port-Hamiltonian solve lands at −100…−112 dB, i.e. cleaner than the OTA everywhere. Pick between them on character, not purity.) |
| noise floor | −50.7 dBFS (c=1, q=8) / absent at c=0; deterministic (two renders null −∞) |
Oversampling & alias floor. character > 0 engages the same drive-gated oversampler (the OTA nonlinearity aliases like drive does), at the SVF's measurement-selected 2× factor (SVF_OS_FACTOR; see § Oversampling & latency). At the worst case (c = 1, drive = 1) the isolated-nonlinearity alias floor is −79.4 dB (lp @ 2 kHz) and −66.4 dB (mode-morph @ 500 Hz) — both clear the §4.3 −60 dB budget at 2× (4× is neutral-to-worse, as for the linear SVF), so svf stays at 2×. Golden clip: svf_ota (svf "lp" c=0.8, driven).
formant vowels#
The vowel path is an arrow-separated string — →, >, -, or spaces all separate. Known vowels: a e i o u ae oo ee. morph (0..1) is the position along the path, interpolating the formant frequencies between adjacent vowels.
diode — TB-303 diode ladder (native)#
diode src cutoff resonance [drive] — the acid filter. Four one-pole TPT stages, but unbuffered: each stage loads the next, so the zero-delay resonance loop doesn't factor into a clean G⁴ the way the transistor ladder does. It uses the Pirkle / mystran closed-form solve — a staggered coefficient cascade (G1..G4 with 0.5 inter-stage loading factors) that solves the coupled linear system in one step. Character:
- Staggered poles — the loading factors detune the four poles from each other, so the diode ladder is famously not a clean 24 dB/oct (measured ~24.7 dB/oct deep in the stopband, but a softer, non-Butterworth knee). Darker than the Moog ladder for a given cutoff.
- Input tanh clipper on the solved loop input — the 303's clipper sits at the loop input, so it bounds the whole resonance loop (external + feedback). Per the literature this is "largely enough": the four forward stages stay linear (no raw per-stage tanh — the Analog Fidelity Stage-2 railing lesson). Grittier than the ladder by design (a higher driven alias floor).
- AC-coupling high-pass (~6 Hz one-pole) in the feedback path models the 303's series capacitor.
- Self-oscillation at the Pirkle-normalized
k≈17;resonance0..1 maps onto that (res 1 lands well past onset for a robust ring at every cutoff). The resonant peak is pre-warped to track the nominal cutoff (measured ±21 cents across 200 Hz–4 kHz), sodiode c resresonates atclike the ladder does — even though the raw diode ladder rings ~0.715·fc. The residual ±21 cents is inherent bilinear/topology behavior (the staggered ZDF poles + the loop tanh), not f32 precision: the Analog Fidelity Stage-5 audit measured it identical to the digit in f32 and f64 states. Acceptable for a 303 model — left as-is. driveboosts the input saturation and engages 4× oversampling (same drive-gatedos_latchedpath assvf/ladder; the factor is per-model — see § Oversampling & latency).
korg35 — MS-20 Korg-35 Sallen-Key (native)#
korg35 "lp"|"hp" src cutoff resonance [drive] — the screaming MS-20 filter. TPT one-pole LP + HP blocks in a Sallen-Key positive-feedback loop with gain K, solved zero-delay. Character:
"lp"is a 12 dB/oct lowpass (measured ~12.9 dB/oct)."hp"is the deliberately-6 dB/oct hybrid highpass (~5.8 dB/oct measured) — a hardware quirk the real Korg-35 has because it reused its lowpass control core. Kept as the character, not smoothed to a textbook 12 dB/oct.- Nonlinearity in the feedback path — a fixed-pivot self-limiter drops the effective feedback gain
Kₑ = K·tanh(K·yₚ)/(K·yₚ)as the previous outputyₚgrows. Soresonance0..1 → K 0..2 self-oscillates near K≈2 (as the hardware does), but instead of the raw Tarr/Pirkle loop growing unbounded past onset (Csound'sk35clamps externally), the pivot pulls the loop gain back to the marginal point: a bounded limit-cycle — it sings but never blows up. This is the creamy self-osc fuzz;driveboosts the pivot saturation. - Tuning tracks tightly to ~2 kHz then drifts sharp at high cutoff (+138 cents at 4 kHz measured). The Analog Fidelity Stage-5 precision audit attributed this to the bilinear/topology large-angle regime, not precision: the Sallen-Key ZDF loop measures +138 cents in both f32 and f64 states (to the digit), and
fast_tanhvs truetanhshifts it < 1 cent — so f64 states buy nothing. A known characteristic (self-oscillating a filter at 4 kHz is a corner case), documented and left as-is. driveengages 4× oversampling on the same sticky-latch path (korg35 was promoted from 2× on 2026-08-05 — at 2× it missed the alias budget by 9–12 dB once the high-resonance corner was graded; see § Oversampling & latency).
Both topologies live in the shared engine core, so desktop and browser render them identically.
Notes & limits#
- Not a replacement for
lpf/hpf/bpf. Those stay as the cheap biquad path; these add the resonant/character filters next to them. - Not a generic filter-designer. Fixed, musical topologies (SVF, ladder, formant) — not arbitrary biquad-coefficient authoring.
cutoff/Q/resonance/drivebounds are runtime clamps, not diagnostics. Every port is clamped at the read —cutoffto[20 Hz, 0.49·sr],resonanceanddriveto[0, 1]— so an out-of-range modulator is silently folded back rather than reported.drivejoined that list on 2026-08-05 (audit F6): it is aTy::Holeport, so a bipolar modulator (ladder 1200Hz 0.5 (lfoSine @ 3Hz)) type-checks and used to reach the core raw, where the models'(1 + d·c)pre-gain went negative belowd = −1/cand inverted the filter — measured at 18.9 % of samples opposite-signed against thedrive = 0render, six flips a second, peak 4.9× up. A negative drive now reads as no drive, bit-identically — at every read, including the separate per-sample evaluator afeedbackregion runs (fb_step), which the first F6 pass missed even though it clampscutoff/resonanceat the identical reads: inside a region the ladder was still 17.0–18.2 % opposite-signed and the diode's peak still 10.0× up until the audit-3 review caught it. Pinned byfilter_drive_port_is_clamped(block arms and a region scene) and theladder_bipolar_drivegolden clip. The compiler also warns now (FilterDriveRange) when a drive interval runs below 0 or sits entirely above 1 — the clamp is safe but silent, and a bipolar "drive tremolo" is half dead without a diagnostic.svfis exempt only because its every drive term already sits behind ad > 0gate, so negative drive was inert there to begin with. Music-meaningful refinement bounds — a diagnostic instead of a silent clamp — are the Refinements §5 work; this doc just notes the gap so the two compose when refinements land.- Self-oscillation is bounded by the feedback
tanh, so a screaming filter can't blow up — but a wideunisonstack of self-oscillating filters is N filter instances; mind the CPU ceiling at high polyphony (composes with Polyphony and voice management). - Formant is harmonic-agnostic — it filters whatever you feed it; a harmonically rich source (saw, pulse) gives the clearest vowels.
Deliberate tradeoffs#
Per circuit-modeling §9's contract, the legitimate deviations from the modeling guide live here in the reference doc, not buried in code comments. Each is a considered choice; the note states what changing it would cost.
- Full
1+kresonance-loss makeup, applied inside the model. A real Moog ladder loses passband level as resonance rises — the negative feedback subtracts the fundamental, for the classic ~−12 dB drop at self-oscillation — and the guide's §5.2 position is to expose that authentic drop, with makeup as a per-patch choice. Volta instead bakes the full1 + kmakeup into the ladder core (ladder_tick: the linear path returnsy4 · (1 + res); the driven port-Hamiltonian path is level-matched to it), so cutoff/resonance sweeps hold their level. This is a deliberate deviation: it keeps every existing song's balance stable under a resonance sweep (a knob that both opened the filter and ducked the level would re-mix hundreds of saved performances). The cost is that the ladder is a touch "politer" than a bare Moog at high resonance. The authentic drop is the opt-inmakeupargument (live 0..1, default 1):ladder cutoff resonance [drive] [makeup]scales the output by(1 + m·k)/(1 + k)(k = 4·resonance,filters.rs::ladder_makeup_scale), somakeup = 0is the bare ladder's DC gain1/(1 + k). It is an output scale, so the loop, self-oscillation, drive and oversampling are untouched, on the voice and the feedback-loop paths alike. An absent or literal-1makeuplowers to the old IR (nomakeupkey) and renders identically. Measured (ladder_makeup_zero_exposes_the_resonance_passband_loss): a 100 Hz sine through a 4 kHz ladder at resonance 0.95 drops −13.62 dB atmakeup = 0(the model predicts −13.6 dB for k = 3.8), explicitmakeup = 1equals the default sample for sample, and resonance 0 loses nothing. The default stays full makeup; flipping it would re-balance every saved song.
- The ladder switches solver topology at
drive == 0. With no drive the ladder runs a linear ZDF solve (closed-form implicit loop) with a singlefast_tanhon the solved loop input to bound self-oscillation. With any drive (drive > 0) it switches to the port-Hamiltonian non-iterative solver — a realtanhtransistor stage at each of the four poles, integrated by a provably dissipative discrete-gradient step. These are two different structures, so there is a response step right at thedrive = 0 → 0+threshold (the linear path's single tanh on the solved loop input vs. the four in-loop ones plus the input pair). Level-wise it is the same step the retired fixed pivot had, and it is level-dependent, not constant: on a 220 Hz sine intofc = 900 Hzit measures −0.02 dB at −20 dBFS, −0.4 dB at −6 dBFS, −1.5 dB at 0 dBFS (with THD rising from −89 to −57 dB and −49 to −17 dB respectively — which is the point of turning drive on). Keeping the cheaper linear path for the commondrive = 0case is worth it — the driven solve costs 2.0× more. What is not left to chance is the state: the two paths keep it in different spaces (trapezoid integrator states vs. the paper's transformedz), and the tick converts between them exactly in both directions, so automatingdriveacross zero mid-note does not click (ph_tpt_handoff_is_continuous). Documented so it is a known edge, not a surprise. (svf/diode/korg35share the same drive-gated pattern.)
- Filter oversampling is a sticky per-note latch. The nonlinear filter loop oversamples (2× or 4×, per model — see below) when drive is engaged (
d > 0) or —ladder/diode/korg35— when normalized resonance ≥ 0.8 (FILTER_OS_RES, 2026-08 audit #6: near self-oscillation the always-on loop tanh / input tanh / feedback limiter limit-cycles, and Zavalishin §6 shows the loop itself must be oversampled — swept un-oversampled self-osc audibly "steps" between alias-coincidence frequencies, DAFx-16 paper 41 §6.3). A linear filter (svfatd = 0,c = 0; low-res clean ladder) stays single-rate and bit-identical. Once the oversampled path engages within a note it stays latched on for the rest of that note (os_latchedon each filter channel), even if drive later returns to 0. Toggling the rate mid-note would re-warp the integrator gaing(it is computed against the effective sample rate —factor·srwhen oversampling) — a response step — and discontinuously reset the halfband decimator's state. Latching avoids both. The cost is that a note which briefly touched drive keeps paying the oversampled cost until it ends; that is cheaper than the alternative artifacts. The latch resets at the next note-on (fresh voice state). The one flip the latch cannot avoid — its ownfalse → trueedge — is re-seated rather than latched around (os_reseat_decimators, above).
Oversampling & latency#
The driven filter cores oversample their nonlinear loop, with the factor chosen per model by measurement (circuit-modeling §7's "ship the lowest factor that clears the budget"). The gate is the §4.3 alias budget: aliased components ≤ −60 dB vs the fundamental at maximum musical drive.
Selected factors and measured alias floors — every figure below re-measured 2026-08-11 on the shipped build (drive = 1, res 0.6, 48 kHz; engine/tests/dsp_validation.rs::measure_os_factors -- --nocapture):
| Model | Shipped factor | Hard 3.5 kHz sine @ 2× | @ 4× | ~2 kHz sine @ shipped | Selection rationale |
|---|---|---|---|---|---|
ladder | 4× | −61.6 dB | −69.7 dB | −97.0 dB | anti-imaging input fixed 2026-08-11. A two-stage HIIR allpass upsampler replaces cubic interpolation for this model only: hard-sine residue improves −64.2 → −69.7 dB and the broadband saw improves −52.2 → −66.9 dB. 2× just clears the isolated budget, but misses the broadband budget at −52.7 dB, so 4× stays |
diode | 4× | −66.7 dB | −71.7 dB | −85.5 dB | 4× lowers it ~5 dB. Not gritty on the isolated nonlinearity — the acid grit is the input tanh clipper on program material, not aliasing |
korg35 | 4× | −68.6 dB | −67.7 dB | −87.2 dB | promoted 2× → 4× on 2026-08-05 (audit F2, corrected by its review — see below). The nominal res-0.6 cell is already clean at either factor, but the current full high-resonance grid reads −53.4 dB at 2× vs −62.1 dB at 4×, so the grid still requires 4× |
svf | 2× | −48.7 dB (hp — the documented above-Nyquist edge below) | −47.2 dB | −86.7 dB | lp regime clears comfortably; 4× adds upsampler images with no gain |
Table hygiene. Until 2026-08-05 the
ladderrow still carried the pre-source-average numbers (−59.9 / −63.3) and concluded "2× misses the −60 dB budget by 0.1 dB, so 4× is required" — flatly contradicting the § Ladder nonlinear solve prose above, which the source-average commit had updated and which says the opposite. The prose was right; only this table was stale (2026-08-03 audit F3). Refresh it from a freshmeasure_os_factorsrun whenever a kernel on any of these paths changes:korg35_os_factor_marginnow fails loudly if the korg35 margin moves, but the other three rows are still hand-maintained.
The floors above are measured with the guide's §4.3 method — a hot inharmonic sine (odd FFT bin, so folded harmonics land off-grid), which isolates the filter's own nonlinear aliasing. The broadband saw recipe also carries the PolyBLEP source's own −44.3 dB floor, so it is not a pure filter-generated-alias measurement. It is nevertheless the regression that exposed the ladder input boundary: cubic images entered its five nonlinear stages and left −52.2 dB of residue; anti-imaging lowers that to −66.9 dB while the ladder low-pass also attenuates the source's high-frequency aliases. All four models clear the −60 dB budget in their intended regimes at their shipped factor.
One documented edge: an svf highpass fed a high fundamental at full drive sits at ≈ −47 dB, and no factor helps — a 3.5 kHz tone's 7th harmonic is 24.5 kHz, above the 24 kHz Nyquist, so it cannot be represented at 48 kHz and must alias regardless of oversampling (physics, not the filter). In its lowpass regime — the common case, and the svf_driven golden — that harmonic is attenuated and svf clears comfortably.
korg35 was promoted to 4× — the 2026-08-05 decision#
This factor has been chosen three times, and the first two were wrong the same way: too narrow a probe. The history is kept because the failure mode is the interesting part.
Decision 1 justified 2× with "already clears −60 by 10 dB; 4× is neutral-to-worse (its feedback path has no signal-path clipper)". Wave 2's A2 put a clipper in the signal path — korg35_sat, soft asymmetric rails ±1.0/0.82 on the true feedback at the solved output — and drive extends the loop gain to k ≤ 3.6. Both clauses died and nothing re-measured them.
Decision 2 re-measured and kept 2×, but graded only cells below k = 1.8, on the theory that past the k = 2 self-oscillation onset the limit cycle at fc would be scored as alias. That hid 7 of 16 cells — and it is exactly where the corpus lives: all ten shipped korg35 patches are lowpass at resonance 0.8…0.92, and the korg35_driven golden is res 0.8 / drive 0.7, i.e. k = 2.84.
The theory was also false. With a hot 0.9 probe the fixed-pivot self-limiter quenches the ring, and separating the two energies proves it — ring bins (within ±3 of |m·fc ± n·f₀|, m,n ≤ 4) measure −120 to −171 dB against a fold at −18 to −50 dB, i.e. 20–47 dB below it in every cell including res = drive = 1. The exclusion bought nothing.
Decision 3, the one in force. Graded on the ring-masked fold floor over the full res × drive grid, hot 0.9 sine, inharmonic fc = 1.7·f₀ (at 2·f₀ the ring lands on the probe's 2nd harmonic and is miscounted as signal). At res = drive = 1:
| model (ships) | 2 kHz probe, 2× / 4× | 3.5 kHz probe, 2× / 4× |
|---|---|---|
ladder (4×) | −110.5 / −113.5 | −71.6 / −73.2 |
diode (4×) | −73.0 / −82.5 | −47.5 / −56.5 |
korg35 | −50.9 / −62.3 | −30.7 / −40.8 |
svf (2×) | −92.7 / −85.2 | −55.7 / −53.1 |
(48 kHz; at 44.1 kHz korg35 reads −47.7 / −77.7 and −29.2 / −52.8.) At 2× korg35 was 20–52 dB worse than every other model at the same setting and the only one missing the −60 dB budget at the budget probe. 4× clears it and lands beside diode. No cell measured got worse at 4× — 30 cells across two geometries × three rates, plus 9 inharmonic-lowpass cells; decision 2's "neutral-to-worse in 8 of 12 cells" was measurement noise on sub-onset cells already sitting at −80…−108 dB.
Cost: +99 % on the node, 54.8 → 108.8 ns/sample/channel — the same doubling ladder and diode already pay, and still under a third of the driven ladder's 363 ns. svf stays at 2×: it is the one model 4× measurably hurts (−92.7 → −85.2).
The regression pin, dsp_validation.rs::korg35_os_factor_margin, now grades all 120 cells (5 resonances × 4 drives × 2 geometries × 3 rates) with no exclusion, asserts the ring stays ≥12 dB below the fold before trusting a fold-only grade, and replaces decision 2's "is 4× more than 6 dB better?" guard — which measured how much the other factor would buy rather than whether the shipped one suffices, ran at one cell at 48 kHz only, and would have missed that the same cell at 44.1 kHz already showed a 14.36 dB gap. The guard now asks the question that actually decides the const: would half the shipped factor still clear the budget? (Today: no — 2× reads −47.68 dB over the same grid.)
A rate-adaptive factor is on the table and deliberately not taken here. 2× fails only at the two music rates: at 96 kHz it already clears (−74.24 dB against the same grid), because the fold has an extra octave of room. So a korg35 running at 96 kHz is paying the second decimator stage for nothing, and *_OS_FACTOR could become a function of sr rather than a constant. That is a change to the compile-time-constant contract these consts are documented under ("not runtime-adaptive"), it would make every model's factor rate-dependent to stay consistent, and it buys CPU only for sessions that are already running at double rate — so it belongs in a cost pass, not in an alias repair. Recorded with the number so that pass does not have to re-derive it.
Two things this measurement cannot do, recorded so the next pass does not rediscover them. The floor is dominated by wherever a strong harmonic lands nearest the fold: at 48 kHz a 2000.2 Hz probe puts its 12th harmonic within 0.01 % of the 24 kHz fold, which no factor moves, and that single coincidence is why the shipped 4× clears the budget by only 0.08 dB (−60.08 dB) at its worst cell rather than comfortably. And at the 3.5 kHz stress probe korg35 remains ~17 dB behind ladder/diode even at 4×; that residual is the drive law's aggressiveness — boost = 1 + 3d into a rail saturator at k = 3.6 gives arg ≈ 13 — not the oversampling factor, and is tracked separately.
Eco quality — 2× on request#
{ quality = "eco" } on ladder / diode / korg35 lowers to the IR's oversample: 2; "standard" or no argument leaves the field out, so the IR — and every existing song and golden — is byte-identical and the per-model *_OS_FACTOR const applies. The engine carries the factor on the compiled node (os_factor) and dispatches the eval arm's 2×/4× tick on it; svf is not offered the option (it already ships at 2×). The single-rate feedback-loop arm (voice/feedback.rs) is unaffected.
Why it exists: a blind, loudness-matched A/B (dsp_research C4, nine pairs of acid, reese and lead-sweep renders through each model at 2× and 4×) found no audible difference to the owner, while the measured cost roughly halves. What 2× does cost is the alias floor this section's budget grades, so eco is an opt-in, never the default:
| model | audible-band alias, 110 / 523 Hz, 2× | 4× | whole-engine speed-up, 8 driven voices |
|---|---|---|---|
ladder | −70 / −57 dB | −84 / −71 dB | 1.7× |
diode | −62 / −52 dB | −65 / −55 dB | 1.9× |
korg35 | −117 / −79 dB | −124 / −99 dB | 1.8× |
(Alias figures from dspr lab filter, below 12 kHz; speed-ups from dspr run suites/eco.json, best of 7, measured on a loaded machine — order of magnitude only. Level and spectral centroid matched to within 1 Hz. Measured offline; the listening result is one listener on nine pairs.) The compiler's cost model counts an eco filter at half (compiler/src/cost.rs::eco_scale). Pinned by engine/tests/filter_quality.rs and compiler/tests/named_options.rs.
In-loop decimator: IIR, not FIR. The decimation runs a polyphase-allpass half-band (HalfbandDec; 4× cascades two stages as HalfbandDec2, voice/filters.rs). Since 2026-08 (audit #2) the three allpass coefficients are the elliptic/minimax design (hiir PolyphaseIir2Designer, transition bw 0.1): equiripple ≥ 53 dB across the whole stopband past 0.60π and a −0.05 dB passband at 0.45π, where the original Butterworth tan²(kπ/14) split delivered only −10 dB at 0.55π / −19.5 dB at 0.60π (the exact region that folds to 16.8–21.6 kHz) and −0.45 dB of passband droop — same three-allpass cost. IIR is the guide's approved choice for feedback contexts: its near-zero in-loop latency is what a resonant loop needs, and its phase distortion near Nyquist is inaudible in-loop. The linear-phase FIR half-band pair (engine/src/resample.rs, passband ripple < 0.01 dB to 0.45·fs, stopband ≥ 90 dB, constant (N−1)/2 group delay) is the tool for non-feedback resampling (static-shaper oversampling, offline renders, any path with a parallel dry sum) — never inside a tight loop.
In-loop upsampler. The ladder's 4× path uses two cascaded HIIR-style polyphase-allpass halfbands (HalfbandUp2) as a real anti-imaging boundary. The same three minimax coefficients as the decimator give maximum images of −53.2 / −53.2 / −55.6 / −36.9 dBc for 5 / 10 / 15 / 20 kHz host tones. Input and output filters have independent state, and the input cascade is seeded from the prior source sample when the sticky oversampling latch flips, so its added phase delay does not create a transition click. There is no parallel dry path, so no delay compensation is required. The SVF, diode and Korg-35 4× paths retain their causal cubic-Hermite interpolation; changing them is a separate sound and measurement decision. The 2× paths retain linear midpoint interpolation.
drive = 0 is bit-identical. Oversampling touches only the driven path; a clean filter runs the single-rate core unchanged, proven by the null test (ladder_drive0_is_bit_identical_to_linear_reference).