Repository navigation
Reverb behind the canceller: reverb_mix stage, Dattorro ASG vs wet/decay, dry-only-shift variant, dattorro_paper cost table (phase 2 item 7, part 1) - #83
Merged
Conversation
…sured behind the canceller (phase 2 item 7, part 1) include/mutap/reverb_stage.h: tap::mu::reverb_mix<Sample, Reverb>, an afc_stage for the chain's reverb slot, y = (1 - w) x + w r with r the reverb's L or (L + R) / 2 return, and shifted_dry_mix<Sample, Shifter, Reverb>, the dry-only-shift topology (the reverb fed the unshifted bus). Any reverb with the FAUST shim's process(in, out, n); header-only, no FFT, no ABI tag, latency() 0, allocation only at construction. Float plumbing suite test_reverb_mix.cpp in both emulated selections (7 tests). Harness: decorrelated_loop gains a forward stage (reset per probe; bit-identical without one against origin/main on the karaoke measurement), tests/support/reverb_rig.h, karaoke_ramp_dump --reverb and tools/notebook/reverb_audible.py (PROTOCOL 7.3 flags, per-run records, WAVs deleted after analysis, --recover, --control). Measured (macOS 15.7 x86_64, AppleClang 17, Release, five seed sets; docs/reverb-afc.md): every held-note row converged at 10 s (decay 0.85 included), the speech envelope did not everywhere by 160 s; behind the canceller the plate costs at most +1.46 dB of runaway ASG (six rooms, S1; none at S3) while the audible limit falls with wet and decay; the dry-only shift is audible later in 13 of 16 pairs, bisected runaway favours the whole-bus shift; the dattorro_paper bare-loop table and T30s. Gated host rows in test_reverb_stage.cpp; the MUTAP_SLOW sweep in test_reverb_stage_sweep.cpp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDhgJqxUsdmWPKh6pKrV5A
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 item 7, part 1: the reverb behind the canceller, measured. The spectral reverb returns on this harness in part 2 (HANDOFF item 11).
What lands
include/mutap/reverb_stage.h:tap::mu::reverb_mix<Sample, Reverb>(anafc_stageforafc_chain's reverb slot: y = (1 − w)·x + w·r, r = the reverb's L or (L + R)/2) andtap::mu::shifted_dry_mix<Sample, Shifter, Reverb>(y = (1 − w)·shift(x) + w·r(x), the reverb fed the unshifted bus). Any reverb with the FAUST shim'sprocess(const Sample* const*, Sample* const*, int) noexcept;set_wet()/set_return_mode()noexcept;latency()0; header-only, holds no FFT, no ABI tag; allocates its return buffers at construction only. FAUST stays inthird_party/faust/. Added tomutap.h.tests/test_reverb_mix.cpp: float/double plumbing with a stand-in reverb (formula, both returns, any block length, in place, dry-only topology, validation, RT contract). Both emulated selections run its float half (reverb_mix_test/0.*:ReverbMixConfigValidation.*:ReverbMixRtContract.*, 7 tests; the bare-metal count comment is now 108). FAUST is not compiled on the emulated targets (it was not before either).decorrelated_loopgains a forward stage (the chain's reverb slot, reset per probe) — bit-identical without one: the karaoke measurement (cabin and mt5, held and speech, with and without the 5 Hz shift, seed 2, 5 s) gives the same limits to 17 digits on origin/main's and this branch's loop.tests/support/reverb_rig.h(plates, rigs, T30, mix level, phase 0's bare-loop analysis).karaoke_ramp_dump --reverb shipped|paper --wet --decay --damping --return --topology bus|dry --lib-shift --reverb-ir-only, 4th WAV channel = the chain output.tools/notebook/reverb_audible.py(PROTOCOL §7.3 flags;--control; per-run records; WAVs deleted after analysis;--recover).tests/test_reverb_stage.cpp;MUTAP_SLOWsweeptests/test_reverb_stage_sweep.cpp(ProbeConvergence, Grid, Shift, BareLoopCost).docs/reverb-afc.md, HANDOFF item 13 (item 11 now points at part 2), README component bullet, karaoke doc and FAUST README pointers.Every number below: macOS 15.7.9 x86_64 (i9-8950HK), AppleClang 17.0.0, Release, double in the loop, five seed sets (2 22 42 62 82), shared machine. No second host ran the sweep or the audible driver.
Probe convergence (cabin, mt5; S1; wet 0.30; median ASG, dB)
Held note: every row converged at 10 s, decay 0.85 included (20–160 s identical, seed by seed). Speech: 3 of 8 rows did not converge by their longest probe; no speech row is gated. Grid and shift rows ran the held note on 20 s probes and speech on 80 s; the gated rows on 10 s.
Runaway ASG and cost behind the canceller (Grid; six rooms, both generator families)
Median over rooms of each room's five-seed median; cost = canceller-alone − with-plate (positive = the plate costs); [room range] {per-seed range}; matched = cost − mix level.
Held note, S1 (canceller alone +11.74 [+11.05, +12.42]):
Held note, S3 (canceller alone +18.40 [+17.25, +18.59]): every raw cost ≤ −0.10 dB (largest matched +1.62); full table in the doc. Speech, S1, 80 s (canceller +19.75): shipped w 0.30 at decay 0.5 / 0.7 / 0.85 costs −1.17 / −0.88 / −0.29 (matched +0.51 / +0.67 / +0.84).
Did not separate: damping 0.5 vs 0.0005, (L+R)/2 vs L, paper vs shipped at decay 0.5 — every difference (≤ 1.07 dB) inside the rows' room ranges.
Shift rows (runaway, bisected; six rooms; held note, 20 s; shipped plate, w 0.30)
Audible rows (cabin, held, ramp + howl criterion on c per PROTOCOL §7.3; dB over exact_msg_db; medians of 5 seeds)
† Worst case for the shift (synthetic held note). Shift-alone "control": c re-analysed with each plate's T30 in the hold, as the product rows are (1.185 / 2.117 s). ‡ Some seeds audible below the dry loop's limit (per seed −13.55 … +1.89 at 0.5); not examined. Dry-only − whole-bus per-seed median > 0 in 13 of 16 pairs (audible) and 16 of 16 (ramp runaway) — the opposite of the bisected runaway at S1. The canceller row is not karaoke-afc.md's +14.96: §7.3 flags (no
--dechirpwithout a shift) vs that doc's--dechirpon every row.dattorro_paper bare-loop cost (phase 0's method: 4 fixtures at 4096 taps, d = 480, L, median of 4 = mean of middle two; bound / exact, dB; reproduces phase 0's as-shipped table to the digit)
T30 L / (L+R)/2 at decay 0.3 / 0.5 / 0.7 / 0.85: paper 1.7252 / 2.1186 / 3.7882 / 8.2457 s (1.5975 / 2.3626 / 3.8598 / 8.1939); as shipped 1.0144 / 1.1846 / 2.1173 / 4.6220 s. At equal decay the paper plate costs more; at equal T30 (paper 0.5 vs shipped 0.7) less (+0.30 vs +1.11 bound at w 0.30).
Gated (
ReverbStage.*, host-only)CancellerHoldsBehindThePlates(cabin, S1, held, 10 s): median cost < +3.5 dB for shipped 0.5 w 0.15 / 0.30, 0.7, 0.85 and paper 0.5 at w 0.30 (measured −0.28 / +0.56 / +0.28 / −1.69 / +0.00); median ASG > +7 (min measured +10.72).WholeBusShiftHoldsMoreRunawayThanDryOnly(cabin, S1, 2 Hz, decay 0.5, w 0.30): per-seed median whole − dry > 0 (measured +3.09; seeds +3.94 +3.09 +7.59 +0.84 +2.53).Verification
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DMUTAP_WERROR=ON -DMUTAP_BUILD_KARAOKE_DUMP=ON && cmake --build build: clean.ctest --test-dir build --output-on-failure: 100% tests passed, 0 tests failed out of 396 (3233.67 s; the 3 locally flaky tests passed this run).CancellerHoldsBehindThePlates133.29 s,WholeBusShiftHoldsMoreRunawayThanDryOnly63.73 s on 4 threads.git diff --name-only origin/main | xargs pre-commit run --files: Passed.scripts/tidy.shon the three new TUs: clean; directclang-tidy-18 -p build-tidyon each: 0 warnings, 0 errors.mutap.hinclude, which onlytools/capiuses).Wall times
The audible driver's first invocation filled the scratch disk (≈ 180 MB of WAV per run) and died after 230 runs; it now deletes WAVs and checkpoints per run, and
--recoverrebuilt those 230 records (runaway from the gain log, scratch-checked against the WAVs in 230 of 230).Not done
Speech at S3; the paper plate in the loop at decay 0.7 / 0.85; bisected shift rows at wet 0.15 / 0.50; audible rows beyond the cabin held note and the shipped plate; float32 in the loop; backing track; two mics; the
dattorro~Max external (out of scope).🤖 Generated with Claude Code
https://claude.ai/code/session_01DDhgJqxUsdmWPKh6pKrV5A