Look up the processors that add space, movement, color and dynamics, with their arguments, placement rules and detailed behavior.
Reverb, plate, spring, shimmer, galactic, kPlate, crunchCoat, rotary, optical and fet require a track receiver. Their numeric amount/mix knobs are dimensionless: Hz, seconds and dB are rejected even through aliases, factories or imports. Shimmer's fixed pitch interval accepts cents or semitones; kPlate's mode is a prepared string. Live signals cannot select those structural values. Partial calls retain named options: f = reverb track followed by f 0.4 {wet = 0.2}. A prefix still missing a required argument stays callable.
The same contracts cover 31 more dynamics, color and spatial processors, including width, srsly, transient, dynEq, the mastering compressors, saturate, crush, clipOnly, inflate, pyewacket and the track-flavor family. Dimensionless knobs reject unrelated physical units. Frequency and dB ports retain their own units through reusable function values.
ClipOnly and cornerClip ceilings accept fixed dB values or linear ratios; live ceiling controls use linear ratios. Limiter release accepts milliseconds, seconds or prepared musical durations. Saturation accepts either an optional prepared shape string or a numeric wet mix: saturate track 0.4 0.2 and saturate track 0.4 "tube" 0.2 retain the same meaning through aliases.
Resource and aggregate effects use the same receiver contracts. Resource names are prepared strings. Harmonizer degrees and sympathetic pitches prepare as dimensionless numbers and Pitch values respectively. The explicit EQ legs require EqBand lists, and neural bias lists accept dimensionless persistent controls. Their list structure prepares while live EQ parameters and neural bias values remain live; live selection of a resource or list shape is rejected.
EQ band constructors (peak, lowShelf, highShelf, lowpass, highpass, notch) check frequency, gain and Q through aliases, factories and containers. Frequency accepts Hz, gain accepts dB, and Q is dimensionless. Numeric surface values and persistent controls are supported; unrelated units and audio signals are rejected. A macro declared in Hz or dB retains that surface unit. Bare dimensionless macros retain their normalized range mapping. Targets read the final value of each render slice; coefficients smooth on an audio-anchored 32-sample grid. Shelves take no Q; the existing band defaults are unchanged.
Live read timing follows the processor. Reverb/plate/spring motion and wet controls read per sample. Shimmer feedback, Galactic geometry, and optical/FET amounts read the final value of each effect render slice, with their existing internal smoothing. These are distinct from a voice control that holds its first sample; a signal's rate alone does not select a read policy.
ONE Rust engine renders these on every target — desktop, the plugin, and the browser (the same crate compiled to wasm in an AudioWorklet; rebuild it with ./build-engine-wasm.sh). There is no separate browser approximation: the TypeScript Web Audio path was deleted 2026-07-24, so the per-effect "browser" divergences this doc used to list no longer exist (One audio engine across hosts). Example: examples/songs/morefx.volta.
Hear it and change it#
Reverberation is the dense tail of reflections after a sound. A longer decay suggests a larger or more resonant space, but a louder wet mix also moves a sound farther into that space. They are separate controls.
Listen for: a diffuse tail filling the gaps between the same plucks.
tempo = 120bpm
timingVersion = 2
play "pluck" "c4 ~ g4 ~" |> cutoff 2200Hz |> release 80ms
|> reverb 0.47 0.3
The first value, 0.47, controls tail length on a 0–1 scale; the second, 0.3, is the wet blend. The tail setting maps to about 0.3–3.5 seconds.
Make it yours: change 0.47 to 0.06 for a short space, then restore it and change 0.3 to 0.12. If the melody becomes blurred, reduce the wet mix or shorten the decay. Explore The effects reference for plate, spring and other reverb colors; Convolution uses a recording of a space or system's response.
Per-track effects#
These lower to ir::TrackEffect entries and instantiate once on the mixer bus (TrackFx in engine/src/effect.rs, built per strip in engine/src/lib.rs). Their Normalized knobs (feedback, mix, amount) are live-modulatable: a bare macro at that slot rides the value live (Macros and automation §4). Every knob that multiplies full-scale audio — each wet/dry mix crossfade, harmonize's wet gain, the bbd / ensembleChorus blend, decorrelate's and sympathetic's depth, width's side gain, panTrack's auto-pan — interpolates per sample across the block; coefficient-heavy knobs (EQ, compressor/de-esser curves, crush's level count, saturate's drive, the STFT parameters of tune / formantShift / spectral) refresh once per block, or once per 16-sample slice while their macro moves — see Macros and automation §4 "Control rate" for the split and why. Feedback on delayTrack, pingpong, bbdDelay and tape reads each sample, whether or not another delay control is animated. Fixed beat/bar times convert with the compilation meter; live times require seconds or milliseconds. Under a tempoMap a fixed cycle/beat/bar time follows the transport's tempo (the line is prepared for the map's slowest tempo; Tempo changes and independent clocks "Musical effect times"), so pingpong (3/16c) stays a dotted sixteenth through a ramp; at constant tempo it folds to exact seconds as before. Sample counts need an explicit target-clock conversion. Prepared transition strings can come from ordinary functions and containers. One knob spelled mix is deliberately in the second group: grooveWear's, because it engages whole cascade stages rather than crossfading, so ramping it inside a block would step the topology mid-block. All four of toVinyl's knobs are block-rate for the same reason — it has no wet/dry at all.
What happens when a track stops#
An insert's tail rings out after its track stops — a reverb or an echo decays exactly as it would have. The inserts that would otherwise keep sounding with no input at all are gated on the track's activity instead: once the track stops or leaves the scene (for a bus or master insert, once every track has stopped), a gate closes over 0.2 s (effect::STOP_FADE_SEC):
freezereleases its held frame (its amount fades to 0, then it unfreezes; the next freeze captures afresh);galacticwithreplacebelow 0.5 is pulled up towardreplace 0.5, so a near-hold tank decays instead of holding its tail for ever;crunchCoatwithregenabove 0.5 is pulled down towardregen 0.5, so a lossless tank decays;neuralMod/neuralfade their wet stream (a model generates on silent input); the dry path is untouched.
The gate reopens at once when the track starts again, so a playing track renders bit-identically to an ungated insert. engine/tests/stop_silence.rs renders every registered track-effect kind (and every IR kind) through a stop and fails on one that stays flat; a feedback echo at a high setting passes by still decaying. A freeze on a bus keeps holding while any track plays.
Idle inserts — which effects sleep#
A many-track song installs every chain's inserts even when one track plays, so an insert that keeps ticking an empty tank costs callback time for nothing (a stack of idle tanks alone can eat the block deadline). The heavy ones put themselves to sleep: reverb and the FDN inside hallTail. A sleeper keeps its tail after a track stops; once its entire internal memory — every delay, reflection and filter cell, not just its output — is below −240 dBFS it clears that residue, skips its DSP, and wakes on the first nonzero input. Quiet gaps between reflections do not cut it off. This is automatic on desktop and in the browser, on tracks, returns and the master.
Every engine effect declares its idle class (effect::IdleCost: Sleeps, Light, Awake, External), exhaustively, so a new effect must choose; effect::tests::idle_classes_are_declared_and_sleepers_sleep checks the corpus inserts against a pinned table and drives every Sleeps insert through a burst and a drain to prove it really sleeps and wakes. The Awake set is the known debt — heavy and still ticking idle: plate, tape, spring, shimmer, galactic, crunchCoat, kPlate, bbdDelay, tripleSpread, toVinyl, silken, limiter, the STFT family (blur, freeze, …), tune, formantShift, harmonize, the coupled-body inserts and granularize. idle_insert_cost_inventory (ignored; cargo test --profile fast -p volta-engine --lib idle_insert_cost_inventory -- --ignored --nocapture) prints each one's idle ns per block; probe-circuit-tour with CIRCUIT_PROBE_FX=selected|none separates inserts from voices in a real song.
Making one sleep, without changing its sound:
- Sleep per effect, never by skipping a track: capture, generators and plugins on a silent track still run.
- Gate on the effect's whole memory, never on a quiet OUTPUT block — a quiet block does not prove the tank is empty.
- A free-running modulator keeps its exact per-sample additions while asleep (the FDN's
mod_phase += dphiloop); a block-sized phase jump rounds differently and changes the next note. - Clear only sub-threshold residue, and keep the filter form and ring heads (an all-zero ring reads the same from any head); prove it with a twin that never sleeps — bit-exact until the first sleep, within the residue after (
fdn::tests::sleeping_room_wakes_with_the_continuous_render_phase_and_samples). - Then A/B real songs, not just the insert: clearing a −240 dB residue is inaudible in the insert (a never-sleeping twin stays within 1e-9), yet a trial sleeping
springmoved thebashmentandcsg-08exports by up to −105 dBFS for a second or so after a re-entry — so the chain around an insert can carry the difference further than the insert does. A sleep that moves corpus renders is a sound change for the owner to accept.
Design of the room's sleep: Idle Room Reverb.
plate amount [wet] — Dattorro plate reverb#
A figure-8 plate reverb: an input bandwidth low-pass, four series allpass input diffusers — one such chain per input channel, each feeding one half of the tank, so a stereo image survives into the tank (2026-08-06 audit R1: the old mono sum made an anti-phase source exactly silent and gave hard-left and hard-right sources the identical reverb; a mono source renders bit-identically either way) — then a cross-coupled tank of two halves (each a modulated decay-diffusion allpass, a long delay, a one-pole HF damper, a decay multiply, a second allpass, and a second delay), read out through the canonical 7-tap network (engine/src/plate.rs). Brighter and denser than the FDN reverb — the studio plate shimmer. amount sets the tank decay (≈0.4 s … 4.4 s); the optional wet overrides the blend (else 0.15 + amount·0.35). Amount is live: 5 ms smoothing and a 16-sample feedback-coefficient clock retain the tank. Omitted wet follows the applied amount.
Because a hard-panned source drives one branch only, the tank alone would leave the OPPOSITE channel exactly silent for the first 70.9 ms of that source's tail (the FDN reverb had the same hole at 52.6 ms). Both are filled by the shared feed-forward early-reflection FIR in front of the tanks — deterministic stereo velvet noise from 10 ms to 70 ms, far-side pulses at 0.62 gain so a hard-panned image survives (engine/src/early_reflections.rs, the audit's R2 stage). An input crossfeed was rejected instead: 0.1 of it costs 1.7 dB of the correlation-flatness the per-channel input buys. The far onset is a fixed time, not a sample count, and is pinned at 10–12 ms by far_channel_early_onset_is_sample_rate_aware. Both render that FIR block-wise — the history is kept linear, and tiles of 16 outputs sum their taps in the per-sample order, so the output is bit-identical — the reverb (FDN) for every call of 16 samples or more and, on the live-decay path, per 128-sample chunk ahead of the per-sample tank (FdnReverb::process_live; a tank that drains and resets mid-chunk restarts them). From evolve runs volta_reverb #23 and volta_plate #6. On the live-decay path a settled reverb chunk (constant request, smoother on target, line lengths on their targets, band gains derived from exactly that decay; the tank awake with no sleep check due inside the chunk; every line longer than the chunk plus its modulation reach) renders its tank whole-block too (FdnReverb::fast_block, evolve volta_reverb2 #30): all line reads before any write, the per-line band filters four lines at a time, sin skipped where the modulation offset provably clamps to 1. Bit-identical to the per-sample tank; measured ≈ 109 → 27 ns/frame (48 kHz, reverb 1.9 s, loaded machine).
The plate's tank also runs block-wise: each stage over the whole (≤ 128-sample) chunk in dependency order, which is exact because every read that crosses stages reaches further back than a block (checked at construction; a rate whose rings are too short, below ~8 kHz, keeps the per-sample tank). On the live-decay path the per-sample tank gain and wet are derived first (effect/tails.rs), with a settled block (constant request, smoother on target, gain current) skipping straight to its clock. Bit-identical to the per-sample plate; measured ≈ 470 → 24 ns/frame (48 kHz, plate 2.0 0.2, loaded machine).
The topology is the full Dattorro one, deterministic and allocation-free.
tape time feedback [wet] [bias] — tape echo#
A feedback delay whose loop carries the analog character: evolved capstan/pinch motion modulating the fractional delay time, Jiles–Atherton hysteretic magnetization, and level-dependent HF loss — so each repeat gets warmer and darker. The musical complement to the clean delayTrack. time is the delay (a Cycles constant converts via the cycle length, and every spelling of one length agrees — (1/8c) ≡ (1c/8) ≡ 0.125c, §28 X1); feedback ∈ [0, 0.95]; optional wet (default 0.35); optional bias controls the hysteresis loop and gap-loss corner. Time and bias accept live controls: time has a ten-second prepared maximum; bias (0–1, default 0.5) uses 5 ms smoothing and a prepared calibration table without resetting magnetization. See Bring a sound to life for transition and update policies.
- Native: two
TapeLines share one capstan trajectory (one motor), while pinch motion evolves with independent L/R phase/rate variation. Every LFO cycle deterministically re-rolls within a bounded rate window, so a held pad does not expose one permanent repeating wobble and replay/block partitioning remain bit-identical. The reusable record/play chain is detailed in Tape echo and tape color.
saturate amount ["shape"] [mix] — bus saturation#
The "drive", "tube", "clip", and "fold" shapes keep their existing gain-compensated ADAA paths. "tape" now instantiates the same reusable record/play model as TapeLine — record amp, 8× Jiles–Atherton hysteresis (field-integrated, turning points inserted; evolve volta_tape #93), 30 Hz coupling high-pass, level-dependent gap-loss low-pass, and head bump — with no transport delay and no feedback dependency. It is stateful and deterministic; the machine is 7 samples late, the dry path is delayed to match and the insert reports 7 samples of latency, so mix=0 is the exact dry signal 7 samples late (aligned by PDC).
clipOnly [ceiling] — transparent safety clipper#
A faithful port of Airwindows ClipOnly3 (Chris Johnson, MIT). The one dynamics device in the set that is defined by what it does not do: below its ceiling the output is the input bit for bit, and there is no gain reduction, no release, no lookahead beyond one sample and no program dependence anywhere. Nothing under the ceiling can move, so it cannot pump, breathe, or round transients the way limiter does — and correspondingly it makes no true-peak guarantee, so it does not replace limiter for delivery. The pair is the intended combination: clip the isolated spikes off, then limit what is left.
ceiling is normalised by one rule shared by the track pipe and the mixer insert list: positive is a linear ratio, zero or negative is dB. The two domains cannot overlap (a linear ceiling ≤ 0 is silence, a dB ceiling > 0 is above full scale) and they agree at the boundary — 0 and 1.0 are both exactly full scale. The rule dates from when the mixer grammar's parser dropped unit suffixes, so inserts = [clipOnly -0.5dB] reached the lowerer as the bare number −0.5; since Named options and parameter values Stage 2 the insert grammar reads a dB literal for a dB slot, and the sign rule still holds for bare numbers. In a track pipe |> clipOnly -1dB is a negative literal (since 2026-09-02; parens still work). Default: 0.94 (≈ −0.54 dBFS), Airwindows' own. ceiling is a Param, so it rides a macro live. limiter also accepts live ceiling controls, applied to each frame after its lookahead; its different peak contract is in Mastering and delivery.
How the corner softening works. A hard clipper's output is min(max(x)), whose corners are discontinuities in the first derivative — that is the buzz. ClipOnly3 instead holds each sample one step, so when a sample crosses the threshold it can see both neighbours, and replaces the corner samples with a blend of the threshold and the neighbouring sample. The blend weight carries a small deterministic noise term (an xorshift register, ±7.6 %), which decorrelates the corner treatment from the waveform's period. A second stage lowers the output ceiling as a function of the local slew rate (0.94 / (1 + slew × 1.3986)), so the fastest edges — the ones that would ring hardest — are given the least headroom. Everything below the threshold falls through all of it untouched.
- Native: the port lives in
engine/src/effect.rs(ClipOnlyFx,ClipOnlyChannel), f64 throughout, true stereo with the two channels independently seeded. The user'sceilingis realised as a uniform gain around the unmodified core (in × 0.94/c→ core →× c/0.94), so every internal threshold scales together and the character is identical at any setting; at the default the gain is exactly 1 and the multiplies are skipped. Verified bit-identical to the upstream C++ (built-ffp-contract=off) across 36 corpus cases — sines from −12 dBFS to +18 dBFS, log sweeps, seeded noise, squares, DC, silence, denormals, at 44.1/48/88.2/176.4/352.8/705.6 kHz and four ceilings — max abs sample difference 0.0. With clang's default FMA contraction the reference drifts ≈2e-16; that is the C++ fusinga*b + c*d, not a port difference.effect.rs'sclip_only_matches_the_airwindows_reference_bit_for_bitpins a 16-sample vector from that reference so the port cannot drift. - Latency: 1 sample at every rate, reported to PDC. Upstream sizes an
intermediate[18]/slew[34]pair byspacing = floor(sr/44100)and its comment claims "however many samples equals one 44.1 k sample" — but the descending copy loops (a[i-1] = a[i]walking downward) cascade the newest sample all the way to index 0 in a single pass, so they broadcast rather than shift. Measured input-impulse → output-peak is +1 at every rate from 44.1 kHz to 705.6 kHz.spacingsurvives only as the averaging divisor on a clipping sample, and is still computed the upstream way so the port stays exact. - On the "dither" block: upstream's trailing block is labelled floating-point dither, but the two lines that would add dither to the output are commented out in the shipped source. What the
fpdregisters actually do is feed the corner-blend weight at the top of every sample, so they are live DSP and are ported. They are seeded from two fixed constants rather thanrand(), because Volta renders must be reproducible.
Legal on tracks, buses and master inserts. Example: examples/songs/cliponly.volta.
inflate drive [curve] [mix] — Inflator-style bus density#
A faithful f64 port of Airwindows Inflamer (© Chris Johnson, MIT), the Oxford-Inflator family's move: perceived loudness and density manufactured out of harmonics rather than gain reduction. There is no detector, no envelope and no compression anywhere in it — it is a memoryless 4th-order polynomial, sample-rate independent (the original carries no overallscale term), and stereo (L and R take the same transfer independently).
Two transfers are blended per sample, after the input trim x ← x·gain:
inflamerPlus = 2x − x² -- the "+50" side: essentially pure 2nd harmonic
inflamerMinus = x + x³/4 − (x² + x⁴)/16 -- the "−50" side: odd-harmonic
wet = inflamerPlus·curve + inflamerMinus·negacurve
out = wet·mix + dry·(1 − mix)
The curve is steepest at low and mid level and flattens back toward the line as |x| approaches full scale, which is what makes it read as "louder" without touching the peaks.
Both knobs are stepped, on purpose. Inflamer quantises drive and curve through a power-of-two "bit-shift" ladder (2^-n for n in −2..16, plus a hard mute rung), and that quantisation is preserved verbatim — it is Chris Johnson's original and part of the plugin's character, not a rounding artifact:
drive | input trim | curve | weights (curve, negacurve) | |
|---|---|---|---|---|
| ≤ 0.0053 | 0 (mute) | 0.0 | (0, 0.5) — pure −50 odd side | |
| 0.5 (default) | 0.5× | [0.375, 0.625] | (0.5, 0.5) — neutral dead zone | |
| (0.567, 0.9] | 1× | 1.0 | (0.5, 0) — pure +50 even side | |
| > 0.9 | 2× |
mix (default 1) is a linear dry/wet and nulls exactly at 0. All three knobs ride macros live; drive/curve take the block-end macro value (lerping a stepped parameter per sample would only dither where the step lands — the same treatment crush's level count gets), while mix ramps per sample.
Deliberately not gain-compensated and not anti-aliased: the polynomial is 4th order, so hot content aliases, exactly as in the original. Reach for saturate when you want clean, level-matched drive and inflate when you want this. Legal on tracks, buses and master inserts.
- Native (
InflateFxinengine/src/effect.rs) is the reference. It was diffed against a stubbed standalone build of the original C++ over a 1 kHz / 20 Hz–20 kHz sweep / seeded-noise corpus at 44.1 and 48 kHz across five knob combinations, and over 10 001 knob positions: bit-identical (0 differing samples out of 1.4 M) once the C++ is built with the same op order and FMA contraction off. Against the untouched original — libmpow(x,3)/pow(x,4)and clang's default contraction — the largest disagreement anywhere was 4.4e-16. - Two things are dropped from the original, both fpd-only: the trailing 32-bit floating-point dither block (this engine is f64 end to end) and the
|x| < 1.18e-23 → fpd·1.18e-17denormal guard that existed to feed it. Inflamer has no feedback path, so nothing can denormal-lock, and dropping the guard means an exact zero stays an exact zero.
pyewacket [input] [release] [output] — transient-energy compressor (Airwindows Pyewacket)#
A faithful f64 port of Airwindows Pyewacket (© Chris Johnson, MIT), an old-school broadcast-style compressor and the one dynamics box here that adds no fatness at all. compress/optical/fet/mbcomp all pull the peak down and push the body up — the move that reads as "thick". Pyewacket does the reverse: it leaves the transient's full height alone and only reshapes the energy behind it, so crest factor goes up. Reach for it when a drum bus is turning thick and you wanted it to hit harder.
| arg | Airwindows knob | range | default | what it does |
|---|---|---|---|---|
input | Input Gain | −12…+12 dB | 0 dB | drive — how hard the unit is hit |
release | Release | 0…1 | 0.5 | higher = faster; reshapes where the decay goes |
output | Output Gain | −12…+12 dB | 0 dB | makeup, after the core |
engine/src/pyewacket.rs. All three are Params and ride a macro; the two dB trims are travel-mapped (a macro's [0, 1] spans the full ±12 dB, so 0.5 is 0 dB). A negative dB trim is a negative literal — |> pyewacket 6dB 0.3 -4dB — since 2026-09-02 (a - glued to a digit after a space); parens still work.
How it works#
There is no gain-reduction multiplier anywhere in the unit. A linear envelope chase crossfades every sample between two transfer curves:
chase += attack if chase < |x| -- attack = (release+0.5)·0.006 / overallscale
chase -= decay if chase > |x| -- decay = (release+0.01)·0.0004 / overallscale
wet = chase -- (already clamped to [0, 1])
out = wet > 0.618 ? 0.618 − (wet − 0.618) : wet -- the golden-ratio FOLD
dry = 1 − wet
y = sign(x)·( |x|·dry + blur(1 − cos|x|)·out ) -- |x| clamped to π/2 first
Three details do the work:
- The detector is structurally one sample late.
chasecannot rise until after the sample that would raise it has already gone through, so a transient's leading edge meets the previous, quiet envelope. It costs at most oneattackincrement — 0.0055 of crossfade at the default release, i.e. a measured −0.04 dB. The envelope literally cannot catch its own transient, and that is the whole trick. 1 − cos|x|is the compressive half. It is ≈x²/2near zero, so quiet detail is pushed down hard, and reaches 1 only at the ±π/2 clamp. The louder the recent programme, the more of it is mixed in — so the decay behind a hit is starved toward silence while the hit itself passed at full height.outfolds at the golden ratio. Pastchase= 0.618, pushing harder puts less starved curve in the mix (max 0.618, back down to 0.236 atchase= 1). The unit self-limits into a half-open state instead of clamping shut, which is why it never turns to mush. The starved curve is also blurred one sample against its own last value, weighted bywet·0.382— more smear the harder it works, which takes the edge off the harmonics the squaring generates.
Stereo-linked, exactly as upstream: one chase for the pair driven by max(|L|, |R|), applied to both, so the image never wanders when one side gets loud. The one-sample blur memory is per channel (it belongs to the waveshaper, not the detector).
Measured#
A 0.9-peak drum burst (90 Hz, 80 ms exponential decay) at 48 kHz, input 0 dB, the envelope pre-settled on silence. Windowed RMS change vs the dry burst:
release | peak | 0–1 ms | 1–5 ms | 5–20 ms | 20–50 ms | 50–150 ms | 150–400 ms | crest | max GR |
|---|---|---|---|---|---|---|---|---|---|
| 0.00 | −0.02 dB | −0.61 | −2.33 | −8.17 | −10.00 | −11.04 | −11.94 | 5.0 → 12.0 | 12.2 dB |
| 0.25 | −0.04 dB | −0.93 | −3.86 | −8.80 | −7.24 | −4.45 | −0.80 | 5.0 → 9.7 | 8.2 dB |
| 0.50 | −0.04 dB | −1.24 | −5.87 | −8.53 | −5.31 | −2.85 | −0.74 | 5.0 → 8.8 | 7.2 dB |
| 0.75 | −0.04 dB | −1.56 | −7.73 | −8.21 | −5.16 | −2.79 | −0.74 | 5.0 → 8.8 | 7.0 dB |
| 1.00 | −0.08 dB | −1.88 | −8.95 | −8.05 | −5.08 | −2.76 | −0.72 | 5.0 → 8.8 | 6.9 dB |
Read the rows, not just the peak column: the knob moves where the decay is removed, not just how long for. At release 0 the tail is held ~11 dB down all the way out to 400 ms — the sustain is simply gone. At release 1 the 1–5 ms window is squashed hardest (−8.95 dB, because a fast release also means a fast attack) but by 150–400 ms the tail is back within 0.7 dB of dry. Every row leaves the peak inside 0.1 dB, and every row raises crest factor.
input is the drive control — the unit is level-sensitive, so this is what decides how hard it works (release 0.5, measured against the same trim applied dry, so these are compression numbers, not level):
input | peak | tail 50–400 ms | max GR |
|---|---|---|---|
| −12 dB | −0.07 dB | −0.65 dB | 1.4 dB |
| −6 dB | −0.06 dB | −1.31 dB | 2.9 dB |
| 0 dB | −0.04 dB | −2.64 dB | 7.2 dB |
| +6 dB | −1.19 dB | −6.15 dB | 20.1 dB |
| +12 dB | −7.19 dB | −11.33 dB | 20.9 dB |
Watch the top. The unit clamps its input at ±π/2 (≈ +3.9 dBFS) before shaping — that clamp is upstream's, and it is what bounds the
1 − coscurve. Aninputtrim that pushes peaks past it hard-clips rather than compresses, which is where the −1.19 dB and −7.19 dB peak figures above come from. Below that the peak column stays inside 0.1 dB.
Static gain on a sustained 220 Hz tone (release 0.5) — the curve is gentle until it very much isn't, which is the point: sustain is what it eats.
| steady level | −40 | −30 | −20 | −12 | −6 | −3 | 0 dBFS |
|---|---|---|---|---|---|---|---|
| output change | −0.10 | −0.27 | −0.85 | −2.16 | −4.32 | −6.86 | −19.41 dB |
The bench dynamics meter reads it like any other compressor (gr_db), but the number is measured — the smoothed input/output magnitude ratio across the core, between the two trims — because there is no coefficient to read off. The output trim is makeup and does not flatter the meter.
Faithfulness#
- Native (
engine/src/pyewacket.rs) is the reference, and it is bit-identical to the original: max |Δ| = 0.0, every sample, over a 50-case corpus (drum-like impulse trains, gated 1 kHz sine bursts, seeded white noise, a 20 Hz–20 kHz log sweep, and a hard L-loud/R-silent case × 44.1/48 kHz × 5 knob settings including the defaults and both extremes), each run through irregular block sizes, in both the debug and release profiles. The reference is a standalone C++ build of Airwindows' ownprocessDoubleReplacingwith the VST scaffolding stubbed, compiled-ffp-contract=off. - The
fpdregisters are ported, not dropped — unlikeairBand/inflate, here they are live DSP. Upstream's f64 path comments out the dither add but still advances the registers every sample, and still reads them at the top of the next one: an input under 1.18e-23 — which includes digital silence and every exact zero crossing — is replaced byfpd · 1.18e-17. That substitution feeds the detector, so dropping it would changechase's resting value and break the match on any signal containing a zero. Consequence, upstream's own: a silent input yields a −145.9 dBFS (5.07e-8 peak) positive noise floor rather than digital silence — below the 24-bit floor. - One deviation. Upstream seeds those registers from
rand(). Volta renders must be reproducible, so each channel gets a distinct compile-time constant instead — the same deviationclipOnlymakes, and the C++ reference was given the same two seeds for the diff. - Upstream quirks kept verbatim, all three inert:
drySampleL/drySampleRare assigned and never read;bridgerectifieris re-clamped to π/2 one line after the value it came from was already clamped there; andchaseis initialised (andresetto) 1.0, i.e. fully engaged, so a cold unit takes one release-length to open up. The source comment "chase will be between 0 and ? (if input is super hot)" is stale — the preceding line clamps it to 1.0, which is why theif (wet > 1.0)below can never fire.
examples/songs/pyewacket.volta is the demo.
spring tension decay [mix] — spring reverb#
The surf/dub/guitar-amp "boing". A phenomenological spring tank (analog-fidelity 6.7): a long chain of first-order allpasses in a feedback delay loop — the "spectral delay filter" whose frequency-dependent group delay disperses an impulse into the characteristic descending chirp (high frequencies carry the least group delay and arrive first) — plus an in-loop low-pass (HF damping) and a clamped feedback gain. tension ∈ [0, 1] sets the allpass coefficient magnitude and loop delay (the chirp density/pitch — a tighter spring rings higher and denser); decay ∈ [0, 1] the loop feedback (tail length); the optional mix (default 0.3) is the wet/dry blend and may ride a macro like the plate/FDN reverbs' wet. Tension and decay are live too: 5 ms smoothing, retained dispersion state and a 20 ms geometry glide preserve the tail during motion. Musical default: spring 0.5 0.5 0.3.
- Native: two slightly-detuned tanks (L 100 / R 88 first-order allpasses, ~6 % loop detune), L fed from the left input and R from the right, so the wet field is decorrelated (
engine/src/spring.rs). Measured HF-before-LF dispersion spread ≈38 ms attension 0.5.
shimmer amount [cents] [feedback] — shimmer reverb (pitch shift in the loop)#
The one classic ambient effect Volta lacked (production-gaps.md §11 F1, asked for by three song batches): a pitch shifter inside the reverb's feedback path, so each pass around the tank stacks another transposed copy of the tail — +12 st, then +24, then +36 — into a rising halo the source never played. Before this, songs faked it with a ghost track an octave up (cathedral) or a static shifted bounce (glacier), neither of which cascades.
amount ∈ [0, 1] is the wet mix and feedback ∈ [0, 0.95] (default 0.5) the loop gain; both may ride a macro. cents ∈ [−2400, 2400] (default 1200 = an octave up) is baked, because it sizes the shifter's crossfade window. A downward shift needs parentheses — shimmer 0.5 (-1200) — since a bare - in an argument slot is a binary minus. Musical default: shimmer 0.4 1200 0.7.
Topology (engine/src/shimmer.rs), per channel: bandwidth LP → 4 input diffusers → ×input-gain → tank branch = 2 loop allpasses → long delay → damping LP → (wet tap), and the branch's return is 0.65 × tank + 0.35 × (pitch shifter → 16 kHz pin) → feedback highpass → soft limiter → ×feedback → an orthonormal 2×2 rotation that cross-couples the two branches (the Griesinger figure-8). Canonical Clouds-family lengths at a 32 kHz reference, rescaled to the device rate: input allpasses 113/162/241/399, loop allpasses 1653/2038 and 1913/1663, delays 3411/4782 (≈107 ms / 149 ms at 48 kHz); diffusion 0.625 in, 0.70 in the loop.
That 0.65/0.35 split is a convex blend, not the series (100 %-shifted) loop the obvious reading of "shifter in the feedback path" suggests. The series form was built first and measured wrong twice: it transposes the entire tail upward pass after pass until the damping eats it (a bright but very short tank — the 440 Hz probe fell 60 dB in 2 s where a plate took 5 s), and it leaves no un-transposed body under the halo. Because the blend is convex the branch's gain stays ≤ 1, which is what keeps guarantee 1 below exact.
The shifter sits post-damping on purpose: each octave-up copy is already pre-darkened, so the halo dies before it can pile into the top octave. It is a 2-head crossfaded delay-line shifter (not granular): one phasor ramps the delay at ratio − 1 samples/sample, a second head sits half a window away, and a raised-cosine crossfade w = ½ − ½cos(2πf) — zero exactly at each head's wrap and summing to 1 — splices them at constant amplitude. Windows are 45 ms (branch A) and 52 ms (branch B) so their artifacts do not line up in the image.
Under ~100 cents the shift crossfades into a chorus: an LFO-modulated in-tank tap (±cents at 0.35 Hz, the two branches in quadrature), because near a ratio of 1.0 a delay-line shifter's phasor is so slow it reads as a flanger rather than a transposition. Pure chorus at or below 50 ¢, pure shifter at or above 100 ¢, linear between. The blend narrows that exposure rather than removing it — inside the 50–100 ¢ band the shifter does run, at ratios 1.03–1.06 (phasor periods of 1.5–0.75 s against a 45 ms window), at up to full weight, with the chorus mixed under it carrying most of the sound. Below 50 ¢ it is out of the signal path entirely. The alternative, a hard switch at one cents value, would step the timbre.
Stability is structural, not tuned — four independent guarantees:
- total loop gain =
feedback × tank-matrix gain × shifter gain≤ 0.95, and both those factors are exactly 1 (orthonormal rotation, constant-amplitude crossfade), so the clamp lands onfeedbackitself. Note the two 1s are bounds in different norms — the rotation preserves energy (L²), the crossfade bounds the peak (L∞) — so this keeps the tank musical but is not by itself a boundedness proof; - a soft limiter in the loop (linear below ±0.7, bounded by ±1) is what actually makes the tank state bounded by construction: every branch return passes through it, so what re-enters the tank is capped whatever the linear analysis says;
- the feedback highpass rises with feedback (20 Hz → 120 Hz) so the cascade cannot integrate DC/sub-bass;
- a fixed 16 kHz one-pole after the shifter caps the ×2 pile-up. The input gain also backs off (0.70 at feedback 0 → 0.30 at the 0.95 ceiling) as feedback rises.
Measured (engine/src/shimmer.rs + engine/tests/shimmer_fx.rs, 48 kHz):
| Probe | Result |
|---|---|
| 4 s of full-scale noise at max legal feedback | peak 1.73 → 1.83, RMS 0.33 → 0.41 across seconds 1–4 — bounded, settling, no NaN |
12 s scene render at shimmer 1.0 1200 4 (clamped to 0.95) | per-second peak 0.11–0.14, RMS flat at 0.030 |
| vibrato'd 440 Hz burst, first second of tail | +12 st partial −5.1 dB, +24 st −15.0 dB under the fundamental — against −45.6 dB for the same tank in chorus mode |
| tail band balance (600 Hz…8 kHz over 300…600 Hz), 1 s → 5 s | −2.8 → +25.7 dB (the halo climbs); a plate 2.0 holds −36.8 → −38.5 dB |
| octave partial at 5 s, vs a plate's | −4.5 dB vs −56.8 dB |
| wet level, reverb-family probe (mix = 1) | −1.10 dB (plate −0.97, FDN −1.04, spring −1.11) |
| DC probe, 50 ms DC burst | a hot loop's offset is smaller than an open one's (0.137 vs 0.320), and nothing is left standing at 3 s |
A caution for anyone re-measuring: probe with material that has some motion. A pure steady sine builds a coherent standing wave in any feedback tank — measured here at +25 dB over the same tone with a ±35-cent vibrato — which swamps the halo and makes the fundamental a useless reference.
Implemented in engine/src/shimmer.rs: a stereo figure-8 with one shifter per branch.
galactic replace [brightness] [detune] [bigness] [mix] — the unphysical super-reverb (Airwindows Galactic)#
Port of Airwindows Galactic (c) Chris Johnson, MIT. Every other reverb here models something: plate a steel sheet, spring a coil, reverb a room, shimmer a tank with a shifter in it. Galactic models nothing, on purpose — it is the pad/space wash, enormous and slow to arrive, and it never sits still.
Why it behaves the way it does. Three cascaded four-line blocks run in series (4 → 4 → 4), and each block mixes through M = 2I − J — unity on the diagonal, −1 everywhere else. Since MᵀM = 4I, M is exactly twice an orthogonal matrix: every block is lossless with gain exactly 2, the cascade is exactly ×8, and the output stage divides by exactly 8. Nothing inside the loop damps — the two one-poles brightness drives sit either side of the tank, not in it. So the decay is governed by one number:
loop gain = 8 · regen = 8 · (0.0625 + (1 − replace) · 0.0625)
= 0.5 + (1 − replace) · 0.5
That has three consequences worth internalising before reaching for the knob:
replaceruns backwards. It is not "amount"; it is a crossfade between holding what is in the tank and letting new sound in.replace 1= loop gain 0.5, the shortest tail, the most signal admitted. Lower = longer.replace 0is a perfect freeze that admits nothing. Loop gain is exactly 1.0 (lossless), and the input attenuator(1 − regen/0.125) · 1.333is exactly 0 at the same point. A fresh instance atreplace 0is therefore silent forever. This is upstream behaviour, not a port bug.brightnesscannot shorten the tail. It colours the input and the output only. Reach forreplace/bignessfor length,brightnessfor tone.
True stereo, through exactly one mechanism. The two tanks are otherwise independent and identically sized. The sole L/R interaction is that the left tank's four feedback inputs are the right tank's four feedback outputs, and vice versa — so the tail swaps sides once per round trip instead of decorrelating statistically. Combined with a 257-sample pre-delay whose two channels are in phase quadrature (sin(vibM) / sin(vibM + π/2)), a mono source comes out fully decorrelated — the exact opposite of srsly, which leaves a mono source mono.
The drift never repeats. The pre-delay LFO's rate is re-seeded from the plugin's fpd xorshift register every time it wraps past 2π (oldfpd = 0.4294967295 + fpdL · 6.18e-11). That is why fpd is kept in this port when Volta's other Airwindows ports (airBand, srsly, the slew family) drop it wholesale — here it is not dither, it is the drift generator. What is dropped, as everywhere else in this file, is the trailing dither (already commented out in the plugin's own 64-bit path) and the input denormal substitution — the engine flushes denormals in the FP control register instead, which is the same protection without spraying noise into digital silence. Cf. the airBand fidelity note below.
mix is cubed (wet = 1 − (1 − mix)³, the plugin's curve), so it is much wetter than it reads: 0.2 is already 48.8 % wet, 0.35 is 72.5 %. The default is 0.2, Volta's in-line value rather than the plugin's fully-wet 1.0 — a track insert sits in the signal path, and plate makes the same substitution. You will want a real blend, because the wet path itself is 13.6–19.7 dB below the input: this is a large diffuse field, not a slapback.
Measured (engine/src/galactic.rs + engine/tests/galactic_port.rs, 48 kHz, fully wet, brightness/detune 0.5):
| RT60 (Schroeder T20×3) | replace 1 | replace 0.75 | replace 0.5 | replace 0.25 |
|---|---|---|---|---|
bigness 0 | 0.19 s | 0.27 s | 0.45 s | 0.91 s |
bigness 0.25 | 0.98 s | 1.43 s | 2.27 s | 4.85 s |
bigness 0.5 | 1.84 s | 2.64 s | 4.15 s | 8.80 s |
bigness 0.75 | 2.64 s | 3.79 s | 6.00 s | 12.78 s |
bigness 1 (default) | 3.45 s | 4.97 s | 7.85 s | 16.75 s |
replace 0 is off the table by construction (loop gain 1.0, input gain 0).
| Probe | Result |
|---|---|
| onset — first nonzero wet sample, impulse in | bigness 0 / 0.25 / 0.5 / 0.75 / 1 → 7.6 / 29.6 / 51.7 / 73.7 / 95.7 ms. The tank's shortest path is delayL + delayD + delayH (618 + 862 + 2986 at bigness 1) plus the pre-delay. Nothing arrives before that; the swell is real, not a fade-in. |
| wet level, settled, fully wet, noise in (RMS out ÷ RMS in) | −13.6 dB at replace 1, −16.8 dB at 0.5, −19.7 dB at 0.25. The rest of the family sits near unity on its own wet-level probe; this one is a distant field, and that is the single biggest surprise when reaching for it. |
| mono in → L/R correlation of the tail | −0.019 (detune 0) / +0.030 (detune 0.5) / +0.008 (detune 1); side/mid within ±0.3 dB of unity. Fully decorrelated even with the LFO parked, so it is the crossed feedback doing it. |
tail after 1 s of noise then silence, replace 0.5 bigness 1 | live −32.3 → −48.6 (+2.5 s) → −66.8 (+5 s) → −100.6 (+10 s) → −161.4 dBFS (+20 s) |
same at replace 0.25 bigness 1 (the longest usable) | live −37.8 → −43.4 → −52.3 → −69.6 → −101.4 dBFS. Long, but monotone — it clears the stop-button gate. |
| detune LFO period (the pre-delay swing is a fixed 254 samples) | ≈ 12–20 s at detune 0.25, 1.5–2.4 s at 0.5, 0.45–0.72 s at 0.75, 0.19–0.30 s at 1. The range at each setting is the re-randomisation: the rate is redrawn from fpd on every wrap. |
10 s of digital black in, replace 0.25 | peak exactly 0.0 |
Fidelity. engine/src/galactic.rs was verified against a standalone build of the unmodified C++ over four corpora (lone impulses, a 0.5 s two-tone burst with a 7.5 s silent tail, seeded decorrelated stereo noise, a 20 Hz → 18 kHz exponential sweep) at 44.1 and 48 kHz across seven parameter sets including the defaults, both extremes and a near-freeze — 56 cases, max abs diff exactly 0.0 on both channels. The two documented departures from a byte-for-byte transcription are the fixed fpd seeds (upstream seeds from rand(), so a plugin instance is not reproducible and a Volta render must be) and the dropped denormal substitution, whose contribution was separately measured by building the C++ both ways: ≤ 6.8e-8 (−143 dBFS), and exactly 0.0 on any corpus with no sub-denormal samples.
Implemented in engine/src/galactic.rs.
Cost: 26 delay lines (≈1 MB of f64 per instance) plus two sin calls per sample; the estimator charges 45, between spring (30) and shimmer (55).
crunchCoat room [regen] [derez] [mix] — the cursed retro digital reverb (Airwindows CrunchCoat)#
Port of Airwindows CrunchCoat (c) Chris Johnson, MIT. The other reverbs here are all clean machines with damping in them; the lo-fi tools (crush, bbdDelay) put their damage around a reverb. This one puts it inside.
The mechanism. Four cascaded four-line blocks run in series (4 → 4 → 4 → 4), each mixing through M = 2I − J. As with galactic, MᵀM = 4I, so every block is lossless with gain exactly 2, the cascade is exactly ×16, and the output divides by 16. But the tank does not advance once per host sample. It advances when a fractional accumulator crosses 1.0, and the accumulator's step is
step = derez + lastVerb, lastVerb = (outL + outR) · derez · 0.029
— lastVerb comes from the reverb's own last output. The tank's sample rate therefore jitters with the program material, and that self-modulated zipper is the crunch. Between ticks the output is lastTwo − lastStep · cycle: a linear ramp running backwards off the last computed pair, not a resampler. The aliasing is the point.
It is deterministic — no randomness anywhere, and the fpd register that most Airwindows plugins carry is dropped here (checked, not assumed: the grunge is signal-derived). But the clock is chaotically sensitive: a 1e-8 perturbation eventually shifts a tick, and one shifted tick is an O(signal) difference. Same input, same output, always; nearly-the-same input, eventually quite different.
regen is the loop gain, exactly. The ×16 cascade cancels the 0.0625 in regen = (1 − (1 − x)²)·0.0625, leaving loop gain = 1 − (1 − regen)². Two consequences:
- The default
regen 0is not "a little feedback" — it is none. What you get is a bare sixteen-tap Householder cascade, RT60 0.09 s. That is the plugin's own default and it is a short, hard, boxy ambience. regen 1is a lossless freeze that still accepts input. Loop gain is exactly 1.0, and — unlikegalactic'sreplace 0, where the input attenuator closes at the same point — nothing gates the input here, so the tank integrates without bound and the ±1 output clamp is all that bounds what you hear. Upstream behaviour. Keepregen ≤ 0.8unless the freeze is the gesture.
derez is the tank's clock, and it is sample-rate independent. The step is (derez·0.99)²/overallscale and overallscale cancels the host rate, so the tank runs at (derez·0.99)²·44100 Hz in a 44.1 k session and a 48 k one alike. derez 0 mutes the wet path outright (upstream's explicit special case).
room is 17 discrete rooms, (int)(room·16.999), from a 96-seat room (5–51 ms of delay spread) to a 1541-seat hall (24–203 ms). Moving it clears the sixteen delay lines rather than crossfading — a scene cut, so ride it on a section boundary. (Upstream deliberately does not clear the eight feedback registers or the interpolator, so one round trip of the old room survives; that quirk is kept.)
mix is a submix blend, not a crossfade: wet = min(1, mix·2), dry = min(1, 2 − mix·2). Anything at or below 0.5 leaves the dry at FULL level and only scales the wet, and mix 0.5 is full dry plus full wet. The default is 0.25 — Volta's in-line value, not the plugin's fully-wet 1.0.
Stereo comes from a permutation, not a crossing. There is no L/R feedback path at all; the two channels walk the same sixteen lengths in different orders (L: A B C D → E F G H → I J K L → M N O P; R: D H L P → C G K O → B F J N → A E I M). Same room, transposed itinerary.
Measured (engine/src/crunchcoat.rs + engine/tests/crunchcoat_port.rs, 48 kHz, fully wet):
| RT60 (60 dB from +20 ms after the input stops) | regen 0 | regen 0.25 | regen 0.5 | regen 0.75 |
|---|---|---|---|---|
room 0 (96-seat) | 0.05 s | 0.27 s | 0.71 s | 3.07 s |
room 0.5 (255-seat) | 0.09 s | 0.45 s | 1.21 s | 5.31 s |
room 1 (1541-seat) | 0.21 s | 1.07 s | 2.85 s | 12.45 s |
regen 1 is off the table by construction (loop gain exactly 1.0).
| Probe | Result |
|---|---|
onset — first nonzero wet sample, impulse in, derez 1 | room 0 / 0.25 / 0.5 / 0.75 / 1 → 6.0 / 8.5 / 8.9 / 11.5 / 24.6 ms |
| tank clock | derez 1 / 0.75 / 0.5 / 0.25 / 0.1 → 43.2 / 24.3 / 10.8 / 2.7 / 0.43 kHz, i.e. a tick every 1.1 / 2.0 / 4.4 / 17.8 / 111 host samples at 48 k. Sample-rate independent. |
wet colour vs derez, noise in (HF above 4 kHz, re LF) | +1.7 dB at 1, −1.5 at 0.75, −6.9 at 0.5, −12.3 at 0.35, −21.4 at 0.2. Below ~0.5 it stops sounding like a room and starts sounding like a broken box. |
| wet level, settled, fully wet, noise in (RMS out ÷ RMS in) | 0.0 dB at regen 0, +4.2 dB at regen 0.5, −1.6 dB at derez 0.4. Unlike galactic this is a LOUD reverb — hence the 0.25 default mix. |
| mono in → L/R correlation of the tail | +0.07 … +0.08 across the derez range |
tail after 1 s of noise then silence, room 0.5 derez 1 | regen 0.25: live −11.8 → −86 (+0.5 s) → −270 (+2 s) → −1129 dBFS (+10 s). regen 0.5: −8.4 → −37.5 → −108 → −426. regen 0.75 (the longest usable): −4.2 → −9.4 → −28.0 → −113.5. Monotone throughout — it clears the stop-button gate. |
same at regen 1 | live −2.8 → −1.9 → −1.9 → −2.0 dBFS. It does not decay. |
10 s of digital black in, regen 0.85 | peak exactly 0.0 |
Fidelity. engine/src/crunchcoat.rs was verified against a standalone build of the unmodified C++ over four corpora (sine bursts with long silent tails, a 20 Hz → 18 kHz exponential sweep, seeded decorrelated stereo noise, a layered music-like signal) at 44.1 and 48 kHz across six parameter sets including the defaults and both extremes — 48 cases, max abs diff exactly 0.0 on both channels, 8 s buffers. The one documented departure is the dropped input denormal substitution, and here it is not a rounding footnote: measured by building the C++ both ways it is exactly 0.0 on any corpus that never reaches digital silence, but inside digital black the −145 dBFS floor it injects integrates in the lossless tank — −19 dBFS four seconds in at regen 0.35, −7.5 dBFS at 0.8, railed at regen 1. Cf. the galactic note above, where the same substitution was worth ≤ 6.8e-8; the difference is that Galactic's freeze corner closes its input and this one does not.
Implemented in engine/src/crunchcoat.rs.
Cost: 16 delay lines with independent L/R cursors and five 4×4 matrices per channel, but only on the samples where the clock ticks; the estimator charges 40, just under galactic (45).
dubly input [tilt] [shape] [output] — the noise-reduction round trip (Airwindows Dubly3)#
Port of Airwindows Dubly3 (c) Chris Johnson, MIT. A compander in series with its own decoder — which is what a Dolby-B/C tape chain physically was: encode on the way to the medium, decode on the way back, and what survives the round trip is the error. Nothing else in Volta is shaped like this. tape models a medium; saturate, inflate and tube are memoryless (or ADAA) curves; this is a level-dependent high-band gain driven by a smoothed µ-law estimate of the band's own amplitude, applied twice with opposite sign.
The chain, in order. (1) input gain (input·2)²; (2) encode — a one-pole splits off a high band high = (x − lp)·2.848 + avg (where avg is the previous sample's (x − lp)·1.152, a one-sample-lagged averager, which is why the band overshoots transients), high is clamped to ±1, its magnitude is run through m / (ln(1 + 255·m)/2.40823996531) and smoothed by the same one-pole coefficient, and high · comp · dublyAmount is added back; (3) the medium, sin(clamp(x, ±π/2)) — the only non-gain in the chain; (4) decode, the same structure with weights 2.628/1.372 and a negative depth; (5) output gain output·2.
tilt is the encode/decode balance and it is asymmetric. dublyAmount = tilt·2 runs 0…2, but outlyAmount = max(−1, (1 − tilt)·−2) saturates at −1 for every tilt ≤ 0.5. So the bottom half only fades the encoder out against a fixed full decoder (progressively darker and quieter), while the top half drives the encoder to double against a fading decoder (brighter and louder). 0.5 is the "correct" Dolby round trip: encode ×1 into decode ×−1.
shape splits one coefficient budget between the two stages. iirEncFreq = (1 − shape)/overallscale, iirDecFreq = shape/overallscale; at 0.5 both one-poles sit at ≈4.9 kHz (44.1 k) / ≈4.7 kHz (48 k). The outer quarter is an effect, not an EQ: a coefficient of zero both makes that stage's "high band" the whole signal and freezes its companding smoother at its 1.0 start — full depth, forever — so the level jumps +6.6 dB at shape 0 and +11.3 dB at shape 1. (There is also a rate quirk worth knowing: at exactly 44.1 kHz the other stage's coefficient reaches exactly 1.0, i.e. a one-pole with no memory, so the extremes behave slightly differently at 44.1 k than at 48 k. That is upstream's overallscale divide, kept.)
There is no wet/dry: dubly is a colour and always sits in-line. All four defaults (0.5) are the unity point.
Measured (engine/src/dubly.rs + engine/tests/dubly_port.rs, 48 kHz, at the defaults unless stated):
| 1 kHz sine, peak in | −40 dBFS | −20 | −12 | −6 | 0 |
|---|---|---|---|---|---|
| gain | −0.00 dB | −0.01 dB | −0.05 dB | −0.24 dB | −1.05 dB |
| THD | — | 0.06 % | 0.32 % | 1.13 % | 4.18 % |
| Probe | Result |
|---|---|
input / output range | (x·2)² → 0…4× (+12.0 dB at 1), unity at 0.5; x·2 → 0…2× (+6.0 dB at 1), unity at 0.5. input 0 is an exact mute. |
tilt, white noise, high band (>2 kHz) minus low | −0.96 dB at 0, −0.56 at 0.25, −0.25 at 0.5, +0.62 at 0.75, +1.33 at 1 — and the top half adds level too (+1.7 dB of low band at tilt 1) while the bottom half only takes it away (−0.8 dB at 0). |
shape, overall level on white noise | +6.6 dB at 0, −2.2 at 0.25, −0.5 at 0.5, +1.6 at 0.75, +11.3 dB at 1 |
shape → one-pole corners (44.1 k) | 0 → enc ∞ / dec 0 Hz; 0.25 → 9.7 k / 2.0 k; 0.5 → 4.9 k / 4.9 k; 0.75 → 2.0 k / 9.7 k; 1 → 0 / ∞ Hz |
| 10 s of digital black in, all knobs at 1 | peak exactly 0.0 |
Fidelity. engine/src/dubly.rs was verified against a standalone build of the unmodified C++ over the same four corpora at 44.1 and 48 kHz across six parameter sets including the defaults and both extremes — 48 cases, max abs diff exactly 0.0 on both channels. The one documented departure is the dropped input denormal substitution: exactly 0.0 on any corpus that never reaches digital silence, a −148…−151 dBFS self-noise floor inside a silent stretch where this port is at 0, and a transient of up to 0.024 (−32 dBFS) for 0.4–7.6 ms at a hard onset out of exact silence — because upstream's if (dubly > 0.0) guard freezes the companding smoother through true digital black but not through a noise floor. Both halves of that are upstream's; the guard is kept verbatim, the floor is not.
Implemented in engine/src/dubly.rs.
kPlate mode [input] [regen] [derez] [predelay] [mix] — the modelled plates (Airwindows kPlate140 / kPlate240)#
Port of Airwindows kPlate140 / kPlate240 (c) Chris Johnson, MIT. Where plate is a Dattorro figure-of-eight — an algorithm that happens to sound plate-ish — these are models of specific steel, and they ship as one builtin with a mode string because upstream ships them as two 894-line files that differ in exactly two expressions:
"140" — EMT 140 | "240" — EMT 240 "Gold Foil" | |
|---|---|---|
| the real thing | 2 m × 1 m steel sheet, ~270 kg | small gold-leaf membrane |
| delay table | 11…787 (mean 415) | 11…643 (mean 340) |
vlfpredelay | 16000 | 14000 |
blur | (1 − regen)·0.5 → reaches 0 | (1.618 − regen)·0.25 → floors at 0.155 |
loop gain 3125·regen | 0.609 … 0.922 | 0.719 … 0.875 |
Everything else — all 25 delay lines' topology, SubTight, the spring saturator, the undersampling — is shared, so engine/src/kplate.rs is one parameterised core and not a copy-paste pair.
Why it behaves the way it does. Five stages of five delay lines per channel, with M = 5I − 2J between them (5 on the diagonal, −2 elsewhere). MᵀM = 25I, so every stage is exactly 5× an orthogonal matrix — lossless, with an exactly representable gain. Four stages carry the forward path, so the output divides by exactly 5⁴ = 625; the feedback takes a fifth, 5⁵ = 3125, and regen is scaled to suit. Nothing inside the loop damps except a one-pole blur.
The stereo comes from a transpose, not a detune. Lay the delay letters out as a 5 × 5 grid, column-major:
A F K P U
B G L Q V
C H M R W
D I N S X
E J O T Y
The left tank walks the columns (A B C D E → F G H I J → …); the right walks the rows backwards (E J O T Y → D I N S X → …). Same twenty-five lengths, completely different path sets, and the two tanks' feedback paths are crossed (each tank's stage-0 feedback is the other tank's stage-5 output, as in galactic). No second delay table, no randomisation, no detune. Measured L/R correlation from a mono impulse: +0.277 ("140") / +0.267 ("240") — decorrelated, but not to zero the way galactic is, because both tanks share one modal set. That partial correlation is the plate-ness.
Three things that are not the tank:
- The tank is undersampled, and
derez 1is not "off". Upstream's tick test is> 1.0rather than>= 1.0, so the period is1/derez + 1ticks and even the maximum setting clocks the tank at half the sample rate. Loweringderezdivides further, which stretches every delay in real time — so it makes the plate bigger and darker, not merely grittier. - SubTight. Three cascaded
sin-based stages extract a hard-clipped sub-bass channel, which is subtracted from the tank input and re-added through its own long pre-delay (vlfpredelaytank samples — 725 ms on"140", 635 ms on"240"). It is what keeps the low end from smearing. sb, the spring saturator. Each of the ten feedback taps runsfb += 2·fb²·sb; sb += (fb − sin fb)·sbScale; sb = 64·sin(sb/64)— a slow, signal-driven asymmetry that only bites on loud tails.sbScalegoes to exactly 0 atregen 1, so the longest setting is also the cleanest.
regen runs FORWARDS — 1 is the longest tail. This is the opposite of galactic's replace, and with both on the same shelf it is the single easiest thing to get backwards.
Measured (engine/src/kplate.rs + engine/tests/kplate_port.rs, 44.1 kHz, fully wet, derez 1, predelay 0; RT60 by Schroeder backward integration, T30):
| RT60 | regen 0 | 0.25 | 0.5 (default) | 0.75 | 1 |
|---|---|---|---|---|---|
"140" | 2.58 s | 2.88 s | 3.97 s | 6.10 s | 7.99 s |
"240" | 2.37 s | 2.64 s | 3.12 s | 3.65 s | 3.90 s |
That spread is the reason both modes ship: the big plate has roughly twice the range at the top of the knob, the foil is the tighter and more consistent one. Neither goes truly short — 2.4 s is the floor.
derez at regen 0.5, "140" (the knob is quantised against overallscale, so intermediate values snap to 1/n):
derez | tank clock | onset | RT60 | centroid, first 0.37 s |
|---|---|---|---|---|
| 1 (default) | 22050 Hz | 11.6 ms | 3.97 s | 908 Hz |
| 0.5 | 14700 Hz | 17.4 ms | 5.55 s | 652 Hz |
| 0.25 | 8820 Hz | 29.1 ms | 9.20 s | 454 Hz |
| 0.1 | 4410 Hz | 52.3 ms | 16.30 s | — |
| Probe | Result |
|---|---|
onset vs predelay (impulse in, 44.1 kHz) | 0 / 0.25 / 0.5 / 1 → 11.6 / 193.0 / 374.4 / 737.3 ms, identical on both modes. The scale is the tank clock (16000 tank samples at 22.05 kHz), not the sample rate — so derez moves it too. |
| wet level, settled, fully wet, noise in (RMS out ÷ RMS in) | "140" −10.8 / −7.9 / −4.0 dB at regen 0 / 0.5 / 1; "240" −10.1 / −8.2 / −6.9 dB. Much closer to unity than galactic (−13.6…−19.7 dB) — this is a plate in the room, not a distant field. |
tail after 1 s of noise then silence, regen 1, fully wet | "140" live −19.4 → −39.9 (+3 s) → −76.5 (+8 s) → −110.8 dBFS (+13 s); "240" −20.3 → −65.7 → −138.1 → −207.1 dBFS. Monotone throughout — it clears the stop-button gate. |
10 s of digital black in, regen 1 | peak exactly 0.0, both modes |
| mono in → L/R correlation of the tail | +0.277 / +0.267 |
Against the Dattorro plate, with numbers. Both are on the shelf on purpose; they are not near-duplicates. Impulse in, fully wet, 44.1 kHz:
plate 0.6 | kPlate "140" … 0.5 | kPlate "240" … 0.5 | |
|---|---|---|---|
| RT60 | 2.50 s | 3.97 s | 3.12 s |
| onset | 8.9 ms | 11.6 ms | 11.7 ms |
| echo density at 50 ms | 0.114 | 0.663 | 0.683 |
| spectral centroid | 1940 Hz | 908 Hz | 818 Hz |
| 4 kHz octave, re its own band-sum | −6.2 dB | −10.6 dB | −12.0 dB |
| 8 kHz octave, re its own band-sum | −10.1 dB | −30.7 dB | −35.2 dB |
| L/R correlation, mono in | −0.025 | +0.132 | +0.149 |
So: kPlate is ~6× denser, and ~20 dB darker at 8 kHz (the two reverbs are within 4.4 dB of each other at 4 kHz — the cliff is above that), and it holds a partly-correlated centre. Reach for plate when you want an airy, sparse, wide studio shimmer; reach for kPlate when you want a thick, dark, physically crowded sheet of metal. The darkness is not an EQ choice — it is the undersampled tank plus the Bézier reconstruction, and above ~8 kHz you are hearing reconstruction images of lower content rather than the source's own top end. That is upstream's sound, faithfully.
input (default 1) pads the wet path only — the dry leg is tapped before it, so it trims what enters the plate without touching the through-signal.
mix (default 0.25, the plugin's own) is additive, not a crossfade: wet = (2·mix)² and dry = min(1, 2 − 2·mix), so anything at or below 0.5 leaves the dry completely untouched and 0.5 is unity on both legs. Upstream's stated intent — "if you're adding verb, you're not altering other balances". It is why kPlate keeps the shipped default where galactic's had to be lowered for in-line use, and why mix 1 (dry exactly 0) is the send-effect setting.
Fidelity. engine/src/kplate.rs was verified against a standalone build of the unmodified C++ over six corpora (a left-only impulse, a right-only impulse, a 0.5 s two-tone burst with a 7.5 s silent tail, seeded decorrelated stereo noise, a 20 Hz → 18 kHz exponential sweep, and a music-like chord-plus-click signal with a 4 s tail) at 44.1 and 48 kHz across six parameter sets including the defaults and both extremes, for both modes — 144 cases, max abs diff exactly 0.0 on both channels. The two documented departures are the fixed fpd seeds (upstream seeds from rand(), so a plugin instance is not reproducible and a Volta render must be) and the dropped input denormal substitution. fpd itself is kept, as in galactic and for the same reason: vibrato += fpd · 0.5e-13 is the only modulation in the whole plugin, so dropping the register would freeze the U-line allpass. The substitution's cost was measured by building the C++ both ways — peak 6.13e-8 (−144 dBFS) — but the shape matters more than the peak: with it in, the tail stops decaying and floors at a constant ~3.4e-8 RMS from ~5 s on, because the substituted value rides the dry leg straight to the output. Dropping it is what makes a true-silence-in / true-silence-out test possible at all.
Upstream quirks kept verbatim and documented in the module header: the > 1.0 tick test; derez quantised against overallscale (so the plate is very slightly smaller and brighter at 48 kHz than at 44.1); the vibrato's first interpolation branch being a no-op, which makes the drift a half-wave-rectified 0…1-sample fractional delay; and the pearA…pearF / iirAL / previous* state that is initialised but never read (PearEQ leftovers, not ported).
Implemented in engine/src/kplate.rs.
Cost: four one-poles, four lagged averagers, up to four logs and one sin per sample per channel, no buffers; the estimator charges 4, between airBand (2) and srsly (4).
Cost: 50 tank lines plus two 16000-slot pre-delays (≈1.5 MB of f64 per instance) and ~28 sin calls per tank tick — about 14 per output sample at the 2:1 undersampling. The estimator charges 70, above shimmer (55) and below convolve (80).
width amount [lowAnchorHz] — mid-side width#
Mid-side width as a 2×2 matrix on L/R: L' = a·L + b·R, R' = b·L + a·R with a = (1+w)/2, b = (1−w)/2. A constant amount ∈ [0, 2] maps directly (0 = mono, 1 = unchanged, 2 = doubled side); a live macro's [0, 1] travel maps across [0, 2] (0.5 = unity), so the knob sweeps narrow ↔ wide. The mid is untouched, so it stays mono-summable.
The optional low anchor (production-gaps F4 remainder, 2026-08-16) puts a one-pole highpass on the side channel before the re-encode — Airwindows' Sidepass, and the cheapest honest answer to "wide up top, solid underneath":
padTrack = play pad chords |> width 1.6 140Hz
Everything below the corner loses its difference component and folds back to the centre; everything above keeps the width it was given. 100–250 Hz is the useful range (120–150 Hz for a mix bus, 200 Hz+ for a pad you want anchored); the argument accepts live controls, clamped to [0, 1000] Hz, and 0 — the absent case — is no filter at all, so every width written before this existed is untouched to the bit.
Because only the side is touched, L + R is bit-identical with and without the anchor (measured max|Δ| 0.0 in effect::tests) — which is more than monoBelow's Linkwitz-Riley split can say. The corner is a sample-rate normalized coefficient (a = 1 − e^(−2πf/sr)), so the same song sounds the same at 44.1 and 96 kHz. Reach for monoBelow instead when you want a hard mono below a crossover with the phase relationship above it untouched; reach for the anchor when you want the image to taper into the centre.
ringMod b [mix] — ring modulation#
sig |> ringMod carrier is the bare product — sum and difference tones only, no fundamental. The optional third argument (production-gaps P2, 2026-08-16) is a 0..1 crossfade against the dry first operand, so sig |> ringMod carrier 0.4 is 40 % ring over the untouched signal.
It costs no duplicated sub-graph and no engine node: the lowering is the algebraic identity a·b·mix + a·(1 − mix) ≡ a · (1 + mix·(b − 1)), three extra control nodes. mix may be any control signal, so a macro rides the ring depth; mix 1 (and an omitted mix) emits the plain product it always did.
haas amount — precedence widener#
A sub-30 ms fractional delay on the right channel only; the Haas/precedence effect pushes the image wide. amount ∈ [0, 1] scales the delay (0 = identity, 1 ≈ 25 ms). The delay slews toward its target (τ ≈ 5 ms) so riding the knob glides the read head instead of stepping it. Mind mono compatibility at high settings.
The fractional read is 4-point Hermite (dsp::hermite4, the stencil every other native fractional read uses). It used to be 2-point linear, and because only the right channel goes through the line, that interpolator's own passband droop was a pure interchannel imbalance: at delay fraction ½ the right channel sat −2.01 dB at 10 kHz and −8.34 dB at 18 kHz against an untouched left, so the image's top octave pulled hard left and cycled through the whole swing every one sample of delay travel as the knob moved (2026-08-03 DSP audit §A6). Hermite brings that to −0.53 / −5.26 dB; the residual is the Catmull-Rom kernel's own droop, and it is pinned in engine/tests/stereo_interp.rs. Below a 2-sample delay (amount < 0.0017) the stencil has no "newer" tap to read and the linear form stands — it agrees with the Hermite exactly at the amount 0 identity.
srsly center space [level] [q] [nonlin] [mix] — psychoacoustic widener (Airwindows Srsly3)#
A port of Airwindows' Srsly3 (c) Chris Johnson, MIT — a Hughes-SRS-style processor, and a different animal from width/haas. It is not a side-channel scale and not a delay: nine narrow fixed-frequency bandpass sections run in parallel and are summed back, some phase-inverted and some gained up, against a mid/side-rebalanced dry signal. The ear reads the result as sound arriving from outside the physical speaker positions.
The bank (all frequencies fixed in Hz, and scaled with the sample rate, so the tuning holds at 44.1 / 48 / 96 kHz alike):
| path | bands | sign / gain in the sum |
|---|---|---|
| mid (L+R) | 2 kHz, 7 kHz, 10 kHz | +1, −2, −2 |
| side (L−R) | 3 kHz, 5 kHz | +2, −5 |
isolated L and R residual (in − mid/2) | 3 kHz, 7 kHz each | +1, +3 |
Two deliberate asymmetries are carried over from the source because they are load-bearing for the sound: the mid 2 kHz band takes a quarter of the focus figure (a much broader boost than its neighbours), and the two side bands take the mid focus rather than the side one.
Arguments, all ∈ [0, 1] and all live-modulatable by a macro (surface range is [0, 1], so knob travel maps straight through — no travel_range entry):
| arg | default | what it does |
|---|---|---|
center | — (required) | mid-image enhancement; also scales how much dry mid survives |
space | — (required) | side enhancement; also scales the dry side and the residual bands |
level | 1 | output pad |
q | 0.5 | band sharpness — scales the internal focus figure by 0.25…1.25 |
nonlin | 0.5 | analogifies the filters (see below) |
mix | 1 | dry/wet |
nonlin is the "analog" control: each section's input coefficient is multiplied by 1 + x·nonlin² and hard-clamped at 1, so a band's drive softens as signal pushes through it instead of staying perfectly linear.
It enhances stereo; it does not invent it. With L == R the side is zero and each channel's isolated residual (in − mid/2) is zero, so six of the nine bands see silence and only the mid path runs — a mono source comes out exactly mono (max |L − R| == 0, at every nonlin, pinned in engine/tests/srsly_port.rs). This is the sharp difference from haas, which widens anything you give it. Feed srsly material that already has an image.
The mono sum survives. Both the side bank and the two residual legs enter L and R with opposite signs, so L + R keeps the mid path — the dry mid at its gain plus the 2k/7k/10k mid bank. Folding to mono re-EQs and re-levels; it does not cancel. Measured mono-sum RMS against the input on a partly- correlated stereo pair: ×0.99 at defaults, ×1.30 pushed, ×2.22 with everything up, ×0.91 at nonlin 0. So this is safer under fold-down than haas — but it is not the identity on the mono sum the way width is, so listen in mono if the tone matters.
Watch the gain. level defaults to 1 (no pad) exactly as the plugin does, and this bank adds energy: side/mid rises ×1.40 at the defaults and ×3.86 with every knob at 1, and the all-knobs-at-1 corner peaked at 5.27 out of a ±0.5 decorrelated-noise input in the port's own corpus. Pull level down, or follow with limiter.
padTrack = play pad chords |> srsly 0.6 0.7 0.8
- Native:
engine/src/srsly.rs. A faithful f64 port — verified sample-exact (max abs diff 0.0 on both channels, 32 cases) against a standalone build of the original C++ over a stereo corpus (hard-panned 1 kHz sine, seeded decorrelated noise, mono-summed sweep, two detuned saws panned apart) at 44.1 and 48 kHz across four parameter sets including both extremes. The plugin's trailing floating-point-dither block and its fpd-based denormal substitution are dropped — Volta's engine is f64 end to end with no 32-bit output stage to dither into.
tripleSpread spread [tighten] [mix] — stereo tripler (Airwindows TripleSpread)#
A port of Airwindows' TripleSpread (c) Chris Johnson, MIT — and the exact complement of srsly above. Where srsly enhances an image that is already there and leaves a mono source bit-identically mono, this one manufactures width out of a centred source and leaves an already-decorrelated one alone.
Three copies of the signal end up in the image, which is where the name comes from:
| copy | where | what |
|---|---|---|
| dry | centre, both channels | untouched, at 1 − mix |
| tap A | left only | granular pitch tap, detuned sharp |
| tap B | right only | granular pitch tap, detuned flat |
Nothing is panned and nothing is delayed on purpose. The width is entirely the detune, which is why it does not comb away on fold-down (numbers below).
It is two glitchShifts. The plugin's per-channel loop is Airwindows GlitchShifter with the feedback path removed — the same 24-bit integer double buffer, the same zero-crossing registry, the same splice search, the same ±4.0 output clip. The Volta port does not re-transcribe it: it runs two engine/src/glitchfx.rs GlitchShifters with feedback = 0 and opposite speed, and the 98-case C++ sweep below is what proves that substitution exact. So the character of the wet path is glitchShift's character, at very small shift amounts.
Arguments:
| arg | range | default | what it does |
|---|---|---|---|
spread | [−1, 1] | — (required) | the detune. 0 is no spread at all; the sign only mirrors which side goes sharp |
tighten | [0, 1] | 0.5 | granular buffer length and splice recency bias |
mix | [0, 1] | 0.5 | dry/wet — and the default is load-bearing (see below) |
All three are live-modulatable by a macro. spread is the only slot whose surface range is not [0, 1], so a knob's travel maps across the full ±1 (0.5 = centre) — the place convention, and it has a travel_range entry for scene addresses.
mix defaults to 0.5 on purpose. The wet path is the two detuned side copies. At mix 1 the centre copy is gone and you have a duo, not a tripler; half wet is the plugin's own default and the one that makes the name true.
The detune is wide. At spread ±1 each tap moves 0.041666666666667 samples per sample: +70.67 cents on one side and −73.68 on the other, so the pair sits 144.35 cents — nearly a whole tone — apart. That is far too wide to read as a unison detune; it reads as a chorused triad, which is the effect's whole character. (The pitch axis is linear in speed, not in cents, which is why the two sides are not symmetric in cents. Same quirk as glitchShift.)
tighten is the splice character, and it runs the whole distance:
tighten | buffer width | tap laps every (at spread 1) | sound |
|---|---|---|---|
0 | 65536 | 33 s | smooth, slow, the tuning drifts |
0.5 | 16388 | 8.2 s | the default |
0.75 | 4101 | 2.1 s | audible splicing |
1 | 6 | 3 ms | nearly all splice — a fizz, and the width collapses |
That last row is real: at width 6 the two taps land on each other and the side energy falls to 0.04 of the mid on tonal material. Max tighten is a texture, not a wider spread.
One side arrives late — plan around it. On the first sample the tap walking toward the dry end goes negative, finds no zero crossings registered yet, and takes the original's position += width branch, landing a whole buffer back in memory nothing has written. That channel's wet path is therefore silent until the buffer fills: measured at 48 kHz, 0.33 s at tighten 0.5, 1.31 s at tighten 0, 13 ms at tighten 0.9, ~0 at tighten 1. It is upstream behaviour and it is deterministic. If the spread has to land on a downbeat, use tighten 0.85 or above.
Measured, at 48 kHz over 131072 samples (mono stepped-sine arpeggio; all pinned in engine/tests/triplespread_port.rs):
| setting | side/mid RMS | mono fold-down |
|---|---|---|
spread 0, mix 0.5 | 0.00 (output is exactly mono) | ×0.94 |
spread 1, mix 0.5 | 0.59 | ×0.60 (−4.4 dB) |
spread 1, mix 1 | 1.00 | ×0.71 (−3.0 dB) |
The fold-down survives. Two independent detunes are not a polarity trick, so L + R re-EQs and chorus-thins rather than nulling — there is no setting at which the mono sum collapses. Compare haas, which combs.
It does nothing to material that is already decorrelated. Each channel is detuned in place; nothing is crossfed. A hard-panned 220 Hz/277 Hz tonal pair measured a side/mid ratio of ×1.00 — dead unchanged — at every setting. Reach for srsly there instead.
Level. Tonal input passes at unity peak (0.50 in, 0.50 out). Full-scale white noise is the pathological case for the plugin's "air" HF-restoration integrator — exactly as it is for glitchShift — and can drive the tap into the original's ±4.0 hard clip. Follow with limiter, or keep mix down, if the source is broadband and hot.
Digital black is not quite black. The original's fpd denormal substitution is load-bearing here (it survives the ×8388352 into the integer buffer and can move a sample by an LSB) so it is kept, unlike in srsly where it was dropped. A silent bus therefore comes out at −146 dBFS (mix 0) to −138 dBFS (mix 1). Seeded from compile-time constants, so a render replays bit-identically; 90 dB below a 24-bit floor, but not a bit-exact zero.
padTrack = play pad chords |> tripleSpread 0.8 0.85
- Native:
engine/src/triplespread.rs. A faithful f64 port — verified sample-exact (max abs diff 0.0 on both channels, 98 of 98 cases) against a standalone build of the original C++ (stub VST header,-ffp-contract=off -fwrapv, bothfpdseeds poked to the port's constants) over a seven-signal corpus — mono noise in both channels, hard-panned noise, a decorrelated noise pair, a 1 kHz mono sine, a 20 Hz–20 kHz exponential sweep, a stepped-sine arpeggio and a hard-panned tone pair — at 44.1 and 48 kHz across seven parameter sets including the defaults, bothspreadrails, bothtightenrails andmix 0.- Upstream quirks kept verbatim: everything on
glitchShift's list (the never-writtenfourth[]register, the deadcountone/counttwoin the read section, the signed rather than absolute splice metric, the double splice in one sample, the extrapolating out-of-range guard). - One upstream quirk is dead code and is not reproduced: just before the output the plugin computes
mid = (L+R)·(1−wet)andside = L−R, re-encodes them intoinputSampleL/R, and then immediately overwrites both with thedry·(1−wet) + tap·wetblend.midandsideare locals, so the stanza has no state and no side effect. It is noted because it looks like the mid/side widener the plugin's name suggests, and is not. - One structural difference: the original keeps
gcount,flipandlastwidthin one place across both channels; the port has one per tap. They are value-independent, so the two instances step through identical sequences — and the hard-panned and decorrelated corpora are in the sweep precisely because a desynchronisedgcountwould show up there immediately.
- Upstream quirks kept verbatim: everything on
toVinyl [midHz] [sideHz] [hfLimit] [wear] — vinyl-mastering chain (Airwindows ToVinyl4)#
A port of Airwindows' ToVinyl4 (c) Chris Johnson, MIT — the chain a cutting engineer puts in front of the lathe. Where crackle / hiss / wow add the noise a record makes, toVinyl and grooveWear model the medium's own physics: what the cutter head can carve and what the stylus can read back.
Every argument is optional — a bare |> toVinyl cuts the plugin's reference lacquer. All four are live-modulatable by a macro; the two corners are Hz, so their [0, 1] knob travel maps across the full 10–300 Hz range (a travel_range entry, the place/width convention).
| arg | default | range | what it does |
|---|---|---|---|
midHz | 22Hz | 10–300 Hz | high-pass corner on the sum (L+R) |
sideHz | 44Hz | 10–300 Hz | high-pass corner on the difference (L−R) |
hfLimit | 0.32 | [0, 1] | cutter-head HF limiting (a de-esser) |
wear | 0.064 | [0, 1] | groove wear, inside mid/side |
The elliptical EQ is the reason to reach for it, and it is inherently mid/side. The bus is encoded to mid/side and each half runs its own 26-stage cascade of leaky one-pole integrators (resonance = 0.992, so each stage sits very slightly slower than the last — the elliptical part: a steep, staggered rolloff rather than one slope). The cascade's output is the low content; it is subtracted, so mid and side are each high-passed at their own corner. Raising sideHz above midHz strips bass from the difference while leaving it in the sum — which is physically what "make the bass mono" means to a lathe: a lateral groove modulation the cutter can track, not a vertical one that lifts the stylus out.
Measured −3 dB corners (44.1 kHz, engine/tests/tovinyl_port.rs):
| setting | mid corner | side corner |
|---|---|---|
22Hz 44Hz (default) | 17.2 Hz | 34.7 Hz |
22Hz 120Hz | 17.2 Hz | 96.6 Hz |
50Hz 200Hz | 39.4 Hz | 166 Hz |
10Hz 300Hz | 7.8 Hz | 260 Hz |
The realised corner lands ~0.78–0.87 × the nominal Hz (26 staggered poles each spend a little before −3 dB is reached), and the mid corner does not move when only sideHz does — the two cascades are genuinely independent. Below the corner the slope steepens as it descends: on the side path at sideHz 120, 40→20 Hz costs 36 dB and 20→10 Hz costs another 83 dB. A one-pole would spend 6 dB on each. Above 200 Hz both paths are within 0.17 dB of unity.
Watch out: the plugin defaults barely narrow anything. On 41 Hz hard-panned left, the side/mid RMS ratio only goes 1.00 → 0.83 at 22Hz 44Hz, because at 41 Hz the side path is a mere 1.5 dB below the mid path. That setting is a rumble filter, not a bass-mono'er. For the classic move raise sideHz: 22Hz 120Hz takes the same content to 0.18 (81 % narrower).
How it differs from monoBelow (Mastering and delivery §monoBelow freq). monoBelow f is a statement: a Linkwitz-Riley crossover, exactly mono below f and untouched above. toVinyl never collapses anything — it attenuates the side across a wide staggered transition and always leaves a residual (0.18, never 0). Use monoBelow when you need a guarantee for a cutting spec; use toVinyl when you want the transition to sound like a record rather than like a crossover. They compose fine: |> toVinyl 22Hz 90Hz |> monoBelow 40Hz shapes the narrowing and then guarantees the very bottom.
hfLimit triggers on the second difference of the signal — how fast the slope is changing — not on band energy, because a cutting amplifier's limit is acceleration, not level. Where it fires, the sample is crossfaded toward a 3-sample moving average, and the final output is additionally lerped toward the previous output by half the same trigger.
wear is the same slew erosion as grooveWear below, applied inside mid/side and asymmetrically — the mid path gets a 10-tap average scaled by wear·9 + 1, the side path a shallower wear·4.5 + 1, because records wear their loud lateral content faster. Measured on the mid path with hfLimit 0: wear 0.5 gives +4.7 dB at 4 kHz and −3.9 dB at 20 kHz; wear 1 gives +1.8 dB at 8 kHz and −3.5 dB at 20 kHz — the top-end loss is not monotone in wear, exactly as in the standalone.
mixer {
master = { inserts = [toVinyl 22Hz 120Hz 0.4 0.15] } -- the whole mix, off a record
}
- Native:
engine/src/tovinyl.rs. A faithful f64 port — verified sample-exact (max abs diff 0.0 on both channels, 50 cases) against a standalone build of the original C++ over five stereo corpora (seeded decorrelated noise, a layered music-like mix, hard-panned bass, a 20 Hz→18 kHz log sweep, bright transient material) at 44.1 and 48 kHz across five parameter sets including the defaults and both extremes. Two quirks are kept verbatim and documented in the module: the de-ess trigger'sm2term uses(s1−s2)where a textbook second difference would use(s2−s3), and the second groove-wear stage adds to the correction budget without feeding forward. The plugin's fpd denormal substitution is dropped — checked, not assumed: two different fpd seeds give max abs diff 0.0 across the whole corpus, because it only fires on literal digital silence (where it would sit at −156 dBFS). The one API change: the plugin's normalisedA/Bknobs areA²·290 + 10Hz and Volta takes the Hz directly.
grooveWear wear [mix] — stylus wear (Airwindows GrooveWear)#
A port of Airwindows' GrooveWear (c) Chris Johnson, MIT — the top end of a record that has been played a few hundred times.
| arg | default | what it does |
|---|---|---|
wear | — (required) | width of the slew-averaging window; 0 is a bit-exact bypass |
mix | 1 | how many of the four stages are engaged (not a dry/wet) |
Four cascaded stages, each keeping a 20-tap history of the input's first difference (x[n] − x[n−1], the sample-to-sample slew), forming a weighted moving average of it, and subtracting the part of the current slew the average does not account for — the slew a worn stylus can no longer track. wear spreads a total tap gain of wear²·19 + 1 across the 20 taps, so Σ f[k] = 1 always and wear 0 leaves exactly one filled tap: the average equals the current slew, the correction is identically zero, bypass.
It is a linear filter, but it is not a lowpass. Be precise about which claim is which: nothing here is nonlinear (doubling the input doubles the output exactly — pinned), so it is a filter. What it is not is a lowpass, and the measured magnitude response says why (44.1 kHz, mix 1, dB):
| Hz | wear 0.25 | wear 0.5 | wear 1 |
|---|---|---|---|
| 1 000 | +0.22 | +0.78 | +1.56 |
| 4 000 | +2.66 | +4.16 | −0.67 |
| 8 000 | +4.75 | −5.99 | −4.15 |
| 12 000 | +0.28 | −8.62 | −13.26 |
| 16 000 | −13.94 | −31.14 | −31.20 |
| 20 000 | −48.73 | −52.13 | −69.48 |
| 22 000 | −85.27 | −108.58 | −171.83 |
Three things a lowpass never does:
- It boosts — up to +4.8 dB through the presence region before the rolloff. That forward, slightly hard midrange is the sound of a worn pressing, and it is why
grooveWearreads as a different effect from turning down a filter. - The rolloff is a comb edge, not a slope. Algebraically the stage is
y[n] = x[n−1] + Σ f[k]·slew[n−k], blended 50/50 with its own input; at Nyquist the delayed term is −1 and the slew term vanishes, so the blend lands on exactly zero. The last octave collapses (−172 dB at 22 kHz atwear 1), and the ripple below it is non-monotone. - The corner does not track the knob. Measured −3 dB points:
wear 0.25→ 13.3 kHz,wear 0.5→ 7.0 kHz,wear 1→ 6.3 kHz. Past ~0.5 the knob mostly deepens and sharpens the ripple rather than moving the corner down.
mix engages stages rather than crossfading — mix·4 is spent filling four stages in turn, and each full stage is blended 50/50 with whatever entered it (the source: "the GrooveWear algorithm works best combined with dry"). So it reads as depth, and it moves the corner the other way: wear 1, mix 0.5 puts the −3 dB point back up at 8.5 kHz and halves the depth of everything.
Both knobs are live-modulatable, both at block rate — mix cannot ramp per sample without stepping the stage structure mid-block.
Sample-rate note: GrooveWear takes no sample rate. Its window is in samples, not seconds, so at 48 kHz the whole response shifts up by 48/44.1 — the same number of grooves per revolution, spinning faster.
drums = play kit beat |> grooveWear 0.4 0.8
- Native:
engine/src/groovewear.rs. A faithful f64 port — verified sample-exact (max abs diff 0.0 on both channels, 50 cases) over the same five-corpus × two-rate × five-parameter-set sweep astoVinyl. The plugin's trailing dither block is already commented out in its own f64 path; the fpd denormal substitution is dropped on the same measured grounds (two seeds, max abs diff 0.0; it only fires on digital silence, where it would sit at −151 dBFS).
mbcomp amount — multiband compressor#
A three-band compressor: a Linkwitz-Riley crossover (fixed 200 Hz / 2 kHz) splits the bus into low/mid/high, each band runs its own compressor (the same single-knob curve as compress), and the bands sum. The LR4 split reconstructs to a flat magnitude when the compressors sit near unity, so amount → 0 is roughly transparent.
True LR4 crossovers (two cascaded Butterworth biquads per filter) feed three Compressors.
stage room x depth / hallTail room — seats in a hall#
|> stage "hall" (-0.6) 0.2 { listener = "row10", radiator = "violin" } puts a track on the stage of a shoebox room ("chamber", "hall", "cathedral", "crypt") and outputs what a stereo pair at the listener ("conductor", "row10", "balcony") hears in the first 0.3 s: the direct sound and 62 image-source reflections, each with its own delay, absorption and the instrument's two-band directivity. x, depth and early are live. bus hall = hallTail "hall" { listener = "row10" } is the room's shared late tail (Eyring band RT60s, Sabine/Barron level for a 0 dB send of the dry track). Model, presets and measurements: Strings, bells and resonating objects § The hall.
reverb amount [wet] — FDN reverb, now per-band decay#
The existing reverb (the 8-line Householder FDN, engine/src/fdn.rs) gained frequency-dependent (Jot/Zita) decay: each feedback line is split at ~1.2 kHz into a low and a high band with independent RT60 gains (bass rings ≈1.5× the nominal decay, treble ≈0.45×), replacing the old single HF-damping pole. The result is the natural-room "good FDN" tail — bass blooms, treble dies first — at no API cost.
The tank is a pure per-sample state machine: fed the same signal it returns bit-identical output whatever the host's buffer size — verified at 128 / 96 / 64 / 37 and one sample per call, at both decay regimes (the >2.5 s tables run the deeper read modulation), and end-to-end through compile → engine → render with the wet on a settled macro (fdn::tests::the_tank_is_chunk_length_invariant, macro_ramp::a_static_reverb_is_block_size_invariant). A swept wet is a different question: the residual disagreement between two device periods is the macro smoother's piecewise-linear reconstruction (audit E7), which every ramped effect shares — the reverb simply multiplies it by the widest wet−dry span in the family.
bbdDelay time feedback [wet] — bucket-brigade analog delay#
A darker, grittier echo than tape: the delayed signal is companded (compressed into the line via |x|^0.7, expanded out), heavily bandwidth- limited (~3 kHz, vs tape's ~5 kHz), and soft-clipped in the feedback, with a slow clock-wobble — so repeats degrade into the classic analog-delay-pedal murk. time/feedback/wet as tape.
Two companded BbdLines (engine/src/effect.rs).
delayTrack time feedback [damp] [wet] [transition] / pingpong time feedback [damp] [wet] [transition] — the clean echoes, optionally damped#
Time accepts a composed seconds control, with capacity reserved before playback and a live range of one sample–10 seconds. The default "glide" transition moves a fractional head with 5 ms smoothing and Doppler pitch movement. "crossfade" blends stationary taps for 20 ms, retaining pitch while coalescing new requests until the current fade completes. On pingpong a gliding head reads its fractional position through a 24-tap Kaiser-windowed sinc (272-phase table, engine/src/effect/sinc_read.rs, evolve volta_pingpong #37) instead of the linear read, whose fraction-dependent low-pass drooped a 12 kHz echo by 1.7 dB and put AM sidebands on high partials; integer positions still read the ring sample, and a head within 12 samples of either ring end keeps the linear read. delayTrack keeps the linear read. A pingpong whose live controls are all settled for the block (a bound but constant wet/feedback/damp, a landed glide, an idle crossfade) runs the fixed path's contiguous segments, bit-identical to the per-sample loop. Tape and BBD time use physical Doppler motion, retaining their transport/clock histories; BBD reconstruction coefficients update every 16 samples. Their live histories reserve 10.1 seconds including motion headroom. Literal durations keep the legacy allocation.
The two plain digital echoes — delayTrack a mono line applied identically to both channels, pingpong a true L↔R bounce — take an optional damp ∈ [0, 1] (default 0), a one-pole low-pass inside the feedback loop. Every recirculation passes through it, so repeat n has been filtered n−1 times and the tail darkens as it decays, the way tape, a bucket brigade or a room does. A plain ring buffer does not: without damp repeat six is exactly as bright as repeat one, just quieter — spectrally static, and the reason a clean digital tail crowds a mix (DSP audit 2026-08-01, D9).
- The filter is in the recirculating path only, never on the wet tap: the dry signal and the first repeat are identical to the undamped line; what changes is what the tail becomes. On
pingpongboth hops are damped (the L→R cross-feed and the R→L return), so each bounce across the field is darker than the one before. dampmaps exponentially onto the one-pole corner:0→ no filter at all,0.25≈ 8.5 kHz (a gentle analog-ish loss per pass),0.5≈ 4 kHz,1≈ 900 Hz (a dub tank dissolving into murk). Because the loss compounds, small values go a long way at high feedback.- Below
0.05the knob is a fade-in rather than a corner sweep: even an 18 kHz one-pole costs ≈1.65 dB per pass at Nyquist, so a bare corner map would make0 → 0⁺the largest jump in the knob's travel. Instead the filter's output is crossfaded in over(0, 0.05), making the knob continuous from exactly-transparent (pinned bydamp_is_continuous_at_zero). That crossfade is a smoothstep,t²(3−2t)witht = damp/0.05— it was linear in the knob until 2026-08-05, which left the travel C⁰ but not C¹: the local slope of the tail's HF tilt read −158.7 dB/unit just below 0.05 and −38.7 just above, a 4.10× kink, and the fade span was the steepest region of the whole knob. Smoothstep has zero slope at both ends, so those two secants now read −42.2 and −38.7 (1.09×) and the approach to exactly-transparent is smooth too (−91.8 → −2.7 dB/unit atdamp = 5e-4). Nothing at or above0.05moved — the blend is a flat 1.0 there, which the untoucheddelay_damped/pingpong_dampedgolden hashes confirm. Pinned bydamp_mix_fade_is_c1_at_both_endsand thedelay_damp_fadegolden clip (2026-08-03 audit F7). dampaccepts live controls. A 5 ms smoother feeds a persistent 16-sample coefficient clock; the delay and damper histories remain running. The optional trailingwet(default 0.4) reads each sample, including dense control curves. Use named options to change wet without spelling damping. Zero wet keeps the loop running.tapeandbbdDelaywet controls use the same sample policy.- Omitting
damp(or passing 0) is bit-identical to the pre-dampengine — no filter is built and the original loop runs unchanged; pinned byundamped_delay_is_bit_identical_to_the_plain_ring(engine/src/effect.rs) and thedelay_clean/pingpong_cleangolden-audio clips. For a dirtier, more characterful darkening (saturation + wow/flutter + a fixed ~5 kHz loss) reach fortape/bbdDelayinstead;dampis the clean, surgical version. - Same surface in the mixer grammar:
inserts = [delay 0.25 0.5 0.6],inserts = [pingpong 0.125 0.4 0.3]. - The in-loop one-pole lives in
DelayLine/PingPong(engine/src/effect.rs); the undamped path is a separately monomorphized loop, so it carries no cost and no arithmetic change. - Example song:
examples/songs/darkecho.volta(the clean/damped A/B).
optical amount — LA-2A optical compressor#
A gentle, musical compressor: a soft knee, a low ratio (2:1 … 6:1), and a smooth, slow, program-dependent release. Three time constants shape the recovery, and it is worth knowing that the two obvious ones are not the important one:
- detector release, 0.5 s — the peak follower ahead of everything. This is what actually sets how long the unit hangs on: a peak follower releasing at τ falls linearly in dB at
8.686/τdB/s, so the gain computer downstream sees a ramp, and from 30 dB down that ramp alone runs ~1.7 s; - fast cell, 0.1 s and slow cell, 1.2 s — two dB-domain release cells whose average is applied, so half the recovery is quick and the rest is carried by the slow one, the photocell's long "memory". (Their
minwould collapse to the slow cell alone: both attack together, so the slow one is always the smaller gain.)
The "breathing" optical sound, distinct from the snappy compress/fet.
Since the 2026-08-03 audit (§B6) it runs on the same log-domain detector as the rest of the family (below) — it was the last unit smoothing the linear gain, which made its attack depend on how deep the reduction was rather than on the programme. Against its declared 10 ms attack τ (the LA-2A figure):
| final GR | attack 10–90 %, before → after | release 10–90 % |
|---|---|---|
| 5.1 dB | 34.3 ms → 13.3 ms | 2074 ms |
| 18.3 dB | 34.9 ms → 11.6 ms | 2369 ms |
| 25.0 dB | 37.6 ms → 11.1 ms | 2564 ms |
| 30.0 dB | 40.2 ms → 10.8 ms | 2723 ms |
The attack's spread across depth — the thing the linear domain got wrong — goes from 5.9 ms to 2.5 ms. The release is deliberately still depth-dependent: that is the detector ramp, and it is what "program-dependent" means for an opto. The steady-state gain reduction is unchanged to the last printed digit at every depth, so existing mixes sit exactly where they sat.
The slow constants were re-derived when the domain changed (2026-08-05). The same τ is not the same memory in the two domains — a linear-gain pole recovering from −30 dB is 87 % of the way back in dB after one τ, a dB-domain pole 63 % — so the slow cell moved 1.5 s → 1.2 s and the detector release 0.6 s → 0.5 s. The 2026-08-03 batch carried them across unchanged and lost the detector release entirely, which took half-recovery from 1597 ms to 335 ms and re-voiced the unit while every test still passed. The recovery trajectory is now pinned against a frozen replica of the pre-log-domain unit (
the_optical_recovery_trajectory_matches_the_pre_log_domain_unit), within 8 % at t10/t50/t90/t95 and at two depths, because a steady-state pin provably cannot see this class of change.
fet amount — 1176 FET compressor#
The aggressive counterpart: near-instant attack, a hard-ish knee, a high ratio (4:1 … 20:1), and a gentle tanh output coloration for the FET's harmonic bite. Fast and punchy. Its release is program-dependent (ButterComp2's trick): the deeper the current reduction, the slower the recovery — 80 ms becomes ~130 ms at 12 dB down.
- Attack. The declared 0.05 ms detector τ used to be swamped by the output gain slew (τ 0.25 ms = 12 samples, 3× the FET's own 4-sample control-rate hold): measured attack was 0.65–1.13 ms, i.e. the first millisecond of every kick went past ungrabbed (audit 2026-08-03 §B5). The slew now sits at exactly that hold — the rule
Compressorstates and follows, since the slew's only job is to bridge it — and the measured 10–90 % attack is 0.271 ms at 15 dB down, 0.292 ms at 23 dB, 0.312 ms at 28 dB (was 0.750 / 0.854 / 0.958 ms). The remaining floor is the 4-sample hold itself. The output
tanhis antialiased. It runs through the same second-order MinusX ADAA kernel as every other block-path saturator (dsp::drive_adaa2); it was the last un-antialiased saturator in the engine (audit §B8). Measured atfet 0— the gentle-glue setting, where the gain reduction stays shallow enough that the tanh really engages — on full-scale tones, worst non-harmonic component:f0 raw tanhADAA2 9 kHz −33.9 −65.2 dBc 15 kHz −33.9 −54.9 dBc ← worst 17 kHz −33.9 −55.2 dBc 19 kHz −33.9 −66.3 dBc So: 21 dB of worst-case suppression, to −54.9 dBc. It does not clear the −60 dB alias budget the audit series uses at every tone — the fold lands wherever
3·f0 − fslands and the suppression varies by 25 dB across the top octave, so the single-tone −65.2 dBc figure first published for this fix was unrepresentative. Cost: one sample of latency, like every other ADAA2 arm (not PDC-reported, same assaturate).- Tonal cost of the ADAA. The kernel's three-point average is a mild lowpass on the harmonics the transfer is meant to make, not only on the ones that would fold. Third harmonic relative to the fundamental: −33.97 dB at 302 Hz, −34.15 at 1 kHz, −34.83 at 2 kHz, −38.79 at 4.5 kHz — transparent below ~1 kHz, 4.9 dB down at 4.5 kHz (fifth harmonic 11.7 dB down there). Bass and low-mid material through
fetkeeps its bite exactly; a full-scale 4.5 kHz tone is measurably softer than the memoryless transfer would be. (The "4.5 kHz THD −33.9 → −38.8 dB" line first published for this fix was measuring precisely this droop and crediting it as antialiasing; it is withdrawn.)
transient attack [sustain] — differential-envelope transient shaper#
The threshold-free punch tool (production-gaps §30 G1): attack boosts or cuts the onsets, sustain the tails, both in dB (±24), and nothing else moves. Where pyewacket reshapes the energy under crests and the compressors trade level for density, transient responds to the envelope's shape — a steady tone passes at unity no matter the dials, and the same pluck 20 dB quieter gets the same shaping (both pinned in engine/tests/transient_dyneq.rs).
drums = play kit beat |> transient 6 (-3) -- snap up, ring down
room = play pad padPat |> transient (0-4) 6 -- soften onsets, swell tails
A negative dial is a negative literal — transient 6dB -4dB (since 2026-09-02; the limiter (-3dB) paren spelling still works). Both dials ride a macro (the [0, 1] travel maps across ±24 dB, 0.5 = flat — the formantShift convention).
Mechanism (the SPL differential-envelope law; the linear-ratio ancestor is Airwindows Point's nib/nob factor):
- one crest-riding envelope (0.3 ms attack / 80 ms release) feeds two detector pairs;
- attack = the envelope against its own 20 ms symmetric lowpass — during an onset the envelope runs ahead of the lowpass and their dB ratio engages the dial for the ~30–60 ms they take to reconverge; in steady state a lowpass of a constant is that constant, so the detector reads 0;
- sustain = two crest-followers with releases a decade apart (120 ms / 600 ms) — during a decay the fast one falls away from the slow one for the length of the tail (floored at −80 dBFS so silence is unity); at a steady level both sit pinned at the crest;
- a 1 dB deadband keeps a steady low tone's residual envelope ripple (≈ 0.5 dB at 60 Hz on the sustain pair) from reading as permanent "tail"; 8 dB of detector past the deadband maps to the full dial;
- the gain recomputes every
GAIN_DECIM/4samples and de-zippers through the matching slew — the FET's cadence, for the same reason (a 0.3 ms detector quantized to 0.33 ms would miss its own point).
Measured (48 kHz, the test suite's pluck rig): a +12 dB attack dial lifts the first 12 ms by ≥ 6 dB and moves the 150 ms-in decay < 1 dB; a −12 dB sustain dial takes ≥ 4 dB off the tail while moving the onset < 1.5 dB and tracks the same pluck at −20 dB within 1 dB; both dials at +12 move a held tone's sustain < 0.3 dB.
No gr_db readout on purpose: the gain is bidirectional and threshold-free, so a "reduction" meter would read as noise. (The dynamics view shows the compressor family; transient is not one.)
The detector shared by compress / mbcomp / optical / fet / deess#
(deess uses this smoother on its own band-limited sibilance sidechain against a program-tracked threshold — see Vowels, voice and pitch effects § The de-esser, in detail.)
Native only, and worth knowing because it is what these units sound like. The detector is log-domain (Giannoulis et al.; DAFx-22 paper 35 §3.2 fits the same structure to an LA-2A): the gain computer runs first, and the branching attack/release one-pole smooths the gain reduction in dB, not the level in linear. Two consequences:
- No sub-bass intermodulation. A rectified sine's envelope ripples at 2·f0; smoothing the level handed that ripple to the gain as amplitude modulation. Measured peak-to-peak gain modulation at 20:1: 60 Hz 0.128 → 0.007 dB, 30 Hz 0.292 → 0.029 dB.
- Attack and release mean what they say. An exponential in the linear domain is a straight ramp in dB, so the declared 3 ms / 150 ms used to measure 13.3 ms / 165 ms 10–90 %; now 6.6 ms / 265 ms against a one-pole reference of 6.6 ms / 330 ms.
Steady-state gain reduction is deliberately unchanged (worst drift 0.22 dB across the whole amount curve), so existing mixes still sit where they sat. ott's downward half gets the same peak-follower ahead of its fast envelope; optical joined the family in the 2026-08-03 audit (§B6, above), keeping its two-cell fast/slow average but running it on the dB reduction; its ballistics and the invariance of its steady-state reduction are measured in engine/tests/mastering_fx.rs.
airBand air [ground] [slew] [limit] — Kalman air-band EQ (Airwindows Air4)#
A port of Airwindows Air4 (c) Chris Johnson, MIT — the air EQ that is not a filter. engine/src/airband.rs.
Per sample and per channel a four-sample Kalman-style predictor estimates where the signal should go next: three successive differences of its own history (pv → acc → acc2, a discrete 1st/2nd/3rd derivative) extrapolate the next sample, and the error between that estimate and the arriving sample is the air band. The estimate is fed back into the predictor scaled by a gain that tracks how badly the last estimate missed (averaged, and capped at 0.3·sqrt(sr/44100)), so the split adapts to the programme rather than sitting at a fixed corner. The complementary "ground" is the input minus half the estimate minus a rate-compensated fraction of itself, two-point averaged.
| arg | Airwindows knob | range | default | what it does |
|---|---|---|---|---|
air | Air | 0–1 | 0.5 | gain on the residual; unity at 0.5, >0.5 boosts (steeply — see below), <0.5 cuts |
ground | Gnd | 0–1 | 0.5 | gain on the remainder; unity at 0.5 |
slew | DarkF | 0–1 | 0.52 | the HF slew ceiling: max sample-to-sample excursion is slew²/(sr/44100), relaxed by cos(last²) near zero crossings. Lower = darker |
limit | Ratio | 0–1 | 0.0 | dry/wet of that slew-limited copy — 0 leaves the limiter disengaged |
airBand 0.5 0.5 is an exact null (both gains land on 1.0 and the two paths reassemble the input bit-for-bit), which is what makes the knobs easy to trust.
Measured RMS gain, 48 kHz, −12 dBFS sine, ground 0.5 slew 0.52 limit 0:
air | 1 kHz | 5 kHz | 10 kHz | 15 kHz | 20 kHz |
|---|---|---|---|---|---|
| 0.25 | −0.08 | −1.80 | −5.07 | −0.10 | −3.93 |
| 0.50 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| 0.75 | +2.02 | +11.06 | +13.78 | +2.92 | +13.10 |
| 1.00 | +8.55 | +21.01 | +23.84 | +10.64 | +23.12 |
Two things to read off that table. First, the top of the knob is enormous — above unity the air gain is raised to 3 + sqrt(sr/44100), so air 1 is ~+24 dB of top end and full-scale white noise comes out with a peak of 19.3 (air 1 limit 0, ±0.8 noise in). Past ~0.7 either back off or put a limiter after it. Second, the response is not a shelf and not LTI: the adaptive feedback gain makes it level-dependent (the 15 kHz column is a level-dependent dip — at −34 dBFS the same setting measures +12.2 dB there, at −12 dBFS +2.9 dB), which is exactly why it sounds like "air" rather than like a boosted shelf.
limit is the governor. At air 0.75, 48 kHz, the same sine:
limit | 1 kHz | 5 kHz | 10 kHz | 15 kHz |
|---|---|---|---|---|
| 0.00 | +2.02 | +11.06 | +13.78 | +2.92 |
| 0.25 | +2.02 | +9.69 | +11.79 | +1.62 |
| 0.50 | +2.02 | +8.25 | +9.28 | +0.30 |
| 1.00 | +2.02 | +5.90 | +2.56 | −1.68 |
— the low end is untouched at every setting while the top is progressively tamed, and the peak on that same ±0.8 noise falls 19.3 → 9.5 going from limit 0 to limit 0.5 at air 1. slew sets where the ceiling sits (air 0.75 limit 0.5 at 10 kHz: +7.80 dB at slew 0.1, +9.28 at 0.52, +11.95 at 1.0).
All four knobs ride a macro (refreshed per block — they only feed derived constants, there is no per-sample coefficient to ramp), and the unit adds zero latency. Mixer/bus inserts spell it the same way: inserts = [airBand 0.7 0.5].
Fidelity. The port is diffed against a standalone build of the original processDoubleReplacing over 60 cases — 1 kHz sine, a 20 Hz–20 kHz sweep plus its retrograde, seeded white noise and a bipolar impulse train, at 44.1 / 48 / 96 kHz, at five knob settings including the plugin defaults and both extremes — and measures max |Δ| = 0.0, bit-exact on every sample (reference built -ffp-contract=off; left contracted, clang's fmadd fusion puts the same corpus at 4.4e-13, a property of that build rather than of the algorithm). Two deliberate omissions: the trailing 32-bit floating-point dither (Volta's bus never truncates to 32-bit inside the chain) and, with it, the plugin's fpd-based denormal substitution — the engine flushes denormals in the FP control register on the render thread instead, which is the same protection without injecting noise into digital silence.
Tone by density — stonefire and fatEq#
Two ports of Airwindows' "non-EQ EQs" (Stonefire, designed for its ConsoleX bus, and FatEQ), both (c) Chris Johnson, MIT. One shared engine module, engine/src/densityeq.rs.
They are a category of their own in this palette. Every other tone control Volta has — eqTrack, tilt, even airBand — answers "make this band more present" with level: a filter's output multiplied by a dB figure. These two never do. They split the bus into bands and then change how hard each band is driven into a saturation curve. A band gets denser, grainier and more audible without a filter being boosted anywhere, which has three consequences worth internalising before reaching for either:
- Nothing rings. There is no resonant section to store energy, so there is no ringing, no pre-echo and no phase smear at any setting.
stonefiregoes further and has no filters at all — its bands come out of predictors. - The "boost" is level-dependent, and that is the feature. A peaking EQ set to +12 dB gives +12 dB whether the input is at −30 or 0 dBFS. These do not.
- A density boost survives a limiter. Harmonic content is not peak level, so what these add largely stays after the mastering chain, where a 12 dB peak boost gets eaten.
Here is (2) measured — a 1 kHz sine at seven levels, every band knob pushed from neutral to full, reported as (change in the fundamental) / THD, at 48 kHz:
| input | eqTrack [ peak 1000 12 ] | stonefire all 1.0 | fatEq all 1.0 |
|---|---|---|---|
| −30 dBFS | +12.00 dB / 0.0 % | +12.98 dB / 0.8 % | +23.26 dB / 1.0 % |
| −24 dBFS | +12.00 dB / 0.0 % | +13.05 dB / 1.6 % | +22.50 dB / 3.8 % |
| −18 dBFS | +12.00 dB / 0.0 % | +13.22 dB / 2.9 % | +18.95 dB / 17.0 % |
| −12 dBFS | +12.00 dB / 0.0 % | +13.41 dB / 2.7 % | +13.10 dB / 28.6 % |
| −6 dBFS | +12.00 dB / 0.0 % | +13.44 dB / 1.3 % | +6.71 dB / 39.2 % |
| −3 dBFS | +12.00 dB / 0.0 % | +13.44 dB / 1.0 % | +3.57 dB / 41.1 % |
| 0 dBFS | +12.00 dB / 0.0 % | +13.44 dB / 0.8 % | +0.56 dB / 41.9 % |
Read the three columns as three different machines:
- the EQ is LTI — one number, every level, no harmonics, forever;
stonefiredelivers about the same amount of lift as the EQ but is not linear while doing it, and its grain runs opposite to intuition: harmonics peak on quiet material (2.9 % at −18 dBFS) and fade as the signal gets loud (0.8 % at 0 dBFS), because its predictor tracks a strong signal better than a weak one. It flatters detail rather than crunching peaks;fatEqis the pure case. Its lift collapses from +23 dB to +0.6 dB across 30 dB of input while distortion climbs 1 % → 42 %. Turn a knob up and quiet passages get much louder; the same knob on a hot passage barely moves the level and the "more" arrives as harmonic content instead. It is self-levelling by construction.
Pick accordingly: eqTrack when you want a measurable dB, stonefire when you want console-style bus tone that stays out of the way, fatEq when you want a part to feel bigger inside a mix that has no headroom left.
stonefire air [fire] [stone] [range] — 3-band density tone (Airwindows Stonefire)#
The bus is split by two predictors, not two crossovers:
- an Air3 four-sample predictor (the same one
airBanduses) runs over the input; the two-point-averaged complement of its estimate is the mid+bass body, and what the predictor could not anticipate is the treble; - a deeper Kalman stage runs over that body; its smoothed output is the bass, and the remainder is the mid.
The three bands are then recombined at air / fire / stone — and the gain laws are where the density lives. Above unity the treble gain is raised to 3 + sqrt(sr/44100) and mid/bass are squared; below unity every gain becomes 1 − (1 − g)², a square-law taper that thins a band out rather than turning it down (fire 0 measures 6.9 % THD, not 0 %).
| arg | Airwindows knob | range | default | what it does |
|---|---|---|---|---|
air | Air | 0–1 | 0.5 | treble band; unity at 0.5 |
fire | Fire | 0–1 | 0.5 | mid band; unity at 0.5 |
stone | Stone | 0–1 | 0.5 | bass band; unity at 0.5 |
range | Range | 0–1 | 0.5 | mid/bass crossover — how much signal the Kalman stage sees (1 − range²) |
stonefire 0.5 0.5 0.5 is a null whatever range is doing — all three gains land on exactly 1.0 and the three bands are a complete decomposition of the input. Numerically, not bit-exactly: they are re-summed as (bass + mid) + treble and that association loses the last bit, measured max |Δ| = 5.6e-17 (≈ −325 dBFS).
Which frequencies each knob owns (RMS gain in dB with that knob at 1.0, the rest neutral; 48 kHz, −12 dBFS sine):
| 50 | 100 | 250 | 500 | 1 k | 2 k | 4 k | 8 k | 15 k | |
|---|---|---|---|---|---|---|---|---|---|
air | +0.6 | +0.8 | +1.8 | +4.2 | +8.6 | +14.0 | +19.5 | +23.1 | +10.6 |
fire | +3.9 | +4.6 | +6.9 | +9.4 | +10.9 | +11.3 | +10.6 | +6.3 | +12.0 |
stone | +10.8 | +10.7 | +10.0 | +8.3 | +5.1 | +2.0 | −0.7 | −0.8 | 0.0 |
These are broad and heavily overlapping — that is what splitting with predictors buys, and it is why the unit behaves like a console tone stack rather than a surgical EQ. (air's dip at 15 kHz is the same level-dependent notch documented for airBand, from the same predictor.)
range is more of a mode switch than a frequency (RMS gain of stone 1.0):
range | 50 Hz | 100 Hz | 200 Hz | 400 Hz | 800 Hz |
|---|---|---|---|---|---|
| 0.00 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 0.25 | +2.3 | +2.3 | +2.3 | +2.2 | +1.9 |
| 0.50 | +10.8 | +10.7 | +10.3 | +9.0 | +6.2 |
| 0.75 | +11.4 | +11.3 | +11.3 | +11.0 | +10.0 |
| 1.00 | +12.0 | +12.0 | +11.9 | +11.9 | +11.7 |
At range 0 the Kalman stage's input is scaled by 1 − range² = 0, so the bass band is empty and stone does nothing at all; at range 1 the whole spectrum counts as bass and stone becomes a broadband density control.
There is no output stage. A mix peaking at 0.695 comes out at 3.30 with all three bands full (+13.5 dB), so follow a pushed stonefire with level (the fader, which takes dB) or a limiter:
bus = play kit beat |> stonefire 0.62 0.58 0.55 |> level (-9dB)
All four knobs ride a macro at block rate, and the unit adds zero latency. Mixer/bus inserts spell it the same way: inserts = [stonefire 0.6 0.55].
fatEq high [highMid] [lowMid] [bass] [out] — 4-band density tone (Airwindows FatEQ)#
Four bands from three cascaded "pear" filters — one-poles that mix the input against the previous output and the previous slew, so the skirt steepens with the signal's rate of change. high = in − A, highMid = A − B, lowMid = B − C, bass = C. Each band goes through its own curve and the four are summed back flat: there is no gain on any band anywhere in the algorithm.
The two curves are Taylor series, not tanh:
- above unity, an 11th-order
sin(x·π/2)on the clamped band — a soft saturator that adds harmonics as the band is driven; - below unity, a 10th-order
1 − cos(x)carrying the input's sign — a curve that is quadratic at the origin, so it squashes small signals far harder than large ones and turning a band "down" hollows out its low-level detail.
At exactly unity neither branch fires and the band passes through, which is why the neutral setting nulls. Then the whole structure runs six times per sample, each pass with its own filter state: the crossovers get steeper and the saturation compounds. That is the "fat".
| arg | Airwindows knob | range | default | what it does |
|---|---|---|---|---|
high | High | 0–1 | 0.5 | top band density; unity at 0.5 |
highMid | HMid | 0–1 | 0.5 | high-mid band density; unity at 0.5 |
lowMid | LMid | 0–1 | 0.5 | low-mid band density; unity at 0.5 |
bass | Bass | 0–1 | 0.5 | bass band density; unity at 0.5 |
out | Out | 0–1 | 1.0 | output trim (the only slot that ramps per sample) |
fatEq 0.5 0.5 0.5 0.5 nulls to 3.9e-16 (≈ −308 dBFS) — not bit-exactly, because the four bands are re-summed in a fixed order six times over — and one sample late.
Band ownership (RMS gain in dB, one knob at 1.0, 48 kHz, −12 dBFS):
| 50 | 100 | 250 | 500 | 1 k | 2 k | 4 k | 8 k | 15 k | |
|---|---|---|---|---|---|---|---|---|---|
high | 0.0 | 0.0 | +0.1 | +0.5 | +2.0 | +6.3 | +12.4 | +13.7 | +11.2 |
highMid | +0.5 | +1.8 | +7.7 | +12.8 | +13.6 | +13.6 | +12.2 | +5.9 | +2.8 |
lowMid | +9.6 | +13.2 | +13.5 | +12.0 | +4.4 | −0.6 | −1.3 | −0.4 | 0.0 |
bass | +12.7 | +8.5 | −1.3 | −2.2 | −0.9 | −0.2 | 0.0 | 0.0 | 0.0 |
Remember the caveat those numbers carry: this is the −12 dBFS row of the level table above. Push the same knobs against a hot bus and the dB shrink toward zero while the harmonics take over.
Unlike stonefire, fatEq is self-contained on level: the out trim plus Airwindows' ClipOnly2 output stage keep the six compounding saturation passes from squaring off, so the same 0.695-peak mix comes out at 0.955 (+2.8 dB) with every band full. The price is one sample of latency, PDC-reported and compensated exactly as clipOnly's is.
bassBus = play bass bassline |> fatEq 0.5 0.55 0.7 0.72
The four band knobs ride a macro at block rate (each re-derives the parameter head); out ramps per sample like every other output gain. Mixer/bus inserts: inserts = [fatEq 0.6 0.5 0.6 0.55].
Fidelity of the pair#
Both cores are diffed against standalone builds of the originals' processDoubleReplacing over 693 cases — four sines (100 Hz/−6, 1 kHz/−20, 10 kHz/−3, 1 kHz/−60 dBFS), a 20 Hz–20 kHz exponential sweep, seeded decorrelated noise, and a full-mix-like layered signal (55 Hz sub + eight-partial 220 Hz saw + gated hats + a transient train), at 44.1 / 48 / 96 kHz, across five (Stonefire) and six (FatEQ) parameter sets including the defaults and both extremes, at three block sizes (512 / 64 / 1000). Max |Δ| = 0.0, bit-exact on every sample of both channels in all 693 cases. Reference built -ffp-contract=off. Pinned in engine/tests/densityeq_port.rs.
Three notes on the port:
- Block size is part of Stonefire's contract. Its three band gains are interpolated across the block from the previous block's target to this one's, so the smoothing time is the host's period — which is why the corpus sweeps block sizes, and why the Volta wrapper deliberately does not put a
RampCurveon those knobs: the plugin's own ramp is the interpolation, and layering a second one would change the reference sound. - The
fpdregisters are dropped, as inairBandand unlikeclipOnly. In both plugins' f64 paths the trailing dither is already commented out upstream, leavingfpd's only consumer the denormal substitution; the engine flushes denormals in the FP control register instead, which is the same protection without injecting noise into digital silence. - Two upstream quirks are kept verbatim: FatEQ's ClipOnly2 shift loop copies the new sample all the way down its delay line, so the latency is one sample at every rate rather than
floor(sr/44100); and Stonefire's per-sample gain interpolation runs even when the endpoints are equal, which is not bit-identically a constant. Both are reproduced. (One thing is not kept: upstream declares FatEQ'sintermediate[16]and then indexes[spacing]withspacingclamped to 16 — a one-slot overrun at ≥ 705.6 kHz. The Rust array is sized 17 so the same index is in bounds.)
The Airwindows track-flavors wave — beziComp, weight, silken, mackity, cornerClip#
Five small units, one engine module (engine/src/trackflavors.rs), each here because it does its job with arithmetic nothing else in the language uses. All five are bit-exact f64 ports of Chris Johnson's originals (MIT); the fidelity numbers are at the end of this section.
One thing they share and it matters: the fpd registers are load-bearing in all five and are ported, not dropped. Each opens its sample loop with if (fabs(inputSample) < 1.18e-23) inputSample = fpd * 1.18e-17; and closes it by advancing the xorshift. The dither add is commented out in the plugins' own f64 paths, but the register is still read by the next sample's denormal guard, and the substituted value feeds live DSP in every one of them. Dropping it would put the port off the reference on any signal containing a zero — which is every impulse train, every gated tail and every silent channel. Two consequences, both upstream's: digital silence comes out at the seeded floor — MEASURED 3.85e-8 on the left and 7.89e-9 on the right, i.e. −148 and −162 dBFS, both under the 24-bit floor — rather than at zero, and because upstream seeds from rand() while Volta renders must be reproducible, each channel gets a fixed compile-time seed instead (the same deviation clipOnly and pyewacket make).
beziComp [comp] [speed] [mix] — the Bézier-curve compressor#
The gain path is not an envelope follower. The rectified programme is undersampled into buckets and a quadratic Bézier curve is drawn through the last three:
- a phase accumulator advances by
bezRezper sample whilesampaccumulatesmax(|L|, |R|) · bezRez— so a bucket holds the mean level over its span, not a peak; - when the phase wraps past 1 the buckets shift (
C ← B ← A ← samp); - between wraps the control value is the de Casteljau evaluation at the fractional phase
t, which expands to0.5·B + 0.5·[C(1−t)² + 2B·t(1−t) + A·t²].
That control signal is C¹-continuous: at t = 1 its value is 0.5(A + B) and its slope is A − B; after the shift, at t = 0, the value is 0.5(A + B) and the slope is B′ − C′ = A − B. Both carry over, for any incoming bucket. A one-pole envelope is only C⁰ — its slope jumps every time the detector changes direction — and those slope corners are what a compressor's intermodulation grit is made of. With no corner in the gain signal there is nothing wideband to alias, which is why the detector can run orders of magnitude below audio rate and still sound smooth. (trackflavors.rs's the_bezier_gain_path_is_c1_continuous_across_a_bucket_shift pins exactly this.)
speed sets how far below audio rate, on a sixth-power law:
speed | bucket period @ 48 kHz | bucket rate |
|---|---|---|
| 0 | 227 ms | 4.4 Hz |
| 0.25 | 66 ms | 15.2 Hz |
| 0.5 (default) | 1.4 ms | 694 Hz |
| 0.75 | 0.13 ms | 7.9 kHz |
| 1 | one sample | 44.1 kHz |
MEASURED 10 → 90 % of the gain-reduction meter after a step to −12 dBFS at comp 0.25: 623 ms at speed 0.1, 31 ms at 0.5, 28 ms at 0.9 — the last two are floored by the meter's own 10 ms smoothing, not by the unit.
comp is the threshold and the makeup at once — thresh = comp² · 64, makeup = sqrt(thresh + 1), and the gain is the hard-kneed 1 − min(CBAL · thresh, 1) clamped so the unit mutes rather than inverts. MEASURED RMS gain on a steady 1 kHz tone (speed 0.5, mix 1, 48 kHz):
comp | −40 dB in | −30 dB | −24 dB | −18 dB | −12 dB | −6 dB |
|---|---|---|---|---|---|---|
| 0.05 | +0.64 | +0.62 | +0.59 | +0.53 | +0.42 | +0.19 |
| 0.1 | +2.11 | +2.04 | +1.92 | +1.69 | +1.21 | +0.17 |
| 0.15 | +3.79 | +3.62 | +3.36 | +2.81 | +1.60 | −1.46 |
| 0.2 | +5.37 | +5.06 | +4.57 | +3.52 | +0.95 | −9.15 |
| 0.25 | +6.77 | +6.26 | +5.47 | +3.64 | −1.85 | mute |
| 0.5 (upstream's default) | +11.37 | +8.93 | +3.39 | mute | mute | mute |
comp is a low-range knob, and past a level it CLOSES rather than limiting. The gain is 1 − min(CBAL · thresh, 1), clamped at zero, and CBAL is the mean rectified level — so once mean|x| · comp² · 64 ≥ 1 the multiply is exactly 0 and the output is silence. For a sine (CBAL = (2/π)·amp) that point is:
comp | threshold | makeup | closes at |
|---|---|---|---|
| 0.05 | 0.16 | +0.64 dB | never (above full scale) |
| 0.1 | 0.64 | +2.15 dB | never |
| 0.15 | 1.44 | +3.87 dB | never |
| 0.2 | 2.56 | +5.51 dB | −4.2 dBFS |
| 0.25 | 4.00 | +6.99 dB | −8.1 dBFS |
| 0.5 | 16.0 | +12.30 dB | −20.2 dBFS |
| 1.0 | 64.0 | +18.13 dB | −32.2 dBFS |
Stay under about 0.2 unless a gate that swings the wrong way is the effect you want. That behaviour is upstream's arithmetic, unmodified — it is the reason the plugin is a character compressor rather than a bus glue.
comp 0 is an exact wire (threshold 0, makeup 1). The detector is stereo-LINKED on max(|L|, |R|) and the gain is applied to both channels, so the image cannot wander; the ClipOnly2 stage on the output has its own per-channel state. That stage also gives the unit one sample of latency at every rate, reported to PDC. The mix crossfade happens before it, so the dry path is delayed too and the delay is fully compensable.
One sample and not spacing, despite the source comment's "Latency is however many samples equals one 44.1k sample": upstream's shift loop is for (x = spacing; x > 0; x--) intermediate[x-1] = intermediate[x];, and walking downward copies each slot from the one above after that one has already been overwritten — so the newest sample cascades to index 0 in a single pass. The buffer is a broadcast, not a shift register. (ClipOnly3 has the same loop and clipOnly documents it the same way; there spacing at least survives in an averaging divisor, here in nothing at all.)
Three upstream quirks kept verbatim: the bucket phase is constructed at 1.0 so the very first sample immediately wraps and shifts an all-zero chain; bez_InL/bez_UnInL are declared in the state enum and never touched; and upstream's intermediateL[16] is indexed with a spacing clamped to 16, so a ≥ 705.6 kHz session would write one past the end (Volta's array is 17 long — the arithmetic is unchanged at every rate anyone runs).
Volta's defaults are upstream's 0.5 / 0.5 for comp/speed; mix defaults to 1, not upstream's 0.5, because every other mix slot in the language is fully wet by default and a silently half-dry compressor would be a trap.
weight amount [freq] — sub weight, and not a shelf#
The added band comes out of eight cascaded Holt's-method double exponential smoothers — the textbook forecasting recursion, run on audio, one stage per pass:
trend_i = β·(x − s_i) + (0.999 − β)·t_i // the level's slope
forecast_i = s_i + t_i // where it is headed
x = α·x + (0.999 − α)·forecast_i // blend toward the forecast
The distinguishing move is forecast. An ordinary one-pole blends toward its own last output, so it lags a rising signal forever; Holt's method blends toward the last output plus its extrapolated slope, so it tracks a ramp with no steady-state lag at all. Eight in series give a very steep extraction that nevertheless does not smear a kick's attack, and the result is added to the dry signal rather than replacing any of it.
That is the whole reason to reach for this instead of an eqTrack low shelf. A shelf's transition is one slope, so it lifts the 100–250 Hz where mud lives together with the 30–60 Hz where weight lives. MEASURED gain in dB, 48 kHz:
| setting | 20 Hz | 30 | 40 | 50 | 60 | 70 | 85 | 100 | 120 | 150 | 250 | 500 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
weight 0.5 70Hz | +4.26 | +4.89 | +3.91 | +3.65 | +8.54 | +8.79 | +1.63 | −1.51 | −0.22 | −0.00 | 0.00 | 0.00 |
weight 1 70Hz | +7.25 | +9.09 | +11.26 | +13.84 | +16.79 | +17.68 | +11.39 | −7.89 | −0.78 | +0.04 | 0.00 | 0.00 |
weight 1 30Hz | +8.03 | +5.18 | +0.80 | +0.05 | 0.00 | −0.00 | −0.00 | −0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
weight 1 120Hz | +6.31 | +6.77 | +7.39 | +8.11 | +8.85 | +9.53 | +10.23 | +10.34 | +9.60 | +8.93 | +3.24 | −0.03 |
lowShelf 70Hz +6dB | +5.96 | +5.79 | +5.39 | +4.72 | +3.87 | +3.00 | +1.93 | +1.21 | +0.66 | +0.29 | +0.04 | 0.00 |
Read the last two rows against each other. The shelf is one slope that never stops: it is still adding a dB at 100 Hz and a third of a dB at 150. weight is a resonant lift at the corner with a null immediately above it — −7.9 dB at 100 Hz at the default corner — and is then flat to within 0.05 dB from 150 Hz up. That null is the whole trick and it is also the thing to watch: at freq 70 the dip sits where a kick's body often lives. Move the corner down to 30–45 Hz for sub-only lift (flat by 50 Hz) or up to 120 Hz for a broad low lift that stays +8.9 dB at 150.
Two upstream constants, both kept: the coefficient complements are 0.999 − β and 0.999 − α, not 1 − β/1 − α, so each stage leaks 0.1 % per pass — that is what keeps an eight-deep integrator chain from running away on DC; and α is derived through the square root of the rate compensation (targetFreq = ((knob + 0.53) · 0.2) / sqrt(overallscale)), upstream's own note "must use square root of what the real scale would be, to get correct output".
amount is required in Volta because upstream's default is 0, which is a no-op; it is also part of the resonance (resControl = amount·0.05 + 0.2, β = α · resControl²), so turning the boost up sharpens the extraction slightly. freq is authored in Hz over the plugin's own 20–120 span (default 70). Fully independent stereo; zero latency; both knobs ride a macro.
silken amount [freq] [window] — air-band texture#
A windowed-sinc lowpass, subtracted — which would be an unremarkable linear-phase high shelf, except that the taps are not consecutive samples. Tap i reads the input unprime[i] samples back, where unprime[] is 1 followed by every composite integer (1, 2, 4, 6, 8, 9, 10, 12, 14, 15, …): the primes are skipped. A regular tap grid is a comb, and a comb of N equally spaced taps puts its stopband ripple at exact harmonic multiples — which is what makes a cheap FIR sound grainy on noise-like material. Sampling the sinc on a sequence that is irregular but never resonant (the gaps at every prime break every period) smears that structure toward a noise floor. The filter is still linear-phase — the taps are symmetric about middle — but its error is textured rather than tonal. Hence silken.
The kernel is rebuilt whenever a knob moves (upstream rebuilds it every block; Volta guards on the value actually changing, so a settled macro costs nothing):
freq = freq_knob² · π/2 (floored at 0.0001), then /= overallscale
middle = window · sin(freq_before_scaling) · 0.5
fir[i] = sinc(unprime[|i − middle|] · freq) · sin(π · i / window)
fir[middle] = 1
window = min(window_knob² · 256 · overallscale + 2, 998)
middle — the centre tap, and therefore the group delay — moves with the corner, so a low corner puts the peak near the start of the window and makes the kernel strongly asymmetric. The recombination is out = dry·(amount + 1) − filtered·amount, with the dry read back out of the FIR ring at firp + unprime[middle] rather than taken live, because a linear-phase filter's output has to be summed against an equally delayed dry or the subtraction combs. So amount is not a wet/dry: at 1 the low band stays at unity and the air band is doubled. MEASURED gain in dB, 48 kHz:
| setting | 100 Hz | 500 | 1 k | 3 k | 6 k | 10 k | 16 k |
|---|---|---|---|---|---|---|---|
silken 0.5 | −1.02 | −1.17 | −1.06 | +1.29 | +2.87 | +2.93 | +4.42 |
silken 1 | −2.18 | −2.48 | −2.13 | +3.60 | +5.11 | +5.22 | +7.36 |
silken 1 0.25 | +0.45 | +0.69 | +8.77 | +5.40 | +5.45 | +5.28 | +6.81 |
silken 1 0.8 | −4.28 | −4.11 | −2.65 | +0.89 | +3.23 | +6.38 | +6.86 |
silken 1 0.5 1 | −2.60 | −2.40 | −2.66 | −0.78 | +6.40 | +6.43 | +7.09 |
It is a tilt, not a pure boost: the sinc's passband gain overshoots unity a little, so amount 1 takes about 2 dB off the body while it adds 5–7 dB of air. freq moves where the lift starts — at 0.25 the "air" band already begins under 1 kHz — and window sharpens the transition (at window 1 the 3 kHz point is still −0.8 dB while 6 kHz is already +6.4).
Group delay, MEASURED at 48 kHz — this is why there is no PDC number:
freq / window | group delay |
|---|---|
| 0 / 0.5 | 1 sample |
| 0.25 / 0.5 | 6 samples |
| 0.5 / 0 | 1 sample |
| 0.5 / 0.5 (defaults) | 21 samples |
| 0.5 / 1 | 74 samples |
| 1 / 1 | 182 samples |
Quirks kept verbatim:
- the convolution loop starts at
fip = 1, sofir[0]is never read. Whenmiddle > 0that costs nothing (itssin(0)window term is zero anyway), but at a corner low enough thatmiddle == 0the centre tap — thefir[middle] = 1.0that carries the whole passband — is the one skipped, and the filter outputs only its skirt. That is upstream's behaviour at every lowfreqsetting and it is part of how the bottom of the knob sounds; - upstream's constructor clears
firBuffer[0..32766]although the array is 32768 long, leaving the last slot indeterminate in C++. Volta zeroes the whole ring, and the C++ reference harness is made to match so the comparison is against defined behaviour; firPositionis constructed at 0 and decrements, so the second sample wraps to 32767 and the ring is walked backwards;- the gain compensation is
sqrt(freq · 0.618033988749894848204586)— the golden ratio, for no stated reason. Kept digit for digit.
It reports no PDC latency even though it is an FIR: the group delay is unprime[middle] samples, middle moves with two macro-rideable knobs, and the internal dry and filtered taps are themselves one sample apart, so there is no single number that would be right and a stale one is worse than none. With amount 0 the output is exactly the input delayed by unprime[middle] (silken_amount_zero_is_the_delayed_dry_tap pins it). Fully independent stereo, one shared kernel. window is the one knob in this whole section with a real CPU cost — up to 998 multiply-accumulates per sample per channel.
mackity [trim] [pad] — the Mackie 1202 input stage#
Five stages, in the order the circuit does them:
- a DC/subsonic one-pole at
0.001860867 / overallscale(≈ 13 Hz), subtracted rather than applied — the input transformer's low-end loss; - the input trim,
(trim · 10)², a square law from silence to ×100 (+40 dB) with unity landing at the default 0.1; - a 19160 Hz lowpass, Q ≈ 0.4317 — deliberately below Butterworth, so it droops rather than peaks. Band-limiting before the nonlinearity is what stops the harmonics folding;
- the nonlinearity: hard clamp to ±1, then
x − 0.1768·x⁵. A pure quintic, so the transfer's slope at the origin is untouched — no low-level compression at all, quiet material stays genuinely clean — and it bends only near full scale. Expandingsin⁵gives(10 sin θ − 5 sin 3θ + sin 5θ)/16, so it makes a 3rd and a 5th in a fixed 5 : 1 ratio — MEASURED −49.2 dB and −63.2 dB at unity trim, exactly 14 dB apart — with everything above that coming from the clamp. A fixed, level-independent 3rd/5th pair over a clean floor is the console-input signature, not the progressive 2nd-and-3rd of a tube stage or the softtanhof a transformer; - a second 19160 Hz biquad, Q ≈ 1.158 — above Butterworth, so it peaks a little right under the corner — to tame what step 4 made, then a much slower subsonic one-pole at
0.000287496 / overallscale(≈ 2 Hz) and the output pad.
Two lowpasses at the same corner with different Qs, one on each side of the shaper, is the whole voicing: the pair is close to flat in the passband, but the pre-emphasis/de-emphasis around the nonlinearity changes which harmonics survive. MEASURED at 48 kHz (gain and THD on a 1 kHz tone, harmonics relative to the fundamental at −6 dBFS in):
trim | linear gain | small-signal gain | THD @ −6 dBFS | h2 | h3 | h5 | h7 |
|---|---|---|---|---|---|---|---|
| 0.1 (default) | ×1.00 | −0.01 dB | 0.353 % | −242 dB | −49.2 | −63.2 | −243 |
| 0.12 | ×1.44 | +3.15 dB | 1.55 % | −243 | −36.3 | −50.3 | −243 |
| 0.15 | ×2.25 | +7.03 dB | 10.4 % | −245 | −19.8 | −34.9 | −50.8 |
| 0.2 | ×4.00 | +12.03 dB | 26.5 % | −240 | −12.0 | −22.2 | −41.9 |
| 0.3 | ×9.00 | +19.07 dB | 38.3 % | −231 | −10.0 | −15.2 | −19.5 |
| 0.5 | ×25.0 | +27.89 dB | 42.5 % | −228 | −9.6 | −14.1 | −17.1 |
| 1.0 | ×100 | +34.40 dB | 43.8 % | −238 | −9.5 | −13.8 | −16.5 |
The 2nd harmonic sits 240 dB down at every setting — the shaper is an odd polynomial and there is nothing asymmetric anywhere in the chain. Past trim ≈ 0.15 a −6 dBFS input is already into the ±1 clamp, which is where the 3rd harmonic overtakes the 5th.
THD versus level at unity trim, which is the "only bends the top" claim in numbers:
| input | THD |
|---|---|
| −40 dBFS | 0.00000 % |
| −20 dBFS | 0.00056 % |
| −12 dBFS | 0.0223 % |
| −6 dBFS | 0.356 % |
| −1 dBFS | 3.80 % |
Frequency response at unity trim:
| 20 Hz | 50 | 100 | 1 k | 5 k | 10 k | 15 k | 19 k | 22 k |
|---|---|---|---|---|---|---|---|---|
| −1.60 dB | −0.30 | −0.08 | −0.01 | −0.12 | −0.52 | −1.51 | −5.46 | −32.8 |
trim defaults to 0.1 because that is unity — a 0.5 default would be +28 dB of drive on every bare |> mackity. Small moves at the bottom of the knob are large moves in dB, which is the 1202's own trim pot. pad is a plain linear output attenuator applied after everything, so you can slam the stage and still land at level. Fully independent stereo (the two biquads share coefficients but keep separate DF1 state), zero latency, both knobs ride a macro. Two quirks kept: both one-poles hard-flush to 0 below 1.18e-37 (some 270 orders of magnitude above an actual f64 denormal, so it is a real state reset that fires on very quiet material), and pow(x, 5) is kept as a powf rather than an unrolled multiply because the two differ in the last ulp and the reference uses pow.
cornerClip [drive] [posCeiling] [negCeiling] [voicing] [mix] — OneCornerClip#
clipOnly (ClipOnly3) softens both the entry into and the exit out of a clip, which is why it is transparent: the clipped region is symmetric and the ear reads it as a slightly rounded flat top. OneCornerClip softens the entry only. A sample over the threshold is replaced by
out = last · voicing + threshold · (1 − voicing)
— a one-pole lag toward the ceiling seeded from the previous output — and the clip target for the next sample becomes that value rather than the ceiling. So the waveform slides into the clip over several samples and snaps out of it in one. That asymmetry is a DC-free way of putting real odd-harmonic energy in without a hard corner's buzz, and unlike a soft-knee waveshaper it costs nothing below the threshold: the sample is passed through bit for bit (upstream literally re-reads the input pointer when nothing engaged), with no latency at all.
The difference from clipOnly in one line: clipOnly is a transparent ceiling you put on a master to stop overs; cornerClip is a flavour you drive into. clipOnly has one sample of latency and no drive; cornerClip has zero latency, ±24 dB of drive, independent positive and negative ceilings, and a knob for how round the corner is.
All the numbers below are MEASURED at 48 kHz on a 1 kHz sine at 0 dBFS, with the default 0.966 ceilings unless a row says otherwise. voicing first — the corner only shows once the unit is working, so this is at +12 dB drive:
voicing | THD | h3 | h5 | h7 | h9 | peak |
|---|---|---|---|---|---|---|
| 0 (hard) | 35.95 % | −10.2 dB | −16.1 | −21.4 | −27.4 | 0.9660 |
| 0.3 | 34.62 % | −10.4 | −16.6 | −22.2 | −28.2 | 0.9660 |
| 0.618 (default) | 32.43 % | −11.0 | −17.4 | −22.6 | −27.2 | 0.9660 |
| 1 | 34.91 % | −10.8 | −16.0 | −20.1 | −23.8 | 0.8907 |
Rounding the corner mostly trades the upper harmonics down; at voicing 1 the lag is a pure repeat of the last sample, which starts pulling the peak itself down (0.891 instead of 0.966).
Splitting the two ceilings is asymmetric clipping — even harmonics, the "one side of the transformer saturates first" sound:
| ceilings (+12 dB drive) | h2 | h3 | h4 | max | min |
|---|---|---|---|---|---|
| 0.966 / 0.966 | −225.5 dB | −11.0 | −225.5 | +0.9660 | −0.9660 |
| 0.7 / 0.966 | −27.8 dB | −10.7 | −30.1 | +0.7000 | −0.9660 |
| 0.4 / 0.966 | −23.3 dB | −10.9 | −25.0 | +0.4000 | −0.9660 |
Symmetric, the even harmonics are at the numerical floor — it is a pure odd clipper. Split the ceilings and the 2nd harmonic comes up 200 dB.
And the drive ladder (peak out and THD, same 0 dBFS sine):
drive | peak out | fundamental | THD |
|---|---|---|---|
| −12 dB | 0.2512 | −12.00 dB | 0.00 % |
| −6 dB | 0.5012 | −6.00 dB | 0.00 % |
| 0 dB | 0.9660 | −0.06 dB | 1.16 % |
| +3 dB | 0.9659 | +0.96 dB | 14.20 % |
| +6 dB | 0.9660 | +1.34 dB | 22.94 % |
| +12 dB | 0.9660 | +1.58 dB | 32.43 % |
| +24 dB | 0.9660 | +1.60 dB | 33.91 % |
The peak is pinned at the ceiling from 0 dB up and the fundamental rises only 1.6 dB over the whole 24 dB of drive — everything else you add turns into harmonics. The entry ramp, MEASURED into a 0.5 ceiling at voicing 0.9 from silence: 0.087, 0.158, 0.217, 0.266, 0.307, 0.340, 0.368, 0.391, 0.410, … over about fourteen samples, rather than a jump to 0.5.
Below threshold at drive 0 the residual is exactly zero: cornerclip_is_bit_transparent_below_threshold asserts bit equality, because (0 dB knob · 36) − 12 lands on exactly 0.0 and 10^0 is exactly 1.0, so upstream's raw-passthrough branch really does fire.
Quirks kept verbatim:
the clip targets are block-local upstream. They are declared inside
processDoubleReplacingand re-initialised to the thresholds at the top of every block, so a clip still sliding when a block ends restarts from the ceiling in the next one. The output is therefore very slightly block-size dependent — MEASURED below — and Volta reproduces that exactly (CornerClipFx::processre-arms on entry; the C++ corpus is run at five different block sizes to pin it);It takes a deliberate signal to see. Once a clip has settled, the target converges to the threshold itself (
out = out·v + thr(1−v)⇒out = thr), so a mid-clip boundary changes nothing; only a boundary landing inside an entry ramp, and then only for a sample between the lowered target and the threshold, diverges. MEASURED: an impulse-then-plateau signal designed to straddle boundaries differs in 32 of 256 samples by up to 5.5e-2, while a plain 311 Hz sine at 1.4 amplitude differs in 0 of 8192 samples.
limitPosL/limitNegL/limitPosR/limitNegRare members, cleared in the constructor and never read — dead state from an earlier revision;lastSampleis updated even when the unit is bypassing, so the corner of a clip that starts after a quiet passage is seeded from real audio;- the golden-ratio special case fires here and cannot fire upstream.
if (voicing == 0.618) voicing = 0.618033988749894848204586;compares adoubleagainst the literal0.618, but the VST2 parameter store is afloat, so the left side is(double)0.618f = 0.61799997091293335…and the branch is dead in every VST2 build. Volta carries the knob in f64, so the default does land on the exact golden ratio the comment promises. The difference is ~5e-5 in one coefficient — inaudible — and this direction is plainly the author's intent, so the code is kept as written rather than the f32 rounding being emulated. It is an equality, so only exactly 0.618 triggers it.
Fully independent stereo. All five knobs ride a macro; |> cornerClip -3dB is a negative literal in a pipe and an inserts list alike. The two ceilings read positive as a linear ratio and zero-or-negative as dB, sharing cliponly_ceiling_from_surface with clipOnly so the two spell a ceiling the same way.
Fidelity#
Every one of the five is diffed against a standalone C++ build of its own processDoubleReplacing (VST scaffolding stubbed, -ffp-contract=off — clang's default contraction fuses a*b + c*d into fmadds Rust never emits and shows ~1e-16 ghosts). The corpus per plugin is 6 signals — a 1 kHz sine, a 20 Hz–20 kHz log sweep, seeded xorshift noise, a bipolar impulse train over exact digital silence, drum-like bursts, and an over-unity-left / digitally-silent-right case — × 44.1 kHz and 48 kHz × 5 knob settings (6 for Silken, to reach its middle == 0 and two-tap-window corners), each run through a different irregular block size (1, 64, 129, 512 and 4096 frames, which is what pins OneCornerClip's block-local targets):
| unit | cases | max |Δ| |
|---|---|---|
beziComp | 60 | 0.0 |
weight | 60 | 0.0 |
silken | 72 | 0.0 |
mackity | 60 | 0.0 |
cornerClip | 60 | 0.0 |
Bit-identical, every sample of every case. Each unit additionally pins a 64-sample slice of its reference dump in engine/src/trackflavors.rs as an assert_eq! on raw f64 bits, so a regression fails cargo test -p volta-engine without the (uncommitted, tens of MB) dumps being present.
One harness-only adaptation. The VST2 parameter store is float, so a knob written 0.966 reaches the plugin's DSP as (double)0.966f and differs from Volta's f64 knob in the 8th decimal — three orders of magnitude above the 1e-9 gate, for a reason that has nothing to do with the algorithm. The harness widens float A..E to double; nothing else in the sources is touched. Its one visible consequence is cornerClip's golden-ratio branch, documented above.
Vocal & pitch family — tune, formantShift, harmonize, deess#
The pro vocal chain, catalogued in Vowels, voice and pitch effects: tune strength [speedMs] (monophonic autotune toward the module key, formant-preserved, strength macro-ridable), formantShift semis (envelope-only warp, ±12 — formant-on-sample), harmonize [degrees] [mix] (up to 4 diatonic voices 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 around freqHz, and the cut is a bell there — not a shelf over everything above it). All four render on every target; tune/formantShift/ harmonize carry a fixed 768-sample STFT latency.
Per-voice effects#
These splice into a patch's out DAG (engine/src/graph.rs + engine/src/voice/eval.rs), signal-first so they pipe.
EQ band constructors (peak, lowShelf, highShelf, lowpass, highpass, notch) produce EqBand values. These can pass through ordinary helpers and containers into an EQ band list; they cannot serve as numeric shaping controls.
Voice shaping controls#
drive, vca, bitcrush, fold, clip and tube accept dimensionless amplitudes and shaping controls, including live signals; Hz, seconds and other physical units are rejected. Aliases, returned functions, imports and containers keep those constraints. Partial drive and vca calls also keep their named bias and character options.
The controls have different read policies during voice rendering:
| Control | Read policy |
|---|---|
Audio input; vca gain control | Every active sample |
bitcrush amount | First active value, held for the render slice |
drive amount/bias; vca character; fold, clip, tube amount | First and last active values, interpolated across the render slice |
A render slice can end at a scheduling boundary as well as a callback boundary. Endpoint interpolation is not general audio-rate modulation. Supported feedback processors read controls at each sample-by-sample feedback step. State stays with the consuming voice or graph. A reusable bitcrush function can accept either a signal or a track; a Track receiver selects the bus crush processor.
Stereo law for modulation effects#
chorus, flanger, phaser, bbd and ensembleChorus no longer downmix an upstream stereo signal for their dry contribution. Chorus, generic BBD, Juno BBD and the triple ensemble still feed the circuit's intentionally shared delay line from (L+R)/2, but mix that wet result back with the original dry L/R pair. Flanger and phaser instead run independent channel state, with the right sweep offset 90°, so mono input gains stereo motion and stereo input keeps its image through both dry and wet paths. At amount=0 every member is an exact stereo pass-through.
The modulation effects' LFOs FREE-RUN (audit M2)#
chorus, flanger, phaser, bbd and ensembleChorus are the five per-voice effects with an internal sweep LFO. All five clock that LFO off the absolute transport sample, voice::free_lfo_phase(n, sr, rate) — the same global clock Drift, Chaos and PatternMod use, and the same one every track-level insert in effect.rs has always used. Never a per-note accumulator, never wall clock (so export ≡ live stays bit-identical). Two consequences, both wanted:
- A note does not restart the sweep. The phase is where the transport put it. Until 2026-08
Voice::triggerzeroed it throughreset_node_state, so on anything but a pad every note re-walked the same tiny arc fromsin 0— a 1/16 at 120 bpm is 125 ms, 0.075 of a 0.6 Hz cycle. Measured over 16 identical 16ths, the per-note comb notch of a voice-levelbbd 1.0 "i"sat at 240–247 Hz (sd 1.9 Hz); free-running it roams 83–245 Hz (sd 44.6 Hz), matching the same effect as a bus insert to within 0.1 Hz of spread. Pinned byengine/tests/modfx_freerun.rs. - Every voice of a chord shares the phase. That is the hardware: one LFO and one bucket-brigade line serve all the notes going through the box. The per-note version instead started every voice of a chord at phase 0, so they beat coherently — one detuned voice, not an ensemble.
What a note-on still clears: the delay ring and write head, the filter/allpass memory, the compander envelopes, the clock integrator's tap position (0 re-derives it from the target for whatever phase the transport is at).
The wet tap fades in, and not from the note-on. Clearing the ring means the read tap spends the note's first dt samples reading zeros and then switches on in a single sample — dt after the note-on, which for flanger is anywhere from 1.6 ms to 6 ms depending on where the LFO is. chorus and flanger therefore fade the tap (and, in the flanger, its feedback) in with a 4 ms smoothstep starting from warm − dt — from the crossing itself, not from the note-on, which is what makes it land on the step at every LFO phase. Measured max |Δ²x| in the note's first 15 ms against the body's p99: chorus 8.50e-2 (×475) → 6.85e-4 (×3.8), flanger 1.64e-1 (×548) → 5.99e-4 (×2.0); peak >6 kHz excursion over the body p99 +16.8 → +5.5 dB and +18.8 → +3.3 dB — i.e. below the note-locked engine this change started from. NOT clearing the ring is worse, not better: the previous note's tail then splices in through the feedback path. The trade-off, stated plainly: a note's first echo lands exactly on the fade's zero, so a percussive hit's flange/chorus onset is softened over 4 ms; from dt + 4 ms on nothing is altered.
bbd and ensembleChorus clear the same kind of ring and stepped on the same way — they were simply left out of the first pass, and read ×68 / ×18.5 against a body p99 that later halved when the reconstruction bank went 4th-order, so the same untouched steps eventually read ×137 / ×86. They now fade too, but sized as a quarter of each line's own shortest tap rather than a flat 4 ms: those are bucket-brigade lines whose taps sit at 1.5–5.5 ms (Juno) and 2.85–6.35 ms (Solina), so a 4 ms fade is longer than the whole tap and does not soften the first echo, it deletes it (the comb a percussive hit stamps disappears — notch spread 44.6 → 0.0 Hz). At a quarter of the tap (0.33 ms Juno i/ii, 0.7 ms Solina, 1.6 ms on the generic 12 ms line) the switch-on measures ×9.1 and ×5.4 with the comb sweep intact (sd 43.1 Hz against the free-running track insert's 44.6). A track-level insert never clears its line and so never fades.
phaser keeps a smaller onset transient of its own (a cleared allpass cascade, no delay line and so no tap to fade); it predates this and is untouched.
chorus amount [rate] [depth] — tunable stereo chorus#
The original chorus amount spelling remains valid. Its defaults are a 0.6 Hz sine LFO and normalized depth 0.35, which maps to ±1.925 ms around the base delay and about 12.6 cents peak deviation at full amount (the old full-scale default was about 35.5 cents). Optional rate and depth are regular live control nodes, so macros/LFOs may ride them without rebuilding the effect.
The wet circuit reads a shared mono ring through quadrature L/R taps; the dry path retains the original stereo pair. amount uses wet=0.5·amount and dry=1−wet, reaching equal dry/wet at full amount without the former +1.9 dB sum. Zero amount is an exact bypass. The free-running absolute clock and the tap warm-up fade described above still apply at every rate.
The taps read a band-limited fractional delay. Each tap is a 16-tap least-squares fractional-delay kernel (256 polyphase rows, linearly blended; engine/src/frac_delay.rs, from the evolve run volta_chorus #114) in place of the 4-point Hermite. The Hermite's loss depended on the fractional position (−3.3 dB at sr/3 for a half-sample fraction), and the sweeping delay turned that into an amplitude modulation of every high partial: a top-octave shimmer with sidebands up to ±350 Hz at the defaults. The kernel is flat to 0.34·sr at every fraction. On the evolve harness (48 kHz, signal-to-error re an exact band-limited ideal of the same modulation law) a 16 kHz partial went from 12.7 to 74.9 dB, 8 kHz from 33 to 93 dB, 2 kHz from 73 to 92 dB; measured offline, not listened to. The LFO is an f64 sin/cos of the phase (it was an f32 sin of the phase rounded to f32). The modulation law, fade and mix are unchanged, and the engine's per-voice cost fell from 14.0 to 8.3 ns/frame (see the commit).
The node runs as independent passes (evolve volta_chorus #111). Per callback the ring is written first (it is mirrored, each sample stored twice, so no 16-tap window wraps), then the LFO, the read positions and gains, then the reads. The LFO re-evaluates sin/cos on an absolute 128-sample grid (and at any rate change) and in between rotates the anchor by a tabulated sincos(2π·k·step) — a pure function of k and the step, kept in the ring's tail for the current rate — so no sample's value depends on the one before it, and renders stay identical whatever the callback size. Same law and read; the output moves by rounding only (≤ −100 dB re peak through eight chained choruses). Engine A/B, same process, one held voice, per chorus node: 8.4 → 7.1 ns/frame for chorus 0.5 (1.19x), 1.16x with constant rate/depth, 1.10x with a moving amount, 1.19x with an LFO on the rate.
phaser's coefficient grid is absolute too. The allpass stages are recomputed every 16 samples and held (the sweep is sub-Hz; the per-sample biquads still run every sample). That grid used to re-anchor to the first sample of each callback, so the render depended on the host's buffer size unless it was a multiple of 16 — measured 2.6e-4 (−61 dB relative to peak) at 100 and 127 frames and at the ragged 100/60 tails a device period leaves behind. It is now keyed off n % 16 on the absolute clock, so every callback size renders bit-identically. BLOCK is a multiple of 16, so offline renders were never affected and did not move.
quality = "hq" — the voice saturators without aliasing#
drive, clip, fold and tube take a quality selector: "standard" (the default) or "hq":
source = saw (freq pitch) |> drive 0.5 { keepLows = 120Hz, quality = "hq" }
Standard drive is oversampled. An unsplit drive (no mix/trim/keepLows) runs second-order ADAA at 8× (engine/src/os_shaper.rs, ported from the evolve run volta_shaper_aa #107): a 12-tap half-sample FIR, polyphase-allpass halfbands, and antiderivative tables in place of the dilogarithm. On the evolve harness's coherent band-limited saws its mean alias suppression is 93.5 dB (worst 75 dB) against 36.7 dB for the 1× kernel it replaced, harmonic error 0.38 dB, at 46 against 100 ns/sample on that harness (measured there, not in the engine). It is ≈ 7.5 host samples late; graph_quality::align compensates 7 of them on parallel paths and in the track's PDC, and the remaining half sample is left uncompensated (the 1× kernel's whole sample was). The response is within ±0.25 dB to 16 kHz. The evolved program itself used a 72-tap interpolator that is +47 dB at Nyquist (its probes had nothing above 20 kHz, so noise or a near-Nyquist partial would have been blown up into the shaper); the port uses the same least-squares design without that extension, which is bounded and costs the harness score 20 dB of alias (113.8 → 93.5). drive's split path, the feedback-region arm and the effect-bus saturate keep their 1× kernels. Since evolve volta_drive #92 (round 3) each host sample's eight 8× points take one of three paths: all on one tanh plateau → the rail; small steps (|Δz| < 0.6 in z = k·(x + bias)) → a direct moment expansion of the second difference (one table tanh per point, no division); otherwise the exact interval means as before. Output is within 1e-5 (−100 dB) of the exact path; 1.1–1.9× cheaper in the engine depending on drive. #92's other changes were not ported: its 40-tap final decimator is −2 dB at 12 kHz and −6.7 dB at 20 kHz (the harness scores harmonics only below 10 kHz), and its 48-tap interpolator is +4.4 dB at 22 kHz, which only that decimator hides.
Standard clip is oversampled too. clip runs second-order ADAA (the clamp's segment moments, f32) at 4× between short linear-phase FIRs (os_shaper::clip_block, from the evolve run volta_clip_aa #7). On that harness: alias mean 88.2 dB (worst 76.9) against 30.8 dB shipped, harmonic error 0.20 dB against 23.2, peak −0.10 dB re the band-limited ideal, 5.7 ns/sample (the 1× kernel was 5.3). Latency ≈ 7.3 host samples, 7 compensated like drive's. The region arm and the bus saturate "clip" stay 1×. Since evolve volta_clip_rel #59 (round 3) the same filters run restructured: the 4× stream is kept as host-rate frames, everything after the clamp is one composite filter over the clamped phases, and only frames whose segments cross a knee add their exact second-order residual. Same sound (within 5e-7 of the stage-by-stage pipeline), ≈ 2.5× cheaper in the engine (7.1 → 2.8 ns/sample per channel). #59 also narrowed the decimators' passbands; that bought 0.1 dB of harness alias for 0.2–0.6 dB more droop at 12–20 kHz and was not taken.
Standard tube too. tube runs its backlash and asymmetric soft clip at 2× with first-order Direct ADAA (f32 exp and ln(1+t) polynomials whose coefficients were fitted for this kernel), between an 8-tap Kaiser interpolator and a 3-tap minimax halfband decimator (os_shaper::tube_block, from the evolve run volta_tube_fit #9, which replaced volta_tube #19's longer filters and series), the DC blocker still at the host rate. On that harness: alias mean 74.0 dB (worst 59.2) against 32.5 shipped 1×, harmonic error 0.49 dB against 4.41, 5.0 against 255 ns/sample. Latency ≈ 6.3 host samples, 6 compensated. The engine's C5 probe (tube 0.4) reads −73.5 dB standard against −73.3 hq (#19: −71.0). The region arm and the bus saturate "tube" stay 1×.
What standard does on bright input. The other 1× arms use second-order MinusX ADAA. Its add-back term s·x[n−1] is scaled by the drive gain (s up to 25), so on a band-limited edge the averaging leak is full-scale; the kernel's range clamp then turns the edge into a one-sample step, and the alias floor is the same as a naive tanh with no antialiasing at all. It does work on smooth input (lpf 1.5kHz before a drive 0.3 measures −82 dB; at 4 kHz −63 dB; at 9 kHz back to naive). About half the corpus's drive/clip/tube calls take an unfiltered oscillator, so that is where hq matters.
What hq does. First-order Direct ADAA (no add-back, no leak) on a 2× stream between the linear-phase FIR halfbands user kernels oversample with (kernel::oversample). Measured on a C5 saw (coherent, peak non-harmonic bin below 12 kHz vs the fundamental; engine/tests/shaper_quality.rs):
| chain | standard | hq |
|---|---|---|
drive 0.3 | −43.2 dB | −72.6 dB |
drive 0.6 | −43.0 dB | −70.9 dB |
clip 0.75 | −43.0 dB | −69.4 dB |
tube 0.4 | −43.4 dB | −72.4 dB |
fold 0.3 | −38.5 dB | −48.1 dB (the fold's own harmonics reach past 2×) |
A fold 0.3 chord's spectral centroid reads 667 Hz at 48 kHz standard, 462 Hz hq, and 458 Hz standard at 96 kHz — the standard path's extra brightness is aliasing. Small-signal response against standard: −0.005 dB at 1 kHz, −0.12 dB at 5 kHz, −0.48 dB at 10 kHz (the first-order boxcar, now at the 2× rate); program level within 0.03 dB. CPU per voice (8 voices, best of 7, load ≈ 16 — order of magnitude only): drive ≈ 130 ns vs 240 ns standard and tube ≈ 120 vs 240 (the standard second-order kernel evaluates a dilogarithm), clip ≈ 64 vs 14, fold ≈ 70 vs 18. Nobody has blind-listened to this FIR build; the lab's IIR prototype of the same idea has a page in dsp_research (C9).
Latency. An hq shaper is 31 host samples late (the FIR round trip). The graph's alignment pass delays parallel paths to match (osc + (osc |> drive … { quality = "hq" }) stays comb-free) and the track reports it to PDC. drive's split path (mix, trim, keepLows) delays its dry and low bands by the same 31 samples inside the node, so mix = 0 is an exact delay of the input. The first-order kernel adds a further quarter host sample that is not compensated (−0.16 dB at 12 kHz on a 50 % blend). Inside a feedback region hq is ignored — the shaper runs its region arm (first-order, 1×), because 31 samples would retune the loop.
Saturators sound different inside a feedback region — the numbers#
drive, clip, fold and tube are antialiased by antiderivative antialiasing (ADAA), and the arm that runs them is not the same in both places:
| kernel | linear response | added latency | |
|---|---|---|---|
| ordinary block path | ADAA2 MinusX (dsp::drive_adaa2 & friends); unsplit drive: ADAA2 at 8×, clip: ADAA2 at 4×, tube: ADAA1 at 2× (os_shaper) | flat — −0.000 dB at 16 kHz (8× drive: −0.22 dB measured; 2× tube: the ADAA1 boxcar at 2×, cos(πf/2fs) = −1.25 dB analytic) | 1 sample (drive/clip/tube: ≈ 7.5 / 7.3 / 6.3, 7 / 7 / 6 compensated) |
inside a feedback region | ADAA1 Direct (dsp::drive_adaa & friends) | cos(πf/fs) — −1.25 dB at 8 kHz, −6.02 dB at 16 kHz (48 kHz) | ½ sample |
This is the small-signal law, and it does not survive contact with a real level. The table's cos(πf/fs) is the ADAA operator's linear response, so it describes the divergence only while the saturator is barely engaged. Measured end-to-end at four input amplitudes (drive 0.6, region minus block, 2026-08-05):
| input amp | 1 kHz | 8 kHz | 16 kHz |
|---|---|---|---|
| 0.004 (small-signal) | −0.019 dB | −1.254 dB | −6.025 dB |
| 0.05 | −0.031 | −1.755 | −6.618 |
| 0.2 | −0.065 | −0.297 | −3.054 |
| 0.5 | −0.003 | −0.173 | −3.001 |
analytic cos(πf/fs) | −0.019 | −1.249 | −6.021 |
So at a normal signal level the difference is 4.2× smaller at 8 kHz and 2.0× smaller at 16 kHz than the small-signal law predicts — once the transfer is compressing, the fundamental's level is set by the nonlinearity rather than by the operator's averaging. It is not monotone either: around 0.05 the droop is slightly deeper than small-signal (−1.76 / −6.62 dB). Quote the −1.25 / −6.02 figures as the worst case, not as what a mix will show.
The small-signal rows below are what the ADAA operators do in isolation, measured on sine f * 0.004 |> drive 0.6 with and without a feedback \fb -> … + fb * 0.0 wrapper. The two level columns share drive 0.6's +23.8 dB of makeup against an arbitrary reference — only their difference means anything:
| f | outside | inside | Δ | analytic cos(πf/fs) |
|---|---|---|---|---|
| 1 kHz | 26.249 | 26.230 | −0.019 dB | −0.019 |
| 4 kHz | 26.251 | 25.948 | −0.302 dB | −0.301 |
| 8 kHz | 26.254 | 25.001 | −1.254 dB | −1.249 |
| 12 kHz | 26.884 | 23.869 | −3.015 dB | −3.010 |
| 16 kHz | 26.259 | 20.234 | −6.025 dB | −6.021 |
With no saturator in the chain at all the two arms agree to within 0.001 dB, so this is the ADAA operator and nothing else — not the region machinery.
tube is the one member of the family this probe cannot characterise at all: its backlash deadband is 0.06·amount wide, so at tube 0.6 the deadband is 0.036 — nine times the 0.004 probe. A small-signal measurement of tube sits entirely inside the deadband and measures the operator around a signal the effect is not yet passing. Take the tube row as the ADAA kernel's behaviour, not the effect's.
Practically: wrapping an existing |> drive 0.6 in a feedback region to add a repeat costs the dry tone at most ~1.25 dB at 8 kHz and ~6 dB at 16 kHz, and that worst case is a quiet signal — at a normal level (amp 0.2) it is ~0.3 dB and ~3 dB. Moving the saturator back out returns them. Below ~4 kHz the difference is under a tenth of a dB at any level. If you want the loop and the untouched top end, put the saturator outside the region and feed the region's output through it.
Why it is not unified. Before wave 1 both arms drooped identically and were consistent; wave 1 removed the droop from the block side only. Neither direction closes now:
- Region → ADAA2. MinusX add-back is only safe at second order, and second order costs a full sample of latency inside the loop. The audit's suggested compensation is to subtract that sample from the region's declared loop delay, and two concrete mechanisms block it. (a) There is no per-path latency equalization. The loop delay is one scalar —
FeedbackReadreads the closer's ring attime · srsamples — but a region body is a DAG that can reach the binder along several paths with different saturator counts (\fb -> fb * 0.5 + (fb |> drive 0.4) * 0.5has one path with a sample of added latency and one with none). Subtracting a single scalar detunes whichever path it does not match; equalizing them would mean inserting compensating delays per path, which is a scheduler feature, not a constant. (b)feedbackAtreads at sub-sample resolution. Its read is a fractional, linearly-interpolated tap that a Karplus-Strong tuning drives as short as one sample; subtracting a whole sample there is either a large detune or a negative delay, and neither is representable. - First-order MinusX in the region. The add-back misaligns by half a sample and leaks
s·(x[n] − x[n−1])/2— a differentiator scaled by the transfer's slope at zero, which is up to 25 fordrive/clip. Clamping the leak turns it into hard clipping, i.e. into the aliasing ADAA is there to prevent. (dsp.rs's MinusX module comment carries this analysis in full.)
So the divergence is load-bearing, and it is pinned rather than fixed: dsp_validation.rs::region_saturator_droop_is_the_documented_boxcar asserts the region arm tracks cos(πf/fs) and the block arm stays flat, so neither side can drift without a test saying so (2026-08-03 audit F4).
tube amount — tube/tape saturation with hysteresis#
Warmth a memoryless waveshaper can't give: an asymmetric soft clip (the positive half saturates harder than the negative → even harmonics) driven through a backlash/play operator — a deadband that lags the input, giving the transfer curve an actual hysteresis loop (memory) — with a DC blocker to remove the offset the asymmetry introduces. amount drives the pre-gain and backlash width.
The full backlash + asymmetric saturation + DC blocker runs per channel.
discontinuity topDb — the nonlinearity of air itself#
A port of Airwindows Discontinuity (© Chris Johnson, MIT — https://github.com/airwindows/airwindows), the first Airwindows algorithm in the palette (see docs/notes/airwindows-gap-analysis-2026-08-15.md Tier 1 #3). It models what a pressure wave does when it is genuinely, physically loud: the peaks travel faster than the troughs, so the wave steepens on its way to the listener. Musically that is a distance/intensity dimension no EQ or bus saturator reproduces — a sound reads as viscerally loud and CLOSE without gaining a single dB.
Mechanism (engine/src/dsp.rs, Discontinuity / disc_top): three cascaded 90-sample delay lines. Each writes the signal in and reads it back at a fractional distance driven by the instantaneous amplitude — a smoothed (one-pole, coefficient 0.5), asymmetric function of the sample, |x·(0.25x − 0.5)·0.5|, scaled by the line length — so loud parts of the waveform are read later than quiet parts. The signal is scaled into the model's ±0.222 working domain on the way in and back out on the way out, which makes the effect unity-gain.
topDb is the parameter the plugin calls "Top dB", and it is a loudness in dB, not a 0..1 amount: the loudness you are asking the model to believe the sound already is. It is pinned to 70…140 dB (the same rail the plugin's own 0..1 knob maps onto, A·70 + 70), so a habitual discontinuity 0.5 lands on the gentlest setting rather than doing something strange. Measured on a 0 dBFS 1 kHz sine at 44.1 kHz:
topDb | fundamental | H2 | H3 | THD |
|---|---|---|---|---|
| 70 | −0.00 dBFS | −133.9 dBc | −59.6 dBc | 0.11 % |
| 87.5 | −0.00 dBFS | −99.6 dBc | −42.1 dBc | 0.80 % |
| 105 (centre) | −0.01 dBFS | −64.6 dBc | −24.8 dBc | 5.9 % |
| 120 | −1.36 dBFS | −36.4 dBc | −11.4 dBc | 31 % |
| 130 | −19.7 dBFS | −8.9 dBc | −0.4 dBc | tearing |
| 140 (top) | −9.7 dBFS | −7.2 dBc | −27.0 dBc | 49 %, hard-limited |
Odd harmonics dominate until the very top; past ~130 dB the ±0.222 clamp bites and the effect also hard-limits (peak output ≈ 0.222/top — 0.296 at 44.1 kHz / 140 dB). The internal scale tracks the sample rate (overallscale = sr/44100), so the model's absolute delay is in seconds, not samples.
- Placement: a voice effect, like
tube— put it late in a patch'soutchain. That is the point: per-sound loudness readings are what create the depth cue (the kick at 128 dB in front of a pad at 92 dB). It is not a track insert. - Mono: the node is a collapse point — a stereo input is downmixed
(L+R)·0.5before the lines, exactly likedelay/comb. - Live knob:
topDbis read at the block edges and the working scale is ramped across the block, so automating it doesn't zipper (the plugin recomputes once per buffer). With a still knob the ramp is exact, so the render is bit-identical to the reference. - Not allowed inside
feedback: it is absent fromfb_loop_kind_allowed(compiler/src/lower/expr.rs), so a loop that names it is a compile error rather than a render-time surprise.
Verification. The port is f64 and follows the original's op order exactly (including overallscale = (1.0/44100.0)·sr, which is not bit-identical to sr/44100.0); the trailing fpd dither and its denormal kick are dropped, since this engine is f64 end to end. An offline harness compiled the original C++ against a stub VST header (dither commented out) and ran a deterministic corpus through both: 1 kHz sines at 0.1 / 1.0 / 4.0, a 20 Hz–20 kHz sweep, seeded white noise, and DC, at 44.1 and 48 kHz, at 70 / 87.5 / 105 / 120 / 130 / 140 dB — 72 cases, all bit-identical (max abs diff 0.0). Two of those cases are pinned in CI as dsp::tests::discontinuity_matches_the_airwindows_reference, which compares against the C++'s numbers, not the port's own.
Example song: examples/songs/discontinuity.volta.
The slew-shaping family — slew, slewNoise, bloom#
These three effects and sweeten, hypersoft, tremolo and voice gatelope accept dimensionless controls, including live signals. Physical units such as Hz and seconds are rejected even through aliases, returned functions or imports. Every knob reads per sample; the processor may then smooth or step its value as described below. A partial call preserves named options: for example, f = bloom (sine 220Hz) followed by f 0.5 {wet = motion}. Existing defaults apply to omitted options. A generic Gatelope helper may process either a voice signal or a track; the track form keeps its existing processing policy.
Three voice-level inserts that work on the sample-to-sample difference rather than on a filter band. All three are faithful f64 ports of the L channel of Chris Johnson's MIT-licensed Airwindows plugins (engine/src/slewfx.rs) — op order preserved verbatim, the 32-bit output dither and fpd denormal substitution dropped because this engine is f64 end-to-end. Parity against the C++ was measured sample-by-sample over sines / a 20 Hz–20 kHz sweep / white noise / an impulse train, at 44.1 and 48 kHz, at 4–5 parameter settings each (168 cases): max |Δ| = 0.0, exactly. A slice of that is pinned as unit tests in slewfx.rs.
All three process mono audio: a stereo input collapses (L+R)·0.5 at the node, the same seam delay uses. Every knob is read per sample; each DSP struct caches its own block-constant prelude and recomputes it only when a knob moves, so a static setting is free and a swept one is exact. None of them may appear inside a feedback region (the compiler says so with a clear message).
slew amount — slope limiter (Airwindows Slew2)#
Clamps the step between consecutive samples to (1 − amount)^4 / (2·sr/44100) and adds the clamping difference back through Airwindows' two-tap "ata" antialiasing network, so the correction rides on the untouched dry sample. De-glaring with no filter band and no phase smear: quiet material passes through, loud fast edges lose their edge. amount ∈ [0, 1], live.
Note that amount = 0 is not bypass — it clamps at half full-scale per sample, which starts biting around 7 kHz at 44.1 kHz. amount = 1 clamps at zero slope and mutes. Useful range for de-glaring is roughly 0.2–0.6.
Two quirks are reproduced from the original because they are what it sounds like: the "first half" clamp compares a value against itself and can never fire (its lasting effect is seeding the second half's reference with the interpolated midpoint), and the antialiasing accumulators are driven with that identical zero.
slewNoise dust [wet] — worn-media dust (Airwindows TapeDust)#
Every sample draws one uniform, which does two things: it scales an additive grain by (1 − |Δ|)·dust²·0.01, and it sets a random per-sample gain 1/rDepth ∈ (0, 1]. The gain scatter is the audible part — broadband, signal-proportional grit that vanishes in silence. Worn tape, dusty vinyl, a tired sampler. dust = 0 is exact bypass; wet (default 1) blends against dry.
Higher dust also pulls the level down, because the random gain never exceeds 1 — at dust = 1 the mean gain is about −10 dB. Add makeup after it.
The uniform stream is a Mulberry32 seeded from the voice's performance coordinate through rng::draw (module seed, synth name, track, cycle, event, node id, "slewnoise") — exactly how crackle seeds — so a render replays bit-identically and two slewNoise nodes in one patch decorrelate.
Faithfulness note. Airwindows' shipped source guards the
b[]history shift withfor (int count = 9; count < 0; count--), a loop that never iterates, in every published build (LinuxVST, WinVST, MacVST, MacSignedVST). Sob[1..]stay zero and the intended 9-tap moving average collapses tob[0]·f[0]. This port reproduces that exactly: matching what TapeDust actually sounds like is the point of porting it. Fixing the loop would turn it into a random-length low-pass — a different effect, and one nobody has ever heard from this plugin.
bloom amount [output] [wet] — slew-domain smoother (Airwindows Smooth)#
Five cascaded VCAs. Each measures |x − x_prev| at its own point in the chain, runs it through sin(Δ · amount²·24), and chases its gain toward 1 − that, clamped into [0, 1]. Fast edges duck, flat material passes: a de-pointer that is not a low-pass. Then a makeup gain of (1 + amount²·1.6)·output, then the wet blend. Both optional knobs default to 1.
The fun is what happens past sin's hump. Once Δ·amount²·24 > π/2 the term turns over and eventually goes negative, the gain chases above 1 and pins at its rail, and the makeup runs unopposed — so instead of softening, a drum hit blooms, and at amount = 1 it explodes. Peaks reach ~2.6× the input there; refine's interval analysis knows this and will warn you about the headroom. Low amount on a bright synth is the polite half of the same knob.
The voice flavors — sweeten, hypersoft, tremolo, gatelope#
Four more voice-level Airwindows ports (engine/src/voiceflavors.rs), each of them one small opinionated thing that is awkward to spell out of the existing parts. Same construction rules as the slew family: faithful f64 ports of the L channel of Chris Johnson's MIT-licensed sources with op order preserved, the 32-bit output dither and the fpd denormal substitution dropped, mono (a stereo input collapses (L+R)·0.5 at the node), every knob read per sample against a cached block-constant prelude, and none of them legal inside a feedback region.
Parity. Sines at four levels (one of which overdrives every stage) / a 20 Hz–20 kHz sweep / seeded white noise / an impulse train on digital silence / gated drum-like bursts / a long sustained tone, at 44.1, 48 and 88.2 kHz (which reaches sweeten's extra oversampling branches), over 5–6 parameter sets per plugin including the defaults and both rails — 594 cases, max |Δ| = 0.0, exactly. Two settings per plugin are pinned as unit tests in voiceflavors.rs against the C++'s numbers, not the port's own.
On fpd. In all four, fpd only feeds if (fabs(x) < 1.18e-23) x = fpd * 1.18e-17, and the 64-bit path's dither add is commented out upstream — so it can only perturb an input already at −460 dBFS. (This is the check glitchShift failed, where the substituted value is multiplied by 8388352 into an integer buffer; there the register is kept.) The C++ reference was run with the same two lines neutralised. The one visible consequence is in gatelope, below.
sweeten amount — isolated second harmonic (Airwindows Sweeten)#
Band-limit, square, band-limit again, subtract from the dry. Filtering on both sides of the nonlinearity is what makes the harmonic clean: the pre-filter takes the ultrasonics out before the squaring can alias off them, the post-filter takes out what the squaring made anyway.
amount is stepped, not continuous — sweetBits = 10 − floor(amount·10) picks one of eleven power-of-two trims, so the knob moves H2 in 6 dB increments. Measured on a 1 kHz sine at −12 dBFS, 48 kHz:
amount | trim | H2 | H3 | H4 |
|---|---|---|---|---|
| 0.0 | 1/1024 | −78.3 dBc | −194.8 | −219.7 |
| 0.25 | 1/256 | −66.3 dBc | −194.8 | −219.7 |
| 0.5 | 1/32 | −48.2 dBc | −194.9 | −219.8 |
| 0.75 | 1/8 | −36.2 dBc | −195.0 | −219.9 |
| 0.9 | 1/2 | −24.2 dBc | −195.0 | −216.8 |
| 1.0 | 1 | −18.1 dBc | −193.6 | −212.1 |
H3 and above are numerical zero at every setting — there is genuinely nothing in there but H2. The harmonic is x², so its level also tracks the input: +6 dB in is +6 dB of H2 relative to the fundamental.
DC.
x²carries a DC term as well as H2, and the port subtracts it verbatim, sosweetenemits a signal-power-proportional negative offset: atamount = 1, −0.0005 at −30 dBFS in, −0.0498 at −10, −0.498 at 0 dBFS. Chris relies on downstream DC blocking, and Volta's master chain has one — but put anhpafter it if you are feeding afeedbackregion or anexport stemsthat skips the master.
The output peak is input + input², i.e. 1.5× at half scale and 2× at full scale; refine bounds it that way.
hypersoft drive [depth] [bright] [level] — extreme soft clipper (Airwindows Hypersoft)#
sin(x) + sin(2x)/2, then up to twelve further sin(n·x)/n³ terms. The stack is slew-gated: each term is scaled by max(0, 1 − |Δx·bright·n²|), so a term disengages exactly where its own harmonics would only alias, and bright sets how eagerly. Odd-symmetric, so it makes no even harmonics at all (H2 measures −177 dBc).
drive and level ride the ConsoleX fader curve (g = 2·knob, then g² above unity and 1 − (1 − g)² below), which puts unity at 0.5 — where all three optional knobs default. level is then scaled by 0.68.
Gain at the defaults is not unity, and is deliberately level-dependent (1 kHz sine, 48 kHz):
| in | out | gain | out peak |
|---|---|---|---|
| −40 dBFS | −33.2 | +6.8 dB | 0.022 |
| −20 dBFS | −13.7 | +6.3 dB | 0.203 |
| −10 dBFS | −5.5 | +4.5 dB | 0.501 |
| 0 dBFS | +0.5 | +0.5 dB | 0.924 |
| +6 dBFS | +1.9 | −4.1 dB | 0.985 |
so at its centre detents it behaves like a soft compressor with makeup rather than a clean clipper. depth picks how many stages run (2 at 0, 14 at 1) and that is where the grit comes from — 1 kHz at −6 dBFS: H9 climbs from −127 dBc at depth 0 to −53 dBc at 1, H13 from −197 to −82. bright relaxes the brake: energy above 8 kHz rises 19 dB from bright 0 (−62.7 dBc) to bright 1 (−43.6 dBc), with H7 going −49.9 → −40.7 dBc.
The output has a hard ceiling of ~4.0 at level = 1 no matter how hard you hit it (measured 3.93 at a 0 dBFS sine, 3.88 at +24 dBFS); refine bounds the node at faderCurve(level)·0.68·1.48, so a modest level does not trip the headroom advisory.
tremolo speed [depth] — the saturation-curve tremolo (Airwindows Tremolo)#
This is not a gain LFO, and that is the whole reason it is here. The control signal blends the waveform between two saturation curves — sin(|x|) on its boosted half, 1 − cos(|x|) on its starved half — and only then applies the (1 − control)·2 duck. The note changes shape as it ducks.
Measured against the control that settles the question: the identical LFO, skew and depth law with only the saturation curve removed (leaving bridgerectifier = |x|, which makes the blend collapse algebraically to x and leaves a pure gain LFO with a bit-identical envelope). 220 Hz sine, 48 kHz, speed 0.5:
| in | depth | H3 — tremolo | H3 — same LFO, gain only | THD — tremolo | THD — gain only |
|---|---|---|---|---|---|
| −20 dBFS | 0.25 | −51.4 dBc | −108.5 | −51.3 dB | −108.5 |
| −20 dBFS | 1.0 | −23.3 dBc | −209.2 | −23.2 dB | −194.6 |
| −6 dBFS | 0.25 | −39.0 dBc | −108.5 | −38.9 dB | −108.5 |
| −6 dBFS | 0.5 | −34.5 dBc | −105.3 | −34.4 dB | −105.2 |
| −6 dBFS | 1.0 | −0.5 dBc | −209.2 | −0.4 dB | −194.6 |
The gain-only column is numerical zero throughout — an amplitude LFO cannot put anything at 3·f, by construction. The distortion is odd-order only (H2 sits at −130 dBc or below) and it tracks input level, so the effect breathes with playing dynamics instead of applying a fixed colour.
speed runs on a fourth-power taper and is sample-rate independent (rate = 44100·(1e−4 + speed⁴/1e3)/π Hz, confirmed identical at 44.1, 48 and 88.2 kHz):
speed | 0 | 0.25 | 0.5 (default) | 0.75 | 1.0 |
|---|---|---|---|---|---|
| rate | 1.40 Hz | 1.46 Hz | 2.28 Hz | 5.85 Hz | 15.44 Hz |
depth (default 1) does three things at once: it deepens the swing (1 − (1 − depth)^5), skews the control (1 + depth^9) so it lingers at the extremes, and sets density = (1 − depth)·2 − 1, which chooses which curve the control signal itself is bent through. depth = 0 is exact bypass. Peak gain over a sine / Nyquist-square / noise / impulse stress corpus is 1.09×.
The compile-time peak bound (refine.rs::tremolo_gain) is k·peak(in) with k the curve's exact worst case for the depth knob's top value: the large-signal ratio (largest near |x| ≈ 1.57) rises from 1.0 at depth 0 to 1.271 by depth 0.55, and the small-signal makeup term 2·depth⁹ (the (1 − control)·2 duck at a starved trough) takes over above 0.95, reaching ×2 only at depth 1. At the 0.5 default that is ×1.27, where the bound was a flat ×2 — 3.9 dB of headroom an agent no longer has to trim away. Rendered against it in engine/tests/peak_calibration.rs (measured offline, not listened to).
Parameter cadence (the one deviation). The original recomputes
speedSpeed/depthSpeedonce per host buffer from alastSpeed/lastDepthhysteresis pair, so it is the one plugin of the four whose output depends on the host's block size — and permanently, because a different first block leaves the LFO at a different phase forever (measured against the same port, the C++ at 512-sample blocks differs by up to 1.05 on a hot sine, all of it phase). This port runs that prelude per sample: the finest cadence, the one that lets a macro sweepspeedcontinuously, and the one the parity harness pins at max |Δ| = 0.0. The only audible consequence is at note onset —depthAmountchases up from 0 with a ~300-sample time constant, so the tremolo fades in over ~7 ms rather than inside the first buffer. That is a click-free onset, not a defect.
gatelope threshold [trebleSustain] [bassSustain] [snap] [mix] — the gate that closes filters (Airwindows Gatelope)#
A gate with no fader in it. Above threshold a one-pole low-pass sits at "wire" (coefficient ≥ 1) and a high-pass holds zero, so the signal passes untouched. Below it, the two corners walk toward each other — the low-pass corner slides down, the high-pass corner slides up — and the instant they meet the output is hard zero. A tail filters shut from both ends instead of fading, which is why it reads as snappy and why it cannot click. Two filter banks alternate every sample (flip) sharing one pair of corner states; that halves each bank's effective rate and is part of the sound.
Corner walk while closed, 48 kHz (the −3 dB corner of each one-pole; 24 kHz means "wire", 0 Hz means "off"):
trebleSustain / bassSustain | 10 ms | 50 ms | 100 ms | 200 ms | 500 ms |
|---|---|---|---|---|---|
| 1 / 0.5 (defaults) | 24k / 207 | 24k / 1065 | 24k / 2216 | 24k / 4878 | 24k / 24k |
| 1 / 0 | 24k / 846 | 24k / 4878 | 24k / 13651 | 24k / 24k | — |
| 0 / 1 | 24k / 0 | 24k / 0 | 9179 / 0 | 2142 / 0 | 80 / 0 |
| 0 / 0 | 24k / 801 | 24k / 3546 | 22486 / 5918 | 10073 / 8002 | 8653 / 8637 |
So trebleSustain holds the low-pass open (1 = the treble never closes) and bassSustain holds the high-pass down (1 = the bass never closes). The gate reaches hard zero when the high-pass reaches the top or the corners cross: 129 ms at 1 / 0, 515 ms at the 1 / 0.5 defaults, never at 0.9 / 0.9.
Against a gain gate with the identical close time, on a −44 dBFS bed after a 40 ms hit (spectral centroid of the tail):
| t after the hit | gatelope | gain gate |
|---|---|---|
| 0 ms | 133 Hz | 118 Hz |
| 5 ms | 159 Hz | 134 Hz |
| 10 ms | 207 Hz | 133 Hz |
| 20 ms | 349 Hz | 107 Hz |
| 40 ms | 652 Hz | 110 Hz |
The fader holds the bed's timbre and just makes it quieter; gatelope takes the bottom out from underneath it first. That is the difference you are buying.
threshold is squared internally — 0.25 opens above −24.1 dBFS, 0.5 above −12.0 dBFS, 1.0 only at full scale. snap is Airwindows' AttackS and is a speed, not a time: higher is faster, 7.6 ms to open at snap 0 down to 8 µs at 1 (48 kHz). Upstream also slows the opening in proportion to how loud the hit is (attackSpeed = slowAttack·(1 − |x|/2), its own click-softener), which is kept verbatim. mix defaults to 1.
The one place dropping
fpdshows. With the denormal substitution gone, exact digital silence really is0.0here rather than ~5·10⁻⁸. Atthreshold = 0the original's substituted floor is> 0and holds the gate open through digital silence;0.0is not> 0, so this port lets silence close it. It only differs on bit-exactly-zero input, and closing on silence is what a gate is for. Both the impulse-train and drum-burst corpora sit on exact zeros and match the C++ exactly with the substitution neutralised on both sides.
Example song: examples/songs/voiceflavors.volta.
stringBody body [mix] — acoustic string-instrument body#
sig |> stringBody "violin"|"viola"|"cello"|"bass"|"none" [mix] radiates any voice signal through one of the authored instrument bodies bowed { body = … } uses (engine/src/string_body.rs): a parallel bank of nine or ten resonators per body (A0 air mode, CBR, B1−/B1+, mid modes, the bridge hill or the larger instruments' lower bridge resonances) plus a high-passed direct path and a 12 dB/oct roll-off. body is a compile-time string; "none" is an exact passthrough. mix (0..1, default 1) is live, read per sample, and crossfades x + mix·(body(x) − x), so mix = 0 is the input bit for bit. Mono (a stereo input folds). Loudness is trimmed per body so pink noise and sawtooth notes in the body's own register both land within ±3 dB of the bare signal. The mode tables, sources and measurements are in Strings, bells and resonating objects §1.12.
pizz = patch { source = string (freq pitch) 0.4 0.7 0.2 (pick 0.6) |> stringBody "cello", gain = 0.3 }
zFilter mode cutoff q [drive] [level] — the Z-filter family (Airwindows ZLowpass2 / ZHighpass2 / ZBandpass2 / ZNotch2)#
The character filter, next to the clean svf / ladder / korg35: a faithful f64 port (engine/src/zfilter.rs) of the Emu e6400-style distorted sampler filters. mode is a compile-time string literal — "lp", "hp", "bp" or "notch" (long names "lowpass"/"highpass"/"bandpass"/ "bandreject" and "br" also parse) — the bbd/korg35/svf pattern.
One module and one IR node cover all four because the four upstream plugins are literally the same ~440-line file: diffing them shows they differ only in the block prelude (cutoff mapping, fixed reso, clipFactor recipe, and five coefficients). ZHighpass2 has its clipFactor lines commented out, which the port expresses as clip_factor = 1.0 — division by 1.0 is exact, so it stays bit-identical to the source that omits the divisions.
Why it is a different kind of object from svf#
- The clipping is inside the loop. Every biquad stage clamps its output to ±1 before that value is written back into
s1/s2. The nonlinearity is therefore integrated by the filter state — resonance and distortion are one mechanism, not two stages in series. - The drive into the clippers is a function of cutoff. The input is scaled by
trim = 0.1 + 3.712·fand divided by the per-modeclipFactoronce before each engaged stage and once after the last, so low corners are hit softly and high corners hard. That coupling is the sound. - A sweep is an event, not a retune. See "control smoothing" below.
The knobs#
q is Airwindows' Poles, not a resonance. It progressively engages up to four cascaded stages (dWet = q²·4 walks four independent 0→1 blends), so it is a steepness and a wet/dry — and q = 0 is exact dry bypass, which makes automating q up from zero a filter fading in.
cutoff is in Hz and is mapped back onto the plugin's own knob curve, so each mode reaches a different range. Asking for a corner outside a mode's range pins to that mode's rail rather than erroring:
| mode | upstream curve | range @ 44.1 kHz | range @ 48 kHz | fixed reso | clipFactor |
|---|---|---|---|---|---|
"lp" | B³·18930 | 71 Hz – 19.0 kHz | 78 Hz – 19.0 kHz | 0.7071 | 1.212 − (1−B)·0.496 |
"hp" | B⁴·9500 | 34 Hz – 9.53 kHz | 36 Hz – 9.54 kHz | 1.0 | none (1.0) |
"bp" | B⁴·14300 | 35 Hz – 14.3 kHz | 38 Hz – 14.3 kHz | 0.314 | 1.0 − (1−B)·0.304 |
"notch" | B³·4700 | 44 Hz – 4.74 kHz | 48 Hz – 4.75 kHz | 1/φ | 0.91 − (1−B)·0.15 |
drive ∈ [0, 1] (default 0) and level ∈ [0, 1] (default 0.3) are the plugin's Input and Output. Two deliberate deviations, both knob tapers — the DSP itself is untouched:
- Upstream
inTrim = (Input·10)⁴, which spreads +80 dB of pre-clip gain across the knob while the input is hard-clipped to ±1 immediately after; on a −6 dBFS signal everything aboveInput ≈ 0.12is already a square wave.driveinstead sweeps 0 → +30 dB of pre-clip gain (Input = 10^(0.375·drive − 1)), which is the whole usable range with a usable taper.drive = 0is exactly the plugin's unity. levelis theOutputknob (output trim =10·level), but it defaults to 0.3, not the plugin's1.0, because the internal staging makes the plugin's default about +10 dB hot here. Pass1.0for the plugin's own default.
Control smoothing (ported — it is part of the sound)#
Upstream the host sets parameters once per buffer; the prelude moves last buffer's coefficients into an A set, computes a new B set, and crossfades A → B across the buffer (along with the input trim, output trim and wet). Volta reads knobs per sample and its block length varies, so a naive port would make the sound depend on the host buffer size. Instead the node owns its blocking: it latches the knobs on a private 128-sample phase counter, runs the prelude there, and interpolates across the next 128 samples exactly as the original does across a buffer. Consequences worth knowing:
- A fast cutoff sweep ramps between two filters rather than retuning continuously — the sampler-ish zipper is authentic, not an artifact.
- A knob change takes up to one control block (2.9 ms @ 44.1 kHz) to arrive.
- Output is identical however the caller chunks its calls, so a live pass and an offline render agree (
zfilter::tests::output_is_independent_of_caller_chunking).
fpd — kept, because here it is load-bearing#
Unlike the slew family, the fpd denormal guard is ported: if (|x| < 1.18e-23) x = fpd · 1.18e-17 substitutes a pseudo-random ~−146 dBFS floor for near-silent input, and the xorshift advance runs every sample (upstream's 64-bit path advances fpd even though its dither add is commented out). The 32-bit dither add is not ported — this engine is f64 end-to-end. fpd is seeded from a fixed constant rather than upstream's rand(), so renders are reproducible.
Measured behaviour#
All figures at 48 kHz, fc 2 kHz, measured by engine/src/zfilter.rs's harness.
Self-noise (silent input — the fpd floor, q = 1, level = 1):
| mode | RMS | peak |
|---|---|---|
"lp" | −158.4 dBFS | −139.4 dBFS |
"hp" | −148.5 dBFS | −136.9 dBFS |
"bp" | −151.9 dBFS | −139.9 dBFS |
"notch" | −146.1 dBFS | −132.0 dBFS |
Real, but 25–35 dB below a 24-bit floor. It is a denormal guard, not a texture.
Passband gain at drive = 0, level = 1 (i.e. the plugin's own default) — this is the ~+10 dB the level default backs off:
| mode | q=0.25 | q=0.5 | q=0.75 | q=1 |
|---|---|---|---|---|
"lp" | +3.3 dB | +9.1 dB | +9.6 dB | +10.5 dB |
"hp" | +0.9 dB | +9.9 dB | +10.0 dB | +10.2 dB |
"bp" | +3.9 dB | +10.4 dB | +11.8 dB | +13.7 dB |
"notch" | +3.9 dB | +10.4 dB | +11.7 dB | +13.6 dB |
Resonance rails. There is no resonance knob, so there is no self-oscillation rail — reso is fixed per mode and q only stacks stages. Stopband depth at q = 1 (probe a decade from a 2 kHz corner): "hp" −152 dB, "bp" −28 dB, "notch" −205 dB at the corner. The notch is genuinely deep; the low-pass at q = 1 reads −1.5 dB at its own corner against +10.5 dB in the passband, i.e. a gentle 12 dB/oct knee, not a peak.
How it distorts when driven (0.5-amplitude sine in the passband, q = 1):
drive | pre-clip gain | "lp" THD+N | "hp" | "bp" | "notch" |
|---|---|---|---|---|---|
| 0 | 0 dB | 0.00 % | 0.00 % | 0.02 % | 0.02 % |
| 0.1 | +3 dB | 0.02 % | 0.01 % | 0.08 % | 0.07 % |
| 0.25 | +7.5 dB | 6.5 % | 1.6 % | 1.5 % | 3.9 % |
| 0.5 | +15 dB | 28.8 % | 3.3 % | 8.0 % | 16.6 % |
| 1 | +30 dB | 37.3 % | 3.3 % | 9.4 % | 20.2 % |
"hp" and "bp" saturate early and then stop changing, because their passbands sit above the harmonics the clipper generates; "lp" keeps grinding.
It limits rather than gets louder. Because the clip is at ±1 before the output trim, driving the input harder buys almost no extra output — "lp", fc 2 kHz, q = 1, level = 1:
| input | peak out |
|---|---|
| 0.25 (−12 dB) | −1.6 dB |
| 0.5 (−6 dB) | +4.5 dB |
| 1.0 (0 dB) | +10.5 dB |
| 2.0 (+6 dB) | +11.0 dB |
| 4.0 (+12 dB) | +11.5 dB |
+12 dB more in buys +1 dB more out. That soft ceiling is the sampler-filter character.
The compile-time peak bound (OutputPeak, refine.rs::zfilter_bound) is max(x, 10·level · min(1.52, min(1, x·10^(1.5·drive)) · gain(mode, cutoff))) for an input of peak x: the dry blend (q < 0.5 and the first control block) never exceeds x; the wet path is clamped to ±1, soft-clipped to 0.8232 and then low-passed by the fixed 15.5 kHz biquad whose impulse-response L1 norm is at most 1.846 (44.1 kHz), so 1.52 per unit of trim is a hard ceiling; and gain is a calibrated table of the worst trim-1 peak(out)/peak(in) per mode at 1/3-octave cutoffs (44.1/48/96 kHz, q 0.5–1, input peaks 0.001–1, sines, saws, squares, noise, random signs, impulses), taken over the cutoff's whole range plus 15 %. Measured offline, not listened to, at the default level 0.3:
| mode | trim-1 gain, 200 Hz–4 kHz | at 12–20 kHz | worst render at trim 1 |
|---|---|---|---|
"lp" | 0.47–0.67 | 1.6–2.7 | 1.32 |
"bp" | 0.54–1.05 | 2.4–2.6 | 1.42 |
"hp" | 13 → 1.2 (falls with cutoff) | 2.8 | 1.33 |
"notch" | 20 → 1.3 | 1.26 | 1.14 |
So a 0.9 voice through zFilter "lp" 1kHz 0.5 bounds at 1.52 (old flat bound: 3), while hp/notch keep a bound near the ceiling: their biquads ramp in from all-zero coefficients over the first control block at every note-on, which rings even a −40 dBFS input up to the soft-clip ceiling. The old flat 10·level was below the worst hp/bp render. level is what scales the wet path: at the default 0.3 a full-scale bright input really can reach 3–4.5 (+10–13 dB), so trim level, not the patch gain after it. engine/tests/peak_calibration.rs renders the table's adversarial cases against the bound in both directions.
Parity. Verified sample-by-sample against the upstream C++ compiled with -ffp-contract=off (stub VST headers, real .cpp/Proc.cpp), driven through processDoubleReplacing: 4 modes × 6 corpora (110 Hz / 1 kHz / 5 kHz sines, a 20 Hz–20 kHz log sweep, seeded noise, an impulse train) × 2 sample rates × 5 parameter settings — 4 static plus one with all four parameters swept so the control smoothing is exercised. 240 cases, max abs diff exactly 0.0. Four of them are pinned in CI as zfilter::tests::zfilter_matches_airwindows, which compares against the C++'s numbers, not the port's own.
Example song: examples/songs/zfilter.volta.
glitchShift semitones [trim] [looseness] [feedback] [mix] — the gnarly pitch shifter#
A faithful f64 port of the L channel of Chris Johnson's MIT-licensed Airwindows GlitchShifter (engine/src/glitchfx.rs), and the deliberate opposite of pitchShift: an instrument, not a utility. Volta's clean shifter is an STFT stretch that tries to hide its work; this one celebrates the artifact.
How it works. A variable-length buffer holds the input as 24-bit integers. A read tap walks against the write head at speed samples per sample, so the tap covers 1 + speed samples of source per output sample — that ratio is the pitch shift. Every time the tap laps the buffer end or catches up to the dry tap it has to jump, and rather than jump anywhere it runs a splice search: it scans up to 256 recently-registered zero crossings of the source and picks the one whose four-sample neighbourhood best matches the waveform the tap was in the middle of, biased toward recent crossings. Then it crossfades one sample against the trajectory it was on and carries on. Those splices, and the imperfection of that match, are the sound.
The pitch axis is linear in speed, not in cents#
semitones is the original's stepped ±12 knob (anything else rounds to a whole semitone and pins), and its law is the plugin's own:
speed = semitones / 12 (and ×0.5 when negative, so it can never reach 0 Hz)
ratio = 1 + speed
Only 0 and ±12 are equal-tempered. Everything in between runs sharp — the tritone by a full semitone:
| knob | ratio | cents | ET | error |
|---|---|---|---|---|
| ±12 | 2.000 / 0.500 | ±1200.0 | ±1200 | 0 |
| +7 | 1.583 | +795.6 | +700 | +95.6 |
| +6 | 1.500 | +702.0 | +600 | +102.0 |
| +5 | 1.417 | +603.0 | +500 | +103.0 |
| +3 | 1.250 | +386.3 | +300 | +86.3 |
| +1 | 1.083 | +138.6 | +100 | +38.6 |
| −5 | 0.792 | −404.4 | −500 | +95.6 |
| −7 | 0.708 | −597.0 | −700 | +103.0 |
Measured on a 1 kHz sine (dominant partial): −12 → 500 Hz, −8 → 665 Hz, −3 → 875 Hz, 0 → 1000 Hz, +2 → 1165 Hz, +7 → 1585 Hz, +12 → 2000 Hz.
This is kept because it is what the plugin is, and the two octaves — the settings people actually reach for — are exact. If you want exact equal temperament, trim rides the same linear axis, so put the ratio there and leave semitones at 0:
| interval | trim | interval | trim |
|---|---|---|---|
| +3 | 0.18921 | −3 | -0.318207 |
| +4 | 0.259921 | −4 | -0.412599 |
| +5 | 0.33484 | −5 | -0.501692 |
| +7 | 0.498307 | −7 | -0.66516 |
| +12 | 1.0 | −12 | -1.0 |
(trim = 2^(n/12) − 1 going up, 2·(2^(n/12) − 1) going down. It is continuous, so it is also just a detune: glitchShift 0 0.01 is a slow sharpening drift.)
looseness — how far the tap may wander#
looseness (0…1, default 0.5) is the inverse of the plugin's "Tighten", and what it really sets is the buffer length, from 6 samples at 0 to 65536 (1.49 s at 44.1 kHz) at 1. The tap traverses that buffer at |speed| samples per sample, so the splice rate falls straight out of it:
splices/second ≈ sample_rate · |speed| / width
Measured on a stepped-sine arpeggio at +7 semitones, once the buffer has filled (4 s of source, last 2 s measured — level is unity across the whole range, so this axis is texture, not gain):
looseness | buffer | splices/s | character |
|---|---|---|---|
| 1.00 | 65536 | 0.4 | long unbroken chunks, big time smear; the tap can be a second behind the dry — tape unravelling |
| 0.75 | 36866 | 0.7 | slow drift, occasional lurch |
| 0.50 | 16388 | 1.6 | the plugin's centre: audible splices at a musical rate |
| 0.25 | 4101 | 6.3 | stuttery, granular |
| 0.10 | 661 | 39 | a rough buzz under the pitch |
| 0.00 | 6 | 4288 | splice rate is in the audio band — it stops being a shifter and becomes a ring-mod-ish tone generator |
Two things follow. First, low looseness tracks the source tightly (short buffer = little latency) but pays for it in splice density, which is why the "tight" end is the harsher one. Second, a wide buffer takes real time to fill — at looseness = 1 the first 1.5 s after a note-on is reading silence, which is a legitimate (and useful) gesture but not a mistake to debug.
feedback and mix#
feedback (0…1, default 0, scaled to ≤ 2/3 internally) writes the shifted tap back into the buffer, so each lap re-shifts what the last lap produced: a pitch-rising or pitch-falling spiral, the classic runaway shimmer. On a +7-semitone arpeggio it moves peak level from 0.59 (feedback 0) to 0.94 (feedback 1) — audible but not explosive on musical material. mix (0…1, default 1 — you asked for a shifter, you get the shifter) blends against dry.
Level, and the ±4 rail#
The plugin's own comment is "this plugin can throw insane outputs so we'll put in a hard clip", and it means it. Output is hard-clipped to ±4 and on dense material it gets there: over the parity corpus at 44.1 kHz, arp and sine never clip at moderate settings (peaks 0.59–0.86), but white noise at the defaults clips 59.5 % of samples and the short-buffer/high-feedback corner clips 87–91 % on noise and a sweep. The cause is the plugin's "air" stage — a leaky integrator of the sample-to-sample difference, with a ~127-sample time constant, which white noise drives several times past full scale. That is the plugin, faithfully; put it on notes, not on noise, and refine's interval analysis will tell you the node's range is ±4.
Cost, state, mono#
State is ≈530 kB per voice — a 131076-entry i32 double buffer plus the crossing registers — so NodeState::GlitchShift is a lazily-built Box that is re-armed in place at note-on (the buffer clear is deferred to the first sample of the new note, where width is known and only the reachable prefix has to be touched). It carries the palette's highest cost badge (8.0). Like the rest of the family it is mono inside the voice — a stereo input collapses at the node — and it cannot be a master insert or live inside a feedback region.
Unlike the slew family, the five knobs are read once per block rather than per sample: looseness picks an integer buffer length, and every change to it wipes the zero-crossing registers, so ramping it per sample would leave the splice search with nothing to find. Once per block is also exactly the cadence of the plugin's own parameter prelude.
Faithfulness#
Verified sample-exact against the unmodified C++ (compiled against a stub VST header with -ffp-contract=off -fwrapv) over 60 cases — sine, a 20 Hz–20 kHz sweep, seeded white noise, a stepped-sine arpeggio and silence, at 44.1 and 48 kHz, across six parameter combinations including the defaults, both octave rails, and both ends of looseness: max |Δ| = 0.0, exactly. Six of those are pinned in glitchfx.rs as tests::matches_airwindows_glitchshifter, comparing against the C++'s numbers rather than the port's own.
The
fpdPRNG is load-bearing here and is not dropped the way it was for the slew family. GlitchShifter's denormal guard replaces a near-zero input withfpd · 1.18e-17, and that value is then multiplied by 8388352 on its way into the integer buffer, where it can move the truncated integer by an LSB. The port keeps Airwindows' xorshift32 verbatim and seeds it from the voice's performance coordinate throughrng::draw(module seed, synth name, track, cycle, event, node id,"glitchshift"), exactly asslewNoiseandcrackledo — so a render replays bit-identically and twoglitchShiftnodes in one patch decorrelate. The trailing dither add is absent because it is absent from the original's own f64 path; only its 32-bitprocessReplacingdithers.
Four upstream quirks are reproduced verbatim, because they are what the plugin sounds like. (1) The
fourth[]crossing register is initialised and read but never written, so the fourth term of the splice-match metric is always justfourthtemp. (2)countone/counttwoare recomputed and wrap-checked before the interpolation and then ignored — it readscount+1/count+2raw, which is the reason the buffer is doubled. (3) The match metric is a signed sum of four differences, not a distance, so the search prefers the most negative candidate rather than the closest one. (4) A splice can fire twice in one sample, because the "caught the buffer end" branch can leave the position negative and the "caught the dry tap" branch immediately below then fires too.
Example song: examples/songs/glitchshift.volta.
bbd amount ["i"|"ii"|"i+ii"] — BBD (bucket-brigade) chorus#
The analog "dimension" chorus a clean digital chorus can't give. The bare bbd amount is the generic bucket-brigade path: a ~12 ms modulated delay with companding (a soft x^0.7 compressor in, x^1.43 expander out — the bucket-brigade noise-reduction trick) and a ~4.5 kHz bandwidth-limiting low-pass on the companded delayed signal. amount drives the LFO depth and wet/dry. Its wet core is mono, but an upstream stereo pair is preserved in the dry path and the shared wet signal returns to both channels. The IR remains byte-identical (mode is skip-serialized when absent).
Juno modes (native-reference, analog-fidelity Stage 4)#
An optional trailing mode string switches the native engine to a Juno-faithful recipe (Holters & Parker, DAFx-18), parsed at compile time:
| mode | LFO (triangle) | depth | stereo |
|---|---|---|---|
"i" | ~0.5 Hz | full (±2.0 ms) | right LFO inverted (wide) |
"ii" | ~0.85 Hz | full (±2.0 ms) | right LFO inverted (wide) |
"i+ii" | ~9.5 Hz | shallow (±0.4 ms) | in-phase (fast mono-ish vibrato) |
The recipe differs from the generic path on every point that matters:
- Triangle LFO on the clock period. The delay time tracks the triangle directly (nominal range ~1.5–5.5 ms), so the pitch deviation is the triangle's derivative — a square wave, alternating up/down in piecewise-constant steps rather than the smooth sinusoidal warble a sine-LFO chorus gives. (Measured pitch-deviation flatness 0.92 vs 0.71 for a sinusoid; 3rd/1st harmonic 0.33, the ideal square-wave ratio.)
- No compander — the faint bucket-brigade hiss (a noise floor scaled by wet depth, from the shared seeded noise buffer) is the character.
- Heavy band-limiting: two cascaded ~8 kHz one-pole low-passes + a ~10 Hz one-pole high-pass around the delay path (per channel).
- Stereo out. The wet circuit reads the mono sum while the original dry L/R pair stays distinct; the right wet channel reads the inverted-LFO tap in
i/ii(measured L/R correlation ~0.05, versus ~1.0 for the mono generic path), in-phase ini+ii. - Wet/dry: an equal-mix crossfade on
amount— clean atamount=0, dry + wet mixed equally atamount=1(the classic Juno "dry + wet" balance).
LFO rates/depths are tune-by-ear constants (the literature disagrees at the second decimal); see BBD_JUNO_* in engine/src/voice/modfx.rs.
bbdTrack — the shared-bus Juno chorus#
bbd names a voice processor; when its receiver is a track (play … |> bbd amount ["i"|"ii"|"i+ii"], or bbd amount ["mode"] in a mixer/bus inserts=[…] list) the checker dispatches it to its bus twin bbdTrack, exactly as gatelope becomes gatelopeTrack, and either spelling may be written. It lowers to TrackEffect::BbdChorus — the same Juno recipe, running once on the summed bus with a free-running clock. ensembleChorus has the same pair: on a track it is ensembleChorusTrack. Because the receiver's type decides, a bound voice section (f = bbd 0.5) applied to a track is a type error, and amount must be a plain number on the bus (bbd 3dB is refused there). That is where the hardware put it: the Juno's chorus sat after the voice mix, so its triangle never restarts at note-on. The per-voice node free-runs too — its LFO phase is a pure function of the absolute transport sample (voice::free_lfo_phase), so every voice of a chord reads the same phase, exactly as one physical LFO and one bucket-brigade line would serve all the notes going through the box. (Until the 2026-08 audit finding M2 the per-voice node re-zeroed its phase at every note-on, which stamped an identical comb on every hit: a 1/16 at 120 bpm is 0.075 of a 0.6 Hz cycle, so it barely swept. Measured over 16 identical 16ths, the per-note comb notch went from 240–247 Hz (sd 1.9 Hz) to 83–245 Hz (sd 44.6 Hz) — the same roam as the bus insert on the same material, 83–245 Hz / sd 44.7 Hz.) Choose the bus insert for the classic pad/poly wash (one line for the whole mix, cheaper); keep the per-voice node when a single voice's out chain needs the effect before other voice processing. The delay line carries the mono bus sum (Juno: mono in, stereo out); the dry side stays stereo; amount is live-modulatable via a macro. Mode absent = "i" on the bus (the generic non-Juno recipe stays per-voice only). BbdChorus in engine/src/effect.rs (tap math shared with the voice arm — bbd_juno_sample) renders on every target.
rotary amount [speed] — dual-rotor speaker#
The track/master insert models a Leslie-style cabinet as an exact-complement 800 Hz split feeding a heavy bass drum and a faster horn. Each rotor has its own Doppler delay, amplitude orbit, stereo microphone phase, and asymmetric motor inertia. speed=0 is chorale, speed=1 tremolo (the default); both speed and dry/wet amount accept live macros. amount=0 is bit-exact stereo bypass while the mechanical state continues free-running.
phaser amount [stages] — resonant variable-stage phaser#
The phaser sweeps stages cascaded all-pass stages (2–12, default 4) on a log-spaced 300 Hz … 4 kHz centre grid — more stages place more notches. L/R run independent stage and feedback memory, with the right LFO offset 90°. The feedback path grows to 0.7, is high-passed around 30 Hz and gently damped above 8 kHz, so resonance sharpens the notches without accumulating DC or brittle top-end energy. amount uses wet=0.5·amount, dry=1−wet: equal dry/wet occurs at full amount and notch depth increases monotonically instead of getting shallower past two-thirds. The optional stages is the only IR change (NodeKind::Phaser.stages, skip-serialized when absent so existing IR is byte -identical); no syntax change accompanies the stereo/state correction.
freqShift shiftHz [mix] — single-sideband frequency shifter#
Shipped 2026-09-01. Closes Production Gaps AA1. Core:
engine/src/hilbert.rs.
metal = patch {
source = saw (freq pitch) |> freqShift -137Hz 0.7,
ampEnv = adsr 2ms 300ms 0.4 200ms,
gain = vel * 0.45
}
A frequency shifter adds a constant number of Hz to every partial. It is not a pitch shifter: pitchShift / shimmer / glitchShift all multiply frequencies by a ratio and leave harmonic relationships intact, while a shift of +50 Hz turns a 100/200/300 Hz stack into 150/250/350 — inharmonic, bell-like "detuned metal". It is also not ringMod, which is double-sideband: a ring modulator produces f ± shift together, and the pair beating against each other is the classic robot timbre. freqShift keeps one of them.
How. The wet path needs the analytic signal — the input plus a copy phase-shifted by 90° at every frequency. An FIR Hilbert transformer costs hundreds of taps and a long fixed latency; the classic alternative (the Bell Labs "phasing method", and every hardware Bode/Moog shifter since) is a pair of allpass CHAINS designed so their outputs differ by 90° across the band. Neither branch is filtered — only the phase difference is designed.
hilbert.rs uses Olli Niemitalo's wideband coefficient set: four one-multiply second-order allpass sections per branch (y[n] = a²·(x[n] + y[n−2]) − x[n−2]) with branch A delayed one sample to line the two up. The same coefficients as chowdsp's HilbertFilter; 90° holds from roughly sr/1000 to 0.499·sr (~48 Hz … 24 kHz at 48 kHz). Then
out = i·cos(θ) + q·sin(θ) θ = 2π·shift·t
keeps the upper sideband. Measured in engine/tests/gaps_dsp_2026_09.rs: 220 Hz shifted by +137 puts >26 dB more energy at 357 Hz than at either the mirror (83 Hz) or the carrier.
Knobs. shift is signed — negative shifts down: |> freqShift -150Hz (a negative literal since 2026-09-02; (0 - 150Hz) still works). No separate code path: the same expression with θ < 0. mix (default 1) is a straight wet/dry blend.
Both slots are ordinary signal nodes, so a macro, envelope or LFO rides either one live, and the modulator phase is an accumulator — a swept shift is continuous by construction. It cannot yet run inside a feedback loop (a lowering error lists what can); a barberpole — each pass shifting the recirculating copy again, so partials march endlessly up or down — needs its own shifter and delay line in a process2 kernel (form-constants-04-tunnel.volta builds one).
Zero shift is audibly a bypass (the network is allpass), though the phase is not preserved. Stereo in / stereo out: independent quadrature networks per channel driven from one modulator phase, so the image survives.
phaseDist amount ["curve"] — Casio CZ phase distortion#
Shipped 2026-09-01. Closes Production Gaps G5. Curves:
engine/src/phasedist.rs. (Voice-level, and listed here with its siblings; it is a source modifier, likephaseMod.) Band-limiting:engine/src/phasedist.rs(PdOsc), run byengine/src/voice/modfx.rs, pinned byengine/tests/phasedist_alias.rs.
cz = patch {
source = sine (freq pitch) |> phaseDist filtEnv "sawReso",
filtEnv = adsr 3ms 400ms 0.2 200ms,
ampEnv = adsr 2ms 300ms 0.5 200ms,
gain = vel * 0.45
}
The CZ-101 had no filter. It read a fixed cosine table through a warped phase, and sweeping the warp sounds exactly like a lowpass opening. phaseDist is a signal-first modifier that replaces the carrier oscillator with a phase-distortion oscillator reading its freq port (the phaseMod-carrier promotion precedent), so the node id and every downstream reference to it are unchanged. Only an oscillator or an fmOp can receive it — there is no phase to distort on a sample, a filter output or a summed chain, and the diagnostic says so.
Five classic DCW curves, in two families:
curve | Family | Shape |
|---|---|---|
"saw" (default) | phase warp | two segments meeting at 0.5·(1−amount); amount 0 is a plain cosine, 1 a bright saw |
"square" | phase warp | a whole cosine cycle crammed into the head of the period, then a flat hold — a pulse |
"sawReso" | resonant window | synced cosine × a falling ramp |
"triReso" | resonant window | … × a triangle |
"trapReso" | resonant window | … × a trapezoid (flat, then a ramp) |
The resonant curves are the CZ's famous "resonance": a cosine hard-synced to 1 + amount·15 times the fundamental, multiplied by a window that reaches zero at the end of each fundamental period. The window closing hides the sync reset's carrier jump, which is why sweeping it reads as a resonant filter peak marching up the spectrum rather than a synced saw. It does not make the period continuous: the next period restarts at the window's start value, a full-height step for "sawReso" and "trapReso" ("triReso" restarts at 0, a corner).
amount ∈ [0, 1] is read per sample, so an envelope in the slot is the DCW envelope. At amount = 0 the two warp curves are the unmodified cosine, so |> phaseDist filtEnv is a drop-in on an existing sine carrier.
Band-limited. The oscillator renders the curve at 4× the sample rate, with four-point polyBLEP/polyBLAMP corrections at the resonant windows' step and corners, and decimates through two IIR halfbands. The generator runs ahead of the halfbands' ≈ 2.5-sample delay, so the oscillator adds no latency: a steady tone is phase-aligned with the naive curve, and only amount/pitch modulation arrives ≈ 2.5 samples late. Inharmonic (aliased) energy below 20 kHz, re the tone's total, measured offline (coherent DFT, worst curve at amounts 0.3–0.95; the stock band-limited saw for scale):
| pitch | before (naive) | after | stock saw |
|---|---|---|---|
| G1, 49 Hz | −33 dB (sawReso) | −133 dB | −64 dB |
| G3, 196 Hz | −27 dB (sawReso) | −109 dB | −58 dB |
| G5, 784 Hz | −21 dB (sawReso) | −79 dB | −51 dB |
| G6, 1568 Hz | −15 dB (square 0.95) | −64 dB | −47 dB |
| G7, 3136 Hz | −5 dB (square 0.95) | −48 dB | −42 dB |
The exception is a resonant curve whose synced carrier (ratio·f0) is above the audio band — G7 at amount ≥ 0.6, G6 at 0.95. The naive tone there was mostly alias; the band-limited one keeps only the window's in-band sidebands, so it is 19–48 dB quieter at G7 (1–2 dB at G6), with a residual floor about 25 dB under it. The halfbands' own transition folds 24–26 kHz content onto 22–24 kHz. At G1–G3 the change from the naive curves is −47 dB or less on the warps and "triReso", and −22…−32 dB on "sawReso"/"trapReso", whose difference is mostly the alias that was removed (harmonics move ≤ 0.4 dB below 16 kHz). Cost: ≈ 2 ms per second of audio per voice natively, about 8× the naive curve. Pinned by engine/tests/phasedist_alias.rs.
The phaseMod-carrier idiom (a synced angular modulator at the carrier frequency) still works and still reaches places phaseDist does not; this is the named one-liner for the classic curves.
gatelopeTrack threshold [trebleSustain] [bassSustain] [snap] [mix] — Gatelope on a bus#
Shipped 2026-09-01. Closes Production Gaps AC1.
The voice-level gatelope can only sit in a patch's out chain — and a sound track has no patch, which made the obvious break-surgery move (filter a sampled drum tail shut from both ends, without a click) unspellable. gatelopeTrack is the same voiceflavors::Gatelope core, one instance per channel, on the summed bus:
brk = sound !"amen:0 amen:1 amen:2 amen:3"
|> transient 6dB -4dB
|> gatelopeTrack 0.3 0.7 0.35
Knob meanings, ranges and defaults are the voice form's exactly (threshold is squared internally, trebleSustain defaults to 1, bassSustain to 0.5, snap is a speed not a time, mix to 1); all five are live-modulatable. The -Track suffix follows delayTrack / eqTrack; qualified it is track.gatelope, beside the voice-level aw.gatelope. Buses and master inserts accept it too.
transient has accepted sound tracks since the §30 G1/G2 batch — the gap entry was stale on that half.
delay — fractional interpolation#
The per-voice delay's read is now linearly interpolated between the d-th and (d+1)-th samples back, so a non-integer or tempo-synced delay time lands precisely (no rounding to the sample grid) and smooth time sweeps don't zipper. No IR or API change.