Commit Graph

4 Commits

Author SHA1 Message Date
Matthew Stanley df212a8693 WIP: stage production history gates pending quiet-host timing 2026-09-05 12:38:01 -07:00
Matthew Stanley 8dbe04e6c5 spu/cdrom: live voice loop-address + CD-audio volume matrix (X5 +11dB fix)
Two hardware-faithfulness fixes found by the always-on tap rings + Beetle
oracle A/B on fingerprint-matched attract music (tools/audio_pair_segments):
the recomp played the identical track at -3.8 dBFS vs the oracle's
-14.1 dBFS — +10.3 dB hot, clipping continuously.

1. spu.c: the voice repeat/loop address register (voice reg 7) is LIVE
   state on real hardware — writing it after KEYON retargets where the
   next END+REPEAT block jumps (Beetle spu.cpp:1150/333), and the block
   auto-latch (flag bit 2) updates the guest-visible register (386/1302).
   The recomp cached repeat_addr at KEYON only, so X5's driver idiom —
   KEYON, then point the loop register at a silent tail block; no KEYOFF
   ever — left voices looping the LOUD sample body forever. Measured: 3
   stuck voices re-looping ~1400x/s, 160k SPU events/min vs the oracle's
   633, zero game KEYOFF writes (the game never needs one), +11 dB.

2. cdrom.c: implement the CD controller's decode volume matrix
   ([src][port], 0x80 unity, pending banks + apply latch at 1F801803.3
   bit 5 — mapping copied from Beetle cdc.cpp:1483-1501, applied after
   the XA resample per PS1 hardware tests). X5 drives it constantly:
   0x73-0x7E steady, ramped to 0x00 at scene changes (measured via the
   new beetle `cdc_volume` command + PS_CDC::PSXRecomp_GetDecodeVolume
   peek). Without it music fades never happened and steady level sat
   ~0.6 dB hot.

Also: AUDIO_TAP_VOICES (T0 voice-sum tap) for source-by-source mix
decomposition, per-tap WAV rates, bridge stats in audio_stats.

Measured after (MMX5 attract, 20-25 s windows):
  spu_out RMS: -3.8 dBFS (clipping)  ->  -15.5 dBFS   [oracle -14.1]
  music-only chain already matched the oracle at -14.8 dBFS, confirming
  decoder + mixer gain equivalence; the entire excess was stuck voices.
  underruns: 2 events / 98 s (delivery fix holding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 01:54:46 -07:00
Matthew Stanley 9cf627113a audio: clock-domain DRC bridge for SPU output (fixes sustained crackle)
Replace the push-model SDL_QueueAudio output with the shared band-limited
polyphase resampler + P-only fill servo (recomp_audio_drc.h), fed by an SPSC
ring under SDL_LockAudioDevice and drained by a pull callback. The SPU renders
44100/60 per wall-clock frame while the host consumes on its own crystal; the
unsynchronized clocks drifted the queue to underrun (silence gaps = sustained
crackle). The bridge nudges the resample ratio <=+/-0.5% to hold the ring near
target so the queue never runs dry.

Measured on MMX6: legacy push ~1.5-2.5 underruns/s (queue draining to 0ms) ->
bridge 0 underruns, fill held ~50ms. PSXRECOMP_AUDIO_LEGACY=1 keeps the old
push path for A/B; PSXRECOMP_AUDIO_PROBE=1 prints the fill/underrun trend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:50:55 -07:00
Matthew Stanley 5a5922d98f audio: always-on PCM tap rings + oracle WAV capture (SNES-campaign port)
Measurement infrastructure for the audio-quality campaign (MMX5 complaint),
ported from snesrecomp's audio_trace/oracle methodology and the
_audio_round2 tap model. No behavior change to the audio path itself.

- audio_trace.{c,h}: three always-on 95 s PCM rings — T1 spu_out (canonical
  spu_render output), T2 cd_in (XA/CD-DA PCM entering the SPU CD bus),
  T3 host (exact post-fade bytes handed to SDL_QueueAudio) — plus a 512k
  event ring (every SPU reg write stamped with the SPU sample clock;
  RENDER/PUMP_SKIP/UNDERRUN/MUTE/UNMUTE/CD_PUSH/SPU-DMA), audible-vs-
  silent counters (-42 dBFS gate), queue watermarks, and a server-side
  44100 Hz WAV dumper (rate exactness matters: snesrecomp da33c06).
- psx-beetle: the libretro audio callbacks stop discarding Beetle's fully-
  mixed reference SPU PCM and record it into the same tap ring. Port 4380
  gains audio_stats/audio_wav/audio_events with the identical wire protocol
  as the runtime (rule 16 symmetric harness) — the oracle WAV capture.
- debug_server: audio_stats / audio_wav / audio_events on the runtime port.
- tools: audio_ab_diff.py + audio_spectral_ab.py (drift-tolerant offline
  A/B: drift ppm, onsets, cents, timbre, click rate) and audio_analyze.py
  (first-dirty-tap cross-tap decision tree) ported from snesrecomp/
  _audio_round2; audio_capture_ab.py dumps both processes symmetrically.

Probes query history; nothing is armed at probe time.
2026-07-10 00:00:09 -07:00