Skip to content

spectral_reverb returns under the ABI rules, measured against the plates (phase 2 item 7, part 2) - #86

Merged
tap merged 2 commits into
mainfrom
feat/spectral-reverb
Oct 8, 2026
Merged

tap merged 2 commits into
mainfrom
feat/spectral-reverb

Conversation

@tap

@tap tap commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Phase 2 item 7, part 2 (HANDOFF item 11). tap::mu::spectral_reverb, the per-bin reverb from the karaoke branch (8abe958), comes back under the ABI rules and is measured on part 1's harness beside the as-shipped Dattorro plate.

Verdict: it loses to the plate everywhere measured. It stays in the repo only as a measured negative result, and its header says so. Removing it again is the alternative; I recommend against it, because the gated rows that hold the verdict need the class.

What changed

  • include/mutap/spectral_reverb.h:
    • defined inside tap::mu::inline TAP_DSP_FFT_ABI;
    • the 2 × block FFT size goes through fft_detail::checked_fft_size before any buffer is sized (working note 6 (b): on the M55 an unchecked size is a release-mode HardFault);
    • an afc_stage (process_block(in, out, n) noexcept, n a multiple of the block), allocating only at construction, latency() one block;
    • spectral_shaping::flat / from_path with an explicit shape_max;
    • shaped from F̂ (reshape_from_impulse_response) or from the coherent bus sum |Σ F̂_m| (reshape_from_impulse_responses).
  • tests/test_fft_engine_contract.cpp pins it as the sixth embedder: the tag by qualified name and typeid, and a size-gate sweep in both profiles plus the explicit CMSIS rows. mutap/fft.h comments go from "five" to "six".
  • tests/test_spectral_reverb.cpp covers the float/double plumbing and runs in both emulated selections (bare_metal_main.cpp and TEST_FILTER, kept in sync; the on-target count comment is now 118).
  • decorrelated_loop's forward stage now takes a stage's latency() out of the forward delay. The plates report 0, so their rows repeat: the S1 cost medians match part 1's grid to the printed digit.
  • New harness pieces:
    • tests/support/spectral_rig.h: the measurement and the per-bin hypothesis statistics;
    • gated host rows tests/test_spectral_reverb_host.cpp;
    • the MUTAP_SLOW sweep tests/test_spectral_reverb_sweep.cpp (ProbeConvergence, Grid);
    • karaoke_ramp_dump --reverb spectral and reverb_audible.py --plate spectral.
  • Docs: docs/reverb-afc.md gets a "Spectral reverb" section, and the headline, intro, not-run list and provenance are updated. docs: the S3 wet-0.30 audible rows examined — bounded lines at the plate's own modes, loop-dependent #84's S3 paragraphs are kept. HANDOFF item 11 is rewritten as resolved; items 13 and working note 6 are updated.
  • Follow-up commit d912973:
    • the 80 s host rows move behind MUTAP_SLOW, and 20 s proxies run by default;
    • the doc says, where the plate rows are described, that a stage's latency is budgeted inside the loop delay;
    • the S3 chain-output flags become an open item next to the plates' ‡ lines.

Numbers

All from macOS 15.7 x86_64, AppleClang 17, Release, five seed sets.

Probe convergence (S1, wet 0.30, rt60 1 s; median ASG re the dry open loop). Unlike the plates (10 s), only the cabin's flat, shape_max 1 and 2 held rows converged, at 80 s. shape_max 4 and every mt5 chain row had not converged by 160 s.

Row 10 s 20 s 40 s 80 s 160 s Converged
cabin, held, canceller alone +11.66 +11.74 +11.74 +11.74 10 s
cabin, held, flat −3.87 −4.67 −5.64 −6.62 −6.82 80 s
cabin, held, shape_max 1 +3.36 +2.27 +1.48 −0.37 −0.27 80 s
cabin, held, shape_max 2 +5.41 +3.63 +3.63 +2.27 +2.36 80 s
cabin, held, shape_max 4 −2.01 −3.30 −5.74 −17.46 −19.02 not
mt5, held, flat +1.21 +1.00 +0.41 −0.27 −0.57 not
mt5, held, shape_max 1 +10.57 +8.03 +9.49 +8.03 +8.42 not
cabin, held, flat, no canceller −29.14 −29.75 −30.14 −30.33 −30.43 80 s

The doc has the full table, mt5 and the speech envelope included.

Behind the canceller. Six rooms, held note, 40 s probes, so not converged; the doc explains why this mostly understates the cost. Values are the median of room medians.

Plate 0.5 cost Spectral cost (flat / sm1 / sm2 / sm4)
S1, w 0.15 (rt60 1.18) +0.49 +5.66 / +2.83 / +7.03 / +14.26
S1, w 0.30 (rt60 1.18) +0.88 +13.09 / +10.74 / +13.48 / +30.57
S3, w 0.15 (rt60 1.18) −0.59 +10.16 / +10.94 / +11.82 / +15.04
S3, w 0.30 (rt60 1.18) −1.37 +18.75 / +15.14 / +16.70 / +15.23
  • Paired by seed, no room's median favours the spectral reverb for 22 of 24 rows; for the other two (flat and shape_max 1 at S1, w 0.15), 1 of 6 rooms does.
  • At equal wet the spectral stage is 4.30 to 14.75 dB louder than the plate's mix.
  • Speech envelope (cabin and mt5, S1, w 0.30, 80 s): it costs +4.30 to +17.09 dB where the plate raised the limit.

Open loop (no canceller, 160 s): flat sits 17.15 to 31.80 dB below the dry room (room medians), confirming HANDOFF 11's −18 to −25. Shaped rows sit 12.56 to 29.84 dB below.

Shaped vs flat:

  • shape_max 1 is +1.17 to +4.30 over flat at S1.
  • shape_max 4 is −7.91 to −12.79 at S1, in every room.
  • At S3, w 0.30, every ceiling is +0.98 to +3.71.

Hypothesis (Spearman over the 65 bins, at the moment of shaping, 30 runs per row):

  • ρ(decay time, |F − F̂|) is −0.336 to −0.688. The long decays sit where the absolute misalignment is small.
  • The worst-quarter energy share is 0.022 to 0.178, against flat's 0.246.
  • ρ against the relative misalignment |F − F̂|/|F| is +0.161 to +0.526.
  • The residual-loop indicator (max_k |F − F̂|·g_k against flat) is −0.85 to −3.25 dB at shape_max 1 and +5.49 to +9.72 at shape_max 4.
  • None of these predicts a run's own shaped − flat result: ρ over 30 runs ranges −0.351 to +0.699, with no consistent sign.

Audible (cabin, ramp + criterion, PROTOCOL §7.3, rt60 1.1846 s; chain output, with c in brackets):

S1 aud S1 run S3 aud S3 run
plate 0.5, w 0.15 (part 1) +16.53 +21.46 +10.38 +23.01
plate 0.5, w 0.30 (part 1) +12.89 +22.07 −4.77 ‡ +23.31
flat, w 0.15 −7.42 (−6.74) +4.73 −17.78 (−2.05) +11.66
shape_max 1, w 0.15 +11.90 (+11.90) +18.31 −13.14 (+4.98) +17.98
shape_max 4, w 0.15 −9.40 (−7.99) +1.16 −17.23 (+8.34) +14.11
flat, w 0.30 −6.30 (−6.42) −2.82 −12.77 (−9.24) +3.56

Every spectral row is audible earlier than the plate at the same wet. The doc has all 16 rows.

Gated rows. Converged probes run behind MUTAP_SLOW. The default run carries 20 s, two-seed-set proxies with the same directions. Short probes understate both the spectral cost and the open loop's danger, so a pass is conservative.

Row Run Probe, sets Gate Measured Time (3 threads)
DecayAndLevelBesideThePlate default analytic T30 = rt60 ± 0.005 s; level > 0 dB T30 1.0000 / 1.1846 s; +3.40 … +8.22 dB 0.25 s
UnsafeWithoutTheCancellerShortProbe default 20 s, 2 flat mean < −15; shape_max 1 mean < −8 cabin −29.80 / −19.79; mt5 −22.42 / −13.98 27.03 s
CostsMoreThanThePlateShortProbe default 20 s, 2 plate − spectral: flat mean > +6; shape_max 1 mean > +3 +14.66 / +9.78 (per set: +15.75 +13.56 / +11.62 +7.94) 77.25 s
UnsafeWithoutTheCanceller MUTAP_SLOW 80 s, 5 flat median < −15; shape_max 1 median < −8 cabin −30.51 / −19.61; mt5 −22.77 / −14.69 189.41 s
CostsMoreThanThePlateBehindTheCanceller MUTAP_SLOW 80 s, 5 cost vs canceller alone: flat > +8; shape_max 1 > +4 +17.19 / +8.44 696.95 s

The 20 s values are recorded beside the 80 s ones in the test comments, for this host only so far; CI's legs run the default rows.

Verification

  • cmake -DCMAKE_BUILD_TYPE=Release -DMUTAP_WERROR=ON -DMUTAP_BUILD_KARAOKE_DUMP=ON: built clean.
  • Full ctest, default suite, at d912973: 420/420 passed, 3678.66 s serial with MUTAP_SLOW_THREADS=3. At aadf7db, with the 80 s rows still in the default run, it was 418/418 in 4479.84 s.
    • The spectral default rows took 99.92 s inside ctest: 0.24 s, 26.44 s and 73.24 s.
    • The two slow host rows and the two sweep entries report Skipped.
    • mutap_fingerprint passed.
  • pre-commit via xargs: passed.
  • scripts/tidy.sh on the 8 changed host TUs: clean. A direct clang-tidy-18 -p build-tidy count: 0 project warnings.
  • Fingerprints and icount are unchanged: the new class is in neither the fingerprint harness nor any icount workload, and fft.h changed in comments only.

Sweep wall times, on 3 threads of the shared Intel Mac:

Run Wall time
ProbeConvergence, held 11349.4 s
ProbeConvergence, speech 6849.3 s
Grid, held 35418.6 s
Audible 1380.5 s
UnsafeWithoutTheCanceller 189.41 s
CostsMoreThanThePlateBehindTheCanceller 696.95 s

Not run (the budget)

  • The six-room speech grid; speech is measured in the two ProbeConvergence rooms only.
  • Converged probes for the grid.
  • S3 on speech.
  • float32 in the loop.
  • Two microphones: the bus-sum shaping has plumbing tests only.
  • The backing track.
  • Audible rows beyond the cabin held note at rt60 1.1846 s.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DDhgJqxUsdmWPKh6pKrV5A

tap and others added 2 commits October 8, 2026 10:36
…tes: it loses (phase 2 item 7, part 2)

tap::mu::spectral_reverb (include/mutap/spectral_reverb.h), the per-bin
WOLA reverb from the karaoke branch (8abe958), made legal and measured on
part 1's harness beside the as-shipped Dattorro plate:

- inside tap::mu::inline TAP_DSP_FFT_ABI, its 2 x block FFT size through
  fft_detail::checked_fft_size, pinned as the sixth embedder in
  test_fft_engine_contract.cpp (tag by name and typeid, the size gate swept
  in both profiles); an afc_stage (process_block(in, out, n) noexcept,
  allocation only at construction, latency() one block), a flat mode
  (spectral_shaping::flat) and an explicit shape_max, shaped from F_hat or
  the coherent bus sum |sum F_hat_m|;
- float plumbing test_spectral_reverb.cpp in both emulated selections
  (bare_metal_main.cpp and TEST_FILTER; on-target count 118);
- decorrelated_loop's forward stage takes a stage's latency() out of the
  forward delay (0 for the plates: their rows repeat);
- tests/support/spectral_rig.h, the gated host rows
  test_spectral_reverb_host.cpp, the MUTAP_SLOW sweep
  test_spectral_reverb_sweep.cpp (ProbeConvergence, Grid),
  karaoke_ramp_dump --reverb spectral, reverb_audible.py --plate spectral;
- docs/reverb-afc.md "Spectral reverb", HANDOFF items 11 (resolved) and 13.

Measured (macOS 15.7 x86_64, AppleClang 17, Release, five seed sets):
behind PEM + FD-Kalman, six rooms, held note, S1 and S3, wet 0.15 / 0.30,
flat and shape_max 1 / 2 / 4 (40 s probes, not converged): cost +2.83 to
+30.57 dB (median of room medians) against the plate's -1.37 to +0.88.
Without a canceller the flat reverb sits 17 to 32 dB below the dry room.
Shaping helps only at shape_max 1; the long decays sit where |F - F_hat| is
small (rho -0.336 to -0.688), not where it is worst. Every spectral row is
audible earlier than the plate at the same wet. Verdict: it loses; kept
only as a measured negative result (header says so). Fingerprints and
icount unaffected: the class is not in the fingerprint harness or any
icount workload, and fft.h changed in comments only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDhgJqxUsdmWPKh6pKrV5A
…ies by default

The converged 80 s rows cost 697 s (CostsMoreThanThePlateBehindTheCanceller)
and 189 s (UnsafeWithoutTheCanceller) on 3 threads, more than the sanitizer
legs can afford; both now skip unless MUTAP_SLOW=1. The default run carries
a short proxy of each, 20 s probes, two seed sets, the same directions:

- UnsafeWithoutTheCancellerShortProbe (cabin and mt5, open loop): flat
  -29.80 / -22.42, shape_max 1 -19.79 / -13.98 dB (means; the 80 s medians
  -30.51 / -22.77 and -19.61 / -14.69); gates < -15 and < -8, as the slow
  row's. 27.03 s.
- CostsMoreThanThePlateShortProbe (cabin, behind the canceller): the
  plate's limit minus the spectral reverb's, +14.66 (flat) and +9.78
  (shape_max 1) dB (means of +15.75 / +13.56 and +11.62 / +7.94); gates
  > +6 and > +3. 77.25 s.

Short probes understate both the spectral cost and the open loop's danger
(ProbeConvergence: those medians fall with probe length), so a pass is
conservative. The default spectral host rows take 104.5 s on 3 threads of
the Intel Mac. docs/reverb-afc.md says where the plate rows are described
that a stage's latency is budgeted inside the loop delay, lists the S3
chain-output flags as an open item beside the plates' S3 lines (the ‡), and
gives the new rows and timings; HANDOFF item 11 likewise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDhgJqxUsdmWPKh6pKrV5A
@tap
tap merged commit 9c3c459 into main Oct 8, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant