mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
PBP: the frontend's IsDiscPath never accepted .pbp (kind=none, 'Invalid launch request.'), pbp_read_sector returned 0-on-success against the TS_* convention, and the reader indexed image blocks with the core's 150-based disc LBA. Tracks now live in absolute LBA space like cue/chd, reads return real TS types, the subchannel polarity matches chd, and zipped PBPs rank as launch candidates. Crash chains from the Samsung tombstone: device create()s are calloc'd so a failed psx_init no longer makes psx_destroy free garbage; psx_disc_create and the raw-disc malloc are NULL-checked; queue_destroy tolerates NULL; a shutdown requested between runs is dropped at run() entry instead of killing the next session on frame 1; its SDL_QUIT push is gated on a live loop (the event-queue mutex may be destroyed between sessions) and a stale queued quit is flushed before the loop starts. Also: disc-probe gate links again (psx/perf.c was missing from its sources).
1740 lines
70 KiB
C
1740 lines
70 KiB
C
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "spu.h"
|
|
#include "../log.h"
|
|
#include "../perf.h"
|
|
|
|
#define CLAMP(v, l, h) (((v) <= (l)) ? (l) : (((v) >= (h)) ? (h) : (v)))
|
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
#define SPU_RAM_ADDR_MASK (SPU_RAM_SIZE - 1)
|
|
#define SPU_RAM_WORD_ADDR_MASK (SPU_RAM_ADDR_MASK & ~1u)
|
|
|
|
#define VOICE_COUNT 24
|
|
|
|
/* One SPU RAM halfword written through the transfer FIFO, plus the `audio_diag` accounting
|
|
that says whether the CD-data-to-SPU-RAM path is delivering anything at all and where it
|
|
lands. Advancing taddr is part of the write, so it cannot be forgotten at a call site. */
|
|
static inline void spu_ram_diag_write(psx_spu_t* spu, uint32_t addr, uint16_t value);
|
|
|
|
static inline uint32_t spu_wrap_addr(uint32_t addr) {
|
|
return addr & SPU_RAM_ADDR_MASK;
|
|
}
|
|
|
|
static inline uint32_t spu_wrap_word_addr(uint32_t addr) {
|
|
return addr & SPU_RAM_WORD_ADDR_MASK;
|
|
}
|
|
|
|
static inline uint8_t spu_ram_read8(const psx_spu_t* spu, uint32_t addr) {
|
|
return spu->ram[spu_wrap_addr(addr)];
|
|
}
|
|
|
|
static inline uint16_t spu_ram_read16(const psx_spu_t* spu, uint32_t addr) {
|
|
const uint32_t wrapped = spu_wrap_word_addr(addr);
|
|
const uint32_t next = spu_wrap_addr(wrapped + 1);
|
|
return (uint16_t)(spu->ram[wrapped] | (((uint16_t)spu->ram[next]) << 8));
|
|
}
|
|
|
|
static inline void spu_ram_write16(psx_spu_t* spu, uint32_t addr, uint16_t value) {
|
|
const uint32_t wrapped = spu_wrap_word_addr(addr);
|
|
const uint32_t next = spu_wrap_addr(wrapped + 1);
|
|
spu->ram[wrapped] = value & 0xff;
|
|
spu->ram[next] = value >> 8;
|
|
}
|
|
|
|
static inline uint16_t spu_reg_read16(const uint8_t* ptr, uint32_t offset) {
|
|
uint16_t value;
|
|
memcpy(&value, ptr + offset, sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
static inline uint32_t spu_reg_read32(const uint8_t* ptr, uint32_t offset) {
|
|
uint32_t value;
|
|
memcpy(&value, ptr + offset, sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
static inline void spu_reg_write16(uint8_t* ptr, uint32_t offset, uint16_t value) {
|
|
memcpy(ptr + offset, &value, sizeof(value));
|
|
}
|
|
|
|
static inline void spu_reg_write32(uint8_t* ptr, uint32_t offset, uint32_t value) {
|
|
memcpy(ptr + offset, &value, sizeof(value));
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------
|
|
ACCESS WIDTH AND THE BOUND OF THE REGISTER FILE.
|
|
|
|
Every register access — 8, 16 or 32 bit — resolves against one flat mirror: the
|
|
psx_spu_t fields from voice[0].volumel through vrin, laid out packed so a field's byte
|
|
offset in the struct IS its offset from 1F801C00h.
|
|
|
|
WIDTH. The SPU register file is 16 bits wide. Hardware serves a byte access out of the
|
|
containing halfword — low byte at an even offset, high byte at an odd one — and there is
|
|
nothing else it could do, because there is no byte-addressable storage behind it. This
|
|
file used to implement only 16- and 32-bit access; psx_spu_read8() logged and returned 0
|
|
and psx_spu_write8() logged and dropped the write. Xenogears reads voice ADSR registers
|
|
a byte at a time (offsets 58h, 98h, 118h, 138h = 1F801C08h+N*10h for voices 5, 9, 17 and
|
|
19), so it was polling envelope configuration and being told zero, several times a
|
|
second, forever.
|
|
|
|
BOUND. The mirror is 200h bytes: 1F801C00h..1F801DFFh. The window psx_bus dispatches
|
|
into is PSX_SPU_SIZE = 400h, twice that. 1F801E00h upwards — the internal current-volume
|
|
block and the unused space above it — has NO backing field, and an access there used to
|
|
index the mirror anyway. Past the mirror's end that is not the register file, it is the
|
|
next members of psx_spu_t: taddr, the transfer FIFO, and the 24 voice decoder/ADSR
|
|
contexts. A 32-bit write to 1F801E00h landed squarely on the sound-RAM transfer address.
|
|
|
|
The bound is derived from the struct instead of written as a literal so it follows the
|
|
layout: add a register field and it moves with it.
|
|
--------------------------------------------------------------------------------------- */
|
|
|
|
#define SPU_REG_MIRROR_SIZE \
|
|
((uint32_t)(offsetof(psx_spu_t, vrin) + sizeof(int16_t) - offsetof(psx_spu_t, voice)))
|
|
|
|
static inline int spu_reg_in_mirror(uint32_t offset, uint32_t width) {
|
|
return (offset < SPU_REG_MIRROR_SIZE) && ((SPU_REG_MIRROR_SIZE - offset) >= width);
|
|
}
|
|
|
|
/*
|
|
Unimplemented-access logging, once per offset per direction.
|
|
|
|
The 8-bit read path was log_fatal(), on a poll that repeats several times a second: 1.1 MB
|
|
of log in a few minutes, at the highest severity there is, for something that never stopped
|
|
the emulator. Nothing survives that — a genuine error scrolls past unread. An access with no
|
|
register behind it is a warn, and a warn that repeats thousands of times a second is not
|
|
readable either, so each offset says its piece once.
|
|
|
|
File-scope on purpose: this is host-side log bookkeeping, not guest state, and must stay out
|
|
of the save-state payload the same way reverb_disabled and gen_ring do.
|
|
*/
|
|
static uint8_t g_spu_unimpl_read_seen[PSX_SPU_SIZE / 8];
|
|
static uint8_t g_spu_unimpl_write_seen[PSX_SPU_SIZE / 8];
|
|
|
|
static int spu_warn_once(uint8_t* seen, uint32_t offset) {
|
|
const uint32_t masked = offset & (PSX_SPU_SIZE - 1u);
|
|
const uint8_t bit = (uint8_t)(1u << (masked & 7u));
|
|
|
|
if (seen[masked >> 3] & bit)
|
|
return 0;
|
|
|
|
seen[masked >> 3] |= bit;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* The halfword the mirror holds, or 0 when the offset has no register behind it. No side
|
|
effects — in particular it never touches the transfer FIFO, so it is safe to call on the
|
|
read-modify-write path of an 8-bit write. */
|
|
static inline uint16_t spu_reg_mirror16(const psx_spu_t* spu, uint32_t offset) {
|
|
if (!spu_reg_in_mirror(offset, 2))
|
|
return 0;
|
|
|
|
return spu_reg_read16((const uint8_t*)&spu->voice[0].volumel, offset);
|
|
}
|
|
|
|
static inline uint16_t spu_reg_peek16(const psx_spu_t* spu, uint32_t offset) {
|
|
if (!spu_reg_in_mirror(offset, 2) && spu_warn_once(g_spu_unimpl_read_seen, offset)) {
|
|
log_warn("SPU read at offset %08x has no register behind it, returning 0"
|
|
" (logged once per offset)", offset);
|
|
}
|
|
|
|
return spu_reg_mirror16(spu, offset);
|
|
}
|
|
|
|
// static float interpolate_hermite(float a, float b, float c, float d, float t) {
|
|
// float x = -a/2.0f + (3.0f*b)/2.0f - (3.0f*c)/2.0f + d/2.0f;
|
|
// float y = a - (5.0f*b)/2.0f + 2.0f*c - d / 2.0f;
|
|
// float z = -a/2.0f + c/2.0f;
|
|
// float w = b;
|
|
|
|
// return (x*t*t*t) + (y*t*t) + (z*t) + w;
|
|
// }
|
|
|
|
static const int g_spu_pos_adpcm_table[] = {
|
|
0, +60, +115, +98, +122
|
|
};
|
|
|
|
static const int g_spu_neg_adpcm_table[] = {
|
|
0, 0, -52, -55, -60
|
|
};
|
|
|
|
static const int16_t g_spu_gauss_table[] = {
|
|
-0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001,
|
|
-0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001,
|
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001,
|
|
0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003,
|
|
0x0003, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, 0x0007,
|
|
0x0008, 0x0009, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
|
|
0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0015, 0x0016, 0x0018,
|
|
0x0019, 0x001B, 0x001C, 0x001E, 0x0020, 0x0021, 0x0023, 0x0025,
|
|
0x0027, 0x0029, 0x002C, 0x002E, 0x0030, 0x0033, 0x0035, 0x0038,
|
|
0x003A, 0x003D, 0x0040, 0x0043, 0x0046, 0x0049, 0x004D, 0x0050,
|
|
0x0054, 0x0057, 0x005B, 0x005F, 0x0063, 0x0067, 0x006B, 0x006F,
|
|
0x0074, 0x0078, 0x007D, 0x0082, 0x0087, 0x008C, 0x0091, 0x0096,
|
|
0x009C, 0x00A1, 0x00A7, 0x00AD, 0x00B3, 0x00BA, 0x00C0, 0x00C7,
|
|
0x00CD, 0x00D4, 0x00DB, 0x00E3, 0x00EA, 0x00F2, 0x00FA, 0x0101,
|
|
0x010A, 0x0112, 0x011B, 0x0123, 0x012C, 0x0135, 0x013F, 0x0148,
|
|
0x0152, 0x015C, 0x0166, 0x0171, 0x017B, 0x0186, 0x0191, 0x019C,
|
|
0x01A8, 0x01B4, 0x01C0, 0x01CC, 0x01D9, 0x01E5, 0x01F2, 0x0200,
|
|
0x020D, 0x021B, 0x0229, 0x0237, 0x0246, 0x0255, 0x0264, 0x0273,
|
|
0x0283, 0x0293, 0x02A3, 0x02B4, 0x02C4, 0x02D6, 0x02E7, 0x02F9,
|
|
0x030B, 0x031D, 0x0330, 0x0343, 0x0356, 0x036A, 0x037E, 0x0392,
|
|
0x03A7, 0x03BC, 0x03D1, 0x03E7, 0x03FC, 0x0413, 0x042A, 0x0441,
|
|
0x0458, 0x0470, 0x0488, 0x04A0, 0x04B9, 0x04D2, 0x04EC, 0x0506,
|
|
0x0520, 0x053B, 0x0556, 0x0572, 0x058E, 0x05AA, 0x05C7, 0x05E4,
|
|
0x0601, 0x061F, 0x063E, 0x065C, 0x067C, 0x069B, 0x06BB, 0x06DC,
|
|
0x06FD, 0x071E, 0x0740, 0x0762, 0x0784, 0x07A7, 0x07CB, 0x07EF,
|
|
0x0813, 0x0838, 0x085D, 0x0883, 0x08A9, 0x08D0, 0x08F7, 0x091E,
|
|
0x0946, 0x096F, 0x0998, 0x09C1, 0x09EB, 0x0A16, 0x0A40, 0x0A6C,
|
|
0x0A98, 0x0AC4, 0x0AF1, 0x0B1E, 0x0B4C, 0x0B7A, 0x0BA9, 0x0BD8,
|
|
0x0C07, 0x0C38, 0x0C68, 0x0C99, 0x0CCB, 0x0CFD, 0x0D30, 0x0D63,
|
|
0x0D97, 0x0DCB, 0x0E00, 0x0E35, 0x0E6B, 0x0EA1, 0x0ED7, 0x0F0F,
|
|
0x0F46, 0x0F7F, 0x0FB7, 0x0FF1, 0x102A, 0x1065, 0x109F, 0x10DB,
|
|
0x1116, 0x1153, 0x118F, 0x11CD, 0x120B, 0x1249, 0x1288, 0x12C7,
|
|
0x1307, 0x1347, 0x1388, 0x13C9, 0x140B, 0x144D, 0x1490, 0x14D4,
|
|
0x1517, 0x155C, 0x15A0, 0x15E6, 0x162C, 0x1672, 0x16B9, 0x1700,
|
|
0x1747, 0x1790, 0x17D8, 0x1821, 0x186B, 0x18B5, 0x1900, 0x194B,
|
|
0x1996, 0x19E2, 0x1A2E, 0x1A7B, 0x1AC8, 0x1B16, 0x1B64, 0x1BB3,
|
|
0x1C02, 0x1C51, 0x1CA1, 0x1CF1, 0x1D42, 0x1D93, 0x1DE5, 0x1E37,
|
|
0x1E89, 0x1EDC, 0x1F2F, 0x1F82, 0x1FD6, 0x202A, 0x207F, 0x20D4,
|
|
0x2129, 0x217F, 0x21D5, 0x222C, 0x2282, 0x22DA, 0x2331, 0x2389,
|
|
0x23E1, 0x2439, 0x2492, 0x24EB, 0x2545, 0x259E, 0x25F8, 0x2653,
|
|
0x26AD, 0x2708, 0x2763, 0x27BE, 0x281A, 0x2876, 0x28D2, 0x292E,
|
|
0x298B, 0x29E7, 0x2A44, 0x2AA1, 0x2AFF, 0x2B5C, 0x2BBA, 0x2C18,
|
|
0x2C76, 0x2CD4, 0x2D33, 0x2D91, 0x2DF0, 0x2E4F, 0x2EAE, 0x2F0D,
|
|
0x2F6C, 0x2FCC, 0x302B, 0x308B, 0x30EA, 0x314A, 0x31AA, 0x3209,
|
|
0x3269, 0x32C9, 0x3329, 0x3389, 0x33E9, 0x3449, 0x34A9, 0x3509,
|
|
0x3569, 0x35C9, 0x3629, 0x3689, 0x36E8, 0x3748, 0x37A8, 0x3807,
|
|
0x3867, 0x38C6, 0x3926, 0x3985, 0x39E4, 0x3A43, 0x3AA2, 0x3B00,
|
|
0x3B5F, 0x3BBD, 0x3C1B, 0x3C79, 0x3CD7, 0x3D35, 0x3D92, 0x3DEF,
|
|
0x3E4C, 0x3EA9, 0x3F05, 0x3F62, 0x3FBD, 0x4019, 0x4074, 0x40D0,
|
|
0x412A, 0x4185, 0x41DF, 0x4239, 0x4292, 0x42EB, 0x4344, 0x439C,
|
|
0x43F4, 0x444C, 0x44A3, 0x44FA, 0x4550, 0x45A6, 0x45FC, 0x4651,
|
|
0x46A6, 0x46FA, 0x474E, 0x47A1, 0x47F4, 0x4846, 0x4898, 0x48E9,
|
|
0x493A, 0x498A, 0x49D9, 0x4A29, 0x4A77, 0x4AC5, 0x4B13, 0x4B5F,
|
|
0x4BAC, 0x4BF7, 0x4C42, 0x4C8D, 0x4CD7, 0x4D20, 0x4D68, 0x4DB0,
|
|
0x4DF7, 0x4E3E, 0x4E84, 0x4EC9, 0x4F0E, 0x4F52, 0x4F95, 0x4FD7,
|
|
0x5019, 0x505A, 0x509A, 0x50DA, 0x5118, 0x5156, 0x5194, 0x51D0,
|
|
0x520C, 0x5247, 0x5281, 0x52BA, 0x52F3, 0x532A, 0x5361, 0x5397,
|
|
0x53CC, 0x5401, 0x5434, 0x5467, 0x5499, 0x54CA, 0x54FA, 0x5529,
|
|
0x5558, 0x5585, 0x55B2, 0x55DE, 0x5609, 0x5632, 0x565B, 0x5684,
|
|
0x56AB, 0x56D1, 0x56F6, 0x571B, 0x573E, 0x5761, 0x5782, 0x57A3,
|
|
0x57C3, 0x57E2, 0x57FF, 0x581C, 0x5838, 0x5853, 0x586D, 0x5886,
|
|
0x589E, 0x58B5, 0x58CB, 0x58E0, 0x58F4, 0x5907, 0x5919, 0x592A,
|
|
0x593A, 0x5949, 0x5958, 0x5965, 0x5971, 0x597C, 0x5986, 0x598F,
|
|
0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3
|
|
};
|
|
|
|
psx_spu_t* psx_spu_create(void) {
|
|
return (psx_spu_t*)calloc(1, sizeof(psx_spu_t));
|
|
}
|
|
|
|
void psx_spu_init(psx_spu_t* spu, psx_ic_t* ic) {
|
|
memset(spu, 0, sizeof(psx_spu_t));
|
|
|
|
spu->io_base = PSX_SPU_BEGIN;
|
|
spu->io_size = PSX_SPU_SIZE;
|
|
|
|
spu->ic = ic;
|
|
spu->ram = (uint8_t*)malloc(SPU_RAM_SIZE);
|
|
|
|
memset(spu->ram, 0, SPU_RAM_SIZE);
|
|
|
|
// Mute all voices
|
|
spu->endx = 0x00ffffff;
|
|
spu->irq9addr = 0xffff;
|
|
}
|
|
|
|
static inline void spu_ram_diag_write(psx_spu_t* spu, uint32_t addr, uint16_t value) {
|
|
spu_ram_write16(spu, addr, value);
|
|
|
|
if (g_psx_audio_diag_enabled) {
|
|
g_psx_audio_diag.spu_ram_writes++;
|
|
|
|
if (addr < g_psx_audio_diag.spu_ram_lo)
|
|
g_psx_audio_diag.spu_ram_lo = addr;
|
|
|
|
if (addr > g_psx_audio_diag.spu_ram_hi)
|
|
g_psx_audio_diag.spu_ram_hi = addr;
|
|
}
|
|
|
|
spu->taddr = spu_wrap_word_addr(spu->taddr + 2);
|
|
}
|
|
|
|
void psx_spu_set_reverb_disabled(psx_spu_t* spu, int disabled) {
|
|
if (!spu)
|
|
return;
|
|
|
|
/* The reverb work buffer keeps feeding back off itself; leaving stale echo
|
|
tails in it would bleed back in the moment reverb is re-enabled. */
|
|
if (spu->reverb_disabled != (disabled != 0)) {
|
|
spu->lrsl = 0;
|
|
spu->lrsr = 0;
|
|
}
|
|
|
|
spu->reverb_disabled = disabled != 0;
|
|
}
|
|
|
|
uint32_t psx_spu_read32(psx_spu_t* spu, uint32_t offset) {
|
|
if (!spu_reg_in_mirror(offset, 4)) {
|
|
if (spu_warn_once(g_spu_unimpl_read_seen, offset)) {
|
|
log_warn("SPU read at offset %08x has no register behind it, returning 0"
|
|
" (logged once per offset)", offset);
|
|
}
|
|
|
|
return 0x0;
|
|
}
|
|
|
|
const uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
|
|
|
|
return spu_reg_read32(ptr, offset);
|
|
}
|
|
|
|
uint16_t psx_spu_read16(psx_spu_t* spu, uint32_t offset) {
|
|
if (offset == SPUR_TFIFO) {
|
|
uint16_t data = spu_ram_read16(spu, spu->taddr);
|
|
spu->taddr = spu_wrap_word_addr(spu->taddr + 2);
|
|
|
|
return data;
|
|
}
|
|
|
|
return spu_reg_peek16(spu, offset);
|
|
}
|
|
|
|
/*
|
|
8-bit read: the byte of the containing 16-bit register that this offset selects.
|
|
|
|
Deliberately NOT routed through psx_spu_read16(), because that read has a side effect —
|
|
1F801DA8h pops a halfword of sound RAM and advances the transfer address. Serving a byte
|
|
read out of it would consume a whole halfword per byte, so reading both halves of one
|
|
register would return bytes from two different words and would drag the transfer address
|
|
along with it. nocash lists the FIFO port as write-only; a byte read of it gets the last
|
|
value written (what the mirror holds), and nothing here disturbs a transfer in flight.
|
|
*/
|
|
uint8_t psx_spu_read8(psx_spu_t* spu, uint32_t offset) {
|
|
const uint16_t reg = spu_reg_peek16(spu, offset & ~1u);
|
|
|
|
return (offset & 1u) ? (uint8_t)(reg >> 8) : (uint8_t)(reg & 0xffu);
|
|
}
|
|
|
|
/*
|
|
Sound RAM IRQ (1F801DA4h, enabled by SPUCNT bit 6).
|
|
|
|
Raised when a voice reaches the watched address. It is checked HERE, in the one function
|
|
every path funnels through — spu_read_block() is called after a linear advance, after both
|
|
loop-end jumps, and at key-on — because it previously lived inline in the block-advance
|
|
switch and only in the `case 0: case 2:` branch. The loop-end cases raised nothing.
|
|
|
|
That is fatal for streamed audio. A game double-buffering a ring watches the wrap point so
|
|
it knows when to refill the half that just played, and the wrap point is reached ONLY by the
|
|
loop-end jump — the exact path that skipped the check. The voice then loops the same ~0.6 s
|
|
of sound RAM forever because nothing ever tells the game to refill it, which is heard as a
|
|
phrase replaying a fragment of itself before continuing.
|
|
|
|
Matched over the whole 16-byte block rather than by exact equality: irq9addr is in 8-byte
|
|
units while a voice's current address only ever advances 16 bytes at a time, so an IRQ
|
|
address on an odd 8-byte boundary could never compare equal and the interrupt was
|
|
unreachable for half of all legal settings.
|
|
*/
|
|
static inline void spu_check_irq(psx_spu_t* spu, uint32_t addr) {
|
|
if (!(spu->spucnt & 0x40))
|
|
return;
|
|
|
|
const uint32_t irq_addr = spu_wrap_addr((uint32_t)spu->irq9addr << 3);
|
|
|
|
if ((irq_addr & ~0xfu) != (spu_wrap_addr(addr) & ~0xfu))
|
|
return;
|
|
|
|
psx_ic_irq(spu->ic, IC_SPU);
|
|
|
|
if (g_psx_audio_diag_enabled)
|
|
g_psx_audio_diag.spu_irq_raised++;
|
|
}
|
|
|
|
void spu_read_block(psx_spu_t* spu, int v) {
|
|
uint32_t addr = spu_wrap_addr(spu->data[v].current_addr);
|
|
uint8_t hdr = spu_ram_read8(spu, addr);
|
|
|
|
spu_check_irq(spu, addr);
|
|
|
|
spu->data[v].block_flags = spu_ram_read8(spu, addr + 1);
|
|
|
|
unsigned hdr_shift = hdr & 0x0f;
|
|
|
|
if (hdr_shift > 12)
|
|
hdr_shift = 9;
|
|
|
|
unsigned shift = 12 - hdr_shift;
|
|
unsigned filter = (hdr >> 4) & 7;
|
|
|
|
/* OUT-OF-BOUNDS READ. The filter field is three bits, so it takes 0..7, but both
|
|
coefficient tables have exactly FIVE entries — filters 5..7 read straight off the end.
|
|
Measured values for the overrun: filter=5 -> (0,-1), 6 -> (0,-1), 7 -> (-52,+65535),
|
|
i.e. whatever .rodata follows, decoded as prediction coefficients.
|
|
|
|
Reserved filters are not supposed to appear in well-formed samples, but "not supposed
|
|
to" is not a bound: a mid-stream key-on, a misaligned block, or a buffer that has not
|
|
been filled yet all put arbitrary bytes in the header byte. Real hardware has defined
|
|
behaviour here and DuckStation encodes it as std::min(filter, 4). Note the XA decoder in
|
|
psx/dev/cdrom/audio.c already masks with 0x30 and cannot overrun — the two ADPCM decoders
|
|
in this tree disagreed, and this was the unbounded one. */
|
|
if (filter > 4)
|
|
filter = 4;
|
|
|
|
int32_t f0 = g_spu_pos_adpcm_table[filter];
|
|
int32_t f1 = g_spu_neg_adpcm_table[filter];
|
|
|
|
for (int j = 0; j < 28; j++) {
|
|
uint16_t n = (spu_ram_read8(spu, addr + 2 + (j >> 1)) >> ((j & 1) * 4)) & 0xf;
|
|
|
|
// Sign extend t
|
|
int16_t t = (int16_t)(n << 12) >> 12;
|
|
|
|
/* The saturation below used to be applied to an int16_t, which made it DEAD CODE: the
|
|
sum was truncated into int16 by the assignment, so `s < INT16_MIN` and `s > INT16_MAX`
|
|
were both unreachable and the compiler removed the test entirely (verified: zero
|
|
compare instructions emitted). Samples that should have clipped wrapped instead —
|
|
+58672 came out as -6864, -62767 as +2769 — turning a loud passage into full-scale
|
|
sign inversion.
|
|
|
|
ADPCM prediction is recursive and h[] carries across blocks, so one wrapped sample
|
|
poisons every sample after it. Accumulating in int32 and clamping on the way down is
|
|
what makes the existing intent actually happen. */
|
|
int32_t acc = (t << shift) +
|
|
(((spu->data[v].h[0] * f0) + (spu->data[v].h[1] * f1) + 32) / 64);
|
|
|
|
int16_t s = (acc < INT16_MIN) ? INT16_MIN
|
|
: ((acc > INT16_MAX) ? INT16_MAX : (int16_t)acc);
|
|
|
|
spu->data[v].h[1] = spu->data[v].h[0];
|
|
spu->data[v].h[0] = s;
|
|
spu->data[v].buf[j] = s;
|
|
}
|
|
}
|
|
|
|
#define PHASE spu->data[v].adsr_phase
|
|
#define CYCLES spu->data[v].adsr_cycles
|
|
#define EXPONENTIAL spu->data[v].adsr_mode
|
|
#define DECREASE spu->data[v].adsr_dir
|
|
#define SHIFT spu->data[v].adsr_shift
|
|
#define STEP spu->data[v].adsr_step
|
|
#define LEVEL_STEP spu->data[v].adsr_pending_step
|
|
#define LEVEL spu->data[v].cvol
|
|
|
|
/*
|
|
____lower 16bit (at 1F801C08h+N*10h)___________________________________
|
|
15 Attack Mode (0=Linear, 1=Exponential)
|
|
- Attack Direction (Fixed, always Increase) (until Level 7FFFh)
|
|
14-10 Attack Shift (0..1Fh = Fast..Slow)
|
|
9-8 Attack Step (0..3 = "+7,+6,+5,+4")
|
|
- Decay Mode (Fixed, always Exponential)
|
|
- Decay Direction (Fixed, always Decrease) (until Sustain Level)
|
|
7-4 Decay Shift (0..0Fh = Fast..Slow)
|
|
- Decay Step (Fixed, always "-8")
|
|
3-0 Sustain Level (0..0Fh) ;Level=(N+1)*800h
|
|
____upper 16bit (at 1F801C0Ah+N*10h)___________________________________
|
|
31 Sustain Mode (0=Linear, 1=Exponential)
|
|
30 Sustain Direction (0=Increase, 1=Decrease) (until Key OFF flag)
|
|
29 Not used? (should be zero)
|
|
28-24 Sustain Shift (0..1Fh = Fast..Slow)
|
|
23-22 Sustain Step (0..3 = "+7,+6,+5,+4" or "-8,-7,-6,-5") (inc/dec)
|
|
21 Release Mode (0=Linear, 1=Exponential)
|
|
- Release Direction (Fixed, always Decrease) (until Level 0000h)
|
|
20-16 Release Shift (0..1Fh = Fast..Slow)
|
|
- Release Step (Fixed, always "-8")
|
|
*/
|
|
|
|
enum {
|
|
ADSR_ATTACK,
|
|
ADSR_DECAY,
|
|
ADSR_SUSTAIN,
|
|
ADSR_RELEASE,
|
|
ADSR_END
|
|
};
|
|
|
|
/*
|
|
ADSR configuration, read LIVE from 1F801C08h+N*10h / 1F801C0Ah+N*10h.
|
|
|
|
This is the THIRD field in this file found snapshotted at key-on, after the voice volume and
|
|
the ADPCM repeat address. spu_adsr_cfg(spu, v) was built once in spu_kon() and every envelope
|
|
phase was then derived from that frozen copy, so an ADSR register written after key-on did
|
|
nothing at all.
|
|
|
|
It matters most on RELEASE, because that is the one phase a game configures immediately
|
|
before it needs it. The standard way to stop a looping sound effect is: write a release rate
|
|
to 1F801C0Ah, then key off at 1F801D8Ch. spu_koff() -> adsr_load_release() was reading the
|
|
release shift captured at key-on instead, so a voice keyed on with a slow release rate could
|
|
take effectively forever to fall silent no matter what the game asked for on the way out —
|
|
a sound effect that outlives the state that started it, and outlives a game over.
|
|
|
|
The sustain level (bits 3-0 of the low half) has the same problem and is derived live too.
|
|
|
|
data[v].envctl and data[v].adsr_sustain_level are still written at key-on and still in the
|
|
save-state payload; keeping them preserves the existing format for fields nothing reads now.
|
|
*/
|
|
static inline uint32_t spu_adsr_cfg(const psx_spu_t* spu, int v) {
|
|
return (((uint32_t)spu->voice[v].envctl2) << 16) | (uint32_t)spu->voice[v].envctl1;
|
|
}
|
|
|
|
static inline int spu_adsr_sustain_level(const psx_spu_t* spu, int v) {
|
|
return (((int)(spu->voice[v].envctl1 & 0xf)) + 1) * 0x800;
|
|
}
|
|
|
|
void adsr_calculate_values(psx_spu_t* spu, int v) {
|
|
CYCLES = 1 << MAX(0, SHIFT - 11);
|
|
LEVEL_STEP = STEP << MAX(0, 11 - SHIFT);
|
|
|
|
if (EXPONENTIAL && (LEVEL > 0x6000) && !DECREASE)
|
|
CYCLES *= 4;
|
|
|
|
if (EXPONENTIAL && DECREASE)
|
|
LEVEL_STEP = (LEVEL_STEP * LEVEL) >> 15;
|
|
|
|
spu->data[v].adsr_cycles_reload = CYCLES;
|
|
}
|
|
|
|
void adsr_load_attack(psx_spu_t* spu, int v) {
|
|
EXPONENTIAL = spu_adsr_cfg(spu, v) >> 15;
|
|
DECREASE = 0;
|
|
SHIFT = (spu_adsr_cfg(spu, v) >> 10) & 0x1f;
|
|
STEP = 7 - ((spu_adsr_cfg(spu, v) >> 8) & 3);
|
|
LEVEL = 0;
|
|
PHASE = ADSR_ATTACK;
|
|
|
|
adsr_calculate_values(spu, v);
|
|
}
|
|
|
|
void adsr_load_decay(psx_spu_t* spu, int v) {
|
|
EXPONENTIAL = 1;
|
|
DECREASE = 1;
|
|
SHIFT = (spu_adsr_cfg(spu, v) >> 4) & 0xf;
|
|
STEP = -8;
|
|
LEVEL = 0x7fff;
|
|
PHASE = ADSR_DECAY;
|
|
|
|
adsr_calculate_values(spu, v);
|
|
}
|
|
|
|
void adsr_load_sustain(psx_spu_t* spu, int v) {
|
|
EXPONENTIAL = spu_adsr_cfg(spu, v) >> 31;
|
|
DECREASE = (spu_adsr_cfg(spu, v) >> 30) & 1;
|
|
SHIFT = (spu_adsr_cfg(spu, v) >> 24) & 0x1f;
|
|
STEP = (spu_adsr_cfg(spu, v) >> 22) & 3;
|
|
LEVEL = spu_adsr_sustain_level(spu, v);
|
|
STEP = DECREASE ? (-8 + STEP) : (7 - STEP);
|
|
PHASE = ADSR_SUSTAIN;
|
|
|
|
adsr_calculate_values(spu, v);
|
|
}
|
|
|
|
void adsr_load_release(psx_spu_t* spu, int v) {
|
|
EXPONENTIAL = (spu_adsr_cfg(spu, v) >> 21) & 1;
|
|
DECREASE = 1;
|
|
SHIFT = (spu_adsr_cfg(spu, v) >> 16) & 0x1f;
|
|
STEP = -8;
|
|
PHASE = ADSR_RELEASE;
|
|
|
|
spu->endx |= 1 << v;
|
|
|
|
adsr_calculate_values(spu, v);
|
|
}
|
|
|
|
void spu_handle_adsr(psx_spu_t* spu, int v) {
|
|
if (CYCLES) {
|
|
CYCLES -= 1;
|
|
|
|
return;
|
|
}
|
|
|
|
adsr_calculate_values(spu, v);
|
|
|
|
LEVEL += LEVEL_STEP;
|
|
|
|
switch (spu->data[v].adsr_phase) {
|
|
case ADSR_ATTACK: {
|
|
LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
|
|
|
|
if (LEVEL == 0x7fff)
|
|
adsr_load_decay(spu, v);
|
|
} break;
|
|
|
|
case ADSR_DECAY: {
|
|
LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
|
|
|
|
if (LEVEL <= spu_adsr_sustain_level(spu, v))
|
|
adsr_load_sustain(spu, v);
|
|
} break;
|
|
|
|
case ADSR_SUSTAIN: {
|
|
LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
|
|
|
|
/* Not stopped automatically, need to KOFF */
|
|
} break;
|
|
|
|
case ADSR_RELEASE: {
|
|
LEVEL = CLAMP(LEVEL, 0x0000, 0x7fff);
|
|
|
|
if (!LEVEL) {
|
|
PHASE = ADSR_END;
|
|
CYCLES = 0;
|
|
LEVEL_STEP = 0;
|
|
|
|
spu->data[v].playing = 0;
|
|
}
|
|
} break;
|
|
|
|
case ADSR_END: {
|
|
spu->data[v].playing = 0;
|
|
} break;
|
|
}
|
|
|
|
spu->voice[v].envcvol = spu->data[v].cvol;
|
|
|
|
CYCLES = spu->data[v].adsr_cycles_reload;
|
|
}
|
|
|
|
#undef PHASE
|
|
#undef CYCLES
|
|
#undef MODE
|
|
#undef DIR
|
|
#undef SHIFT
|
|
#undef STEP
|
|
#undef PENDING_STEP
|
|
|
|
void spu_kon(psx_spu_t* spu, uint32_t value) {
|
|
for (int i = 0; i < VOICE_COUNT; i++) {
|
|
if ((value & (1 << i))) {
|
|
spu->data[i].playing = 1;
|
|
spu->data[i].current_addr = spu_wrap_addr((uint32_t)spu->voice[i].adsaddr << 3);
|
|
spu->data[i].repeat_addr = spu_wrap_addr((uint32_t)spu->voice[i].adraddr << 3);
|
|
/* A fresh voice takes its repeat address from the sample data again. */
|
|
spu->data[i].ignore_loop_addr = 0;
|
|
/* `audio_diag`. 1F801C00h+N*10h is NOT a plain unsigned magnitude: bit15 selects
|
|
sweep mode, and in volume mode bits14-0 are a SIGNED 15-bit value (-4000h..+3FFFh).
|
|
The conversion below reads all 16 bits as unsigned, so this counts how often the
|
|
game actually uses the two encodings that reading cannot represent. Both are gain
|
|
errors of up to 4x in the wrong direction, and gunfire is where a game reaches for
|
|
them. Key-on only: ~tens of events a second, not a hot path. */
|
|
if (g_psx_audio_diag_enabled) {
|
|
g_psx_audio_diag.kon_voices++;
|
|
|
|
if ((spu->voice[i].volumel & 0x8000) || (spu->voice[i].volumer & 0x8000))
|
|
g_psx_audio_diag.kon_vol_sweep++;
|
|
else if ((spu->voice[i].volumel & 0x4000) || (spu->voice[i].volumer & 0x4000))
|
|
g_psx_audio_diag.kon_vol_negative++;
|
|
}
|
|
|
|
spu->data[i].lvol = ((float)(spu->voice[i].volumel) / 32767.0f) * 2.0f;
|
|
spu->data[i].rvol = ((float)(spu->voice[i].volumer) / 32767.0f) * 2.0f;
|
|
spu->data[i].adsr_sustain_level = ((spu->voice[i].envctl1 & 0xf) + 1) * 0x800;
|
|
spu->data[i].envctl = (((uint32_t)spu->voice[i].envctl2) << 16) |
|
|
(uint32_t)spu->voice[i].envctl1;
|
|
|
|
adsr_load_attack(spu, i);
|
|
spu_read_block(spu, i);
|
|
|
|
spu->voice[i].envcvol = 0x7fff;
|
|
}
|
|
}
|
|
|
|
spu->endx &= ~(value & 0x00ffffff);
|
|
}
|
|
|
|
void spu_koff(psx_spu_t* spu, uint32_t value) {
|
|
for (int i = 0; i < VOICE_COUNT; i++)
|
|
if (value & (1 << i))
|
|
adsr_load_release(spu, i);
|
|
}
|
|
|
|
int spu_handle_write(psx_spu_t* spu, uint32_t offset, uint32_t value) {
|
|
switch (offset) {
|
|
case SPUR_KONL: case SPUR_KONH: {
|
|
int high = (offset & 2) != 0;
|
|
|
|
if (!value)
|
|
return 1;
|
|
|
|
spu_kon(spu, value << (16 * high));
|
|
} return 1;
|
|
|
|
// case SPUR_SPUIRQA: {
|
|
// spu->irq9addr = value << 3;
|
|
// } return 1;
|
|
|
|
case SPUR_KOFFL: case SPUR_KOFFH: {
|
|
int high = (offset & 2) != 0;
|
|
|
|
if (!value)
|
|
return 1;
|
|
|
|
spu_koff(spu, value << (16 * high));
|
|
} return 1;
|
|
|
|
case SPUR_TADDR: {
|
|
spu->ramdta = value;
|
|
spu->taddr = spu_wrap_word_addr((uint32_t)value << 3);
|
|
} return 1;
|
|
|
|
case SPUR_TFIFO: {
|
|
spu->ramdtf = value;
|
|
|
|
/*
|
|
OUT-OF-BOUNDS WRITE, fixed by the bound below.
|
|
|
|
This was `spu->tfifo[spu->tfifo_index++] = value;` with no check against the
|
|
32-entry array, and the index is only ever reset inside the `== 32` branch —
|
|
which is itself gated on the transfer mode being DMA-write. Any run of more
|
|
than 32 FIFO writes in any other mode therefore walked straight off the end.
|
|
|
|
It is far worse than a normal overrun because psx_spu_t is PACKED and
|
|
tfifo_index is the field immediately after the array: tfifo[32] IS
|
|
tfifo_index. Measured layout — tfifo at 544, tfifo_index at 608, data[0] at
|
|
626, sizeof(psx_spu_t) 4374. So the 33rd write sets tfifo_index to the ADPCM
|
|
sample word itself, the ++ makes it value+1, and the NEXT write lands at
|
|
tfifo[value+1] — an arbitrary offset up to 128 KB past a 4374-byte struct.
|
|
Voice decoder state (data[24], from offset 626, i.e. tfifo[41] onward) is the
|
|
first casualty, and everything past the allocation is heap corruption.
|
|
|
|
Dropping the write when the FIFO is full is what a full hardware FIFO does,
|
|
and in the working case (DMA-write mode) behaviour is bit-identical: the FIFO
|
|
still fills to exactly 32, flushes, and resets.
|
|
|
|
Memory-safety fix. NOT claimed to be the cause of any reported symptom until
|
|
spu_fifo_drop / spu_ram_writes come back from a capture and say so.
|
|
*/
|
|
if (spu->tfifo_index < 32) {
|
|
spu->tfifo[spu->tfifo_index++] = value;
|
|
} else if (g_psx_audio_diag_enabled) {
|
|
g_psx_audio_diag.spu_fifo_drop++;
|
|
}
|
|
|
|
if (spu->tfifo_index >= 32) {
|
|
if (((spu->spucnt >> 4) & 3) == 2) {
|
|
for (int i = 0; i < 32; i++) {
|
|
spu_ram_diag_write(spu, spu->taddr, spu->tfifo[i]);
|
|
}
|
|
|
|
spu->tfifo_index = 0;
|
|
}
|
|
}
|
|
} return 1;
|
|
|
|
case SPUR_SPUCNT: {
|
|
spu->spucnt = value;
|
|
spu->spustat &= 0xffc0;
|
|
spu->spustat |= value & 0x3f;
|
|
|
|
if ((value >> 4) & 3) {
|
|
const int pending = (spu->tfifo_index > 32) ? 32 : (int)spu->tfifo_index;
|
|
|
|
for (int i = 0; i < pending; i++) {
|
|
spu_ram_diag_write(spu, spu->taddr, spu->tfifo[i]);
|
|
}
|
|
|
|
spu->tfifo_index = 0;
|
|
}
|
|
} return 1;
|
|
|
|
case SPUR_MBASE: {
|
|
spu->mbase = value;
|
|
spu->revbaddr = spu_wrap_word_addr((uint32_t)spu->mbase << 3);
|
|
} return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
The CPU taking ownership of a voice's ADPCM Repeat Address (1F801C0Eh+N*10h).
|
|
|
|
Hardware latches this: once the CPU has written the register, a Loop Start flag in the
|
|
sample data must NOT overwrite it, and the latch is released on key-on. DuckStation models
|
|
it as Voice::ignore_loop_address for the same reason.
|
|
|
|
Without it, a streaming voice replays a FRAGMENT: the game points the repeat address at the
|
|
next buffer half, then the decoder walks onto a block carrying Loop Start and drags the
|
|
repeat address backwards to that block. The next loop jump lands a couple of blocks early,
|
|
so a word or two is heard twice before playback continues correctly — "contact me contact
|
|
me by codec".
|
|
|
|
Called with the byte width of the register write, and latches on any OVERLAP with either
|
|
byte of the register: a game that sets the address with two 8-bit writes still means it.
|
|
*/
|
|
static inline void spu_note_repeat_addr_write(psx_spu_t* spu, uint32_t offset, uint32_t width) {
|
|
const uint32_t last = offset + width - 1u;
|
|
|
|
if (offset >= (uint32_t)(VOICE_COUNT * 16))
|
|
return;
|
|
|
|
for (uint32_t v = offset >> 4; (v <= (last >> 4)) && (v < (uint32_t)VOICE_COUNT); v++) {
|
|
const uint32_t reg = (v << 4) + 0x0eu;
|
|
|
|
if ((offset <= (reg + 1u)) && (reg <= last))
|
|
spu->data[v].ignore_loop_addr = 1;
|
|
}
|
|
}
|
|
|
|
void psx_spu_write32(psx_spu_t* spu, uint32_t offset, uint32_t value) {
|
|
// Handle special cases first
|
|
if (spu_handle_write(spu, offset, value))
|
|
return;
|
|
|
|
if (!spu_reg_in_mirror(offset, 4)) {
|
|
if (spu_warn_once(g_spu_unimpl_write_seen, offset)) {
|
|
log_warn("SPU write at offset %08x (%08x) has no register behind it, ignored"
|
|
" (logged once per offset)", offset, value);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
uint8_t* ptr = (uint8_t*)&spu->voice[0];
|
|
|
|
spu_note_repeat_addr_write(spu, offset, 4);
|
|
|
|
spu_reg_write32(ptr, offset, value);
|
|
}
|
|
|
|
void psx_spu_write16(psx_spu_t* spu, uint32_t offset, uint16_t value) {
|
|
// Handle special cases first
|
|
if (spu_handle_write(spu, offset, value))
|
|
return;
|
|
|
|
if (!spu_reg_in_mirror(offset, 2)) {
|
|
if (spu_warn_once(g_spu_unimpl_write_seen, offset)) {
|
|
log_warn("SPU write at offset %08x (%04x) has no register behind it, ignored"
|
|
" (logged once per offset)", offset, value);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
uint8_t* ptr = (uint8_t*)&spu->voice[0].volumel;
|
|
|
|
spu_note_repeat_addr_write(spu, offset, 2);
|
|
|
|
if (offset != 0x0c)
|
|
spu_reg_write16(ptr, offset, value);
|
|
}
|
|
|
|
/*
|
|
8-bit write: merge the byte into the containing 16-bit register and dispatch that.
|
|
|
|
The register file is 16 bits wide, so hardware has no narrower write than this. Going
|
|
back through psx_spu_write16() is the point rather than a shortcut — it is what keeps
|
|
a byte write to a register with behaviour attached working at all: KON/KOFF still key
|
|
voices, 1F801DA6h still recomputes the transfer address, 1F801DAAh still flushes the
|
|
FIFO, 1F801DA2h still moves the reverb base, and spu_note_repeat_addr_write() still
|
|
latches, which its own comment already anticipated ("a game that sets the address with
|
|
two 8-bit writes still means it"). Before this, all of that was a printf and a dropped
|
|
write.
|
|
|
|
The merge reads the mirror directly and never the FIFO, so composing a halfword out of
|
|
two byte writes cannot disturb a sound-RAM transfer. Registers whose writes are consumed
|
|
by spu_handle_write() never reach the mirror, so KON/KOFF merge against 0 and a byte
|
|
write keys exactly the voices that byte names — which is the hardware behaviour.
|
|
*/
|
|
void psx_spu_write8(psx_spu_t* spu, uint32_t offset, uint8_t value) {
|
|
const uint32_t aligned = offset & ~1u;
|
|
const uint16_t current = spu_reg_mirror16(spu, aligned);
|
|
|
|
const uint16_t merged = (offset & 1u)
|
|
? (uint16_t)((current & 0x00ffu) | ((uint16_t)value << 8))
|
|
: (uint16_t)((current & 0xff00u) | (uint16_t)value);
|
|
|
|
psx_spu_write16(spu, aligned, merged);
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
Save state.
|
|
|
|
Saved: the full 512 KiB sound RAM, the 24 voice register sets, every global
|
|
mixer/reverb register, the data-transfer FIFO, and the 24 per-voice decoder +
|
|
ADSR envelope contexts (including the 28-sample ADPCM block and the two
|
|
decoder history taps, without which the first block after a load decodes
|
|
wrong).
|
|
|
|
NOT saved: spu->ic (host wiring) and spu->ram (the allocation itself — its
|
|
CONTENTS are saved and restored into the existing buffer).
|
|
|
|
Host-side audio is deliberately NOT core state. The front-end's SDL audio
|
|
queue keeps whatever it had queued at load time; it drains within a few
|
|
milliseconds and then follows the restored SPU. The CD audio the CD-ROM
|
|
pushes into the SPU is part of the CD-ROM section, not this one.
|
|
--------------------------------------------------------------------------- */
|
|
|
|
void psx_spu_save_state(psx_spu_t* spu, psx_state_writer_t* w) {
|
|
int i;
|
|
int j;
|
|
|
|
psx_sw_u32(w, SPU_RAM_SIZE);
|
|
psx_sw_bytes(w, spu->ram, SPU_RAM_SIZE);
|
|
|
|
for (i = 0; i < 24; i++) {
|
|
psx_sw_u16(w, spu->voice[i].volumel);
|
|
psx_sw_u16(w, spu->voice[i].volumer);
|
|
psx_sw_u16(w, spu->voice[i].adsampr);
|
|
psx_sw_u16(w, spu->voice[i].adsaddr);
|
|
psx_sw_u16(w, spu->voice[i].envctl1);
|
|
psx_sw_u16(w, spu->voice[i].envctl2);
|
|
psx_sw_u16(w, spu->voice[i].envcvol);
|
|
psx_sw_u16(w, spu->voice[i].adraddr);
|
|
}
|
|
|
|
psx_sw_u16(w, spu->mainlvol);
|
|
psx_sw_u16(w, spu->mainrvol);
|
|
psx_sw_u16(w, spu->vlout);
|
|
psx_sw_u16(w, spu->vrout);
|
|
psx_sw_u32(w, spu->kon);
|
|
psx_sw_u32(w, spu->koff);
|
|
psx_sw_u32(w, spu->pmon);
|
|
psx_sw_u32(w, spu->non);
|
|
psx_sw_u32(w, spu->eon);
|
|
psx_sw_u32(w, spu->endx);
|
|
psx_sw_u16(w, spu->unk_da0);
|
|
psx_sw_u16(w, spu->mbase);
|
|
psx_sw_u16(w, spu->irq9addr);
|
|
psx_sw_u16(w, spu->ramdta);
|
|
psx_sw_u16(w, spu->ramdtf);
|
|
psx_sw_u16(w, spu->spucnt);
|
|
psx_sw_u16(w, spu->ramdtc);
|
|
psx_sw_u16(w, spu->spustat);
|
|
psx_sw_u32(w, spu->cdaivol);
|
|
psx_sw_u32(w, spu->extivol);
|
|
psx_sw_u32(w, spu->currvol);
|
|
psx_sw_u32(w, spu->unk_dbc);
|
|
psx_sw_u16(w, spu->dapf1);
|
|
psx_sw_u16(w, spu->dapf2);
|
|
psx_sw_u16(w, (uint16_t)spu->viir);
|
|
psx_sw_u16(w, (uint16_t)spu->vcomb1);
|
|
psx_sw_u16(w, (uint16_t)spu->vcomb2);
|
|
psx_sw_u16(w, (uint16_t)spu->vcomb3);
|
|
psx_sw_u16(w, (uint16_t)spu->vcomb4);
|
|
psx_sw_u16(w, (uint16_t)spu->vwall);
|
|
psx_sw_u16(w, (uint16_t)spu->vapf1);
|
|
psx_sw_u16(w, (uint16_t)spu->vapf2);
|
|
psx_sw_u16(w, spu->mlsame);
|
|
psx_sw_u16(w, spu->mrsame);
|
|
psx_sw_u16(w, spu->mlcomb1);
|
|
psx_sw_u16(w, spu->mrcomb1);
|
|
psx_sw_u16(w, spu->mlcomb2);
|
|
psx_sw_u16(w, spu->mrcomb2);
|
|
psx_sw_u16(w, spu->dlsame);
|
|
psx_sw_u16(w, spu->drsame);
|
|
psx_sw_u16(w, spu->mldiff);
|
|
psx_sw_u16(w, spu->mrdiff);
|
|
psx_sw_u16(w, spu->mlcomb3);
|
|
psx_sw_u16(w, spu->mrcomb3);
|
|
psx_sw_u16(w, spu->mlcomb4);
|
|
psx_sw_u16(w, spu->mrcomb4);
|
|
psx_sw_u16(w, spu->dldiff);
|
|
psx_sw_u16(w, spu->drdiff);
|
|
psx_sw_u16(w, spu->mlapf1);
|
|
psx_sw_u16(w, spu->mrapf1);
|
|
psx_sw_u16(w, spu->mlapf2);
|
|
psx_sw_u16(w, spu->mrapf2);
|
|
psx_sw_u16(w, (uint16_t)spu->vlin);
|
|
psx_sw_u16(w, (uint16_t)spu->vrin);
|
|
|
|
psx_sw_u32(w, spu->taddr);
|
|
psx_sw_u16_array(w, spu->tfifo, 32);
|
|
psx_sw_u16(w, spu->tfifo_index);
|
|
psx_sw_u32(w, spu->revbaddr);
|
|
psx_sw_i32(w, spu->lrsl);
|
|
psx_sw_i32(w, spu->lrsr);
|
|
psx_sw_i32(w, spu->even_cycle);
|
|
|
|
for (i = 0; i < 24; i++) {
|
|
psx_sw_i32(w, spu->data[i].playing);
|
|
psx_sw_u32(w, spu->data[i].counter);
|
|
psx_sw_u32(w, spu->data[i].current_addr);
|
|
psx_sw_u32(w, spu->data[i].repeat_addr);
|
|
psx_sw_u32(w, spu->data[i].prev_sample_index);
|
|
|
|
for (j = 0; j < 4; j++)
|
|
psx_sw_u16(w, (uint16_t)spu->data[i].s[j]);
|
|
|
|
psx_sw_i32(w, spu->data[i].block_flags);
|
|
psx_sw_i16_array(w, spu->data[i].buf, 28);
|
|
|
|
for (j = 0; j < 2; j++)
|
|
psx_sw_u16(w, (uint16_t)spu->data[i].h[j]);
|
|
|
|
psx_sw_f32(w, spu->data[i].lvol);
|
|
psx_sw_f32(w, spu->data[i].rvol);
|
|
psx_sw_i32(w, spu->data[i].cvol);
|
|
psx_sw_i32(w, spu->data[i].eon);
|
|
psx_sw_i32(w, spu->data[i].reverbl);
|
|
psx_sw_i32(w, spu->data[i].reverbr);
|
|
|
|
psx_sw_i32(w, spu->data[i].adsr_phase);
|
|
psx_sw_i32(w, spu->data[i].adsr_cycles_reload);
|
|
psx_sw_i32(w, spu->data[i].adsr_cycles);
|
|
psx_sw_i32(w, spu->data[i].adsr_mode);
|
|
psx_sw_i32(w, spu->data[i].adsr_dir);
|
|
psx_sw_i32(w, spu->data[i].adsr_shift);
|
|
psx_sw_i32(w, spu->data[i].adsr_step);
|
|
psx_sw_i32(w, spu->data[i].adsr_pending_step);
|
|
psx_sw_i32(w, spu->data[i].adsr_sustain_level);
|
|
psx_sw_u32(w, spu->data[i].envctl);
|
|
}
|
|
}
|
|
|
|
int psx_spu_load_state(psx_spu_t* spu, psx_state_reader_t* r) {
|
|
uint32_t ram_size;
|
|
int i;
|
|
int j;
|
|
|
|
ram_size = psx_sr_u32(r);
|
|
|
|
if (r->error)
|
|
return PSX_STATE_ERR_TRUNCATED;
|
|
|
|
if (ram_size != SPU_RAM_SIZE)
|
|
return PSX_STATE_ERR_GEOMETRY;
|
|
|
|
psx_sr_bytes(r, spu->ram, SPU_RAM_SIZE);
|
|
|
|
for (i = 0; i < 24; i++) {
|
|
spu->voice[i].volumel = psx_sr_u16(r);
|
|
spu->voice[i].volumer = psx_sr_u16(r);
|
|
spu->voice[i].adsampr = psx_sr_u16(r);
|
|
spu->voice[i].adsaddr = psx_sr_u16(r);
|
|
spu->voice[i].envctl1 = psx_sr_u16(r);
|
|
spu->voice[i].envctl2 = psx_sr_u16(r);
|
|
spu->voice[i].envcvol = psx_sr_u16(r);
|
|
spu->voice[i].adraddr = psx_sr_u16(r);
|
|
}
|
|
|
|
spu->mainlvol = psx_sr_u16(r);
|
|
spu->mainrvol = psx_sr_u16(r);
|
|
spu->vlout = psx_sr_u16(r);
|
|
spu->vrout = psx_sr_u16(r);
|
|
spu->kon = psx_sr_u32(r);
|
|
spu->koff = psx_sr_u32(r);
|
|
spu->pmon = psx_sr_u32(r);
|
|
spu->non = psx_sr_u32(r);
|
|
spu->eon = psx_sr_u32(r);
|
|
spu->endx = psx_sr_u32(r);
|
|
spu->unk_da0 = psx_sr_u16(r);
|
|
spu->mbase = psx_sr_u16(r);
|
|
spu->irq9addr = psx_sr_u16(r);
|
|
spu->ramdta = psx_sr_u16(r);
|
|
spu->ramdtf = psx_sr_u16(r);
|
|
spu->spucnt = psx_sr_u16(r);
|
|
spu->ramdtc = psx_sr_u16(r);
|
|
spu->spustat = psx_sr_u16(r);
|
|
spu->cdaivol = psx_sr_u32(r);
|
|
spu->extivol = psx_sr_u32(r);
|
|
spu->currvol = psx_sr_u32(r);
|
|
spu->unk_dbc = psx_sr_u32(r);
|
|
spu->dapf1 = psx_sr_u16(r);
|
|
spu->dapf2 = psx_sr_u16(r);
|
|
spu->viir = (int16_t)psx_sr_u16(r);
|
|
spu->vcomb1 = (int16_t)psx_sr_u16(r);
|
|
spu->vcomb2 = (int16_t)psx_sr_u16(r);
|
|
spu->vcomb3 = (int16_t)psx_sr_u16(r);
|
|
spu->vcomb4 = (int16_t)psx_sr_u16(r);
|
|
spu->vwall = (int16_t)psx_sr_u16(r);
|
|
spu->vapf1 = (int16_t)psx_sr_u16(r);
|
|
spu->vapf2 = (int16_t)psx_sr_u16(r);
|
|
spu->mlsame = psx_sr_u16(r);
|
|
spu->mrsame = psx_sr_u16(r);
|
|
spu->mlcomb1 = psx_sr_u16(r);
|
|
spu->mrcomb1 = psx_sr_u16(r);
|
|
spu->mlcomb2 = psx_sr_u16(r);
|
|
spu->mrcomb2 = psx_sr_u16(r);
|
|
spu->dlsame = psx_sr_u16(r);
|
|
spu->drsame = psx_sr_u16(r);
|
|
spu->mldiff = psx_sr_u16(r);
|
|
spu->mrdiff = psx_sr_u16(r);
|
|
spu->mlcomb3 = psx_sr_u16(r);
|
|
spu->mrcomb3 = psx_sr_u16(r);
|
|
spu->mlcomb4 = psx_sr_u16(r);
|
|
spu->mrcomb4 = psx_sr_u16(r);
|
|
spu->dldiff = psx_sr_u16(r);
|
|
spu->drdiff = psx_sr_u16(r);
|
|
spu->mlapf1 = psx_sr_u16(r);
|
|
spu->mrapf1 = psx_sr_u16(r);
|
|
spu->mlapf2 = psx_sr_u16(r);
|
|
spu->mrapf2 = psx_sr_u16(r);
|
|
spu->vlin = (int16_t)psx_sr_u16(r);
|
|
spu->vrin = (int16_t)psx_sr_u16(r);
|
|
|
|
spu->taddr = psx_sr_u32(r);
|
|
psx_sr_u16_array(r, spu->tfifo, 32);
|
|
spu->tfifo_index = psx_sr_u16(r);
|
|
spu->revbaddr = psx_sr_u32(r);
|
|
spu->lrsl = psx_sr_i32(r);
|
|
spu->lrsr = psx_sr_i32(r);
|
|
spu->even_cycle = psx_sr_i32(r);
|
|
|
|
for (i = 0; i < 24; i++) {
|
|
spu->data[i].playing = psx_sr_i32(r);
|
|
spu->data[i].counter = psx_sr_u32(r);
|
|
spu->data[i].current_addr = psx_sr_u32(r);
|
|
spu->data[i].repeat_addr = psx_sr_u32(r);
|
|
spu->data[i].prev_sample_index = psx_sr_u32(r);
|
|
|
|
for (j = 0; j < 4; j++)
|
|
spu->data[i].s[j] = (int16_t)psx_sr_u16(r);
|
|
|
|
spu->data[i].block_flags = psx_sr_i32(r);
|
|
psx_sr_i16_array(r, spu->data[i].buf, 28);
|
|
|
|
for (j = 0; j < 2; j++)
|
|
spu->data[i].h[j] = (int16_t)psx_sr_u16(r);
|
|
|
|
spu->data[i].lvol = psx_sr_f32(r);
|
|
spu->data[i].rvol = psx_sr_f32(r);
|
|
spu->data[i].cvol = psx_sr_i32(r);
|
|
spu->data[i].eon = psx_sr_i32(r);
|
|
spu->data[i].reverbl = psx_sr_i32(r);
|
|
spu->data[i].reverbr = psx_sr_i32(r);
|
|
|
|
spu->data[i].adsr_phase = psx_sr_i32(r);
|
|
spu->data[i].adsr_cycles_reload = psx_sr_i32(r);
|
|
spu->data[i].adsr_cycles = psx_sr_i32(r);
|
|
spu->data[i].adsr_mode = psx_sr_i32(r);
|
|
spu->data[i].adsr_dir = psx_sr_i32(r);
|
|
spu->data[i].adsr_shift = psx_sr_i32(r);
|
|
spu->data[i].adsr_step = psx_sr_i32(r);
|
|
spu->data[i].adsr_pending_step = psx_sr_i32(r);
|
|
spu->data[i].adsr_sustain_level = psx_sr_i32(r);
|
|
spu->data[i].envctl = psx_sr_u32(r);
|
|
|
|
/* Not in the payload (the save-state format is deliberately unchanged), so it is reset
|
|
to a defined value rather than inherited from whatever this instance was doing. The
|
|
next write to 1F801C0Eh+N*10h re-establishes it. */
|
|
spu->data[i].ignore_loop_addr = 0;
|
|
}
|
|
|
|
/* Samples already generated describe the machine that was just thrown away. Dropping them
|
|
also drops the budget, so the frame the load lands in generates nothing further and the
|
|
front-end fills it inline — one frame of the old pull model, then back to normal. */
|
|
psx_spu_flush_samples(spu);
|
|
|
|
return r->error ? PSX_STATE_ERR_TRUNCATED : PSX_STATE_OK;
|
|
}
|
|
|
|
void psx_spu_destroy(psx_spu_t* spu) {
|
|
free(spu->ram);
|
|
free(spu);
|
|
}
|
|
|
|
// To-do: Optimize reverb
|
|
|
|
int16_t spu_read_reverb(psx_spu_t* spu, uint32_t addr) {
|
|
uint32_t mbase = spu->mbase << 3;
|
|
|
|
uint32_t relative = (addr + spu->revbaddr - mbase) % (0x80000 - mbase);
|
|
uint32_t wrapped = (mbase + relative) & 0x7fffe;
|
|
|
|
return (int16_t)spu_ram_read16(spu, wrapped);
|
|
}
|
|
|
|
void spu_write_reverb(psx_spu_t* spu, uint32_t addr, int16_t value) {
|
|
uint32_t mbase = spu->mbase << 3;
|
|
|
|
uint32_t relative = (addr + spu->revbaddr - mbase) % (0x80000 - mbase);
|
|
uint32_t wrapped = (mbase + relative) & 0x7fffe;
|
|
|
|
spu_ram_write16(spu, wrapped, (uint16_t)value);
|
|
}
|
|
|
|
#define R16(addr) (spu_read_reverb(spu, addr))
|
|
#define W16(addr, value) spu_write_reverb(spu, addr, value)
|
|
|
|
#define SAT(v) CLAMP(v, INT16_MIN, INT16_MAX)
|
|
|
|
void spu_get_reverb_sample(psx_spu_t* spu, int inl, int inr, int* outl, int* outr) {
|
|
uint32_t mbase = spu->mbase << 3;
|
|
uint32_t dapf1 = spu->dapf1 << 3;
|
|
uint32_t dapf2 = spu->dapf2 << 3;
|
|
uint32_t mlsame = spu->mlsame << 3;
|
|
uint32_t mrsame = spu->mrsame << 3;
|
|
uint32_t dlsame = spu->dlsame << 3;
|
|
uint32_t drsame = spu->drsame << 3;
|
|
uint32_t mldiff = spu->mldiff << 3;
|
|
uint32_t mrdiff = spu->mrdiff << 3;
|
|
uint32_t dldiff = spu->dldiff << 3;
|
|
uint32_t drdiff = spu->drdiff << 3;
|
|
uint32_t mlcomb1 = spu->mlcomb1 << 3;
|
|
uint32_t mlcomb2 = spu->mlcomb2 << 3;
|
|
uint32_t mlcomb3 = spu->mlcomb3 << 3;
|
|
uint32_t mlcomb4 = spu->mlcomb4 << 3;
|
|
uint32_t mrcomb1 = spu->mrcomb1 << 3;
|
|
uint32_t mrcomb2 = spu->mrcomb2 << 3;
|
|
uint32_t mrcomb3 = spu->mrcomb3 << 3;
|
|
uint32_t mrcomb4 = spu->mrcomb4 << 3;
|
|
uint32_t mlapf1 = spu->mlapf1 << 3;
|
|
uint32_t mlapf2 = spu->mlapf2 << 3;
|
|
uint32_t mrapf1 = spu->mrapf1 << 3;
|
|
uint32_t mrapf2 = spu->mrapf2 << 3;
|
|
|
|
float vlin = (float)spu->vlin;
|
|
float vrin = (float)spu->vrin;
|
|
float viir = (float)spu->viir;
|
|
float vwall = (float)spu->vwall;
|
|
float vcomb1 = (float)spu->vcomb1;
|
|
float vcomb2 = (float)spu->vcomb2;
|
|
float vcomb3 = (float)spu->vcomb3;
|
|
float vcomb4 = (float)spu->vcomb4;
|
|
float vapf1 = (float)spu->vapf1;
|
|
float vapf2 = (float)spu->vapf2;
|
|
float vlout = (float)spu->vlout;
|
|
float vrout = (float)spu->vrout;
|
|
|
|
int lin = (vlin * inl) / 32768.0f;
|
|
int rin = (vrin * inr) / 32768.0f;
|
|
|
|
// same side reflection ltol and rtor
|
|
int16_t mlsamev = SAT(lin + ((R16(dlsame) * vwall) / 32768.0f) - ((R16(mlsame - 2) * viir) / 32768.0f) + R16(mlsame - 2));
|
|
int16_t mrsamev = SAT(rin + ((R16(drsame) * vwall) / 32768.0f) - ((R16(mrsame - 2) * viir) / 32768.0f) + R16(mrsame - 2));
|
|
W16(mlsame, mlsamev);
|
|
W16(mrsame, mrsamev);
|
|
|
|
/* `audio_diag`. mLSAME/mRSAME IS the feedback line: it is written here and read back on the
|
|
next tick through R16(mlsame - 2). A loop that has railed pins this at full scale and then
|
|
decays only as fast as vIIR/vWALL allow, which is exactly the shape of a sustained tone
|
|
that outlives the sound that started it. Read AFTER SAT() so the arithmetic is untouched;
|
|
see PSX_AUDIO_DIAG_RAILED in psx/perf.h for why post-clamp is enough. */
|
|
if (g_psx_audio_diag_enabled) {
|
|
g_psx_audio_diag.rev_calls++;
|
|
|
|
if (PSX_AUDIO_DIAG_RAILED(mlsamev) || PSX_AUDIO_DIAG_RAILED(mrsamev))
|
|
g_psx_audio_diag.revfb_railed++;
|
|
}
|
|
|
|
// different side reflection ltor and rtol
|
|
int16_t mldiffv = SAT(lin + ((R16(drdiff) * vwall) / 32768.0f) - ((R16(mldiff - 2) * viir) / 32768.0f) + R16(mldiff - 2));
|
|
int16_t mrdiffv = SAT(rin + ((R16(dldiff) * vwall) / 32768.0f) - ((R16(mrdiff - 2) * viir) / 32768.0f) + R16(mrdiff - 2));
|
|
W16(mldiff, mldiffv);
|
|
W16(mrdiff, mrdiffv);
|
|
|
|
// early echo (comb filter with input from buffer)
|
|
int16_t l = SAT((vcomb1 * R16(mlcomb1) / 32768.0f) + (vcomb2 * R16(mlcomb2) / 32768.0f) + (vcomb3 * R16(mlcomb3) / 32768.0f) + (vcomb4 * R16(mlcomb4) / 32768.0f));
|
|
int16_t r = SAT((vcomb1 * R16(mrcomb1) / 32768.0f) + (vcomb2 * R16(mrcomb2) / 32768.0f) + (vcomb3 * R16(mrcomb3) / 32768.0f) + (vcomb4 * R16(mrcomb4) / 32768.0f));
|
|
|
|
// late reverb apf1 (all pass filter 1 with input from comb)
|
|
l = SAT(l - SAT((vapf1 * R16(mlapf1 - dapf1)) / 32768.0f));
|
|
r = SAT(r - SAT((vapf1 * R16(mrapf1 - dapf1)) / 32768.0f));
|
|
|
|
W16(mlapf1, l);
|
|
W16(mrapf1, r);
|
|
|
|
l = SAT((l * vapf1 / 32768.0f) + R16(mlapf1 - dapf1));
|
|
r = SAT((r * vapf1 / 32768.0f) + R16(mrapf1 - dapf1));
|
|
|
|
// late reverb apf2 (all pass filter 2 with input from apf1)
|
|
l = SAT(l - SAT((vapf2 * R16(mlapf2 - dapf2)) / 32768.0f));
|
|
r = SAT(r - SAT((vapf2 * R16(mrapf2 - dapf2)) / 32768.0f));
|
|
|
|
W16(mlapf2, l);
|
|
W16(mrapf2, r);
|
|
|
|
l = SAT((l * vapf2 / 32768.0f) + R16(mlapf2 - dapf2));
|
|
r = SAT((r * vapf2 / 32768.0f) + R16(mrapf2 - dapf2));
|
|
|
|
// output to mixer (output volume multiplied with input from apf2)
|
|
*outl = SAT(l * vlout / 32768.0f);
|
|
*outr = SAT(r * vrout / 32768.0f);
|
|
|
|
if (g_psx_audio_diag_enabled &&
|
|
(PSX_AUDIO_DIAG_RAILED(*outl) || PSX_AUDIO_DIAG_RAILED(*outr)))
|
|
g_psx_audio_diag.revout_railed++;
|
|
|
|
spu->revbaddr = MAX(mbase, (spu->revbaddr + 2) & 0x7fffe);
|
|
}
|
|
|
|
#undef R16
|
|
#undef W16
|
|
|
|
/*
|
|
Main Volume Left / Right (1F801D80h / 1F801D82h).
|
|
|
|
These are in the Volume/Sweep format — the SAME one the voice volumes use — and NOT the
|
|
plain signed-16 format the reverb volumes use. In volume mode (bit15 clear) bits 14-0 hold
|
|
Volume/2, so the gain is value/4000h and 3FFFh is unity.
|
|
|
|
This used to be `* (float)spu->mainlvol / 32767.0f`, i.e. the register read as an unsigned
|
|
magnitude against a plain signed-16 full scale. With the 3FFFh that games actually write
|
|
that is 16383/32767 = 0.49999 — exactly HALF the intended level, a flat 6.02 dB loss on
|
|
everything the SPU produces.
|
|
|
|
Three independent confirmations, not a reading of the spec alone:
|
|
* MEASURED. In a 30 s MGS codec capture the ratio of the pre-clamp dry accumulator to the
|
|
host mix output was 2.0000 in all 49 snapshots (min 2.0002, max 2.0013), with mainvol
|
|
pinned at 3FFFh, dry_clip=0 and mix wrap=0 throughout. Exactly one halving, right here.
|
|
* INTERNALLY INCONSISTENT. spu_kon() already encodes the same /4000h for the identically
|
|
formatted VOICE volume registers, as "/32767.0f * 2.0f". The x2 is simply missing here.
|
|
* CONSISTENT WITH THE REVERB PATH. spu_get_reverb_sample() divides vLOUT/vRIN/etc by
|
|
32768, which is correct for those registers because they ARE plain signed 16-bit. The
|
|
two formats want different divisors and this one had the wrong one.
|
|
|
|
Also decodes the sign properly. Bits 14-0 are a SIGNED 15-bit field, so a negative main
|
|
volume (phase inversion) previously read as a large positive unsigned value and became a
|
|
gain of up to +2.0 with the wrong polarity. Sign-extending bit 14 is the same idiom the XA
|
|
decoder uses for its 4-bit nibbles. Sweep mode (bit15 set) remains unimplemented, but the
|
|
masked magnitude keeps the gain bounded to +/-1.0 instead of the >2x a raw unsigned read
|
|
produced, so this is strictly safer there too.
|
|
|
|
Cannot overflow the int16 result: the caller clamps to int16 BEFORE this scale is applied
|
|
and |gain| <= 1.0, so the product stays inside int16 whatever the mix does.
|
|
*/
|
|
static inline float spu_volume_gain(uint16_t reg) {
|
|
int16_t v = (int16_t)(uint16_t)(reg << 1) >> 1;
|
|
|
|
return (float)v / 16384.0f;
|
|
}
|
|
|
|
uint32_t psx_spu_get_sample(psx_spu_t* spu) {
|
|
spu->even_cycle ^= 1;
|
|
|
|
int left = 0;
|
|
int right = 0;
|
|
int revl = 0;
|
|
int revr = 0;
|
|
/* `audio_diag` only. Counted unconditionally because one increment on a path that already
|
|
runs the Gaussian interpolator is cheaper than the branch that would skip it. */
|
|
int voices = 0;
|
|
|
|
spu->koff = 0;
|
|
spu->kon = 0;
|
|
|
|
/* Accumulated per generated sample, not per frame: the host divides voice_samples by
|
|
samples to get the mean number of voices that were actually sounding. Sampling once
|
|
a frame would miss everything that keys on and off inside it. */
|
|
PSX_PERF_INC(spu_samples);
|
|
|
|
for (int v = 0; v < VOICE_COUNT; v++) {
|
|
if (!spu->data[v].playing)
|
|
continue;
|
|
|
|
++voices;
|
|
|
|
PSX_PERF_INC(spu_voice_samples);
|
|
|
|
spu_handle_adsr(spu, v);
|
|
|
|
uint32_t sample_index = spu->data[v].counter >> 12;
|
|
|
|
if (sample_index > 27) {
|
|
sample_index -= 28;
|
|
|
|
spu->data[v].counter &= 0xfff;
|
|
spu->data[v].counter |= sample_index << 12;
|
|
|
|
/*
|
|
Loop Start (flag bit 2) copies the current address into the voice's REPEAT
|
|
ADDRESS REGISTER, 1F801C0Eh. Writing spu->voice[v].adraddr rather than a private
|
|
copy is what makes the register the single source of truth — see the loop-end
|
|
cases below for why that matters. data[v].repeat_addr is kept in step purely so
|
|
the save-state payload in this file stays byte-compatible; nothing reads it now.
|
|
*/
|
|
if ((spu->data[v].block_flags & 4) && !spu->data[v].ignore_loop_addr) {
|
|
spu->voice[v].adraddr = (uint16_t)(spu->data[v].current_addr >> 3);
|
|
spu->data[v].repeat_addr = spu->data[v].current_addr;
|
|
}
|
|
|
|
/*
|
|
The repeat address is read LIVE from 1F801C0Eh, for exactly the same reason the
|
|
voice volume now is: it used to be a snapshot taken at key-on (spu_kon) and only
|
|
ever refreshed by a Loop Start flag, so a game writing 1F801C0Eh during playback
|
|
was ignored. Redirecting the repeat address mid-stream is how double-buffered
|
|
streaming works — the game refills the half that just finished and points the
|
|
loop at the other one — so a streaming voice would jump back to whatever address
|
|
was current at a key-on long past, replay audio it had already played, and then
|
|
die the moment it wandered onto a block flagged end-without-repeat.
|
|
|
|
That is precisely the reported symptom: MGS codec speech plays for a few seconds,
|
|
repeats itself, then stops for good.
|
|
*/
|
|
const uint32_t repeat_addr = spu_wrap_addr((uint32_t)spu->voice[v].adraddr << 3);
|
|
|
|
if (g_psx_audio_diag_enabled) {
|
|
g_psx_audio_diag.voice_flags[v] = (uint8_t)spu->data[v].block_flags;
|
|
}
|
|
|
|
switch (spu->data[v].block_flags & 3) {
|
|
case 0: case 2: {
|
|
/* The two inline IRQ checks that used to live here moved into
|
|
spu_read_block(), which every branch of this switch calls on the way out —
|
|
so the loop-end jumps below are covered too, and a block is no longer
|
|
tested twice per advance. */
|
|
spu->data[v].current_addr = spu_wrap_addr(spu->data[v].current_addr + 0x10);
|
|
} break;
|
|
|
|
case 1: {
|
|
/* Loop End with Loop Repeat clear. Flag bit 0 means "set ENDX and jump to
|
|
the repeat address" on its own; bit 1 only decides whether the voice is
|
|
ALSO forced into release. ENDX was missing here and set only in case 3,
|
|
so a stream ending on an unrepeated block never raised the flag the game
|
|
polls at 1F801D9Ch to learn that a buffer half had been consumed — it
|
|
would simply never refill. */
|
|
spu->endx |= 1 << v;
|
|
spu->data[v].current_addr = repeat_addr;
|
|
spu->data[v].playing = 0;
|
|
spu->voice[v].envcvol = 0;
|
|
|
|
if (g_psx_audio_diag_enabled)
|
|
g_psx_audio_diag.voice_stop[v]++;
|
|
|
|
adsr_load_release(spu, v);
|
|
} break;
|
|
|
|
case 3: {
|
|
spu->endx |= 1 << v;
|
|
spu->data[v].current_addr = repeat_addr;
|
|
|
|
if (g_psx_audio_diag_enabled)
|
|
g_psx_audio_diag.voice_loopend[v]++;
|
|
} break;
|
|
}
|
|
|
|
spu_read_block(spu, v);
|
|
}
|
|
|
|
// Fetch ADPCM sample
|
|
if (spu->data[v].prev_sample_index != sample_index) {
|
|
spu->data[v].s[3] = spu->data[v].s[2];
|
|
spu->data[v].s[2] = spu->data[v].s[1];
|
|
spu->data[v].s[1] = spu->data[v].s[0];
|
|
}
|
|
|
|
spu->data[v].s[0] = spu->data[v].buf[sample_index];
|
|
|
|
// Apply 4-point Gaussian interpolation
|
|
uint8_t gauss_index = (spu->data[v].counter >> 4) & 0xff;
|
|
int16_t g0 = g_spu_gauss_table[0x0ff - gauss_index];
|
|
int16_t g1 = g_spu_gauss_table[0x1ff - gauss_index];
|
|
int16_t g2 = g_spu_gauss_table[0x100 + gauss_index];
|
|
int16_t g3 = g_spu_gauss_table[0x000 + gauss_index];
|
|
int16_t out = spu->data[v].s[0];
|
|
|
|
// out = interpolate_hermite(
|
|
// spu->data[v].s[3],
|
|
// spu->data[v].s[2],
|
|
// spu->data[v].s[1],
|
|
// spu->data[v].s[0],
|
|
// (spu->data[v].counter & 0xfff) / 4096.0f
|
|
// );
|
|
|
|
out = (g0 * spu->data[v].s[3]) >> 15;
|
|
out += (g1 * spu->data[v].s[2]) >> 15;
|
|
out += (g2 * spu->data[v].s[1]) >> 15;
|
|
out += (g3 * spu->data[v].s[0]) >> 15;
|
|
|
|
float adsr_vol = (float)spu->voice[v].envcvol / 32767.0f;
|
|
|
|
/*
|
|
Voice volume is read LIVE from 1F801C00h+N*10h / 1F801C02h+N*10h.
|
|
|
|
It used to come from spu->data[v].lvol/rvol, which are written in exactly ONE place
|
|
— spu_kon() — so the mixer's per-voice gain was a snapshot taken at key-on and never
|
|
updated again. Nothing in spu_handle_write() refreshes them; a write to a voice
|
|
volume register updates spu->voice[v].volumel and the mixer carried on with the old
|
|
cached float. On hardware these registers take effect immediately (in sweep mode
|
|
they ramp continuously), which is exactly why games write them during playback.
|
|
|
|
That is silent for one-shot sounds, because those are keyed on AFTER their volume is
|
|
set, so the snapshot happens to be right. It is fatal for a STREAMING voice, which is
|
|
keyed on once and then left running for minutes while the game drives its level
|
|
through the registers: the pair carrying MGS's codec speech had been sounding for
|
|
over 30 s (env=7fff, phase=SUSTAIN, unchanged across all 60 snapshots of a capture)
|
|
with the registers reading 3fff/0000 and 0000/3fff, while the mixer used whatever
|
|
those registers held at a key-on far in the past.
|
|
|
|
spu_volume_gain() is the same Volume/Sweep decode the main volume uses — the two
|
|
registers share a format — so this also fixes the sign: the old expression read the
|
|
register as unsigned, turning a negative (phase-inverted) voice volume into a large
|
|
positive gain instead of an inverted one.
|
|
|
|
data[].lvol/rvol are still written by spu_kon() and still saved: psx/state.c has them
|
|
in the save-state payload, and changing that would break existing states for a field
|
|
the mixer no longer consults.
|
|
*/
|
|
float samplel = (out * spu_volume_gain(spu->voice[v].volumel)) * adsr_vol;
|
|
float sampler = (out * spu_volume_gain(spu->voice[v].volumer)) * adsr_vol;
|
|
|
|
left += samplel;
|
|
right += sampler;
|
|
|
|
/* `audio_diag`: what this voice actually contributed, post-decode and pre-mix. The one
|
|
number that separates "decode produced silence" from "the audio exists and something
|
|
downstream dropped it". */
|
|
if (g_psx_audio_diag_enabled) {
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.voice_peak_l[v], (int32_t)samplel);
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.voice_peak_r[v], (int32_t)sampler);
|
|
}
|
|
|
|
if (spu->eon & (1 << v)) {
|
|
revl += samplel;
|
|
revr += sampler;
|
|
}
|
|
|
|
uint16_t step = spu->voice[v].adsampr;
|
|
|
|
/* To-do: Do pitch modulation here */
|
|
|
|
spu->data[v].prev_sample_index = spu->data[v].counter >> 12;
|
|
spu->data[v].counter += step;
|
|
}
|
|
|
|
int16_t clamprl = CLAMP(revl, INT16_MIN, INT16_MAX);
|
|
int16_t clamprr = CLAMP(revr, INT16_MIN, INT16_MAX);
|
|
int16_t clampsl = CLAMP(left, INT16_MIN, INT16_MAX);
|
|
int16_t clampsr = CLAMP(right, INT16_MIN, INT16_MAX);
|
|
|
|
/* `audio_diag`. Peaks are taken from the PRE-clamp accumulators on purpose — how far past
|
|
full scale the mix runs is the whole question, and the clamp below destroys that. */
|
|
if (g_psx_audio_diag_enabled) {
|
|
g_psx_audio_diag.spu_samples++;
|
|
g_psx_audio_diag.spu_voices_sum += (uint64_t)voices;
|
|
|
|
if ((uint32_t)voices > g_psx_audio_diag.spu_voices_peak)
|
|
g_psx_audio_diag.spu_voices_peak = (uint32_t)voices;
|
|
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.dry_peak_l, left);
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.dry_peak_r, right);
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.revin_peak_l, revl);
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.revin_peak_r, revr);
|
|
|
|
if ((left != clampsl) || (right != clampsr))
|
|
g_psx_audio_diag.dry_clip++;
|
|
|
|
if ((revl != clamprl) || (revr != clamprr))
|
|
g_psx_audio_diag.revin_clip++;
|
|
|
|
g_psx_audio_diag.mainvol_l = (int32_t)spu->mainlvol;
|
|
g_psx_audio_diag.mainvol_r = (int32_t)spu->mainrvol;
|
|
|
|
if ((uint32_t)spu->mainlvol < g_psx_audio_diag.mainvol_min_l)
|
|
g_psx_audio_diag.mainvol_min_l = spu->mainlvol;
|
|
|
|
if ((uint32_t)spu->mainrvol < g_psx_audio_diag.mainvol_min_r)
|
|
g_psx_audio_diag.mainvol_min_r = spu->mainrvol;
|
|
|
|
if ((spu->spucnt & 0x4000) == 0)
|
|
g_psx_audio_diag.spu_silent++;
|
|
}
|
|
|
|
if ((spu->spucnt & 0x4000) == 0)
|
|
return 0;
|
|
|
|
uint16_t clampl;
|
|
uint16_t clampr;
|
|
|
|
if ((spu->spucnt & 0x0080) && !spu->reverb_disabled) {
|
|
if (spu->even_cycle)
|
|
spu_get_reverb_sample(spu, clamprl, clamprr, &spu->lrsl, &spu->lrsr);
|
|
|
|
/* `audio_diag`: does the dry+wet sum clip, and how loud is the wet return on its own?
|
|
Symptom "a grating tone that outlives the shot that caused it" lives or dies here. */
|
|
if (g_psx_audio_diag_enabled) {
|
|
const int sum_l = clampsl + spu->lrsl;
|
|
const int sum_r = clampsr + spu->lrsr;
|
|
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.revout_peak_l, spu->lrsl);
|
|
PSX_AUDIO_DIAG_PEAK(g_psx_audio_diag.revout_peak_r, spu->lrsr);
|
|
|
|
if ((sum_l > INT16_MAX) || (sum_l < INT16_MIN) ||
|
|
(sum_r > INT16_MAX) || (sum_r < INT16_MIN))
|
|
g_psx_audio_diag.revsum_clip++;
|
|
}
|
|
|
|
clampl = CLAMP((clampsl + spu->lrsl), INT16_MIN, INT16_MAX) * spu_volume_gain(spu->mainlvol);
|
|
clampr = CLAMP((clampsr + spu->lrsr), INT16_MIN, INT16_MAX) * spu_volume_gain(spu->mainrvol);
|
|
} else {
|
|
clampl = CLAMP(clampsl, INT16_MIN, INT16_MAX) * spu_volume_gain(spu->mainlvol);
|
|
clampr = CLAMP(clampsr, INT16_MIN, INT16_MAX) * spu_volume_gain(spu->mainrvol);
|
|
}
|
|
|
|
return clampl | (((uint32_t)clampr) << 16);
|
|
}
|
|
|
|
/*
|
|
===========================================================================================
|
|
SPU sample generation on the CPU's timeline.
|
|
===========================================================================================
|
|
|
|
THE BUG THIS EXISTS TO FIX (measured, not inferred — /tmp/mgs7.txt, MGS codec call):
|
|
|
|
psx_spu_get_sample() used to be driven exclusively by the front-end, in one burst of a
|
|
whole frame's worth of samples taken AFTER psx_update() had already finished the frame.
|
|
The audio itself was fine. The interrupts were not: every Sound RAM IRQ that
|
|
spu_check_irq() raised inside that burst landed while the CPU was stopped, so all of
|
|
them collapsed into the single pending IC_SPU bit the CPU saw at the frame boundary.
|
|
|
|
The SPU interrupt rate was therefore hard-capped at the VIDEO frame rate, whatever the
|
|
voices were actually doing. That is not a rounding error, it is a different clock.
|
|
|
|
MGS streams codec speech through a textbook double-buffered SPU ring and clocks the
|
|
refill off that interrupt:
|
|
|
|
- voice 23 is a SILENT metronome (vol=0000/0000) over a 0x200-byte ring at 44100 Hz,
|
|
so it wraps 49.2 times a second;
|
|
- the game alternates SPUIRQA between the ring's two halves (0x1010 / 0x1110 in the
|
|
capture, 28/30 split across 58 snapshots), i.e. 2 interrupts per wrap = 98.4/s;
|
|
- every 32nd interrupt it uploads the next 4096 bytes into each of the two speech
|
|
voices (21 and 22), which is 256 ADPCM blocks = 7168 samples = 0.325 s at their
|
|
pitch of 0x800 (22050 Hz). 32 interrupts is 16 wraps is 0.325 s. The two agree to
|
|
four figures: the design wants 98.4 interrupts per second and nothing else.
|
|
|
|
It was getting 57.9 (1678 raises over 29 s), pinned to the 59.29 Hz frame rate exactly as
|
|
the burst model predicts. 57.9/98.4 = 0.588. And the delivered data rate was 7409 bytes/s
|
|
per voice against the 12600 bytes/s those voices consume: 0.588. The interrupt deficit and
|
|
the audio deficit are the same number to three digits, in all three captures.
|
|
|
|
The missing 41% is not silence — the ring simply wraps onto sound RAM the game has not
|
|
got round to overwriting yet, so 0.325 s of speech plays a second time before the refill
|
|
lands and the sentence continues. "you'll have to take have to take".
|
|
|
|
THE FIX: charge the SPU the same device cycles as every other peripheral, from
|
|
psx_update(), and let it spend a per-frame sample budget across the frame instead of all
|
|
at once. The CPU then runs between samples, services each interrupt, and re-arms SPUIRQA
|
|
before the metronome has run past the next target.
|
|
|
|
WHY A BUDGET RATHER THAN A FREE-RUNNING CLOCK: the front-end pulls exactly
|
|
mix_rate/frame_rate samples per emulated frame and resamples afterwards for fast-forward.
|
|
Generating on a free-running divider would make the count per frame depend on how many
|
|
cycles the GPU happened to take to reach vblank, and any mismatch would accumulate into
|
|
underruns or a growing backlog. The budget makes the per-frame total bit-for-bit what it
|
|
was before this change; only WHEN inside the frame each sample is produced has moved.
|
|
Frames that reach vblank in fewer cycles than the budget covers leave a shortfall, which
|
|
the front-end generates inline exactly as it always did.
|
|
|
|
768 is exact, not a rounding: PSX_CPU_CPS is 33868800 = 44100 * 768. No accumulator drift.
|
|
*/
|
|
#define SPU_CYCLES_PER_SAMPLE 768
|
|
|
|
void psx_spu_begin_frame(psx_spu_t* spu, int sample_budget) {
|
|
/* A frame always drains exactly what it granted, so anything still here belongs to a frame
|
|
whose pull never happened — the audio device closed under it, or a state load landed in
|
|
the middle of it. Those samples describe a timeline the host has already moved past, and
|
|
carrying them forward would offset every later frame by their count, permanently. The
|
|
divider remainder is NOT reset with them: it is sub-sample phase, it is still valid, and
|
|
throwing it away every frame would cost one generated sample in most of them. */
|
|
spu->gen_head = 0;
|
|
spu->gen_tail = 0;
|
|
spu->gen_count = 0;
|
|
|
|
if (sample_budget < 0)
|
|
sample_budget = 0;
|
|
|
|
/* Clamped rather than trusted. The ring is the only thing standing between a bogus
|
|
frame-rate value and a write past the end of it. */
|
|
if (sample_budget > PSX_SPU_SAMPLE_RING)
|
|
sample_budget = PSX_SPU_SAMPLE_RING;
|
|
|
|
spu->gen_budget = sample_budget;
|
|
}
|
|
|
|
void psx_spu_tick(psx_spu_t* spu, uint32_t cycles) {
|
|
/* Zero budget is the normal state for every psx_update() the front-end drives outside a
|
|
real frame — runahead's re-simulated frames, the EXE boot spin, rewind. Those must not
|
|
generate audio, and returning before touching the divider means they do not perturb the
|
|
phase of the frame that follows either. */
|
|
if (spu->gen_budget <= 0)
|
|
return;
|
|
|
|
spu->gen_cycles += cycles;
|
|
|
|
while (spu->gen_cycles >= SPU_CYCLES_PER_SAMPLE) {
|
|
spu->gen_cycles -= SPU_CYCLES_PER_SAMPLE;
|
|
|
|
if (spu->gen_budget <= 0)
|
|
break;
|
|
|
|
if (spu->gen_count >= PSX_SPU_SAMPLE_RING)
|
|
break;
|
|
|
|
spu->gen_ring[spu->gen_head] = psx_spu_get_sample(spu);
|
|
spu->gen_head = (spu->gen_head + 1) & (PSX_SPU_SAMPLE_RING - 1);
|
|
spu->gen_count++;
|
|
spu->gen_budget--;
|
|
|
|
if (g_psx_audio_diag_enabled)
|
|
g_psx_audio_diag.spu_gen_ticked++;
|
|
}
|
|
}
|
|
|
|
int psx_spu_pop_sample(psx_spu_t* spu, uint32_t* out) {
|
|
if (spu->gen_count <= 0)
|
|
return 0;
|
|
|
|
*out = spu->gen_ring[spu->gen_tail];
|
|
spu->gen_tail = (spu->gen_tail + 1) & (PSX_SPU_SAMPLE_RING - 1);
|
|
spu->gen_count--;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void psx_spu_flush_samples(psx_spu_t* spu) {
|
|
spu->gen_head = 0;
|
|
spu->gen_tail = 0;
|
|
spu->gen_count = 0;
|
|
spu->gen_budget = 0;
|
|
spu->gen_cycles = 0;
|
|
}
|
|
|
|
int counter = 0;
|
|
|
|
/* One CD sector is 2352 bytes = 588 stereo frames, and psx/dev/cdrom/cdrom.h sizes the only
|
|
buffer ever passed in here — psx_cdrom_t::cdda_buf — as exactly int16_t[CD_SECTOR_SIZE >> 1],
|
|
i.e. 1176 samples. Kept as a named constant rather than derived because spu.c must not
|
|
include the CD-ROM headers. */
|
|
#define SPU_CDDA_SECTOR_FRAMES 588
|
|
|
|
void psx_spu_update_cdda_buffer(psx_spu_t* spu, void* buf) {
|
|
int16_t* ptr = buf;
|
|
int16_t* ram = (int16_t*)spu->ram;
|
|
|
|
/* Bounded read. This loop ran 0x400 = 1024 frames unconditionally, taking 2048 int16 out
|
|
of a 1176-int16 buffer: 872 samples, 1744 bytes, past the end of cdda_buf every single
|
|
call. What it actually copied into SPU RAM as "CD audio" was the psx_cdrom_t fields that
|
|
follow cdda_buf in the struct, and then the head of the raw XA sector buffer.
|
|
|
|
This is a memory-safety fix only. It is NOT claimed to resolve any reported symptom, and
|
|
the SEPARATE question of whether the destination regions are right is deliberately left
|
|
alone: the hardware capture buffers are 1024 BYTES each at 0x000 and 0x400, while the
|
|
int16 indexing below spans bytes 0x000-0x7FF and 0x800-0xFFF. Changing that without
|
|
measurement is exactly the guess this investigation is trying not to make. */
|
|
for (int i = 0; i < SPU_CDDA_SECTOR_FRAMES; i++) {
|
|
ram[i + 0x000] = *ptr++;
|
|
ram[i + 0x400] = *ptr++;
|
|
}
|
|
|
|
// Little bit of lowpass/smoothing
|
|
for (int i = 0; i < 0x400; i += 8) {
|
|
int l = 0, r = 0;
|
|
|
|
for (int j = 0; j < 8; j++) {
|
|
l += ram[i + j];
|
|
r += ram[i + j + 0x400];
|
|
}
|
|
|
|
ram[i + 0x000] = l / 8;
|
|
ram[i + 0x400] = r / 8;
|
|
}
|
|
|
|
// Simulate capture IRQ
|
|
if (spu->ramdtc & 0xc) {
|
|
if (spu->irq9addr <= 0x1ff) {
|
|
if (!counter) {
|
|
psx_ic_irq(spu->ic, IC_SPU);
|
|
}
|
|
|
|
counter++;
|
|
counter &= 0x1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef CLAMP
|
|
#undef MAX
|