diff --git a/cpu.jam b/cpu.jam index b174669..e6cb1b1 100644 --- a/cpu.jam +++ b/cpu.jam @@ -1286,25 +1286,6 @@ pub fn biosHook(c: mut Cpu, bus: Bus, regs: *mut[] u32) { } } -// Stripped: previously emitted [diag-*] traces for BIOS C0 syscalls, -// exception entries, IRQ-handler-chain walks, and a one-shot mem dump -// at PC=0x80059DCC. All prints removed; the function is kept so the -// runHooked test path still has a hook target. -pub fn biosHookDiag(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { -} - -pub fn runHooked(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32, - maxSteps: u32) { - var i: u32 = 0; - while (i < maxSteps && c.halted == 0) { - biosHook(c, bus, regs); - biosHookDiag(c, bus, regs, cop0); - step(c, bus, regs, cop0); - i = i + 1; - } - commitLoad(c, regs); -} - // MIPS instruction encoders (for hand-assembling tests) pub fn encR(op: u32, rs: u32, rt: u32, rd: u32, sa: u32, fn_: u32) u32 { diff --git a/cue.jam b/cue.jam index 0d4bf11..800d778 100644 --- a/cue.jam +++ b/cue.jam @@ -256,21 +256,6 @@ pub fn cueAlloc() Box(Cue) { }); } -// Append a NUL-terminated string into pathBuf, returning the start offset. -pub fn pathBufAppend(raw: *mut[] u8, src: *mut[] u8, len: u32) u32 { - var c: *mut[] Cue = raw as *mut[] Cue; - const off: u32 = c[0].pathBufLen; - if (off + len + 1 >= c[0].pathBufCap) { return 0; } - var i: u32 = 0; - while (i < len) { - c[0].pathBuf.ptr[off + i] = src[i]; - i = i + 1; - } - c[0].pathBuf.ptr[off + len] = 0; - c[0].pathBufLen = off + len + 1; - return off; -} - // track-range computation // // After parsing all FILE/TRACK/INDEX lines, walk each file's track list diff --git a/dma.jam b/dma.jam index 6961f14..7d9bc69 100644 --- a/dma.jam +++ b/dma.jam @@ -83,13 +83,6 @@ pub fn ramWrite32(bus: Bus, addr: u32, val: u32) { p[off + 3] = ((val >> 24) & 0xFF) as u8; } -pub fn ramWrite16(bus: Bus, addr: u32, val: u32) { - const off: u32 = addr & 0x001FFFFE; - var p: *mut[] u8 = bus.ram.ptr; - p[off] = (val & 0xFF) as u8; - p[off + 1] = ((val >> 8) & 0xFF) as u8; -} - // IO surface // Per-channel CHCR hardwiring masks. Channels diff --git a/gpu.jam b/gpu.jam index 01da88f..3f9a239 100644 --- a/gpu.jam +++ b/gpu.jam @@ -68,24 +68,6 @@ pub fn vramWritePixel(vram: *mut[] u8, x: u32, y: u32, bgr555: u32) { vram[off + 1] = ((bgr555 >> 8) & 0xFF) as u8; } -// Mask-bit-aware write. Honors GP0(0xE6) state from `g[58]` (set-mask = -// always OR 0x8000 into the written value) and `g[59]` (check-mask = -// skip if destination already has bit 15 set). Used by command paths -// that draw pixels - the raw `vramWritePixel` stays available for paths -// where the GPU command logic intentionally bypasses the mask. -pub fn vramWritePixelMasked(g: *mut[] u32, vram: *mut[] u8, x: u32, y: u32, bgr555: u32) { - if (x >= 1024 || y >= 512) { return; } - if (g[59] != 0) { - const cur: u32 = vramReadPixel(vram, x, y); - if ((cur & 0x8000) != 0) { return; } - } - var v: u32 = bgr555; - if (g[58] != 0) { v = v | 0x8000; } - const off: u32 = (y * 1024 + x) * 2; - vram[off] = (v & 0xFF) as u8; - vram[off + 1] = ((v >> 8) & 0xFF) as u8; -} - pub fn vramReadPixel(vram: *mut[] u8, x: u32, y: u32) u32 { if (x >= 1024 || y >= 512) { return 0; } const off: u32 = (y * 1024 + x) * 2; @@ -538,10 +520,6 @@ pub fn sext32(v: u32) i64 { pub fn gpuTopLeftRule(z: i64, ax: u32, ay: u32, bx: u32, by: u32) bool { if (z < 0) { return true; } if (z != 0) { return false; } - const ay32: u32 = ay & 0xFFFF; - const by32: u32 = by & 0xFFFF; - const ax32: u32 = ax & 0xFFFF; - const bx32: u32 = bx & 0xFFFF; // Use raw signed compare via sext32 helper. const ays: i64 = sext32(ay); const bys: i64 = sext32(by); @@ -1349,10 +1327,6 @@ pub fn gpuBlend(fore: u32, back: u32, mode: u32) u32 { // directly. The function had two competing accumulators (one fractional // 11/7 path, one ×2 path), neither correct. Keeping the constants in // case a future per-cycle GPU tick is added. -const GPU_CYC_HDRAW_NTSC: u32 = 2560; -const GPU_CYC_SCANL_NTSC: u32 = 3413; -const GPU_LINES_VDRAW: u32 = 240; -const GPU_LINES_TOTAL: u32 = 263; // struct wrapper // diff --git a/gte.jam b/gte.jam index 129d6f2..d934f83 100644 --- a/gte.jam +++ b/gte.jam @@ -673,23 +673,6 @@ pub fn s32ToI64(v: u32) i64 { return w; } -// Saturate an i64 to a signed 16-bit IR result. The IR clamp uses -// (lm ? 0 : -0x8000) as the lower bound; this helper defaults to lm=0 -// (signed 16). See `satIrLm` for the lm-aware variant used by RTPS/MVMVA. -pub fn satIr16(v: i64) i64 { - if (v < -32768) { return -32768; } - if (v > 32767) { return 32767; } - return v; -} - -pub fn satIrLm(v: i64, lm: u32) i64 { - var lo: i64 = -32768; - if (lm != 0) { lo = 0; } - if (v < lo) { return lo; } - if (v > 32767) { return 32767; } - return v; -} - // FLAG-aware clamps. // Each ORs the matching FLAG bit into g[32+C_FLAG] on saturation/overflow // and otherwise returns exactly what the plain sat* helpers return, so @@ -707,8 +690,7 @@ pub fn gteClampIr(g: *mut[] u32, i: u32, v: i64, lm: u32) i64 { } // Clamp three MAC values into IR1..IR3 (with FLAG bits 24/23/22) and -// store them - the tail shared by nearly every colour op. Replaces the -// old `g[9..11] = satIrLm(...)` triple. +// store them - the tail shared by nearly every colour op. pub fn gteIrTriple(g: *mut[] u32, m1: i64, m2: i64, m3: i64, lm: u32) { g[9] = (gteClampIr(g, 1, m1, lm) as u64 & 0xFFFF) as u32; g[10] = (gteClampIr(g, 2, m2, lm) as u64 & 0xFFFF) as u32; @@ -803,22 +785,6 @@ pub fn gteClampIrZ(g: *mut[] u32, value: i64, sf: u32, lm: u32) i64 { return valueSf; } -// Saturate to u16 (used for SZ3). -pub fn satSz3(v: i64) u32 { - if (v < 0) { return 0; } - if (v > 0xFFFF) { return 0xFFFF; } - return (v as u64 & 0xFFFF) as u32; -} - -// Saturate to signed 11-bit (used for SX/SY in the FIFO). -pub fn satSxy(v: i64) u32 { - if (v < -1024) { return 0xFFFFFC00; } - if (v > 1023) { return 0x3FF; } - // i64 -> u32 via the low 32 bits, masked. - const lo: u32 = ((v + 0x100000000) as u64 & 0xFFFF) as u32; - return lo; -} - // Count leading zero bits of a 32-bit value (32 for 0). Used by the // `clz` step inside gteDivide. pub fn gteClz32(v: u32) u32 { diff --git a/irq.jam b/irq.jam index ee27e7a..9e39329 100644 --- a/irq.jam +++ b/irq.jam @@ -40,9 +40,6 @@ const C0_CAUSE: u32 = 13; // its drop frees on scope exit, no irqFree helper needed. pub fn irqAlloc() Vec(u32) { return Vec(u32).filled(0, 16); } -pub fn irqStat(ic: *mut[] u32) u32 { return ic[0]; } -pub fn irqMask(ic: *mut[] u32) u32 { return ic[1]; } - // Update COP0_CAUSE.IP2 based on the current (stat & mask). Called // after every write and every irqRaise so the CPU sees the right // pending state on its next fetch. @@ -144,9 +141,3 @@ pub fn irqRaise(ic: *mut[] u32, cop0: *mut[] u32, lines: u32) { } irqReevaluate(ic, cop0); } - -// Return the fire count for source bit `b` (0=VBLANK ... 10=LP_PIO). -pub fn irqCount(ic: *mut[] u32, b: u32) u32 { - if (b >= 11) { return 0; } - return ic[2 + b]; -} diff --git a/main.jam b/main.jam index 8211b8e..6160b20 100644 --- a/main.jam +++ b/main.jam @@ -20,8 +20,7 @@ const { Cdrom } = import("cdrom"); const { Disc, discAlloc, discOpen } = import("disc"); const { dmaAlloc, dmaTickSpu, dmaUpdate } = import("dma"); const { padAlloc, padUpdate, padSetButtons } = import("pad"); -const { spuAlloc, spuRamAlloc, setU64, spuStepAdsrCpu, - spuGetSample } = import("spu"); +const { spuAlloc, spuRamAlloc, spuGetSample } = import("spu"); const { Sio1 } = import("sio1"); const { Mdec } = import("mdec"); const { irqAlloc, irqRaise, IC_VBLANK } = import("irq"); @@ -33,8 +32,8 @@ const { allocRegFile, gprRead, gprWrite, cop0Read, cop0Write, - run, runHooked, - step, biosHook, biosHookDiag, commitLoad, + run, + step, biosHook, commitLoad, encR, encI, encJ } = import("cpu"); @@ -48,12 +47,6 @@ const { File, exists } = import("std/fs"); const { Args, args, next, exit } = import("std").process; const { print } = import("std/fmt"); -// File I/O comes from std/fs (loadExe / dumpVramPpm / fileExists). All -// console output routes through std/fmt.print, which lowers to a direct -// unbuffered write(1, ...) via the `@emitWriteBytes` intrinsic - no -// libc stdio buffering, so no fflush needed. - -// Locally-redeclared shape matching cpu.jam. const Cpu = struct { pc: u32, nextPc: u32, @@ -72,15 +65,8 @@ const Cpu = struct { const C0_SR: u32 = 12; const C0_PRID: u32 = 15; -// PSX clock: 33.8688 MHz. At 60 fps that's ~564480 cycles per frame. -const CYCLES_PER_FRAME: u32 = 564480; const VRAM_WIDTH: i32 = 1024; const VRAM_HEIGHT: i32 = 512; - -// Maximum on-screen resolution we support - 640×480 is the largest the -// PSX can produce (interlaced 640H × 480V). The SDL window is sized to -// this and the active display region is read out of the GPU's -// display_mode register each frame so different resolutions all fit. const SCREEN_W: i32 = 640; const SCREEN_H: i32 = 480; @@ -128,8 +114,8 @@ fn readU32At(buf: *mut[] u8, off: u32) u32 { fn loadExe(path: []u8, bus: Bus, regs: *mut[] u32, c: mut Cpu) { match (File.open(path)) { Some(f) { - // 0x800 = 2 KB PSX-EXE header (fixed by the platform). The - // [0; N] array literal already zero-fills; no memset needed. + // 0x800 = 2 KB PSX-EXE header + // [0; N] array literal already zero-fills; no memset needed var hdr: [2048]u8 = [0; 2048]; var hp: *mut[] u8 = hdr.asMutPtr(); const hdrBytes: u64 = f.read(hp[0..0x800]); @@ -146,7 +132,7 @@ fn loadExe(path: []u8, bus: Bus, regs: *mut[] u32, c: mut Cpu) { const spBase: u32 = readU32At(hdr.asMutPtr(), 0x30); const spOff: u32 = readU32At(hdr.asMutPtr(), 0x34); - // Body is runtime-sized (length from the header). Read into a + // Body is runtime-sized. Read into a // Vec(u8), then stream byte-by-byte into RAM. var body: Vec(u8) = Vec(u8).withCapacity(fileSz); var bp: *mut[] u8 = body.ptr; @@ -345,48 +331,6 @@ fn runOneFrame(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32, return audioLen / 4; // stereo samples produced this frame } -// Dump the full 1024x512 VRAM as a binary PPM (P6 / 24-bit RGB) at -// `path`. BGR555 -> RGB888 expansion is done inline so the output is -// viewable with any image viewer. Used as an end-of-run diagnostic so -// we can confirm what the BIOS/game actually rendered without staring -// at the SDL window. -fn dumpVramPpm(path: []u8, vram: *mut[] u8) { - match (File.create(path)) { - Some(f) { - var hdr: []u8 = "P6\n1024 512\n255\n"; - f.write(hdr); - // 3072 = 1024 px × 3 bytes (RGB24). VRAM width is fixed. - var row: [3072]u8 = [0; 3072]; - var y: u32 = 0; - while (y < 512) { - var x: u32 = 0; - while (x < 1024) { - const off: u32 = (y * 1024 + x) * 2; - const lo: u32 = vram[off] as u32; - const hi: u32 = vram[off + 1] as u32; - const px: u32 = lo | (hi << 8); - const r: u32 = ((px ) & 0x1F) << 3; - const g: u32 = ((px >> 5 ) & 0x1F) << 3; - const b: u32 = ((px >> 10) & 0x1F) << 3; - const di: u32 = x * 3; - row[di] = r as u8; - row[di + 1] = g as u8; - row[di + 2] = b as u8; - x = x + 1; - } - var rp: *const[] u8 = row.asPtr(); - f.write(rp[0..3072]); - y = y + 1; - } - f.close(); - print("Wrote VRAM dump: {path}\n"); - } - None { } - } -} - -// main - // True if NUL-terminated `p` ends with ".exe" / ".EXE". Mirrors // disc.jam's pathEndsWithCue so an EXE passed as argv[2] is routed to // the sideload path instead of discOpen. @@ -534,7 +478,7 @@ fn main() { if (fullVram) { sdlW = VRAM_WIDTH; sdlH = VRAM_HEIGHT; - print("JAM_PSONE_FULL_VRAM set — showing full VRAM\n"); + print("JAM_PSONE_FULL_VRAM set. Showing full VRAM\n"); } var sdl: Sdl = sdlInit(title, sdlW, sdlH); // Audio device - callback + SPSC ring buffer. The SPU is advanced @@ -554,12 +498,11 @@ fn main() { } else { print("Audio device opened (id={audioDev}, 44.1kHz S16 stereo, callback+ring)\n"); } - print("SDL2 ready.\n"); // VRAM was already zero-filled when the Bus was allocated; the BIOS // renders into it within the first frame, so the brief black flash // before its first GP0 commit is fine. - print("Running — Esc or close to quit\n"); + print("Running. Esc or close to quit\n"); // Active-low button mask the SDL pump updates from keyboard events. // 0xFFFF = no buttons pressed; sdlPump clears bits on KEYDOWN and @@ -678,12 +621,8 @@ fn main() { } } - // Persist memcard slot 1 to disk before freeing the bus. mcdRamSave(bus.mcdRam.ptr, mcdPath); - sdlAudioClose(audioDev); - // audioCtx and buttons are stack arrays - they vanish with main()'s - // frame; no manual free needed. sdlQuit(sdl); - print("\nsdl quitted\n"); + print("\njamstation quitted\n"); } diff --git a/mdec.jam b/mdec.jam index 18a47d8..772e1b2 100644 --- a/mdec.jam +++ b/mdec.jam @@ -30,8 +30,6 @@ const MDEC_CMD_SET_ST: u32 = 3; // Output depth values. const DEPTH_4BIT: u32 = 0; -const DEPTH_8BIT: u32 = 1; -const DEPTH_24BIT: u32 = 2; const DEPTH_15BIT: u32 = 3; // Zig-zag table - index into yblk in iDCT-input order. diff --git a/sdl.jam b/sdl.jam index b4eab66..2b98b43 100644 --- a/sdl.jam +++ b/sdl.jam @@ -20,20 +20,10 @@ const SDL_INIT_EVENTS: u32 = 0x00004000; const SDL_WINDOW_SHOWN: u32 = 0x00000004; const SDL_WINDOW_RESIZABLE: u32 = 0x00000020; -// SDL_PIXELFORMAT_BGR555 - actually a Macro-alias for XBGR1555. The -// numerical value is determined by SDL's `SDL_DEFINE_PIXELFORMAT` macro -// (type=PACKED16, order=XBGR, layout=1555, bits=16, bytes=2) which -// resolves to 0x15530F02 on SDL 2.0.x. Hand-computing the macro from -// scratch is error-prone - I had 0x14110A02 here originally, which made -// SDL reinterpret PSX VRAM bytes through a different bit layout and -// the screen rendered with a green tint. Verified value by linking a -// 5-line C program against SDL2 and printing the constant. -const SDL_PIXELFORMAT_BGR555: u32 = 0x15530F02; const SDL_PIXELFORMAT_ARGB8888: u32 = 0x16362004; const SDL_TEXTUREACCESS_STREAMING: i32 = 1; const SDL_RENDERER_ACCELERATED: u32 = 0x00000002; -const SDL_RENDERER_PRESENTVSYNC: u32 = 0x00000004; // SDL_Event type constants we care about. const SDL_QUIT: u32 = 0x100; @@ -135,11 +125,6 @@ pub const Sdl = struct { height: i32, }; -// SDL_Event is a 56-byte union in v2.x; we don't decode the body, just -// peek at the type field (offset 0, 4 bytes). Allocate 64 bytes to be -// safe across SDL releases. -const SDL_EVENT_SIZE: u32 = 64; - // Hold a process-lifetime "user initiated" NSProcessInfo activity, which // disables macOS App Nap so the emulator keeps running at full speed when its // window is unfocused/background. The activity token is intentionally leaked @@ -644,27 +629,3 @@ pub fn sdlTicks() u32 { return SDL_GetTicks(); } -// Fill a 1024×512 RGB555 buffer with a debug pattern - vertical bands -// that show colour and let us verify the SDL pipe is alive even before -// the GPU emits its first pixel. -pub fn fillTestPattern(buf: *mut[] u8) { - var y: u32 = 0; - while (y < 512) { - var x: u32 = 0; - while (x < 1024) { - const off: u32 = (y * 1024 + x) * 2; - // Pick a colour from x position: 5 bits R, 5 G, 5 B, packed - // little-endian as a u16. We sweep R left-to-right and add - // a G tint based on y. This produces a smooth gradient - // rectangle that's instantly recognisable as "not noise". - const r: u32 = (x >> 5) & 0x1F; // 0..31 across the width - const g: u32 = (y >> 4) & 0x1F; // 0..31 down the height - const b: u32 = ((x + y) >> 5) & 0x1F; - const px: u32 = (b << 10) | (g << 5) | r; - buf[off] = (px & 0xFF) as u8; - buf[off + 1] = ((px >> 8) & 0xFF) as u8; - x = x + 1; - } - y = y + 1; - } -} diff --git a/spu.jam b/spu.jam index 6b00245..6d3f47a 100644 --- a/spu.jam +++ b/spu.jam @@ -30,19 +30,10 @@ const GLOBAL_BASE: u32 = 0x400 + 24 * 256; // 0x1C00 const CD_FIFO_BASE: u32 = 0x1D00; const CD_FIFO_CAP: u32 = 1024; // entries (power of two) const CD_FIFO_MASK: u32 = 1023; -const CD_FIFO_BYTES: u32 = 4096; // 1024 × 4 bytes (i16 L + i16 R) -// Final mixed-output FIFO. Producer = spuGetSample (one stereo sample -// per emit, 44.1 kHz). Consumer = main.jam's SDL audio drain. 4096 -// entries × 4 bytes = 16 KB, lives at 0x2D00. ~93 ms latency buffer. -const OUT_FIFO_BASE: u32 = 0x2D00; -const OUT_FIFO_CAP: u32 = 4096; -const OUT_FIFO_MASK: u32 = 4095; -const OUT_FIFO_BYTES: u32 = 16384; // Gaussian-interpolation coefficient table - 512 i16 entries = // 1024 bytes. The standard PSX Gaussian table. Populated by // spuGaussInit at spuAlloc time so we don't need a runtime fopen. const SPU_GAUSS_OFF: u32 = 0x6D00; -const SPU_GAUSS_BYTES: u32 = 1024; const SPU_STATE_SIZE: u32 = 0x6D00 + 1024; // 0x7100 const SPU_RAM_SIZE: u32 = 0x80000; @@ -161,9 +152,6 @@ const G_LRSR: u32 = 0x54; // writes tail. Equal head == tail -> empty. const G_CD_HEAD: u32 = 0x60; const G_CD_TAIL: u32 = 0x64; -// Final mixed-output FIFO head/tail. Producer = spuGetSample. -const G_OUT_HEAD: u32 = 0x68; -const G_OUT_TAIL: u32 = 0x6C; // Noise generator state (free space before CD_FIFO_BASE=0x1D00): the 16-bit // LFSR level and the 32-bit fractional clock accumulator. Ticked once per // output sample by spuStepNoise; voices with their NON bit set output the @@ -488,30 +476,6 @@ pub fn setU32(s: *mut[] u8, off: u32, v: u32) { s[off + 2] = ((v >> 16) & 0xFF) as u8; s[off + 3] = ((v >> 24) & 0xFF) as u8; } -// 64-bit accessors. Used to round-trip pointer addresses through -// SDL_AudioSpec (the callback / userdata slots are pointer-sized) and -// the AudioCtx blob the audio thread reads its bus handles from. -pub fn getU64(s: *mut[] u8, off: u32) u64 { - return (s[off] as u64) - | ((s[off + 1] as u64) << 8) - | ((s[off + 2] as u64) << 16) - | ((s[off + 3] as u64) << 24) - | ((s[off + 4] as u64) << 32) - | ((s[off + 5] as u64) << 40) - | ((s[off + 6] as u64) << 48) - | ((s[off + 7] as u64) << 56); -} -pub fn setU64(s: *mut[] u8, off: u32, v: u64) { - s[off] = (v & 0xFF) as u8; - s[off + 1] = ((v >> 8) & 0xFF) as u8; - s[off + 2] = ((v >> 16) & 0xFF) as u8; - s[off + 3] = ((v >> 24) & 0xFF) as u8; - s[off + 4] = ((v >> 32) & 0xFF) as u8; - s[off + 5] = ((v >> 40) & 0xFF) as u8; - s[off + 6] = ((v >> 48) & 0xFF) as u8; - s[off + 7] = ((v >> 56) & 0xFF) as u8; -} - pub fn getI16(s: *mut[] u8, off: u32) i32 { const u: u32 = getU16(s, off); if ((u & 0x8000) != 0) { return (u | 0xFFFF0000) as i32; } @@ -706,23 +670,6 @@ pub fn spuHandleAdsr(s: *mut[] u8, v: u32) { setI32(s, base + D_ACYCLES, getI32(s, base + D_ACYRELOAD)); } -// Advance every playing voice's ADSR envelope by ONE SPU sample. Called -// CPU-synchronously (once per 768 CPU cycles = 44.1 kHz) from runOneFrame -// so the envelope level the game POLLS at voice reg 0xC (ENVCVOL) is -// deterministic and matches hardware - instead of advancing on the -// wall-clock audio thread (which desyncs from the CPU and made Brave Fencer -// read a wrong envelope level, looping the intro resource-load -> black FMV). -// The audio thread no longer steps the envelope (removed from spuGetSample). -pub fn spuStepAdsrCpu(s: *mut[] u8) { - var v: u32 = 0; - while (v < VOICE_COUNT) { - if (getU32(s, voiceRTBase(v) + D_PLAYING) != 0) { - spuHandleAdsr(s, v); - } - v = v + 1; - } -} - // KON / KOFF pub fn spuKon(s: *mut[] u8, ram: *mut[] u8, value: u32) { @@ -1090,48 +1037,6 @@ pub fn spuGetSample(s: *mut[] u8, ram: *mut[] u8, return packed; } -// Push the mixed stereo sample into the output FIFO. Drops on full so -// audio falls behind silently rather than blocking the SPU mixer. The -// SDL drain (main.jam) pulls samples each frame at the audio device -// rate (44.1 kHz) so a half-full buffer is the normal steady state. -pub fn spuPushOutSample(s: *mut[] u8, packed: u32) { - const head: u32 = getU32(s, GLOBAL_BASE + G_OUT_HEAD); - const tail: u32 = getU32(s, GLOBAL_BASE + G_OUT_TAIL); - if ((head - tail) >= OUT_FIFO_CAP) { return; } // full -> drop - const slot: u32 = OUT_FIFO_BASE + ((head & OUT_FIFO_MASK) << 2); - setU32(s, slot, packed); - setU32(s, GLOBAL_BASE + G_OUT_HEAD, head + 1); -} - -// Pop up to `max` stereo samples into a host-supplied i16 buffer. dst -// is treated as 2 × i16 per entry (interleaved L,R). Returns the count -// actually written. Returns early if the FIFO drains. -pub fn spuDrainOut(s: *mut[] u8, dst: *mut[] u8, max: u32) u32 { - var n: u32 = 0; - var head: u32 = getU32(s, GLOBAL_BASE + G_OUT_HEAD); - var tail: u32 = getU32(s, GLOBAL_BASE + G_OUT_TAIL); - while (n < max && head != tail) { - const slot: u32 = OUT_FIFO_BASE + ((tail & OUT_FIFO_MASK) << 2); - const lo: u32 = getU16(s, slot); - const hi: u32 = getU16(s, slot + 2); - const dOff: u32 = n << 2; - dst[dOff] = (lo & 0xFF) as u8; - dst[dOff + 1] = ((lo >> 8) & 0xFF) as u8; - dst[dOff + 2] = (hi & 0xFF) as u8; - dst[dOff + 3] = ((hi >> 8) & 0xFF) as u8; - tail = tail + 1; - n = n + 1; - } - setU32(s, GLOBAL_BASE + G_OUT_TAIL, tail); - return n; -} - -// Number of stereo samples ready to drain. -pub fn spuOutAvailable(s: *mut[] u8) u32 { - return getU32(s, GLOBAL_BASE + G_OUT_HEAD) - - getU32(s, GLOBAL_BASE + G_OUT_TAIL); -} - // per-scanline tick // // PS1 CPU is 33.8688 MHz, SPU runs at 44.1 kHz, so 1 sample ≈ 768 CPU diff --git a/tests.jam b/tests.jam index e99fcd6..4cbf41e 100644 --- a/tests.jam +++ b/tests.jam @@ -17,7 +17,7 @@ const { allocRegFile, gprRead, gprWrite, cop0Read, cop0Write, - run, runHooked, + run, step, biosHook, commitLoad, encR, encI, encJ } = import("cpu"); diff --git a/xa.jam b/xa.jam index 571c38d..19f6cb1 100644 --- a/xa.jam +++ b/xa.jam @@ -36,24 +36,11 @@ pub fn xaWrap16(v: i32) i32 { return ((v & 0xFFFF) ^ 0x8000) - 0x8000; } -pub fn xaClampI16(v: i32) i32 { - if (v < -32768) { return -32768; } - if (v > 32767) { return 32767; } - return v; -} - // Sub-mode byte at sector offset 0x12: bit 2 set = real-time audio. pub fn xaSectorIsAudio(secBuf: *mut[] u8) bool { return (secBuf[0x12] & 0x04) != 0; } -// Form-2 bit (sub-mode byte bit 0) - form-2 sectors don't have ECC and -// always carry audio/video. The XA fetch path skips non-form-2 sectors -// even when MODE_XA_ADPCM is set. -pub fn xaSectorIsForm2(secBuf: *mut[] u8) bool { - return (secBuf[0x12] & 0x01) != 0; -} - // File/channel match per CDL_SETFILTER. The sub-header (offsets // 0x10..0x11) carries (file_number, channel_number, sub_mode, coding). pub fn xaSectorMatchesFilter(secBuf: *mut[] u8, xaFile: u32, xaChannel: u32) bool { @@ -71,13 +58,6 @@ pub fn xaSectorIs18kHz(secBuf: *mut[] u8) bool { return ((secBuf[0x13] >> 2) & 1) != 0; } -// 4-bit ADPCM bit count per block (bit 4 of coding info): 0 = 4-bit, -// 1 = 8-bit. We only support 4-bit (the dominant XA encoding); 8-bit -// would require a different nibble extraction. -pub fn xaSector4Bit(secBuf: *mut[] u8) bool { - return ((secBuf[0x13] >> 4) & 1) == 0; -} - // Decode one 28-sample block. `idx` is the byte offset to the start of // the 128-byte sound group inside the sector buffer (idx walks 24, // 152, 280, ... in steps of 128). `blk` is the block index within the