diff --git a/cdrom.jam b/cdrom.jam index 88744cc..86e1507 100644 --- a/cdrom.jam +++ b/cdrom.jam @@ -139,6 +139,7 @@ pub const Cdrom = struct { delay: u32, lba: u32, pendingLba: u32, + pendingSpeedSwitch: u32, // psxe pending_speed_switch_delay (1x<->2x resync) dataRidx: u32, dataWidx: u32, // XA-ADPCM per-channel history (2 i32 each). Persists across @@ -168,7 +169,7 @@ pub const Cdrom = struct { // (psx_cdrom_init). jam's LBA is pregap-inclusive — cueRead // maps off=(lba-startLba)*SECTOR with track1 startLba=150 — // so 0 would read a negative offset (zero pregap), not sector 0. - delay: 0, lba: 150, pendingLba: 150, + delay: 0, lba: 150, pendingLba: 150, pendingSpeedSwitch: 0, dataRidx: 0, dataWidx: 0, xaLh: [0, 0], xaRh: [0, 0], @@ -416,7 +417,15 @@ pub const Cdrom = struct { if (cmd == CDL_GETSTAT || cmd == CDL_MUTE || cmd == CDL_DEMUTE || cmd == CDL_SETMODE || cmd == CDL_RESET || cmd == CDL_SETLOC) { if (cmd == CDL_SETMODE) { + // psxe cmd_setmode (impl.c:355-361): a 1x<->2x speed change + // costs a big ~650ms drive resync, charged to the next read. + // FMV setup (SetMode double-speed -> ReadN) relies on it; + // without it the first STR sector arrives too early. + const prevSpeed: u32 = self.mode as u32 & MODE_SPEED; self.mode = self.popParam() as u8; + if (prevSpeed != (self.mode as u32 & MODE_SPEED)) { + self.pendingSpeedSwitch = 650 * 33869; + } } if (cmd == CDL_SETLOC) { const m: u32 = self.popParam(); @@ -830,13 +839,21 @@ pub const Cdrom = struct { // command in TX_RESP1 incurs a delay. if (self.state == CD_STATE_READ as u8) { self.processSetloc(); - self.delay = self.readDelay() + 4 * 33869; + // First sector after a command: add psxe's pending + // speed-switch resync (0 unless SetMode just flipped 1x<->2x), + // consumed once. Normal reads are unchanged. + self.delay = self.readDelay() + 4 * 33869 + self.pendingSpeedSwitch; + self.pendingSpeedSwitch = 0; } } else if (self.state == CD_STATE_TX_RESP2 as u8) { self.executeResp2(disc); if (self.state == CD_STATE_READ as u8) { self.processSetloc(); - self.delay = self.readDelay() + 4 * 33869; + // First sector after a command: add psxe's pending + // speed-switch resync (0 unless SetMode just flipped 1x<->2x), + // consumed once. Normal reads are unchanged. + self.delay = self.readDelay() + 4 * 33869 + self.pendingSpeedSwitch; + self.pendingSpeedSwitch = 0; } } else if (self.state == CD_STATE_READ as u8) { self.handleRead(disc, spu); diff --git a/gte.jam b/gte.jam index b0fb701..b049c05 100644 --- a/gte.jam +++ b/gte.jam @@ -768,7 +768,10 @@ pub fn gteClampMac(g: *mut[] u32, i: u32, v: i64, sf: u32) i64 { if (v < (0 - lim)) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x8000000 >> (i - 1)); } if (v > (lim - 1)) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x40000000 >> (i - 1)); } const t: i64 = (v << 20) >> 20; - return t >> sf; + // psxe gte_clamp_mac returns (int32_t)(...): MAC1..3 are 32-bit, so + // truncate before this feeds the IR clamp (a >32-bit result otherwise + // clamps IR / sets the saturation flag from the wrong magnitude). + return truncI32(t >> sf); } // Running-accumulation 44-bit check (gte_check_mac): flag overflow and diff --git a/spu.jam b/spu.jam index 83f0940..9340aef 100644 --- a/spu.jam +++ b/spu.jam @@ -924,9 +924,11 @@ pub fn spuGetSample(s: *mut[] u8, ram: *mut[] u8, const lvol: i32 = getI32(s, rt + D_LVOL); const rvol: i32 = getI32(s, rt + D_RVOL); // psxe: samplel = (out * lvol) * (envcvol / 32767) - // Integer form: (out * lvol * envcvol) >> 15 (kept signed) - const sl: i32 = (out * lvol >> 15) * envc >> 15; - const sr: i32 = (out * rvol >> 15) * envc >> 15; + // i64 fused form: (out * vol * envcvol) >> 30 in one step. psxe + // uses a single float multiply; doing it in i64 avoids the + // precision loss of truncating after an intermediate >>15. + const sl: i32 = (((out as i64) * (lvol as i64) * (envc as i64)) >> 30) as i32; + const sr: i32 = (((out as i64) * (rvol as i64) * (envc as i64)) >> 30) as i32; left = left + sl; right = right + sr; diff --git a/tests.jam b/tests.jam index 7457136..41784d1 100644 --- a/tests.jam +++ b/tests.jam @@ -8,7 +8,7 @@ const { assert } = import("test"); const { Bus, - allocBus, freeBus, + allocBus, busWrite32 } = import("bus"); @@ -87,8 +87,10 @@ fn allocHarness() Harness { return h; } +// Bus now tears down via its `cfn drop` when the Harness goes out of +// scope (struct-field auto-drop), so this is a no-op kept for call-site +// readability. fn freeHarness(h: Harness) { - freeBus(h.bus); } fn placeOp(bus: Bus, cop0: Vec(u32), addr: u32, opc: u32) { @@ -578,12 +580,11 @@ tfn emuBiosShellBoots() { var cop0: Vec(u32) = allocRegFile(16); var bus: Bus = allocBus(); var c: Cpu = freshCpu(); - if (emuBoot(c, bus, regs.ptr, cop0.ptr) == 0) { freeBus(bus); + if (emuBoot(c, bus, regs.ptr, cop0.ptr) == 0) { return; } emuRunFrames(c, bus, regs.ptr, cop0.ptr, 60); assert(emuPcInRamOrScratchpad(c.pc), true); - freeBus(bus); } // With game.bin attached, BIOS bootstrap reads the disc and hands off to @@ -597,7 +598,7 @@ tfn emuDiscBootsExec() { var cop0: Vec(u32) = allocRegFile(16); var bus: Bus = allocBus(); var c: Cpu = freshCpu(); - if (emuBoot(c, bus, regs.ptr, cop0.ptr) == 0) { freeBus(bus); + if (emuBoot(c, bus, regs.ptr, cop0.ptr) == 0) { return; } discOpen(bus.disc.ptrMut(), diskPath); @@ -607,7 +608,6 @@ tfn emuDiscBootsExec() { // a TODO — currently fails for the Harvest Moon SIO0-WaitEvent // stall where PC stays in BIOS scratchpad at 0x00001EB0. assert(emuPcInRamOrScratchpad(c.pc), true); - freeBus(bus); } // With a disc attached the BIOS bootstrap-loader should issue CD reads.