Files
psxrecomp/runtime/src/spu.c
T
Shokunin 6162c4e30d SPU: anchor the restored sample phase to the restored clock at load
The SPU's guest-clock phase was applied lazily at the next catch-up, after
the guest had run on, so the phase was taken against a later clock and
lost; a rollback replay rendered SPU samples at other guest cycles than
the live run (voice / SPU RAM split with an identical CPU path,
PSX_RB_DET_SELFTEST). boot_state writes CLOCK before SPU, so the load
anchors it immediately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:38:38 -07:00

2088 lines
89 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* spu.c - PS1 Sound Processing Unit register and ADPCM voice/DSP model.
*
* A compact hardware model: SPU register reads/writes, DMA4 transfers in
* both directions over 512KB SPU RAM, the 24 ADPCM voices with pitch,
* Gaussian interpolation and ADSR envelopes, decoded CD/XA audio on the SPU
* CD input bus, the hardware noise generator (NON voices), volume sweep
* envelopes (per-voice and main L/R), the 22050 Hz reverb engine with its
* SPU-RAM work area, the CD/voice capture buffers at 0x000..0xFFF, and the
* SPU RAM IRQ address compare (I_STAT bit 9, SPUSTAT bit 6).
*
* The reverb/noise/sweep/IRQ model is a CLEAN-ROOM implementation from
* hardware documentation only (psx-spx register map + documented algorithm);
* no emulator source was consulted. Points where the documentation is silent
* are flagged inline with "DOCUMENTED-GAP" comments so the Beetle runtime
* oracle comparison can quantify them from OUTPUT, not source.
*/
#include "spu.h"
#include "spu_gauss.h"
#include "spu_shadow.h"
#include "audio_trace.h"
#include "crc32.h"
#include "psx_cycles.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SPU_RAM_SIZE (512 * 1024)
#define SPU_REG_COUNT 256
#define SPU_VOICE_COUNT 24
#define SPU_BLOCK_SAMPLES 28
#define SPU_SAMPLE_BANKS 32u
#define SPU_PRIVATE_VOICES 24u
#define SPU_MIX_VOICES (SPU_VOICE_COUNT + SPU_PRIVATE_VOICES)
/* Title enhancements preload assets from the owner's disc. Hardware DMA,
* capture buffers and reverb continue to use spu_ram exclusively. */
static struct { uint8_t *data; uint32_t bytes; } sample_banks[SPU_SAMPLE_BANKS];
static uint8_t spu_ram[SPU_RAM_SIZE];
static uint16_t spu_regs[SPU_REG_COUNT];
static uint16_t private_regs[SPU_PRIVATE_VOICES][8];
static uint8_t private_modes[SPU_PRIVATE_VOICES];
static unsigned private_enabled;
static inline uint16_t voice_reg(int voice,int reg);
static uint32_t transfer_addr;
static uint32_t key_on_count;
static uint64_t render_frames;
static uint64_t nonzero_frames;
static int32_t last_peak;
static int32_t peak;
/* End-block-reached latch (SPU register 0x1F801D9C/D9E on real hw).
* Set when a voice decodes a block whose flag byte has bit 0 (loop end).
* Polled by music engines to know when a one-shot voice has finished. */
static uint32_t endx_latch;
/* KEYON/KEYOFF latches: most recent values written, sticky across reads
* so debug snapshots always see what the BIOS last requested even if the
* BIOS clears them quickly. */
static uint32_t kon_latch;
static uint32_t koff_latch;
/* Guest-clocked SPU state (see spu_catch_up). */
#define SPU_CYCLES_PER_SAMPLE 768u
#define SPU_OUT_FRAMES 16384u
static int16_t s_out[SPU_OUT_FRAMES * 2u];
static uint32_t s_out_head, s_out_count;
static uint64_t s_out_drops;
static uint64_t s_clock; /* guest cycle the SPU state is current to */
static uint32_t s_clock_carry; /* snapshot-restored phase, applied lazily */
static int s_clock_valid;
static int s_catching_up;
static int (*s_output_suppressed)(void);
/* ---- SPU IRQ (I_STAT bit 9) ------------------------------------------- */
/* Register 0x1F801DA4 holds the IRQ address in 8-byte units (value << 3 =
* byte address). While SPUCNT bit 6 is set, ANY SPU RAM access touching
* that 8-byte unit latches irq_flag (mirrored into SPUSTAT bit 6) and
* raises interrupt 9. Writing SPUCNT with bit 6 CLEAR acknowledges the
* latch and re-arms it. The raise is edge-triggered on the latch's 0->1
* transition: while the latch is set, further hits do not re-raise. */
extern void psx_irq_raise(uint32_t bit, uint32_t detail);
static uint8_t irq_flag;
/* ---- Noise generator ---------------------------------------------------
* 16-bit LFSR clocked from SPUCNT bits 8-13 (bits 8-9 = frequency STEP
* 4..7, bits 10-13 = frequency SHIFT 0..15). See noise_clock() for the
* divider derivation. Voices with their NON bit set (0x1F801D94/96) output
* the live LFSR value in place of their interpolated ADPCM sample. */
static uint16_t noise_lfsr; /* self-seeds from 0 via the xor-1 parity term */
static int32_t noise_timer;
/* ---- Reverb engine state ------------------------------------------------
* The engine runs at 22050 Hz (one step per TWO 44100 Hz output frames).
* rev_cur is the current buffer address (absolute byte address inside the
* work area [mBASE<<3, 0x80000)). The remaining fields carry the 44.1<->22.05
* kHz boundary state: the held first-of-pair input frame and the last engine
* output used for reconstruction (see rev_reconstruct). */
static uint32_t rev_cur;
static uint8_t rev_phase; /* 0 = first frame of a 2-frame pair */
static int32_t rev_in_hold_l;
static int32_t rev_in_hold_r;
static int32_t rev_out_l; /* most recent engine output (post vLOUT/vROUT) */
static int32_t rev_out_r;
/* ---- Capture buffers ----------------------------------------------------
* Real SPU RAM behaviour: every 44100 Hz sample cycle the SPU writes the CD
* input L to 0x000+pos, CD input R to 0x400+pos, voice 1 post-envelope
* output to 0x800+pos and voice 3 post-envelope output to 0xC00+pos; pos
* advances one halfword and wraps at 0x400. Games may park the IRQ address
* inside these rings, so each write runs the IRQ check. */
static uint32_t capture_pos;
/* ---- Volume sweep envelopes ---------------------------------------------
* Every volume register (24 voices x L/R + main L/R) is either DIRECT
* (bit15=0: effective volume = signed bits14-0 << 1) or a live SWEEP
* envelope (bit15=1) stepped once per 44100 Hz sample with the same rate
* machinery as ADSR (calc_vc_delta). `level` is the authoritative current
* volume in sweep mode and is refreshed from the register on direct writes,
* so a later switch to sweep mode glides from the last direct level. */
typedef struct {
int16_t level; /* live effective volume, full signed 16-bit */
uint32_t divider; /* rate divider, same overflow scheme as ADSR */
} SweepEnv;
static SweepEnv sweep_voice_env[SPU_MIX_VOICES][2]; /* [voice][0=L 1=R] */
static SweepEnv sweep_main_env[2]; /* [0=L 1=R] */
/* External vblank counter (debug_server.c) used as event timestamp. */
extern uint64_t s_frame_count;
/* ---- Always-on event ring -------------------------------------------- */
/* 1M entries × ~32B = 32 MB. Power of 2 so wrap is a mask. Beetle peaks at
* a few hundred audible-voice events per chime second; recomp at <1k total
* per chime. 1M gives ~minutes of headroom for game-scene capture too. */
#define SPU_EVENT_CAP (1u << 20)
static SpuEvent s_events[SPU_EVENT_CAP];
static uint32_t s_event_idx = 0;
static uint64_t s_event_seq = 0;
/* CD input FIFO, fed by the CD-ROM XA decoder at 44.1 kHz stereo. */
#define SPU_CD_RING_FRAMES (44100u * 8u)
static int16_t cd_ring[SPU_CD_RING_FRAMES * 2u];
static uint32_t cd_read_pos;
static uint32_t cd_write_pos;
static uint32_t cd_frame_count;
static uint64_t cd_push_frames;
static uint64_t cd_overflow_frames;
static uint64_t cd_underflow_frames;
/* ADSR phases — match Beetle's order so cross-process diffs read straight. */
#define ADSR_ATTACK 0
#define ADSR_DECAY 1
#define ADSR_SUSTAIN 2
#define ADSR_RELEASE 3
typedef struct {
int active;
uint32_t cur_addr;
uint32_t repeat_addr;
uint32_t sample_bank, pending_bank;
int16_t samples[SPU_BLOCK_SAMPLES];
int16_t previous_samples[3];
int sample_idx;
uint32_t phase;
int16_t hist1;
int16_t hist2;
uint8_t flags;
/* ADSR envelope state. Stepped once per output sample (44.1 kHz). */
uint16_t env_level; /* 0..0x7FFF — applied to raw decoded sample */
uint32_t adsr_divider; /* fixed-point counter; level updates on overflow */
uint8_t adsr_phase; /* ADSR_ATTACK / DECAY / SUSTAIN / RELEASE */
} SpuVoice;
static SpuVoice voices[SPU_MIX_VOICES];
int spu_register_sample_bank(uint32_t bank,const void *adpcm,uint32_t bytes) {
if(!bank || bank>=SPU_SAMPLE_BANKS || !adpcm || !bytes ||
bytes>SPU_RAM_SIZE || (bytes&15u))return 0;
/* Assets cannot change under a playing or queued voice. */
if(sample_banks[bank].data)
return sample_banks[bank].bytes==bytes && !memcmp(sample_banks[bank].data,adpcm,bytes);
uint8_t *copy=malloc(bytes);if(!copy)return 0;
memcpy(copy,adpcm,bytes);sample_banks[bank].data=copy;sample_banks[bank].bytes=bytes;
return 1;
}
int spu_bind_next_voice_bank(unsigned voice,uint32_t bank) {
if(voice>=SPU_VOICE_COUNT || bank>=SPU_SAMPLE_BANKS ||
(bank && !sample_banks[bank].data))return 0;
voices[voice].pending_bank=bank;return 1;
}
static void spu_event_record(uint8_t kind, int voice, uint32_t addr) {
SpuEvent *e = &s_events[s_event_idx & (SPU_EVENT_CAP - 1u)];
e->seq = s_event_seq++;
e->frame = (uint32_t)s_frame_count;
e->kind = kind;
e->voice = (uint8_t)voice;
e->addr = addr;
if (voice >= 0 && voice < SPU_MIX_VOICES) {
/* PSX voice block layout (16-bit register indices from voice base):
* 0=VOL_L 1=VOL_R 2=PITCH 3=START 4=ADSR_LO 5=ADSR_HI 6=CURVOL 7=LOOP */
e->pitch = voice_reg(voice,2);
e->adsr_lo = voice_reg(voice,4);
e->adsr_hi = voice_reg(voice,5);
e->vol_l = voice_reg(voice,0);
e->vol_r = voice_reg(voice,1);
} else {
/* Non-voice-attributable events (e.g. SPU_EV_IRQ, voice=0xFF). */
e->pitch = 0;
e->adsr_lo = 0;
e->adsr_hi = 0;
e->vol_l = 0;
e->vol_r = 0;
}
s_event_idx++;
}
/* PS1 envelope rate decoder. Ported verbatim from Beetle's CalcVCDelta
* (beetle-psx/mednafen/psx/spu.cpp). Each call emits the per-step
* `increment` to add to env_level, and `divinco` added to a divider —
* level is updated only when divider crosses 0x8000. The combination
* encodes both linear and pseudo-exponential ramps at PSX-faithful
* rates (rates 0..127 span ~0.1 ms .. ~30+ s). */
static void calc_vc_delta(uint8_t zs, uint8_t speed, int log_mode, int dec_mode,
int inv_increment, int16_t current,
int *out_increment, int *out_divinco)
{
int increment = (7 - (speed & 0x3));
if (inv_increment) increment = ~increment;
int divinco = 32768;
if (speed < 0x2C)
increment = (unsigned)increment << ((0x2F - speed) >> 2);
if (speed >= 0x30)
divinco >>= (speed - 0x2C) >> 2;
if (log_mode) {
if (dec_mode) {
increment = (current * increment) >> 15;
} else if ((current & 0x7FFF) >= 0x6000) {
if (speed < 0x28) {
increment >>= 2;
} else if (speed >= 0x2C) {
divinco >>= 2;
} else {
increment >>= 1;
divinco >>= 1;
}
}
}
if (divinco == 0 && speed < zs) divinco = 1;
*out_increment = increment;
*out_divinco = divinco;
}
/* Step ADSR envelope by one output sample for voice `idx`. Mirrors
* Beetle's PS_SPU::RunEnvelope. */
static void adsr_run(int idx, SpuVoice *v) {
uint32_t raw = (uint32_t)voice_reg(idx,4)
| ((uint32_t)voice_reg(idx,5) << 16);
int Sl = (int)(raw >> 0) & 0x0F;
int Dr = (int)(raw >> 4) & 0x0F;
int Ar = (int)(raw >> 8) & 0x7F;
int attack_exp = (int)((raw >> 15) & 1);
int Rr = (int)(raw >> 16) & 0x1F;
int release_exp = (int)((raw >> 21) & 1);
int Sr = (int)(raw >> 22) & 0x7F;
int sustain_dec = (int)((raw >> 30) & 1);
int sustain_exp = (int)((raw >> 31) & 1);
int sustain_lvl = (Sl + 1) << 11;
/* Attack tops out at 0x7FFF — switch to Decay (Beetle does this
* before the switch on Phase). */
if (v->adsr_phase == ADSR_ATTACK && v->env_level == 0x7FFF)
v->adsr_phase = ADSR_DECAY;
int increment = 0, divinco = 0;
int16_t uoflow_reset = 0;
switch (v->adsr_phase) {
case ADSR_ATTACK:
calc_vc_delta(0x7F, (uint8_t)Ar, attack_exp, 0, 0,
(int16_t)v->env_level, &increment, &divinco);
uoflow_reset = 0x7FFF;
break;
case ADSR_DECAY:
calc_vc_delta(0x1F << 2, (uint8_t)(Dr << 2), 1, 1, 1,
(int16_t)v->env_level, &increment, &divinco);
uoflow_reset = 0;
break;
case ADSR_SUSTAIN:
calc_vc_delta(0x7F, (uint8_t)Sr, sustain_exp, sustain_dec, sustain_dec,
(int16_t)v->env_level, &increment, &divinco);
uoflow_reset = sustain_dec ? 0 : 0x7FFF;
break;
case ADSR_RELEASE:
calc_vc_delta(0x1F << 2, (uint8_t)(Rr << 2), release_exp, 1, 1,
(int16_t)v->env_level, &increment, &divinco);
uoflow_reset = 0;
break;
default:
return;
}
v->adsr_divider += (uint32_t)divinco;
if (v->adsr_divider & 0x8000u) {
uint16_t prev = v->env_level;
v->adsr_divider = 0;
v->env_level = (uint16_t)((int)v->env_level + increment);
if (v->adsr_phase == ADSR_ATTACK) {
/* If high bit just rolled over (0→1), clamp to uoflow_reset. */
if (((prev ^ v->env_level) & v->env_level) & 0x8000u)
v->env_level = (uint16_t)uoflow_reset;
} else {
if (v->env_level & 0x8000u)
v->env_level = (uint16_t)uoflow_reset;
}
if (v->adsr_phase == ADSR_DECAY && v->env_level < (uint16_t)sustain_lvl)
v->adsr_phase = ADSR_SUSTAIN;
}
}
static inline int16_t clamp16(int32_t v) {
if (v > 32767) return 32767;
if (v < -32768) return -32768;
return (int16_t)v;
}
static inline int32_t abs32(int32_t v) {
return v < 0 ? -v : v;
}
static inline uint32_t reg_index(uint32_t addr) {
return (addr - 0x1F801C00u) >> 1;
}
static inline uint16_t voice_reg(int voice, int reg) {
return voice<SPU_VOICE_COUNT?spu_regs[(uint32_t)voice*8u+(uint32_t)reg]:
private_regs[voice-SPU_VOICE_COUNT][reg];
}
/* Register shorthand: current live value of a named SPU register. */
#define RREG(a) (spu_regs[reg_index(a)])
#define RVOL(a) ((int16_t)RREG(a))
/* Direct-mode volume decode (bit15=0): bits 14-0 are a signed value and the
* effective 16-bit volume is that value << 1 — bit 14 lands on the int16
* sign bit, so sign extension falls out of the shift for free. Effective
* volumes are applied as (sample * vol) >> 15 throughout, which for direct
* registers is bit-identical to the previous (15-bit vol) >> 14 decode. */
static inline int16_t volume_reg_decode(uint16_t raw) {
return (int16_t)((uint16_t)(raw << 1));
}
/* Called on every guest write to a volume register. Direct writes take
* effect immediately; a sweep-mode write starts the sweep FROM the current
* live level (documented reading: the sweep register programs an envelope,
* it does not itself carry a target level). The divider restarts either way. */
static void sweep_env_write(SweepEnv *sw, uint16_t raw) {
if (!(raw & 0x8000u))
sw->level = volume_reg_decode(raw);
sw->divider = 0;
}
/* Step one sweep envelope by one 44100 Hz output sample. No-op for
* direct-mode registers. Sweep register layout (bit15=1):
* bit14 mode 0=linear 1=exponential
* bit13 direction 0=increase 1=decrease
* bit12 phase 0=positive 1=negative
* bit6-0 rate (bits 0-1 step, bits 2-6 shift) — same 7-bit rate
* format as ADSR, so calc_vc_delta is reused verbatim.
*
* DOCUMENTED-GAP: the documentation does not spell out how the "phase"
* bit interacts with a level whose sign disagrees with it. Model chosen:
* the envelope machinery always operates on a 0..0x7FFF working value in
* the phase's domain (negative phase mirrors the level), and a level on the
* wrong side of zero is clamped to 0 before stepping. Increase saturates at
* 0x7FFF, decrease at 0, matching the ADSR clamp behaviour. Candidate for
* oracle verification. */
static void sweep_env_step(SweepEnv *sw, uint16_t raw) {
if (!(raw & 0x8000u)) return;
int exp_mode = (raw >> 14) & 1;
int dec_mode = (raw >> 13) & 1;
int neg_phase = (raw >> 12) & 1;
uint8_t rate = (uint8_t)(raw & 0x7F);
int32_t working = sw->level;
if (neg_phase) working = -working;
if (working < 0) working = 0;
if (working > 0x7FFF) working = 0x7FFF;
int increment = 0, divinco = 0;
calc_vc_delta(0x7F, rate, exp_mode, dec_mode, dec_mode,
(int16_t)working, &increment, &divinco);
sw->divider += (uint32_t)divinco;
if (sw->divider & 0x8000u) {
sw->divider = 0;
working += increment;
if (working < 0) working = 0;
if (working > 0x7FFF) working = 0x7FFF;
sw->level = (int16_t)(neg_phase ? -working : working);
}
}
/* Live effective volume of a volume register: the register decode in direct
* mode, the sweep envelope's current level in sweep mode. */
static inline int16_t chan_volume(uint16_t raw, const SweepEnv *sw) {
if (raw & 0x8000u) return sw->level;
return volume_reg_decode(raw);
}
/* SPU RAM IRQ-address compare, called from EVERY SPU RAM access site (FIFO,
* DMA both directions, ADPCM block fetch, capture-buffer writes, reverb
* work-area reads/writes, and the transfer/IRQ-address/SPUCNT register
* writes). `addr`/`len` describe the accessed byte range; the compare is at
* 8-byte-unit granularity because that is the IRQ address register's unit. */
static void spu_irq_check(uint32_t addr, uint32_t len) {
if (!(RREG(0x1F801DAAu) & 0x0040u)) return; /* SPUCNT.6 IRQ enable */
if (len == 0) return;
uint32_t target = RREG(0x1F801DA4u); /* in 8-byte units */
uint32_t a = addr & (SPU_RAM_SIZE - 1u);
uint32_t first = a >> 3;
uint32_t last = (a + len - 1u) >> 3;
for (uint32_t u = first; u <= last; u++) {
if ((u & 0xFFFFu) == target) {
if (!irq_flag) {
irq_flag = 1;
psx_irq_raise(9u, u << 3);
spu_event_record(SPU_EV_IRQ, 0xFF, u << 3);
}
return;
}
}
}
static inline int16_t cd_input_volume(uint16_t raw) {
/* CD input volume registers use signed 16-bit linear gain; games commonly
* program 0x7FFF for full-scale CD audio. */
return (int16_t)raw;
}
void spu_cd_audio_reset(void) {
memset(cd_ring, 0, sizeof(cd_ring));
cd_read_pos = 0;
cd_write_pos = 0;
cd_frame_count = 0;
cd_push_frames = 0;
cd_overflow_frames = 0;
cd_underflow_frames = 0;
}
void spu_cd_audio_push(const int16_t* stereo, int frames) {
spu_catch_up(); /* samples owed so far consume the CD data queued before this push */
if (!stereo || frames <= 0) return;
uint32_t in_frames = (uint32_t)frames;
if (in_frames > SPU_CD_RING_FRAMES) {
uint32_t skip = in_frames - SPU_CD_RING_FRAMES;
stereo += skip * 2u;
cd_overflow_frames += skip;
in_frames = SPU_CD_RING_FRAMES;
}
if (cd_frame_count + in_frames > SPU_CD_RING_FRAMES) {
uint32_t drop = (cd_frame_count + in_frames) - SPU_CD_RING_FRAMES;
cd_read_pos = (cd_read_pos + drop) % SPU_CD_RING_FRAMES;
cd_frame_count -= drop;
cd_overflow_frames += drop;
}
for (uint32_t i = 0; i < in_frames; i++) {
cd_ring[cd_write_pos * 2u + 0u] = stereo[i * 2u + 0u];
cd_ring[cd_write_pos * 2u + 1u] = stereo[i * 2u + 1u];
cd_write_pos = (cd_write_pos + 1u) % SPU_CD_RING_FRAMES;
}
cd_frame_count += in_frames;
cd_push_frames += in_frames;
/* T2 tap: what the CD/XA decoder feeds the SPU CD input bus. The event
* stamps the GLOBAL GUEST CYCLE clock (low 32 bits) so push-to-push
* spacing measures true delivery cadence — the spu_out sample_idx stamp
* is pump-chunk quantized and useless for that. */
audio_trace_pcm(AUDIO_TAP_CD_IN, stereo, (int)in_frames);
audio_trace_event(AUDIO_EV_CD_PUSH, (uint32_t)psx_get_cycle_count(),
cd_frame_count);
}
/* CD input frames queued for the SPU are guest state: they come from the
* CD-ROM decoder at guest time and the SPU consumes them sample by sample
* (output and the CD capture area of SPU RAM). They used to be outside the
* snapshot and were wiped on rollback loads, so a replay mixed different CD
* audio than the live run (SPU voices / SPU RAM split at R4's race start). */
int g_spu_cd_restored;
uint32_t spu_cd_snapshot_bytes(void) { return 4u + cd_frame_count * 4u; }
void spu_cd_snapshot_write(uint8_t *p) {
uint32_t n = cd_frame_count, pos = cd_read_pos;
p[0] = (uint8_t)n; p[1] = (uint8_t)(n >> 8); p[2] = (uint8_t)(n >> 16); p[3] = (uint8_t)(n >> 24);
p += 4;
for (uint32_t i = 0; i < n; i++) {
uint16_t l = (uint16_t)cd_ring[pos * 2u], r = (uint16_t)cd_ring[pos * 2u + 1u];
p[0] = (uint8_t)l; p[1] = (uint8_t)(l >> 8); p[2] = (uint8_t)r; p[3] = (uint8_t)(r >> 8);
p += 4;
pos = (pos + 1u) % SPU_CD_RING_FRAMES;
}
}
int spu_cd_snapshot_read(const uint8_t *p, uint32_t len) {
uint32_t n;
if (!p || len < 4u) return 0;
n = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
if (n > SPU_CD_RING_FRAMES || len != 4u + n * 4u) return 0;
p += 4;
for (uint32_t i = 0; i < n; i++, p += 4) {
cd_ring[i * 2u] = (int16_t)(uint16_t)(p[0] | (p[1] << 8));
cd_ring[i * 2u + 1u] = (int16_t)(uint16_t)(p[2] | (p[3] << 8));
}
cd_read_pos = 0;
cd_write_pos = n % SPU_CD_RING_FRAMES;
cd_frame_count = n;
g_spu_cd_restored = 1;
return 1;
}
static int cd_audio_pop(int16_t* left, int16_t* right) {
if (cd_frame_count == 0) return 0;
*left = cd_ring[cd_read_pos * 2u + 0u];
*right = cd_ring[cd_read_pos * 2u + 1u];
cd_read_pos = (cd_read_pos + 1u) % SPU_CD_RING_FRAMES;
cd_frame_count--;
return 1;
}
static void decode_block(SpuVoice *v) {
static const int f0[5] = { 0, 60, 115, 98, 122 };
static const int f1[5] = { 0, 0, -52, -55, -60 };
uint32_t addr = v->cur_addr & (SPU_RAM_SIZE - 1u);
if (addr + 16u > SPU_RAM_SIZE) addr = 0;
/* The 16-byte ADPCM block fetch is an SPU RAM access: IRQ-address games
* (CD streaming double-buffers, MDEC audio) park the IRQ inside a voice's
* sample buffer and rely on this exact compare firing. */
const uint8_t *block;
static const uint8_t silent_end[16]={0,1};
if(v->sample_bank) {
uint32_t bank=v->sample_bank;
block=bank<SPU_SAMPLE_BANKS && sample_banks[bank].data &&
addr<=sample_banks[bank].bytes-16u ? sample_banks[bank].data+addr : silent_end;
} else {
spu_irq_check(addr,16u);
block=spu_ram+addr;
}
uint8_t header = block[0];
uint8_t flags = block[1];
int shift = header & 0x0F;
int filter = (header >> 4) & 0x0F;
if (filter > 4) filter = 0;
if (shift > 12) shift = 12;
/* Gaussian interpolation reaches three samples into the preceding ADPCM
* block. KEYON clears samples[], so the first block naturally sees
* silence for its unavailable history. */
v->previous_samples[0] = v->samples[SPU_BLOCK_SAMPLES - 3];
v->previous_samples[1] = v->samples[SPU_BLOCK_SAMPLES - 2];
v->previous_samples[2] = v->samples[SPU_BLOCK_SAMPLES - 1];
int out = 0;
for (int b = 0; b < 14; b++) {
uint8_t packed = block[2u + (uint32_t)b];
for (int n = 0; n < 2; n++) {
int sample4 = (n == 0) ? (packed & 0x0F) : (packed >> 4);
if (sample4 & 0x08) sample4 -= 0x10;
int32_t s = sample4 << 12;
s >>= shift;
s += ((int32_t)v->hist1 * f0[filter] +
(int32_t)v->hist2 * f1[filter] + 32) >> 6;
s = clamp16(s);
v->hist2 = v->hist1;
v->hist1 = (int16_t)s;
v->samples[out++] = (int16_t)s;
}
}
if (flags & 0x04u) {
v->repeat_addr = addr;
/* The auto-latch updates the guest-visible register too (Beetle
* spu.cpp:386 + read path 1302): drivers read it back to save
* loop points. */
int v_idx = (int)(v - voices);
if (v_idx >= 0 && v_idx < SPU_VOICE_COUNT)
spu_regs[(uint32_t)v_idx * 8u + 7u] = (uint16_t)(addr >> 3);
}
v->flags = flags;
v->sample_idx = 0;
v->cur_addr = (addr + 16u) & (SPU_RAM_SIZE - 1u);
/* Latch end-block-reached so the BIOS music engine sees ENDX[v] = 1
* when it polls 0x1F801D9C/D9E. Without this latch one-shot music
* engines never advance, leaving subsequent voices unkeyed. */
if (flags & 0x01u) {
int v_idx = (int)(v - voices);
if (v_idx >= 0 && v_idx < SPU_VOICE_COUNT) {
endx_latch |= (1u << v_idx);
}
}
}
/* ---- Noise generator ---------------------------------------------------- */
/* Documented LFSR update: the new low bit is the parity term
* (bit15 XOR bit12 XOR bit11 XOR bit10 XOR 1) and the register shifts left.
* The XOR-with-1 makes the all-zero state self-starting.
*
* Clock rate derivation from SPUCNT bits 8-13 (step = 4 + bits8-9,
* shift = bits10-13): a down-counter is decremented by `step` once per
* 44100 Hz sample cycle and the LFSR clocks on underflow, whereupon the
* counter is topped up by (0x20000 >> shift) — reloading twice if a single
* reload does not clear the deficit (only reachable at the largest shifts).
* shift=0 gives the slowest clock (lowest frequency), shift=15 the fastest,
* and the 4..7 step scales the rate within a shift octave.
*
* DOCUMENTED-GAP: this divider wiring is the least precisely documented part
* of the noise unit; the shift/step semantics above are the documented
* reading, but the exact counter width/reload behaviour is a candidate for
* oracle verification against Beetle's audio OUTPUT. */
static void noise_clock(uint16_t ctrl) {
int step = 4 + ((ctrl >> 8) & 3);
int shift = (ctrl >> 10) & 0x0F;
int32_t reload = (int32_t)(0x20000u >> shift);
noise_timer -= step;
if (noise_timer < 0) {
uint16_t parity = (uint16_t)(((noise_lfsr >> 15) ^ (noise_lfsr >> 12) ^
(noise_lfsr >> 11) ^ (noise_lfsr >> 10) ^ 1u) & 1u);
noise_lfsr = (uint16_t)((noise_lfsr << 1) | parity);
noise_timer += reload;
if (noise_timer < 0) noise_timer += reload; /* worst case: step 7, reload 4 */
}
}
/* ---- Capture buffers ----------------------------------------------------- */
static void capture_write(uint32_t base, int16_t s) {
uint32_t a = (base + capture_pos) & (SPU_RAM_SIZE - 1u);
spu_irq_check(a, 2u);
spu_ram[a] = (uint8_t)((uint16_t)s & 0xFFu);
spu_ram[a + 1u] = (uint8_t)(((uint16_t)s >> 8) & 0xFFu);
}
/* ---- Reverb engine -------------------------------------------------------
* Register map (psx-spx). All address/offset registers are in units of 8
* bytes; all volumes are signed Q0.15 (divide by 32768). */
#define SPU_R_VLOUT 0x1F801D84u /* reverb output volume L */
#define SPU_R_VROUT 0x1F801D86u /* reverb output volume R */
#define SPU_R_MBASE 0x1F801DA2u /* work area start address */
#define SPU_R_DAPF1 0x1F801DC0u /* APF offset 1 */
#define SPU_R_DAPF2 0x1F801DC2u /* APF offset 2 */
#define SPU_R_VIIR 0x1F801DC4u /* reflection volume 1 */
#define SPU_R_VCOMB1 0x1F801DC6u /* comb volume 1 */
#define SPU_R_VCOMB2 0x1F801DC8u /* comb volume 2 */
#define SPU_R_VCOMB3 0x1F801DCAu /* comb volume 3 */
#define SPU_R_VCOMB4 0x1F801DCCu /* comb volume 4 */
#define SPU_R_VWALL 0x1F801DCEu /* reflection volume 2 */
#define SPU_R_VAPF1 0x1F801DD0u /* APF volume 1 */
#define SPU_R_VAPF2 0x1F801DD2u /* APF volume 2 */
#define SPU_R_MLSAME 0x1F801DD4u /* same-side reflection addr 1 L (src/dst) */
#define SPU_R_MRSAME 0x1F801DD6u /* same-side reflection addr 1 R (src/dst) */
#define SPU_R_MLCOMB1 0x1F801DD8u /* comb addr 1 L (src) */
#define SPU_R_MRCOMB1 0x1F801DDAu /* comb addr 1 R (src) */
#define SPU_R_MLCOMB2 0x1F801DDCu /* comb addr 2 L (src) */
#define SPU_R_MRCOMB2 0x1F801DDEu /* comb addr 2 R (src) */
#define SPU_R_DLSAME 0x1F801DE0u /* same-side reflection addr 2 L (src) */
#define SPU_R_DRSAME 0x1F801DE2u /* same-side reflection addr 2 R (src) */
#define SPU_R_MLDIFF 0x1F801DE4u /* diff-side reflection addr 1 L (src/dst) */
#define SPU_R_MRDIFF 0x1F801DE6u /* diff-side reflection addr 1 R (src/dst) */
#define SPU_R_MLCOMB3 0x1F801DE8u /* comb addr 3 L (src) */
#define SPU_R_MRCOMB3 0x1F801DEAu /* comb addr 3 R (src) */
#define SPU_R_MLCOMB4 0x1F801DECu /* comb addr 4 L (src) */
#define SPU_R_MRCOMB4 0x1F801DEEu /* comb addr 4 R (src) */
#define SPU_R_DLDIFF 0x1F801DF0u /* diff-side reflection addr 2 L (src) */
#define SPU_R_DRDIFF 0x1F801DF2u /* diff-side reflection addr 2 R (src) */
#define SPU_R_MLAPF1 0x1F801DF4u /* APF addr 1 L (src/dst) */
#define SPU_R_MRAPF1 0x1F801DF6u /* APF addr 1 R (src/dst) */
#define SPU_R_MLAPF2 0x1F801DF8u /* APF addr 2 L (src/dst) */
#define SPU_R_MRAPF2 0x1F801DFAu /* APF addr 2 R (src/dst) */
#define SPU_R_VLIN 0x1F801DFCu /* reverb input volume L */
#define SPU_R_VRIN 0x1F801DFEu /* reverb input volume R */
/* Signed Q0.15 volume multiply (the reverb pipeline's only multiply shape). */
static inline int32_t rev_mul(int16_t vol, int32_t s) {
return (int32_t)(((int64_t)vol * (int64_t)s) >> 15);
}
static inline uint32_t rev_mbase(void) {
return (((uint32_t)RREG(SPU_R_MBASE)) << 3) & 0x7FFFEu;
}
/* Effective work-area address for an address register plus a byte bias
* (bias -2 implements the documented [x-2] taps, bias -dAPFn*8 the APF
* delay taps). A register value r denotes byte offset r*8 from the current
* buffer address; the result wraps inside [mBASE<<3, 0x80000) and never
* escapes it, whatever the register values. */
static uint32_t rev_ea(uint32_t reg_addr, int32_t bias) {
uint32_t base = rev_mbase();
uint32_t size = SPU_RAM_SIZE - base;
int64_t rel = (int64_t)rev_cur - (int64_t)base
+ ((int64_t)RREG(reg_addr) << 3) + (int64_t)bias;
rel %= (int64_t)size;
if (rel < 0) rel += (int64_t)size;
return (base + (uint32_t)rel) & 0x7FFFEu;
}
static int16_t rev_read(uint32_t reg_addr, int32_t bias) {
uint32_t a = rev_ea(reg_addr, bias);
spu_irq_check(a, 2u);
return (int16_t)((uint16_t)spu_ram[a] | ((uint16_t)spu_ram[a + 1u] << 8));
}
/* Work-area store, saturated to signed 16-bit. Writes (and their IRQ
* checks) happen only while SPUCNT bit 7 is set; reads always occur.
* DOCUMENTED-GAP: whether a SUPPRESSED write still drives the address bus
* (and could therefore trip the IRQ compare) is not documented; this model
* only checks on accesses that actually happen. */
static void rev_write(uint32_t reg_addr, int32_t v, int wren) {
if (!wren) return;
uint32_t a = rev_ea(reg_addr, 0);
int16_t s = clamp16(v);
spu_irq_check(a, 2u);
spu_ram[a] = (uint8_t)((uint16_t)s & 0xFFu);
spu_ram[a + 1u] = (uint8_t)(((uint16_t)s >> 8) & 0xFFu);
}
/* One documented reverb step at 22050 Hz. `in_l`/`in_r` are the reverb-send
* mix (voices with EON set + CD audio if SPUCNT bits 0 and 2). Leaves the
* vLOUT/vROUT-scaled result in rev_out_l/rev_out_r and advances the buffer
* address. RAM accesses are performed in the documented statement order so
* the dAPF1==0 / dAPF2==0 corner (re-read of a just-written cell) behaves
* as literally written. */
static void reverb_step(int32_t in_l, int32_t in_r, int wren) {
int32_t dapf1 = -((int32_t)RREG(SPU_R_DAPF1) << 3);
int32_t dapf2 = -((int32_t)RREG(SPU_R_DAPF2) << 3);
int16_t vIIR = RVOL(SPU_R_VIIR);
int16_t vWALL = RVOL(SPU_R_VWALL);
int16_t vAPF1 = RVOL(SPU_R_VAPF1);
int16_t vAPF2 = RVOL(SPU_R_VAPF2);
/* Input from the mixer. */
int32_t Lin = rev_mul(RVOL(SPU_R_VLIN), in_l);
int32_t Rin = rev_mul(RVOL(SPU_R_VRIN), in_r);
/* Same-side reflection (L->L, R->R):
* [mLSAME] = (Lin + [dLSAME]*vWALL - [mLSAME-2])*vIIR + [mLSAME-2] */
{
int32_t prev_l = rev_read(SPU_R_MLSAME, -2);
rev_write(SPU_R_MLSAME,
rev_mul(vIIR, Lin + rev_mul(vWALL, rev_read(SPU_R_DLSAME, 0))
- prev_l) + prev_l,
wren);
int32_t prev_r = rev_read(SPU_R_MRSAME, -2);
rev_write(SPU_R_MRSAME,
rev_mul(vIIR, Rin + rev_mul(vWALL, rev_read(SPU_R_DRSAME, 0))
- prev_r) + prev_r,
wren);
}
/* Different-side reflection (L->R, R->L) — note the CROSSED d*DIFF
* sources: mLDIFF takes dRDIFF, mRDIFF takes dLDIFF. */
{
int32_t prev_l = rev_read(SPU_R_MLDIFF, -2);
rev_write(SPU_R_MLDIFF,
rev_mul(vIIR, Lin + rev_mul(vWALL, rev_read(SPU_R_DRDIFF, 0))
- prev_l) + prev_l,
wren);
int32_t prev_r = rev_read(SPU_R_MRDIFF, -2);
rev_write(SPU_R_MRDIFF,
rev_mul(vIIR, Rin + rev_mul(vWALL, rev_read(SPU_R_DLDIFF, 0))
- prev_r) + prev_r,
wren);
}
/* Early echo: 4-tap comb filter. */
int32_t lout = rev_mul(RVOL(SPU_R_VCOMB1), rev_read(SPU_R_MLCOMB1, 0))
+ rev_mul(RVOL(SPU_R_VCOMB2), rev_read(SPU_R_MLCOMB2, 0))
+ rev_mul(RVOL(SPU_R_VCOMB3), rev_read(SPU_R_MLCOMB3, 0))
+ rev_mul(RVOL(SPU_R_VCOMB4), rev_read(SPU_R_MLCOMB4, 0));
int32_t rout = rev_mul(RVOL(SPU_R_VCOMB1), rev_read(SPU_R_MRCOMB1, 0))
+ rev_mul(RVOL(SPU_R_VCOMB2), rev_read(SPU_R_MRCOMB2, 0))
+ rev_mul(RVOL(SPU_R_VCOMB3), rev_read(SPU_R_MRCOMB3, 0))
+ rev_mul(RVOL(SPU_R_VCOMB4), rev_read(SPU_R_MRCOMB4, 0));
/* Late reverb all-pass filter 1:
* Lout = Lout - vAPF1*[mLAPF1-dAPF1]; [mLAPF1] = Lout;
* Lout = Lout*vAPF1 + [mLAPF1-dAPF1] */
lout = lout - rev_mul(vAPF1, rev_read(SPU_R_MLAPF1, dapf1));
rev_write(SPU_R_MLAPF1, lout, wren);
lout = rev_mul(vAPF1, clamp16(lout)) + rev_read(SPU_R_MLAPF1, dapf1);
rout = rout - rev_mul(vAPF1, rev_read(SPU_R_MRAPF1, dapf1));
rev_write(SPU_R_MRAPF1, rout, wren);
rout = rev_mul(vAPF1, clamp16(rout)) + rev_read(SPU_R_MRAPF1, dapf1);
/* Late reverb all-pass filter 2. */
lout = lout - rev_mul(vAPF2, rev_read(SPU_R_MLAPF2, dapf2));
rev_write(SPU_R_MLAPF2, lout, wren);
lout = rev_mul(vAPF2, clamp16(lout)) + rev_read(SPU_R_MLAPF2, dapf2);
rout = rout - rev_mul(vAPF2, rev_read(SPU_R_MRAPF2, dapf2));
rev_write(SPU_R_MRAPF2, rout, wren);
rout = rev_mul(vAPF2, clamp16(rout)) + rev_read(SPU_R_MRAPF2, dapf2);
/* Output to mixer. */
rev_out_l = rev_mul(RVOL(SPU_R_VLOUT), clamp16(lout));
rev_out_r = rev_mul(RVOL(SPU_R_VROUT), clamp16(rout));
/* Documented advance: BufferAddress = MAX(mBASE, (BufferAddress+2) AND 7FFFEh) */
{
uint32_t base = rev_mbase();
rev_cur = (rev_cur + 2u) & 0x7FFFEu;
if (rev_cur < base) rev_cur = base;
}
}
/* ⚠ DOCUMENTED-GAP — the ONE known deliberate deviation from hardware.
* The reverb engine runs at 22050 Hz but the real SPU's 22.05 -> 44.1 kHz
* reconstruction filter (and the matching 44.1 -> 22.05 kHz input
* decimation) is NOT specified in the documentation this implementation is
* built from, and consulting an emulator's filter is off-limits for license
* reasons. Chosen documented-behaviour stand-ins, both trivially swappable:
* - input: box decimation (average of the two 44.1 kHz frames per step),
* - output: this function — linear interpolation between successive
* 22050 Hz engine results (even output frame = the step result,
* odd frame = midpoint of the two neighbouring results).
* A later Beetle-output oracle comparison should quantify the spectral
* difference; replace ONLY this function (and the input average at the
* call site in spu_render) if a better-documented filter emerges. */
static inline int32_t rev_reconstruct(int32_t prev_step, int32_t cur_step) {
return (prev_step + cur_step) >> 1;
}
/* ---- Verified-enhancement shadow tap (opt-in; see spu_shadow.{h,c}) ------
*
* When the float SPU shadow is enabled, the mix loop records — per output
* frame, per contributing voice — the EXACT inputs the canon used to produce
* that voice's contribution: the four decoded samples bracketing the current
* sub-sample phase, the fractional phase, the envelope level, and the per-voice
* L/R volumes. The shadow re-renders those in float with cubic interpolation.
* Sourcing the decoded data + envelope from the canon means the shadow can only
* differ in HOW a note is resampled, never in WHICH note plays.
*
* Inert and zero-cost when the shadow is disabled (s_shadow_tap_on == 0): the
* mix loop's recording is guarded by that flag, set once from spu_render. */
typedef struct {
int16_t s[4]; /* decoded samples at sample_idx-1 .. sample_idx+2 */
float frac; /* fractional phase in [0,1) at this output frame */
uint16_t env; /* env_level (0..0x7FFF) */
int16_t vol_l; /* per-voice volume, 1.14 scale (effective int16 >> 1) */
int16_t vol_r;
uint8_t active;
} SpuShadowVoiceTap;
/* One frame's worth of per-voice taps, plus the global scale used this block. */
typedef struct {
SpuShadowVoiceTap voice[SPU_MIX_VOICES];
int16_t main_l;
int16_t main_r;
int enabled; /* SPU control enable bit this block */
} SpuShadowFrameTap;
/* Sized to the largest spu_render block (main.cpp caps at 2048 frames). */
#define SPU_SHADOW_TAP_FRAMES 2048
static SpuShadowFrameTap s_shadow_tap[SPU_SHADOW_TAP_FRAMES];
static int s_shadow_tap_on = 0; /* set by spu_render when enabled */
static int s_shadow_tap_frame = 0;
/* The shadow reads s_shadow_tap as SpuShadowFrameTapPub[] (spu.h). Guarantee
* the internal and public layouts are identical so the cast is safe. */
typedef char spu_shadow_tap_voice_size_check[
(sizeof(SpuShadowVoiceTap) == sizeof(SpuShadowVoiceTapPub)) ? 1 : -1];
typedef char spu_shadow_tap_frame_size_check[
(sizeof(SpuShadowFrameTap) == sizeof(SpuShadowFrameTapPub)) ? 1 : -1];
typedef char spu_shadow_voice_count_check[
(SPU_MIX_VOICES == SPU_SHADOW_MAX_VOICES) ? 1 : -1];
const void* spu_shadow_tap_buffer(void) { return s_shadow_tap; }
int spu_shadow_tap_count(void) { return s_shadow_tap_frame; }
static int16_t voice_next_sample(int idx) {
SpuVoice *v = &voices[idx];
if (!v->active) return 0;
if (v->sample_idx >= SPU_BLOCK_SAMPLES) {
if (v->flags & 0x01u) {
/* END flag: the decode pointer jumps to the latched repeat
* address in BOTH the loop and stop cases — Beetle spu.cpp:333
* (CurAddr = LoopAddr) does this unconditionally. */
v->cur_addr = v->repeat_addr & (SPU_RAM_SIZE - 1u);
if (v->flags & 0x02u) {
spu_event_record(SPU_EV_END_LOOP, idx, v->repeat_addr);
} else {
/* END without REPEAT: hardware forces the envelope to ZERO
* and enters Release (Beetle spu.cpp:341-352 — "Force
* enveloping to 0 if not looping"). The voice keeps decoding
* from the repeat address, silently.
*
* The previous shape decoded FORWARD past the terminator
* with the envelope intact, assuming the release would mask
* whatever follows. X4's SFX bank breaks both assumptions:
* its one-shot samples end on an END-only block whose
* successor is non-silent filler (0x77 nibbles at shift 0 =
* +28672 DC) with a self-looping LOOPSTART flag, and its
* ADSR release shift is ~infinite. Every finished SFX voice
* parked there at full envelope; a handful of ice-block
* hits in the X4 attract demo railed the whole mix at
* +32767 for ~35 s ("static" / music cut-out). */
spu_event_record(SPU_EV_END_STOP, idx, v->cur_addr);
v->adsr_phase = ADSR_RELEASE;
v->adsr_divider = 0;
v->env_level = 0;
}
}
decode_block(v);
}
/* Noise mode (NON bit set): the voice outputs the live noise LFSR value
* INSTEAD of its interpolated ADPCM sample. Everything else — block
* decoding/address advance, ENDX latching, pitch stepping, the ADSR
* envelope and the volume stages — behaves exactly as for ADPCM. */
int16_t raw_s;
uint32_t non_mask = (uint32_t)spu_regs[reg_index(0x1F801D94u)]
| ((uint32_t)spu_regs[reg_index(0x1F801D96u)] << 16);
if (idx<SPU_VOICE_COUNT?(non_mask & (1u << idx)):
(private_modes[idx-SPU_VOICE_COUNT]&1u)) {
raw_s = (int16_t)noise_lfsr;
} else {
raw_s = spu_gaussian_interpolate(v->previous_samples,
v->samples,
v->sample_idx,
v->phase);
}
/* Apply envelope (0..0x7FFF as a 15-bit gain). */
int32_t shaped = ((int32_t)raw_s * (int32_t)v->env_level) >> 15;
if (shaped > 32767) shaped = 32767;
if (shaped < -32768) shaped = -32768;
/* Shadow tap: record the four decoded samples bracketing the current
* sample position + the fractional phase + envelope, BEFORE the phase
* advances. The optional shadow keeps its own cubic, block-clamped window;
* the canonical hardware path above independently uses the preceding
* block's tail for Gaussian interpolation. */
if (s_shadow_tap_on && s_shadow_tap_frame < SPU_SHADOW_TAP_FRAMES) {
SpuShadowVoiceTap *t =
&s_shadow_tap[s_shadow_tap_frame].voice[idx];
int si = v->sample_idx;
int i0 = si - 1, i2 = si + 1, i3 = si + 2;
if (i0 < 0) i0 = 0;
if (i2 >= SPU_BLOCK_SAMPLES) i2 = SPU_BLOCK_SAMPLES - 1;
if (i3 >= SPU_BLOCK_SAMPLES) i3 = SPU_BLOCK_SAMPLES - 1;
t->s[0] = v->samples[i0];
t->s[1] = v->samples[si];
t->s[2] = v->samples[i2];
t->s[3] = v->samples[i3];
t->frac = (float)v->phase / 4096.0f;
t->env = v->env_level;
t->active = 1;
}
/* Step envelope once per output sample (44.1 kHz). */
adsr_run(idx, v);
/* Deactivate once Release fully decays to silence. */
if (v->adsr_phase == ADSR_RELEASE && v->env_level == 0) {
v->active = 0;
}
/* Pitch 0 means the sample counter does not advance: the voice holds its
* current sample. It does NOT mean "play at 1.0x" — coercing it to 0x1000
* makes a parked voice stream forward through SPU RAM at full rate,
* decoding whatever follows as ADPCM. Alien Resurrection (SLUS-00633)
* parks its two ambience voices that way when the pause menu opens: pitch
* 0, envelope frozen mid-sustain, no key-off. Coerced, they ran ~0x790
* bytes past their own repeat address and turned the menu into a
* continuous mid-band buzz. DuckStation and Beetle both just add the
* pitch to the counter, so zero holds. */
uint32_t pitch = voice_reg(idx, 2) & 0x3FFFu;
v->phase += pitch;
while (v->phase >= 0x1000u) {
v->phase -= 0x1000u;
v->sample_idx++;
if (v->sample_idx >= SPU_BLOCK_SAMPLES) break;
}
return (int16_t)shaped;
}
static void key_on(uint32_t mask) {
for (int i = 0; i < SPU_VOICE_COUNT; i++) {
if (!(mask & (1u << i))) continue;
SpuVoice *v = &voices[i];
uint32_t bank=v->pending_bank;
memset(v, 0, sizeof(*v));
v->sample_bank=bank;
v->active = 1;
/* Address registers count 8-byte units but hardware ignores bit0
* — block fetches are 16-byte aligned (DuckStation Voice::KeyOn
* `adpcm_start_address & ~1u`; Beetle aligns identically). */
v->cur_addr = ((uint32_t)(voice_reg(i, 3) & ~1u) << 3) & (SPU_RAM_SIZE - 1u);
v->repeat_addr = ((uint32_t)(voice_reg(i, 7) & ~1u) << 3) & (SPU_RAM_SIZE - 1u);
v->sample_idx = SPU_BLOCK_SAMPLES;
/* Reset ADSR — KEYON starts envelope at 0 in Attack phase
* (matches Beetle's PS_SPU::ResetEnvelope). */
v->env_level = 0;
v->adsr_divider = 0;
v->adsr_phase = ADSR_ATTACK;
key_on_count++;
endx_latch &= ~(1u << i); /* KEYON clears ENDX bit on real hw */
spu_event_record(SPU_EV_KEYON, i, v->cur_addr);
}
}
/* KEYOFF triggers Release phase, NOT immediate silence. The voice
* keeps voicing while env_level decays from its current value to 0
* at the configured Release rate. This is the boot-chime fade tail —
* silencing immediately is what made channels appear to "cut out". */
static void key_off(uint32_t mask) {
for (int i = 0; i < SPU_VOICE_COUNT; i++) {
if (!(mask & (1u << i))) continue;
if (!voices[i].active) continue;
spu_event_record(SPU_EV_KEYOFF, i, voices[i].cur_addr);
voices[i].adsr_phase = ADSR_RELEASE;
voices[i].adsr_divider = 0;
/* env_level preserved — release decays from wherever we are now. */
}
}
/* These opt-in voices never appear in hardware registers, KEYON/KEYOFF,
* capture channels or ENDX. They share the native sample DSP and main mix.
* Slots are chosen by the title; a slot's next sound replaces only that slot. */
int spu_private_voice_play(unsigned slot,uint32_t bank,const uint16_t regs[8],unsigned mode) {
if(slot>=SPU_PRIVATE_VOICES || !bank || bank>=SPU_SAMPLE_BANKS ||
!sample_banks[bank].data || !regs || mode>3u)return 0;
unsigned index=SPU_VOICE_COUNT+slot;SpuVoice *v=&voices[index];
private_enabled=1;memcpy(private_regs[slot],regs,sizeof private_regs[slot]);
private_modes[slot]=(uint8_t)mode;
memset(v,0,sizeof *v);v->sample_bank=bank;v->active=1;
v->cur_addr=((uint32_t)(regs[3]&~1u)<<3)&(SPU_RAM_SIZE-1u);
v->repeat_addr=((uint32_t)(regs[7]&~1u)<<3)&(SPU_RAM_SIZE-1u);
v->sample_idx=SPU_BLOCK_SAMPLES;v->adsr_phase=ADSR_ATTACK;
sweep_env_write(&sweep_voice_env[index][0],regs[0]);
sweep_env_write(&sweep_voice_env[index][1],regs[1]);
key_on_count++;spu_event_record(SPU_EV_KEYON,(int)index,v->cur_addr);return 1;
}
void spu_private_voice_stop(uint32_t mask) {
for(unsigned slot=0;slot<SPU_PRIVATE_VOICES;++slot) {
SpuVoice *v=&voices[SPU_VOICE_COUNT+slot];
if(!(mask&(1u<<slot)) || !v->active)continue;
spu_event_record(SPU_EV_KEYOFF,SPU_VOICE_COUNT+(int)slot,v->cur_addr);
v->adsr_phase=ADSR_RELEASE;v->adsr_divider=0;
}
}
int spu_private_voice_volume(unsigned slot,uint16_t left,uint16_t right) {
if(slot>=SPU_PRIVATE_VOICES)return 0;
private_regs[slot][0]=left;private_regs[slot][1]=right;
sweep_env_write(&sweep_voice_env[SPU_VOICE_COUNT+slot][0],left);
sweep_env_write(&sweep_voice_env[SPU_VOICE_COUNT+slot][1],right);return 1;
}
uint32_t spu_private_voice_active(void) {
uint32_t mask=0;
for(unsigned slot=0;slot<SPU_PRIVATE_VOICES;++slot)
if(voices[SPU_VOICE_COUNT+slot].active)mask|=1u<<slot;
return mask;
}
void spu_init(void) {
for(unsigned bank=1;bank<SPU_SAMPLE_BANKS;++bank) {
free(sample_banks[bank].data);sample_banks[bank].data=NULL;sample_banks[bank].bytes=0;
}
memset(spu_ram, 0, sizeof(spu_ram));
memset(spu_regs, 0, sizeof(spu_regs));
memset(voices, 0, sizeof(voices));
memset(private_regs,0,sizeof private_regs);memset(private_modes,0,sizeof private_modes);
private_enabled=0;
memset(s_events, 0, sizeof(s_events));
transfer_addr = 0;
key_on_count = 0;
render_frames = 0;
nonzero_frames = 0;
last_peak = 0;
peak = 0;
endx_latch = 0;
kon_latch = 0;
koff_latch = 0;
irq_flag = 0;
/* Hardware power-on LFSR value is undocumented; 0 is safe because the
* xor-1 parity term self-starts the register within 16 clocks. */
noise_lfsr = 0;
noise_timer = 0;
rev_cur = 0;
rev_phase = 0;
rev_in_hold_l = 0;
rev_in_hold_r = 0;
rev_out_l = 0;
rev_out_r = 0;
capture_pos = 0;
memset(sweep_voice_env, 0, sizeof(sweep_voice_env));
memset(sweep_main_env, 0, sizeof(sweep_main_env));
s_event_idx = 0;
s_event_seq = 0;
spu_cd_audio_reset();
s_shadow_tap_on = 0;
s_shadow_tap_frame = 0;
spu_shadow_reset();
s_clock_valid = 0;
s_clock_carry = 0;
spu_output_flush();
}
static void spu_render_block(int16_t* out_stereo, int frames) {
if (!out_stereo || frames <= 0) return;
uint16_t ctrl = spu_regs[reg_index(0x1F801DAAu)];
int enabled = (ctrl & 0x8000u) != 0;
int cd_on = (ctrl & 0x0001u) != 0;
int cd_rev = cd_on && (ctrl & 0x0004u) != 0; /* CD reverb send needs CD enable */
int rev_wren = (ctrl & 0x0080u) != 0; /* reverb work-area write enable */
int16_t cd_vol_l = cd_input_volume(spu_regs[reg_index(0x1F801DB0u)]);
int16_t cd_vol_r = cd_input_volume(spu_regs[reg_index(0x1F801DB2u)]);
uint32_t eon = (uint32_t)spu_regs[reg_index(0x1F801D98u)]
| ((uint32_t)spu_regs[reg_index(0x1F801D9Au)] << 16);
int any_voice = 0;
int mix_voices=SPU_VOICE_COUNT+(private_enabled?SPU_PRIVATE_VOICES:0);
if (enabled) {
for (int v = 0; v < mix_voices; v++) {
if (voices[v].active) { any_voice = 1; break; }
}
}
/* Shadow tap: arm recording for this block if the float SPU shadow is on.
* Off by default => s_shadow_tap_on stays 0 and the mix loop is unchanged
* and byte-identical to upstream. */
s_shadow_tap_on = spu_shadow_enabled() ? 1 : 0;
s_shadow_tap_frame = 0;
if (s_shadow_tap_on) {
int cap = frames < SPU_SHADOW_TAP_FRAMES ? frames : SPU_SHADOW_TAP_FRAMES;
memset(s_shadow_tap, 0, (size_t)cap * sizeof(s_shadow_tap[0]));
}
/* MotK intro FMV (and other XA-only scenes): active_mask stays 0 while
* CD/XA plays. Keep capture / noise / reverb / main sweeps (issue #103
* fidelity — FMV is exactly the CD-with-reverb case) but skip the
* 24-voice walk and per-voice sweep steps. Shadow tap stays on the
* general path. */
if (enabled && !any_voice && !s_shadow_tap_on) {
static int16_t s_voice_silence[2048 * 2];
int voice_tap_n = frames;
if (voice_tap_n > 2048) voice_tap_n = 2048;
memset(s_voice_silence, 0, (size_t)voice_tap_n * 2u * sizeof(int16_t));
for (int off = 0; off < frames; ) {
int n = frames - off;
if (n > 2048) n = 2048;
audio_trace_pcm(AUDIO_TAP_VOICES, s_voice_silence, n);
off += n;
}
int32_t block_peak = 0;
for (int f = 0; f < frames; f++) {
int32_t mix_l = 0;
int32_t mix_r = 0;
int32_t rev_send_l = 0;
int32_t rev_send_r = 0;
int16_t main_l = chan_volume(spu_regs[reg_index(0x1F801D80u)],
&sweep_main_env[0]);
int16_t main_r = chan_volume(spu_regs[reg_index(0x1F801D82u)],
&sweep_main_env[1]);
sweep_env_step(&sweep_main_env[0],
spu_regs[reg_index(0x1F801D80u)]);
sweep_env_step(&sweep_main_env[1],
spu_regs[reg_index(0x1F801D82u)]);
int16_t cd_l = 0;
int16_t cd_r = 0;
if (cd_audio_pop(&cd_l, &cd_r)) {
/* got a frame */
} else if (cd_on && cd_push_frames != 0) {
cd_underflow_frames++;
}
if (cd_on) {
int32_t ccl = ((int32_t)cd_l * cd_vol_l) >> 15;
int32_t ccr = ((int32_t)cd_r * cd_vol_r) >> 15;
mix_l += ccl;
mix_r += ccr;
if (cd_rev) {
rev_send_l += ccl;
rev_send_r += ccr;
}
}
/* No active voices → capture voice 1/3 slots stay 0. */
capture_write(0x0000u, cd_l);
capture_write(0x0400u, cd_r);
capture_write(0x0800u, 0);
capture_write(0x0C00u, 0);
capture_pos = (capture_pos + 2u) & 0x3FFu;
noise_clock(ctrl);
{
int32_t wet_l, wet_r;
if (rev_phase == 0) {
rev_in_hold_l = rev_send_l;
rev_in_hold_r = rev_send_r;
wet_l = rev_out_l;
wet_r = rev_out_r;
rev_phase = 1;
} else {
int32_t prev_l = rev_out_l;
int32_t prev_r = rev_out_r;
reverb_step(clamp16((rev_in_hold_l + rev_send_l) >> 1),
clamp16((rev_in_hold_r + rev_send_r) >> 1),
rev_wren);
wet_l = rev_reconstruct(prev_l, rev_out_l);
wet_r = rev_reconstruct(prev_r, rev_out_r);
rev_phase = 0;
}
mix_l += wet_l;
mix_r += wet_r;
}
mix_l = clamp16(mix_l);
mix_r = clamp16(mix_r);
mix_l = ((int32_t)mix_l * main_l) >> 15;
mix_r = ((int32_t)mix_r * main_r) >> 15;
out_stereo[f * 2 + 0] = clamp16(mix_l);
out_stereo[f * 2 + 1] = clamp16(mix_r);
int32_t frame_peak = abs32(out_stereo[f * 2 + 0]);
int32_t right_peak = abs32(out_stereo[f * 2 + 1]);
if (right_peak > frame_peak) frame_peak = right_peak;
if (frame_peak) nonzero_frames++;
if (frame_peak > block_peak) block_peak = frame_peak;
}
render_frames += (uint64_t)frames;
last_peak = block_peak;
if (block_peak > peak) peak = block_peak;
spu_shadow_process(out_stereo, frames);
audio_trace_pcm(AUDIO_TAP_SPU_OUT, out_stereo, frames);
return;
}
int32_t block_peak = 0;
/* Voice-sum tap is filled once per block (not per sample) — same bytes. */
static int16_t s_voice_sum[2048 * 2];
int voice_sum_cap = frames < 2048 ? frames : 2048;
int voice_sum_pos = 0;
for (int f = 0; f < frames; f++) {
int32_t mix_l = 0;
int32_t mix_r = 0;
/* Main L/R volumes are sweep-aware and can glide per sample. Kept
* live-per-frame; the >>1 below preserves the shadow tap's historical
* 1.14 volume scale (identical bytes for direct-mode registers). */
int16_t main_l = chan_volume(spu_regs[reg_index(0x1F801D80u)], &sweep_main_env[0]);
int16_t main_r = chan_volume(spu_regs[reg_index(0x1F801D82u)], &sweep_main_env[1]);
if (enabled) {
int32_t voice_l = 0;
int32_t voice_r = 0;
int32_t rev_send_l = 0;
int32_t rev_send_r = 0;
int16_t v1_out = 0; /* voice 1 post-envelope output (capture) */
int16_t v3_out = 0; /* voice 3 post-envelope output (capture) */
/* Volume sweeps step once per 44100 Hz sample on the same rate
* machinery as ADSR; no-ops for direct-mode registers. Voice
* sweeps only matter while a voice is active. */
sweep_env_step(&sweep_main_env[0], spu_regs[reg_index(0x1F801D80u)]);
sweep_env_step(&sweep_main_env[1], spu_regs[reg_index(0x1F801D82u)]);
if (any_voice) {
for (int v = 0; v < mix_voices; v++) {
sweep_env_step(&sweep_voice_env[v][0], voice_reg(v, 0));
sweep_env_step(&sweep_voice_env[v][1], voice_reg(v, 1));
}
}
if (any_voice) {
for (int v = 0; v < mix_voices; v++) {
int16_t s = voice_next_sample(v);
if (v == 1) v1_out = s;
if (v == 3) v3_out = s;
int16_t vl = chan_volume(voice_reg(v, 0), &sweep_voice_env[v][0]);
int16_t vr = chan_volume(voice_reg(v, 1), &sweep_voice_env[v][1]);
if (s_shadow_tap_on && f < SPU_SHADOW_TAP_FRAMES) {
SpuShadowVoiceTap *t = &s_shadow_tap[f].voice[v];
/* Tap keeps its historical 1.14 volume scale. */
t->vol_l = (int16_t)(vl >> 1);
t->vol_r = (int16_t)(vr >> 1);
}
if (!s) continue;
int32_t cl = ((int32_t)s * vl) >> 15;
int32_t cr = ((int32_t)s * vr) >> 15;
voice_l += cl;
voice_r += cr;
/* Per-voice reverb send: EON voices feed the reverb input
* bus with their post-envelope, post-voice-volume output. */
if (v<SPU_VOICE_COUNT?(eon & (1u << v)):
(private_modes[v-SPU_VOICE_COUNT]&2u)) {
rev_send_l += cl;
rev_send_r += cr;
}
}
}
mix_l = voice_l;
mix_r = voice_r;
if (voice_sum_pos < voice_sum_cap) {
s_voice_sum[voice_sum_pos * 2 + 0] = clamp16(voice_l);
s_voice_sum[voice_sum_pos * 2 + 1] = clamp16(voice_r);
voice_sum_pos++;
}
/* CD input bus. The bus runs continuously while the SPU is
* enabled (the capture buffers record it regardless of SPUCNT
* bit 0); bit 0 only gates its contribution to the mix, and
* bit 2 (with bit 0) its reverb send. */
int16_t cd_l = 0;
int16_t cd_r = 0;
if (cd_audio_pop(&cd_l, &cd_r)) {
/* got a frame */
} else if (cd_on && cd_push_frames != 0) {
cd_underflow_frames++;
}
if (cd_on) {
int32_t ccl = ((int32_t)cd_l * cd_vol_l) >> 15;
int32_t ccr = ((int32_t)cd_r * cd_vol_r) >> 15;
mix_l += ccl;
mix_r += ccr;
if (cd_rev) {
rev_send_l += ccl;
rev_send_r += ccr;
}
}
/* Capture buffers: CD input L/R at 0x000/0x400, voice 1/3
* post-envelope output at 0x800/0xC00; one halfword per 44100 Hz
* sample, wrapping every 0x400 bytes. Each store runs the IRQ
* check — games park the IRQ address here. The CD samples are
* recorded PRE CD-volume (the raw input bus).
* DOCUMENTED-GAP: whether the CD capture value is pre- or
* post-CD-volume is not settled by the documentation; the raw
* input-bus reading was chosen. Candidate for oracle check. */
capture_write(0x0000u, cd_l);
capture_write(0x0400u, cd_r);
capture_write(0x0800u, v1_out);
capture_write(0x0C00u, v3_out);
capture_pos = (capture_pos + 2u) & 0x3FFu;
/* Noise LFSR clock: free-running at the SPUCNT bits 8-13 rate,
* once per output sample cycle. Clocked AFTER the voice walk, so
* noise voices see the value from the previous cycle (whether
* hardware clocks before or after voice processing within the
* sample cycle is undocumented). */
noise_clock(ctrl);
/* Reverb: the engine runs one step per TWO output frames
* (22050 Hz). The engine always steps while the SPU is enabled —
* SPUCNT bit 7 gates only its work-area WRITES (reads, IRQ
* checks on those reads, address advance and output still
* happen), so a frozen work area keeps ringing through vLOUT
* until the game silences it. See rev_reconstruct for the
* 22.05 <-> 44.1 kHz boundary model. */
{
int32_t wet_l, wet_r;
if (rev_phase == 0) {
/* First frame of the pair: hold the input, output the
* most recent engine result. */
rev_in_hold_l = rev_send_l;
rev_in_hold_r = rev_send_r;
wet_l = rev_out_l;
wet_r = rev_out_r;
rev_phase = 1;
} else {
int32_t prev_l = rev_out_l;
int32_t prev_r = rev_out_r;
reverb_step(clamp16((rev_in_hold_l + rev_send_l) >> 1),
clamp16((rev_in_hold_r + rev_send_r) >> 1),
rev_wren);
wet_l = rev_reconstruct(prev_l, rev_out_l);
wet_r = rev_reconstruct(prev_r, rev_out_r);
rev_phase = 0;
}
mix_l += wet_l;
mix_r += wet_r;
}
/* The 16-bit mix bus saturates BEFORE the main volume applies
* (documented mixer order: sum -> saturate -> main volume). */
mix_l = clamp16(mix_l);
mix_r = clamp16(mix_r);
mix_l = ((int32_t)mix_l * main_l) >> 15;
mix_r = ((int32_t)mix_r * main_r) >> 15;
}
out_stereo[f * 2 + 0] = clamp16(mix_l);
out_stereo[f * 2 + 1] = clamp16(mix_r);
int32_t frame_peak = abs32(out_stereo[f * 2 + 0]);
int32_t right_peak = abs32(out_stereo[f * 2 + 1]);
if (right_peak > frame_peak) frame_peak = right_peak;
if (frame_peak) nonzero_frames++;
if (frame_peak > block_peak) block_peak = frame_peak;
if (s_shadow_tap_on && f < SPU_SHADOW_TAP_FRAMES) {
/* Tap keeps its historical 1.14 volume scale. */
s_shadow_tap[f].main_l = (int16_t)(main_l >> 1);
s_shadow_tap[f].main_r = (int16_t)(main_r >> 1);
s_shadow_tap[f].enabled = enabled;
s_shadow_tap_frame = f + 1;
}
}
if (enabled) {
/* Flush voice-sum tap; if the block was longer than the staging buf,
* emit the remainder as silence (MotK FMV blocks are ≤2048). */
if (voice_sum_pos > 0)
audio_trace_pcm(AUDIO_TAP_VOICES, s_voice_sum, voice_sum_pos);
for (int left = frames - voice_sum_pos; left > 0; ) {
static int16_t z[256 * 2];
int n = left > 256 ? 256 : left;
memset(z, 0, (size_t)n * 2u * sizeof(int16_t));
audio_trace_pcm(AUDIO_TAP_VOICES, z, n);
left -= n;
}
}
render_frames += (uint64_t)frames;
last_peak = block_peak;
if (block_peak > peak) peak = block_peak;
/* Verified-enhancement shadow: re-render this block in float from the
* tap, verify against the canon mix in `out_stereo`, and substitute only
* while proven. No-op (byte-identical) when disabled. The canon mix above
* stays the authoritative output AND the verify oracle. */
spu_shadow_process(out_stereo, frames);
/* T1 tap: the SPU's final output block as handed to the host layer
* (post-shadow, pre host fade/mute). Placed here so every spu_render
* block spu_catch_up renders is covered. */
audio_trace_pcm(AUDIO_TAP_SPU_OUT, out_stereo, frames);
}
/* ---- Guest-clocked SPU (beads-eio.3.321) ------------------------------
* The SPU runs on the guest clock: 768 CPU cycles per 44.1 kHz sample.
* spu_catch_up() renders every whole sample owed up to the current guest
* cycle before any guest-visible SPU access (register read/write, DMA, CD
* input push) and before a snapshot, so what the guest reads (ENVX, ENDX,
* capture position, IRQ flag) reflects every sample produced by now. A
* KEYON's envelope therefore starts moving at the next sample, not at the
* next host audio pump. Rendered output queues in s_out until the host
* pump drains it; host-side drops never change SPU state. */
void spu_set_output_gate(int (*suppressed)(void)) { s_output_suppressed = suppressed; }
static void spu_out_push(const int16_t *src, uint32_t frames) {
for (uint32_t i = 0; i < frames; i++) {
if (s_out_count == SPU_OUT_FRAMES) { /* host fell behind: drop oldest */
s_out_head = (s_out_head + 1u) % SPU_OUT_FRAMES;
s_out_count--;
s_out_drops++;
}
const uint32_t at = (s_out_head + s_out_count) % SPU_OUT_FRAMES;
s_out[at * 2u] = src[i * 2u];
s_out[at * 2u + 1u] = src[i * 2u + 1u];
s_out_count++;
}
}
void spu_catch_up(void) {
const uint64_t now = psx_get_cycle_count();
if (!s_clock_valid) {
s_clock = now >= s_clock_carry ? now - s_clock_carry : now;
s_clock_carry = 0;
s_clock_valid = 1;
}
if (now < s_clock) { s_clock = now; return; } /* guest clock rewound */
uint64_t frames = (now - s_clock) / SPU_CYCLES_PER_SAMPLE;
if (!frames || s_catching_up) return;
s_catching_up = 1;
s_clock += frames * SPU_CYCLES_PER_SAMPLE;
const int keep = !(s_output_suppressed && s_output_suppressed());
static int16_t block[2048 * 2];
while (frames) {
const int n = frames > 2048u ? 2048 : (int)frames;
spu_render_block(block, n);
if (keep) spu_out_push(block, (uint32_t)n);
frames -= (uint64_t)n;
}
s_catching_up = 0;
}
uint32_t spu_output_available(void) { return s_out_count; }
uint32_t spu_output_pop(int16_t *out_stereo, uint32_t max_frames) {
uint32_t n = s_out_count < max_frames ? s_out_count : max_frames;
for (uint32_t i = 0; i < n; i++) {
out_stereo[i * 2u] = s_out[s_out_head * 2u];
out_stereo[i * 2u + 1u] = s_out[s_out_head * 2u + 1u];
s_out_head = (s_out_head + 1u) % SPU_OUT_FRAMES;
}
s_out_count -= n;
return n;
}
void spu_output_drop(uint32_t frames) {
if (frames > s_out_count) frames = s_out_count;
s_out_head = (s_out_head + frames) % SPU_OUT_FRAMES;
s_out_count -= frames;
}
void spu_output_flush(void) { s_out_head = 0; s_out_count = 0; }
uint64_t spu_output_overflow_drops(void) { return s_out_drops; }
void spu_debug_info(SpuDebugInfo* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
out->ctrl = spu_regs[reg_index(0x1F801DAAu)];
/* Historical 1.14 scale (effective int16 volume >> 1) for continuity
* with older captures; identical values for direct-mode registers. */
out->main_l = (int16_t)(chan_volume(spu_regs[reg_index(0x1F801D80u)],
&sweep_main_env[0]) >> 1);
out->main_r = (int16_t)(chan_volume(spu_regs[reg_index(0x1F801D82u)],
&sweep_main_env[1]) >> 1);
out->cd_l = cd_input_volume(spu_regs[reg_index(0x1F801DB0u)]);
out->cd_r = cd_input_volume(spu_regs[reg_index(0x1F801DB2u)]);
for (int i = 0; i < SPU_VOICE_COUNT; i++) {
if (voices[i].active) out->active_mask |= (1u << i);
}
out->key_on_count = key_on_count;
out->render_frames = render_frames;
out->nonzero_frames = nonzero_frames;
out->last_peak = last_peak;
out->peak = peak;
out->cd_frames = cd_frame_count;
out->cd_push_frames = cd_push_frames;
out->cd_overflow_frames = cd_overflow_frames;
out->cd_underflow_frames = cd_underflow_frames;
}
uint32_t spu_read(uint32_t addr) {
spu_catch_up();
if (addr >= 0x1F801C00u && addr <= 0x1F801DFFu) {
uint32_t idx = reg_index(addr);
if (idx < SPU_REG_COUNT) {
if (addr == 0x1F801DAEu) {
/* SPUSTAT (psx-spx): bits 5-0 mirror SPUCNT bits 5-0 (the
* current SPU mode), bit 6 is the IRQ flag (cleared by
* writing SPUCNT with bit 6 clear), bit 7 follows SPUCNT.5
* (DMA r/w request), bit 10 is the data-transfer busy flag —
* 0 here because this runtime completes FIFO/DMA transfers
* instantly. The old hardcoded 0x0400 held busy PERMANENTLY
* asserted; Sony code paths never polled it to zero, but
* OpenBIOS's shell MOD player waits for (SPUSTAT & 0x7FF)
* == 0 after clearing SPUCNT and spun forever. Bit 11 is
* "currently writing the SECOND half of the capture
* buffers" (capture offset >= 0x200). */
uint16_t cnt = spu_regs[reg_index(0x1F801DAAu)];
uint32_t st = (uint32_t)((cnt & 0x3Fu) | (((cnt >> 5) & 1u) << 7));
if (irq_flag) st |= 0x40u;
if (capture_pos & 0x200u) st |= 0x800u;
return st;
}
/* Current main volume L/R (psx-spx 1F801DB8h/1F801DBAh): the
* LIVE sweep-aware level as a signed 16-bit value. */
if (addr == 0x1F801DB8u) {
return (uint32_t)(uint16_t)chan_volume(
spu_regs[reg_index(0x1F801D80u)], &sweep_main_env[0]);
}
if (addr == 0x1F801DBAu) {
return (uint32_t)(uint16_t)chan_volume(
spu_regs[reg_index(0x1F801D82u)], &sweep_main_env[1]);
}
/* A volume register in SWEEP mode reads back the envelope's
* CURRENT level, not the sweep-parameter word — the live value
* is what actually multiplies the samples.
* DOCUMENTED-GAP: the exact readback encoding for a sweeping
* volume register is not settled; the live level as a signed
* 16-bit value was chosen. Candidate for oracle verification. */
if (idx < (uint32_t)SPU_VOICE_COUNT * 8u && (idx & 7u) <= 1u
&& (spu_regs[idx] & 0x8000u)) {
return (uint32_t)(uint16_t)
sweep_voice_env[idx >> 3][idx & 7u].level;
}
if ((addr == 0x1F801D80u || addr == 0x1F801D82u)
&& (spu_regs[idx] & 0x8000u)) {
return (uint32_t)(uint16_t)
sweep_main_env[(addr >> 1) & 1u].level;
}
/* ENDX (end-block-reached latch). Real hw sets bit v when voice
* v decodes a block whose flag byte has bit 0; KEYON[v] clears
* it. Without this latch, music engines that poll ENDX to wait
* for a sample to finish never advance and downstream voices
* never get keyed on. */
if (addr == 0x1F801D9Cu) {
return endx_latch & 0xFFFFu;
}
if (addr == 0x1F801D9Eu) {
return (endx_latch >> 16) & 0xFFu;
}
/* Voice register 6 (byte offset 0x0C) is CURVOL / ADSR_LEVEL —
* returns the live envelope level. PSX music engines poll this
* to pick a "free" voice (env_level == 0). Without exposing the
* real envelope, the BIOS sees every voice as silent and over-
* recycles voices 0-3 instead of fanning across all 24. */
if (addr >= 0x1F801C00u && addr < 0x1F801D80u
&& (idx & 7u) == 6u) {
int v = (int)(idx >> 3);
if (v >= 0 && v < SPU_VOICE_COUNT)
return voices[v].env_level;
}
return spu_regs[idx];
}
}
/* Per-voice CURRENT volume L/R (psx-spx 1F801E00h..1F801E5Fh): the live
* sweep-aware effective volume of each voice, two halfwords per voice. */
if (addr >= 0x1F801E00u && addr < 0x1F801E60u) {
uint32_t half = (addr - 0x1F801E00u) >> 1;
int v = (int)(half >> 1);
int ch = (int)(half & 1u);
if (v < SPU_VOICE_COUNT) {
return (uint32_t)(uint16_t)chan_volume(
voice_reg(v, ch), &sweep_voice_env[v][ch]);
}
}
return 0;
}
void spu_write(uint32_t addr, uint32_t value) {
spu_catch_up();
if (addr >= 0x1F801C00u && addr <= 0x1F801DFFu) {
uint32_t idx = reg_index(addr);
if (idx < SPU_REG_COUNT) {
/* Event-ring every register store with its SPU_OUT sample-clock
* timestamp: this is what lets offline analysis correlate a
* write (key-on, pitch, volume) with the exact output sample
* the render loop first honored it at — the write-to-render
* quantization audit (snesrecomp 8ffc797 class). */
audio_trace_event(AUDIO_EV_REG_WRITE, addr, value & 0xFFFFu);
spu_regs[idx] = (uint16_t)value;
/* Voice repeat/loop address (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). X5's driver
* uses exactly this to end one-shots on looped samples — KEYON,
* then point the loop register at a silent tail block; no KEYOFF
* is ever sent. Caching repeat_addr only at KEYON left voices
* looping the loud sample body forever (measured: 3 stuck voices
* re-looping ~1400x/s, +11 dB over the oracle, clipping). */
if (idx < (uint32_t)SPU_VOICE_COUNT * 8u && (idx & 7u) == 7u) {
int v = (int)(idx >> 3);
/* bit0 ignored (16-byte alignment) — same masking as KEYON. */
voices[v].repeat_addr =
((uint32_t)((uint16_t)value & ~1u) << 3) & (SPU_RAM_SIZE - 1u);
}
/* Volume registers feed the sweep envelopes: a direct write
* takes effect immediately, a sweep-mode write starts gliding
* from the current live level. */
if (idx < (uint32_t)SPU_VOICE_COUNT * 8u && (idx & 7u) <= 1u) {
sweep_env_write(&sweep_voice_env[idx >> 3][idx & 7u],
(uint16_t)value);
}
if (addr == 0x1F801D80u)
sweep_env_write(&sweep_main_env[0], (uint16_t)value);
if (addr == 0x1F801D82u)
sweep_env_write(&sweep_main_env[1], (uint16_t)value);
/* Reverb work-area base: writing mBASE also resets the current
* buffer address to the area start. */
if (addr == 0x1F801DA2u) {
rev_cur = rev_mbase();
}
/* IRQ address write: re-evaluate the compare against the
* current transfer address. DOCUMENTED-GAP: whether pointing
* the IRQ address AT the resting transfer address fires
* immediately (vs only on the next actual access) is not
* settled; the immediate re-check was chosen so a match is
* never missed. Candidate for oracle verification. */
if (addr == 0x1F801DA4u) {
spu_irq_check(transfer_addr, 2u);
}
if (addr == 0x1F801D88u) {
kon_latch = (kon_latch & 0xFFFF0000u) | (uint32_t)(uint16_t)value;
key_on((uint32_t)(uint16_t)value);
}
if (addr == 0x1F801D8Au) {
kon_latch = (kon_latch & 0x0000FFFFu) | ((uint32_t)(uint16_t)value << 16);
key_on((uint32_t)(uint16_t)value << 16);
}
if (addr == 0x1F801D8Cu) {
koff_latch = (koff_latch & 0xFFFF0000u) | (uint32_t)(uint16_t)value;
key_off((uint32_t)(uint16_t)value);
}
if (addr == 0x1F801D8Eu) {
koff_latch = (koff_latch & 0x0000FFFFu) | ((uint32_t)(uint16_t)value << 16);
key_off((uint32_t)(uint16_t)value << 16);
}
if (addr == 0x1F801DA6u) {
transfer_addr = ((uint32_t)(uint16_t)value) << 3;
if (transfer_addr >= SPU_RAM_SIZE) transfer_addr = 0;
/* Setting the transfer address is an IRQ compare site (same
* DOCUMENTED-GAP as the IRQ-address write above). */
spu_irq_check(transfer_addr, 2u);
}
if (addr == 0x1F801DA8u) {
/* Manual FIFO write: an SPU RAM access at the transfer
* address — run the IRQ compare before storing. */
spu_irq_check(transfer_addr, 2u);
if (transfer_addr + 1 < SPU_RAM_SIZE) {
spu_ram[transfer_addr] = (uint8_t)(value & 0xFF);
spu_ram[transfer_addr + 1] = (uint8_t)((value >> 8) & 0xFF);
}
transfer_addr = (transfer_addr + 2) % SPU_RAM_SIZE;
}
if (addr == 0x1F801DAAu) {
/* SPUCNT: writing with bit 6 CLEAR acknowledges the IRQ
* latch and re-arms it; writing with bit 6 SET re-evaluates
* the compare against the resting transfer address (same
* DOCUMENTED-GAP as the address-register writes above). */
if (!(value & 0x0040u)) {
irq_flag = 0;
} else {
spu_irq_check(transfer_addr, 2u);
}
}
}
}
}
void spu_dma_write(uint32_t word) {
spu_catch_up();
spu_irq_check(transfer_addr, 4u);
if (transfer_addr + 3 < SPU_RAM_SIZE) {
spu_ram[transfer_addr] = (uint8_t)(word & 0xFF);
spu_ram[transfer_addr + 1] = (uint8_t)((word >> 8) & 0xFF);
spu_ram[transfer_addr + 2] = (uint8_t)((word >> 16) & 0xFF);
spu_ram[transfer_addr + 3] = (uint8_t)((word >> 24) & 0xFF);
}
transfer_addr = (transfer_addr + 4) % SPU_RAM_SIZE;
}
/* DMA4 SPU->CPU direction: read one little-endian 32-bit word from SPU RAM
* at the transfer address, advance it by 4 and run the IRQ compare — the
* exact mirror of spu_dma_write. Titles carry state through SPU RAM across
* EXE transitions via this path; returning zeros breaks them. */
uint32_t spu_dma_read(void) {
spu_catch_up();
uint32_t word = 0;
spu_irq_check(transfer_addr, 4u);
if (transfer_addr + 3 < SPU_RAM_SIZE) {
word = (uint32_t)spu_ram[transfer_addr]
| ((uint32_t)spu_ram[transfer_addr + 1] << 8)
| ((uint32_t)spu_ram[transfer_addr + 2] << 16)
| ((uint32_t)spu_ram[transfer_addr + 3] << 24);
}
transfer_addr = (transfer_addr + 4) % SPU_RAM_SIZE;
return word;
}
int spu_dma_ready(void) {
return 1;
}
const uint8_t* spu_get_ram(void) {
return spu_ram;
}
void spu_get_voice_state(int idx, SpuVoiceState* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
if (idx < 0 || idx >= SPU_VOICE_COUNT) return;
SpuVoice *v = &voices[idx];
out->active = v->active;
out->vol_ctrl_l = voice_reg(idx, 0);
out->vol_ctrl_r = voice_reg(idx, 1);
out->pitch = voice_reg(idx, 2);
out->start_lo = voice_reg(idx, 3);
out->adsr_lo = voice_reg(idx, 4);
out->adsr_hi = voice_reg(idx, 5);
out->loop_lo = voice_reg(idx, 7);
out->cur_addr = v->cur_addr;
out->repeat_addr = v->repeat_addr;
out->last_flags = v->flags;
out->sample_idx = (uint8_t)v->sample_idx;
out->phase = (uint16_t)v->phase;
out->env_level = v->env_level;
out->adsr_phase = v->adsr_phase;
out->vol_cur_l = chan_volume(voice_reg(idx, 0), &sweep_voice_env[idx][0]);
out->vol_cur_r = chan_volume(voice_reg(idx, 1), &sweep_voice_env[idx][1]);
}
/* Debug peek into SPU RAM (spu_ram TCP command). Returns bytes copied. */
uint32_t spu_ram_peek(uint32_t addr, uint8_t *out, uint32_t len) {
if (!out || addr >= SPU_RAM_SIZE) return 0;
if (len > SPU_RAM_SIZE - addr) len = SPU_RAM_SIZE - addr;
memcpy(out, spu_ram + addr, len);
return len;
}
uint16_t spu_ctrl_read(void) {
return spu_regs[reg_index(0x1F801DAAu)];
}
void spu_get_global_state(SpuGlobalState* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
out->ctrl = spu_regs[reg_index(0x1F801DAAu)];
out->main_vol_l = spu_regs[reg_index(0x1F801D80u)];
out->main_vol_r = spu_regs[reg_index(0x1F801D82u)];
out->kon_latch = kon_latch & 0xFFFFFFu;
out->koff_latch = koff_latch & 0xFFFFFFu;
out->pmon = (uint32_t)spu_regs[reg_index(0x1F801D90u)] |
((uint32_t)spu_regs[reg_index(0x1F801D92u)] << 16);
out->non = (uint32_t)spu_regs[reg_index(0x1F801D94u)] |
((uint32_t)spu_regs[reg_index(0x1F801D96u)] << 16);
out->eon = (uint32_t)spu_regs[reg_index(0x1F801D98u)] |
((uint32_t)spu_regs[reg_index(0x1F801D9Au)] << 16);
out->endx = endx_latch & 0xFFFFFFu;
uint32_t am = 0;
for (int i = 0; i < SPU_VOICE_COUNT; i++)
if (voices[i].active) am |= (1u << i);
out->active_mask = am;
/* ---- SPU DSP fidelity state (issue #103) ---- */
out->irq_flag = irq_flag;
out->reverb_on = (out->ctrl >> 7) & 1u;
out->irq_addr = ((uint32_t)spu_regs[reg_index(0x1F801DA4u)]) << 3;
out->reverb_mbase = rev_mbase();
out->reverb_cur = rev_cur;
out->capture_pos = capture_pos;
out->noise_lfsr = noise_lfsr;
out->noise_pad = 0;
uint32_t sl = 0, sr = 0;
for (int i = 0; i < SPU_VOICE_COUNT; i++) {
if (voice_reg(i, 0) & 0x8000u) sl |= (1u << i);
if (voice_reg(i, 1) & 0x8000u) sr |= (1u << i);
}
out->sweep_l_mask = sl;
out->sweep_r_mask = sr;
out->sweep_main = ((spu_regs[reg_index(0x1F801D80u)] >> 15) & 1u)
| (((spu_regs[reg_index(0x1F801D82u)] >> 15) & 1u) << 1);
}
uint64_t spu_event_total(void) { return s_event_seq; }
uint32_t spu_event_get(SpuEvent* out, uint32_t max_count) {
if (!out || max_count == 0) return 0;
uint64_t avail = s_event_seq < (uint64_t)SPU_EVENT_CAP
? s_event_seq : (uint64_t)SPU_EVENT_CAP;
if ((uint64_t)max_count > avail) max_count = (uint32_t)avail;
/* Most recent N, oldest first. */
uint32_t start = (s_event_idx + SPU_EVENT_CAP - max_count) & (SPU_EVENT_CAP - 1u);
for (uint32_t i = 0; i < max_count; i++) {
out[i] = s_events[(start + i) & (SPU_EVENT_CAP - 1u)];
}
return max_count;
}
void spu_event_reset(void) {
s_event_idx = 0;
s_event_seq = 0;
memset(s_events, 0, sizeof(s_events));
}
/* ---- boot snapshot: complete SPU register + voice state (LE field wire) ---- */
#include "pst_wire.h"
/* SpuVoice host sizeof has padding; wire is packed LE fields. 94 bytes of
* classic voice state + 12 bytes of per-voice volume-sweep envelope state
* (L then R: int16 level + uint32 divider each), plus 8 bytes for optional
* immutable sample-bank bindings = 114 bytes. Older section sizes are rejected. */
#define SPU_VOICE_WIRE_BYTES ( \
4u + 4u + 4u + (28u * 2u) + (3u * 2u) + 4u + 4u + 2u + 2u + 1u + 2u + 4u + 1u \
+ 2u * (2u + 4u) + 8u)
static int spu_w_voice(PstW *w, int idx) {
const SpuVoice *v = &voices[idx];
if (!pst_w_i32(w, (int32_t)v->active) || !pst_w_u32(w, v->cur_addr) ||
!pst_w_u32(w, v->repeat_addr) || !pst_w_u32(w,v->sample_bank) ||
!pst_w_u32(w,v->pending_bank))
return 0;
for (int i = 0; i < SPU_BLOCK_SAMPLES; i++)
if (!pst_w_i16(w, v->samples[i])) return 0;
for (int i = 0; i < 3; i++)
if (!pst_w_i16(w, v->previous_samples[i])) return 0;
if (!(pst_w_i32(w, (int32_t)v->sample_idx) && pst_w_u32(w, v->phase) &&
pst_w_i16(w, v->hist1) && pst_w_i16(w, v->hist2) && pst_w_u8(w, v->flags) &&
pst_w_u16(w, v->env_level) && pst_w_u32(w, v->adsr_divider) &&
pst_w_u8(w, v->adsr_phase)))
return 0;
for (int ch = 0; ch < 2; ch++)
if (!pst_w_i16(w, sweep_voice_env[idx][ch].level) ||
!pst_w_u32(w, sweep_voice_env[idx][ch].divider))
return 0;
return 1;
}
static int spu_r_voice(PstR *r, int idx) {
SpuVoice *v = &voices[idx];
int32_t active = 0, sample_idx = 0;
if (!pst_r_i32(r, &active) || !pst_r_u32(r, &v->cur_addr) ||
!pst_r_u32(r, &v->repeat_addr) || !pst_r_u32(r,&v->sample_bank) ||
!pst_r_u32(r,&v->pending_bank))
return 0;
if(v->sample_bank>=SPU_SAMPLE_BANKS || v->pending_bank>=SPU_SAMPLE_BANKS)return 0;
v->active = (int)active;
for (int i = 0; i < SPU_BLOCK_SAMPLES; i++)
if (!pst_r_i16(r, &v->samples[i])) return 0;
for (int i = 0; i < 3; i++)
if (!pst_r_i16(r, &v->previous_samples[i])) return 0;
if (!pst_r_i32(r, &sample_idx) || !pst_r_u32(r, &v->phase) ||
!pst_r_i16(r, &v->hist1) || !pst_r_i16(r, &v->hist2) ||
!pst_r_u8(r, &v->flags) || !pst_r_u16(r, &v->env_level) ||
!pst_r_u32(r, &v->adsr_divider) || !pst_r_u8(r, &v->adsr_phase))
return 0;
v->sample_idx = (int)sample_idx;
for (int ch = 0; ch < 2; ch++)
if (!pst_r_i16(r, &sweep_voice_env[idx][ch].level) ||
!pst_r_u32(r, &sweep_voice_env[idx][ch].divider))
return 0;
return 1;
}
/* Global trailer: 20 classic bytes + 44 bytes of DSP fidelity state
* (IRQ latch, noise LFSR + divider, reverb address/phase/boundary state,
* capture position, main L/R sweep envelopes). */
#define SPU_SNAPSHOT_TAIL_BYTES \
(20u + 1u + 2u + 4u + 4u + 1u + 4u + 4u + 4u + 4u + 4u + 2u * (2u + 4u))
#define SPU_SNAPSHOT_BASE_BYTES ((SPU_REG_COUNT*2u)+(SPU_VOICE_COUNT*SPU_VOICE_WIRE_BYTES)+SPU_SNAPSHOT_TAIL_BYTES)
#define SPU_PRIVATE_WIRE_BYTES (SPU_PRIVATE_VOICES*(16u+1u+SPU_VOICE_WIRE_BYTES))
/* + 4: guest-clock phase (cycles since the SPU's last whole sample). States
* written before the guest-clocked SPU lack it and still load (phase 0). */
static uint32_t spu_snapshot_bytes_v1(void) {
return SPU_SNAPSHOT_BASE_BYTES + (private_enabled ? SPU_PRIVATE_WIRE_BYTES : 0u);
}
uint32_t spu_snapshot_bytes(void) { return spu_snapshot_bytes_v1() + 4u; }
int spu_snapshot_len_ok(uint32_t len) {
return len == SPU_SNAPSHOT_BASE_BYTES || len == SPU_SNAPSHOT_BASE_BYTES + 4u ||
len == SPU_SNAPSHOT_BASE_BYTES + SPU_PRIVATE_WIRE_BYTES ||
len == SPU_SNAPSHOT_BASE_BYTES + SPU_PRIVATE_WIRE_BYTES + 4u;
}
void spu_snapshot_write(uint8_t *p) {
spu_catch_up();
PstW w;
uint32_t n = spu_snapshot_bytes();
pst_w_init(&w, p, n);
for (uint32_t i = 0; i < SPU_REG_COUNT; i++)
pst_w_u16(&w, spu_regs[i]);
for (int i = 0; i < SPU_VOICE_COUNT; i++)
spu_w_voice(&w, i);
pst_w_u32(&w, transfer_addr);
pst_w_u32(&w, key_on_count);
pst_w_u32(&w, endx_latch);
pst_w_u32(&w, kon_latch);
pst_w_u32(&w, koff_latch);
pst_w_u8(&w, irq_flag);
pst_w_u16(&w, noise_lfsr);
pst_w_i32(&w, noise_timer);
pst_w_u32(&w, rev_cur);
pst_w_u8(&w, rev_phase);
pst_w_i32(&w, rev_in_hold_l);
pst_w_i32(&w, rev_in_hold_r);
pst_w_i32(&w, rev_out_l);
pst_w_i32(&w, rev_out_r);
pst_w_u32(&w, capture_pos);
for (int ch = 0; ch < 2; ch++) {
pst_w_i16(&w, sweep_main_env[ch].level);
pst_w_u32(&w, sweep_main_env[ch].divider);
}
if(private_enabled)for(unsigned slot=0;slot<SPU_PRIVATE_VOICES;++slot) {
for(unsigned r=0;r<8;++r)pst_w_u16(&w,private_regs[slot][r]);
pst_w_u8(&w,private_modes[slot]);spu_w_voice(&w,SPU_VOICE_COUNT+(int)slot);
}
pst_w_u32(&w, (uint32_t)(psx_get_cycle_count() - s_clock));
}
int spu_snapshot_read(const uint8_t *p, uint32_t len) {
PstR r;
if (!spu_snapshot_len_ok(len)) return 0;
private_enabled = len >= SPU_SNAPSHOT_BASE_BYTES + SPU_PRIVATE_WIRE_BYTES;
if(!private_enabled) {
memset(voices+SPU_VOICE_COUNT,0,sizeof(SpuVoice)*SPU_PRIVATE_VOICES);
memset(private_regs,0,sizeof private_regs);memset(private_modes,0,sizeof private_modes);
memset(sweep_voice_env+SPU_VOICE_COUNT,0,sizeof(SweepEnv)*2u*SPU_PRIVATE_VOICES);
}
pst_r_init(&r, p, len);
for (uint32_t i = 0; i < SPU_REG_COUNT; i++)
if (!pst_r_u16(&r, &spu_regs[i])) return 0;
for (int i = 0; i < SPU_VOICE_COUNT; i++)
if (!spu_r_voice(&r, i)) return 0;
if (!pst_r_u32(&r, &transfer_addr) || !pst_r_u32(&r, &key_on_count) ||
!pst_r_u32(&r, &endx_latch) || !pst_r_u32(&r, &kon_latch) ||
!pst_r_u32(&r, &koff_latch))
return 0;
if (!pst_r_u8(&r, &irq_flag) || !pst_r_u16(&r, &noise_lfsr) ||
!pst_r_i32(&r, &noise_timer) || !pst_r_u32(&r, &rev_cur) ||
!pst_r_u8(&r, &rev_phase) ||
!pst_r_i32(&r, &rev_in_hold_l) || !pst_r_i32(&r, &rev_in_hold_r) ||
!pst_r_i32(&r, &rev_out_l) || !pst_r_i32(&r, &rev_out_r) ||
!pst_r_u32(&r, &capture_pos))
return 0;
for (int ch = 0; ch < 2; ch++) {
if (!pst_r_i16(&r, &sweep_main_env[ch].level) ||
!pst_r_u32(&r, &sweep_main_env[ch].divider))
return 0;
}
if(private_enabled)for(unsigned slot=0;slot<SPU_PRIVATE_VOICES;++slot) {
for(unsigned reg=0;reg<8;++reg)if(!pst_r_u16(&r,&private_regs[slot][reg]))return 0;
if(!pst_r_u8(&r,&private_modes[slot]) || private_modes[slot]>3u ||
!spu_r_voice(&r,SPU_VOICE_COUNT+(int)slot))return 0;
}
uint32_t phase = 0;
if (len == spu_snapshot_bytes() && !pst_r_u32(&r, &phase)) return 0;
/* Anchor the SPU's sample phase to the restored clock NOW. boot_state
* writes CLOCK before SPU, so the clock is already restored here. It used
* to be applied lazily at the next catch-up -- after the guest had run on,
* so the phase was taken against a later clock and lost: a rollback
* replay rendered SPU samples at other guest cycles than the live run
* (voice state / SPU RAM split with identical CPU paths,
* PSX_RB_DET_SELFTEST). */
{
const uint64_t now = psx_get_cycle_count();
const uint32_t carry = phase % SPU_CYCLES_PER_SAMPLE;
s_clock = now >= carry ? now - carry : now;
s_clock_carry = 0;
s_clock_valid = 1;
}
spu_output_flush();
return 1;
}
uint8_t* spu_get_ram_ptr(void){ return spu_ram; }
uint32_t spu_get_ram_bytes(void){ return (uint32_t)sizeof(spu_ram); }
void spu_snapshot_part_digests(SpuSnapPartDigests *out)
{
static uint8_t *buf;
static uint32_t cap;
uint32_t n;
uint32_t regs_n;
uint32_t voices_n;
uint32_t crc;
if (!out)
return;
memset(out, 0, sizeof(*out));
n = spu_snapshot_bytes();
if (!n)
return;
if (n > cap) {
uint8_t *nb = (uint8_t *)realloc(buf, n);
if (!nb)
return;
buf = nb;
cap = n;
}
spu_snapshot_write(buf);
regs_n = (uint32_t)(SPU_REG_COUNT * 2u);
voices_n = (uint32_t)(SPU_VOICE_COUNT * SPU_VOICE_WIRE_BYTES);
if (regs_n + voices_n + SPU_SNAPSHOT_TAIL_BYTES > n)
return;
crc = 0xFFFFFFFFu;
crc = crc32_update(crc, buf, regs_n);
out->regs = crc ^ 0xFFFFFFFFu;
crc = 0xFFFFFFFFu;
crc = crc32_update(crc, buf + regs_n, voices_n);
out->voices = crc ^ 0xFFFFFFFFu;
crc = 0xFFFFFFFFu;
crc = crc32_update(crc, buf + regs_n + voices_n, n-regs_n-voices_n);
out->tail = crc ^ 0xFFFFFFFFu;
}