VOLTAreference

Reference / For contributors

How Volta checks audio quality

For contributors

Open in the workbench →
On this page · 6 sections

For contributors: the automated render checks for silence, numerical failures, clicks, decay and repeatability.

As-built reference for engine/tests/audio_gates.rs, the CI gate that measures finished song audio. Established 2026-08-15.

Related: ARCHITECTURE §5.2 (the offline render path this drives), engine/tests/golden_audio.rs (the per-DSP-model hashes), engine/tests/export_endings.rs (how a render ends).

Why it exists#

Volta's recurring escaped-bug class is audible but CI-invisible: a click at a scene boundary, a mix that jumps several dB when a knob moves, an arrangement that ends and leaves the engine buzzing. Every one of those is a property of the finished audio of a whole song, and until this suite landed nothing in the test tree looked at that.

The two suites divide the space:

golden_audio.rsaudio_gates.rs
unitone clip per DSP modela real song from examples/songs/
render pathrender_scene_stereocompile → render_export_arrangement (scenes, transitions, macro automation, ring-out)
verdictFNV-1a hash of the sample bitsmeasured statistics vs a baseline with tolerance bands
answers"did this filter change?""does this song still sound like a song?"
stabilitypinned to the debug profile + this machineprofile- and platform-stable by construction

What it renders#

Twelve songs, each truncated to the first few bars of its own arrange schedule — chosen to cross at least one scene boundary, because a scene change is where the track set, the macro ramps and the level all move at once. Every song is deterministic (no live input, no record, no plugin, no neural model) and all but one is sample-free; glossolalia loads runtime/public/samples/piano.wav explicitly so the granular path is gated at all.

Coverage spans drum circuits (machinedrum, gridline), wavetable + FM (glasswing), compile-time-baked shape waveshapers (sculpt), subtractive/ladder (torsion, helix, hydraulic), physical modelling (nineteen), an FM stack (lamellae), reverb-dense mixing (monolith), a curved tempoMap over the full mastering chain (flagship) and granular + formant vocals (glossolalia). The roster and each song's reason are printed by roster_says_why_each_song_is_here.

What it measures#

Per song, over the body (bar 0 → the arrangement horizon):

Baselined — pinned in engine/tests/snapshots/audio_gates.json with a tolerance band:

columnbandwhat a move means
peak_dbfs±0.25 dBa level jump, a limiter that stopped limiting
rms_dbfs±0.25 dBthe mix balance moved
lufs±0.30 LUditto, perceptually weighted (BS.1770)
click_max±10 %the largest sample-to-sample jump — a discontinuity appeared
click_ratio±15 %that jump over the body's p99 jump (scale-free)
stereo_corr±0.03the image collapsed to mono, or flipped polarity
tail_dbfs±2 dBwhere the ring-out ends up

Absolute — invariants that need no baseline, and are not blessable:

  • every sample finite; peak audible (> −30 dBFS) and inside full scale;
  • |DC| ≤ 0.002 per channel;
  • the local click ceiling: the worst jump inside any 10 ms window, over that window's own p99 jump, ≤ 40×;
  • the ending: the ring-out decays ≥ 12 dB across itself, lands ≥ 15 dB under the body, and the file's last sample is within 1e-6 of zero;
  • the stop button: stop_all on the live engine, then the output must fall ≥ 15 dB and reach ≤ −42 dBFS within 2.5 s;
  • determinism: two renders of the same source in one process are bit-identical;
  • cost: the render set stays under 90 s of the rendering thread's CPU time — not the wall clock, which on a machine shared with other builds measures the load (232 s wall for renders that took 26 s of CPU). Not asserted under CI, where the runner's speed is not a property of the suite.

The click statistic, and why there are two of them#

The repo's click metric is max |Δx| judged against the signal's own motion (docs/reference/glide.md; export_endings.rs). A saw's period reset is 0.2 and perfectly musical; the same 0.2 in a sine pad is a pop.

The global ratio (click_max / p99|Δx| over the whole body) is what the baseline pins. It is sensitive but it cannot stand alone as an absolute gate: measured on torsion, a note attacking out of a near-silent passage reads 156× the body's p99, because the p99 of a sparse piece describes its silence. The local form — the worst jump inside a 10 ms window over that window's own p99, ignoring windows whose largest jump is under 0.01 — reads the same event at 4.7×, and is therefore the one that carries an absolute ceiling. Measured when the suite landed, the set's worst is 18.3× (gridline, a drum transient at 7.49 s); median 2.7; the ceiling is 40×. Two songs (monolith, lamellae) have no jump anywhere over 0.01 and so report 0 — their click gate is the baselined click_max alone, which is the honest answer for a piece with no transients in it.

Hunting a click by hand#

The same statistic, by hand: render offline and rank 10 ms windows by max |x[n]−x[n−1]|, printing the median and p99 beside it. A raw saw's own reset edge is ~0.05–0.13, so the absolute number means nothing: a click is max ≫ p99, and after a real fix max ≈ p99. Print the exact sample index, not a rounded time, and prepend silence — a voice starting on frame 0 hides its note-on step. source = const 1 makes the waveform BE the amp envelope, the cleanest envelope probe, but the native per-voice DC blocker reads a DC probe as ~0, so use sine natively; any stateful oracle must likewise stay distinguishable after the output filters settle (a DC-blocked ramp can hide a reset). Check both backends separately — they click for different reasons — and confirm each finding by reverting the fix and re-measuring: a regression test that passes on the old code proves nothing. Bugs mask each other; a fix that makes a neighbouring case worse suggests a second bug, not a revert.

The ending, and why "silent" means "decaying"#

render_export_arrangement stops scheduling at the horizon, release_alls every sounding voice, and rings out. stop_all (the stop button) instead re-anchors the transport and cuts every voice on the lifetime gate's 256-sample (5.3 ms) fade — a declick, not a release: the hard drop it replaced stepped 4.7× the note's own p99 at the stop sample (engine/tests/stop_declick.rs) — but in both cases the track and bus effects keep ringing, and a bus reverb or a ping-pong doing so is correct. So the gate is the shape the bug class actually has: a flat post-stop level is always a bug; a decaying one is music. Both gates therefore assert decay plus an absolute floor, not instant digital silence.

It is not vacuous#

Mutation-tested against the engine when it landed (2026-08-15). Each mutation was applied to engine/src/, the gate run, then reverted:

mutationresult
MASTER_GAIN 0.7 → 0.73 (+0.37 dB)caught — peak_dbfs / rms_dbfs / lufs on all twelve songs
MASTER_GAIN 0.7 → 0.72 (+0.24 dB)not caught — the ±0.25 dB band is the detection threshold, as designed
PatternMod anti-pop glide 5 ms → 0 (a per-step click)caught — hydraulic peak +0.71 dB
offline::FINAL_FADE_SEC → 0 (no closing fade)caught — the end-of-file step gate, on 9 of 12 songs
EDGE_FADE 256 → 1 (no end-of-voice declick)not caught — no song in the set ends a voice on the lifetime gate rather than an envelope release; glide_retrigger.rs covers that path

Blessing#

UPDATE_SNAPSHOTS=1 cargo test -p volta-engine --test audio_gates

rewrites engine/tests/snapshots/audio_gates.json from the current engine, and prints the table first. Review it, decide the move is intended, listen if it is a DSP change, then commit the new baseline together with the change that caused it. Two rules:

  • The profile does not matter. Unlike golden_audio, blessing in debug and in --release produces a byte-identical baseline — measured 2026-08-15 to nine decimal places on every column of every song. (volta-engine is built at opt-level = 3 in the dev profile too, per the root Cargo.toml, so there is barely a profile difference to forgive in the first place.)
  • The absolute gates are not blessable. UPDATE_SNAPSHOTS=1 still fails on them and writes nothing: a baseline records what the audio is, and those constants say what it must never be.

Adding a song: add a row to songs(), say in its why which feature family it buys, bless, and check the suite's printed runtime.

Where it runs#

cargo test -p volta-engine --test audio_gates, so it rides make test-core (cargo test --workspace), CI's Full native gate, and make gate-smart whenever a change can reach volta-engine. It is also its own step in CI's Rust fast gate and in make test-fast — the PR-sized signal — because a gate that only fires after merge does not gate anything, and the bug class it exists for is exactly the one review misses. Keep the CI job and make test-fast in step with each other.

Measured cost (Apple-silicon Mac, debug, when the suite landed): 245 s of rendered audio in ~27 s, ~9× realtime; the whole suite, all five tests, in ~33 s. Every run prints its own figure ([audio gates] … s of audio in … s of CPU).