Files
Brian Degenhardt c2ea1c1f24 SPU2: restore the full core state from AetherSX2-era savestates
The first cut reset the cores and brought back only RAM, registers and
the mixer scalars, and mapped the era's volume levels across raw. Both
halves were wrong. Volume levels are 31-bit in these eras and 15-bit
today, so a full-volume legacy level overflows the mixer's s32 volume
multiply and turns the whole mix into broadband static — loud enough to
amplify even a paused game's silence into noise, which is what made the
defect look independent of the SPU2 payload. And the dropped voice,
streaming and DMA state belongs to a transfer the restored IOP driver
still believes is in flight: left at reset, the driver waits forever
for a completion interrupt no counter will deliver.

Map everything instead: voices (envelope levels scaled 31->15 bits, the
era's Releasing flag folded into the phase exactly as its Calculate()
folded it, decode restarting at the current block), the ADMA/DMA
counters (host pointers left null for the engine's own savestate heal,
except the read-path pointer, which the heal misses — rebuilt from the
channel TADR register), and the SPDIF/ring-position/playmode tail.

Now that this is a field-by-field mapping rather than a memory copy, it
moves out of spu2freeze.cpp into its own translation unit, leaving that
file byte-identical to upstream's. The era layout tables are ours to
carry and upstream keeps editing that file, so the two are better apart.

Verified headless against graded audio captures: a mid-gameplay 0x9A2C
resume state (Dragon Quest VIII) comes back with its music grading
music-like (spectral flatness 0.007) where the partial restore graded
noise (0.38+), and pause-menu saves come back silent because their
sound driver parks every voice at zero pitch and volume until unpaused.
92 core and 1714 recompiler tests green.
2026-08-02 11:20:44 -07:00
..
2026-01-28 12:18:43 -05:00
2026-06-20 20:27:55 -07:00
2026-03-11 15:56:49 +01:00