Something went wrong. Try again.
A Playstation One (PS1) emulator fully written in Jam
Something went wrong. Try again.
jamstation cdrom.jam
34 kB · 950 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951// SPDX-License-Identifier: MIT// Copyright (c) 2026 Raphael Amorim
// register map at 0x1F801800..0x1F801803//// every register is bank-selected: writes to address 0x800 set bits 0-1 of the bank index,// then addresses 0x801..0x803 take different meanings per bank. The dispatch table://// bank read 0x800 status (busy/parm-empty/resp-empty/...)// bank read 0x801 pop one byte from the response FIFO// bank read 0x802 pop one byte from the data FIFO (sector buffer)// bank read 0x803 index 0/2 -> IER, index 1/3 -> IFR | 0xE0//// bank 0 write 0x801 command byte (triggers state machine)// bank 0 write 0x802 push parameter byte// bank 0 write 0x803 request register (bit 7 = data req)// bank 1 write 0x802 IER// bank 1 write 0x803 IFR ack (clears bits in IFR that are set in val)
const { irqRaise, IC_CDROM } = import("irq");const { Disc, discRead, discLoaded, discTrackCount, discTrackLba, discTrackNumber, discQuery } = import("disc");const { xaSectorIsAudio, xaSectorMatchesFilter, xaSectorIsStereo, xaSectorIs18kHz, xaDecodeSector} = import("xa");const { spuPushCdSample } = import("spu");
const SECTOR_BYTES: u32 = 2352;
const CD_STATE_IDLE: u32 = 0;const CD_STATE_TX_RESP1: u32 = 1;const CD_STATE_TX_RESP2: u32 = 2;const CD_STATE_READ: u32 = 3;const CD_STATE_PLAY: u32 = 4;
// 1ms at 33.8688MHzconst CD_DELAY_1MS: u32 = 33869;const CD_DELAY_FR: u32 = 50401;const CD_DELAY_INIT_FR: u32 = 81102;// 33_868_800 / 75const CD_DELAY_READ_SS: u32 = 451584;// 33_868_800 / (2*75)const CD_DELAY_READ_DS: u32 = 225792;
const CDL_GETSTAT: u32 = 0x01;const CDL_SETLOC: u32 = 0x02;const CDL_PLAY: u32 = 0x03;const CDL_FORWARD: u32 = 0x04;const CDL_BACKWARD: u32 = 0x05;const CDL_READN: u32 = 0x06;const CDL_MOTORON: u32 = 0x07;const CDL_STOP: u32 = 0x08;const CDL_PAUSE: u32 = 0x09;const CDL_INIT: u32 = 0x0A;const CDL_MUTE: u32 = 0x0B;const CDL_DEMUTE: u32 = 0x0C;const CDL_SETFILTER: u32 = 0x0D;const CDL_SETMODE: u32 = 0x0E;const CDL_GETPARAM: u32 = 0x0F;const CDL_GETLOCL: u32 = 0x10;const CDL_GETLOCP: u32 = 0x11;const CDL_SETSESSION: u32 = 0x12;const CDL_GETTN: u32 = 0x13;const CDL_GETTD: u32 = 0x14;const CDL_SEEKL: u32 = 0x15;const CDL_SEEKP: u32 = 0x16;const CDL_TEST: u32 = 0x19;const CDL_GETID: u32 = 0x1A;const CDL_READS: u32 = 0x1B;const CDL_RESET: u32 = 0x1C;const CDL_GETQ: u32 = 0x1D;const CDL_READTOC: u32 = 0x1E;
// 0 = 0x800 payload, 1 = 0x924 rawconst MODE_SECTOR_SIZE: u32 = 0x20;// 0 = 1x, 1 = 2xconst MODE_SPEED: u32 = 0x80;// 1 = enable XA-ADPCM streamingconst MODE_XA_ADPCM: u32 = 0x40;// 1 = honour CdlSetFilter file/chanconst MODE_XA_FILTER: u32 = 0x08;
const CD_STAT_PLAY: u32 = 0x80;const CD_STAT_SEEK: u32 = 0x40;const CD_STAT_READ: u32 = 0x20;const CD_STAT_SHELLOPEN: u32 = 0x10;const CD_STAT_IDERROR: u32 = 0x08;const CD_STAT_SEEKERROR: u32 = 0x04;const CD_STAT_SPINDLE: u32 = 0x02;const CD_STAT_ERROR: u32 = 0x01;
// error codes (CD_ERR_*).const CD_ERR_NO_DISC: u32 = 0x80;const CD_ERR_INVALID_COMMAND: u32 = 0x40;const CD_ERR_WRONG_PARAM_CNT: u32 = 0x20;const CD_ERR_INVALID_SUBFUNC: u32 = 0x10;const CD_ERR_SEEK_FAILED: u32 = 0x04;
pub const Cdrom = struct { // bank select 0-3 index: u8, // interrupt flag register, low 5 bits ifr: u8, // interrupt enable register ier: u8, // command in progress busy: u8, // CD_STATE_* state: u8, prevState: u8, pendingCmd: u8, // request register bit 7 dataReq: u8, mode: u8, // 0=licensed, 1=audio, 2=unlicensed discType: u8, // 0=Japan, 1=America, 2=Europe region: u8, // CDR_VERSION_* index version: u8, // sector pump active readOngoing: u8, // XA-ADPCM streaming active (status reg bit2 ADPBUSY) xaPlaying: u8, hasDisc: u8, cmdPending: u8, // SetFilter file xaFile: u8, // SetFilter channel xaChannel: u8, // SeekL sets, GetLocP reads seekPrec: u8, // previous MODE.SPEED for SetMode delay prevSpeed: u8, paramR: u8, paramW: u8, respR: u8, respW: u8, // u32 timers / cursors. delay: u32, lba: u32, pendingLba: u32, pendingSpeedSwitch: u32, dataRidx: u32, dataWidx: u32, // XA-ADPCM per-channel history (2 i32 each). Persists across // sectors so block-N's predictor sees block-(N-1)'s tail. xaLh: [2]i32, xaRh: [2]i32, xaPrevL: i32, xaPrevR: i32, // Parameter / response FIFOs. each is a 32-byte ring with u8 // head/tail; the indexes wrap modulo 32 via `& 0x1F`, and the // empty test compares the raw head/tail bytes directly. paramFifo: [32]u8, respFifo: [32]u8, // 2352-byte raw sector buffer (CD-ROM Mode2/2352). dataBuf: [2352]u8,
pub fn init() Self { return Self { index: 0, ifr: 0, ier: 0, busy: 0, state: 0, prevState: 0, pendingCmd: 0, dataReq: 0, mode: 0, discType: 0, region: 1, version: 1, readOngoing: 0, xaPlaying: 0, hasDisc: 0, cmdPending: 0, xaFile: 0, xaChannel: 0, seekPrec: 1, prevSpeed: 0, paramR: 0, paramW: 0, respR: 0, respW: 0, delay: 0, lba: 150, pendingLba: 150, pendingSpeedSwitch: 0, dataRidx: 0, dataWidx: 0, xaLh: [0, 0], xaRh: [0, 0], xaPrevL: 0, xaPrevR: 0, paramFifo: [0; 32], respFifo: [0; 32], dataBuf: [0; 2352], }; }
pub fn attachDisc(self: mut Self, disc: *mut[] Disc) { if (discLoaded(disc) != 0) { self.hasDisc = 1; } }
pub fn pushParam(self: mut Self, v: u32) { const idx: u32 = (self.paramW as u32) & 0x1F; self.paramFifo[idx] = (v & 0xFF) as u8; self.paramW = (((self.paramW as u32) + 1) & 0xFF) as u8; }
pub fn popParam(self: mut Self) u32 { if (self.paramR == self.paramW) { return 0; } const idx: u32 = (self.paramR as u32) & 0x1F; const v: u8 = self.paramFifo[idx]; self.paramR = (((self.paramR as u32) + 1) & 0xFF) as u8; return v as u32; }
pub fn paramEmpty(self: Self) bool { return self.paramR == self.paramW; }
pub fn paramFull(self: Self) bool { return (((self.paramW as u32) - (self.paramR as u32)) & 0x1F) == 16; }
pub fn clearParam(self: mut Self) { self.paramR = 0; self.paramW = 0; }
pub fn pushResp(self: mut Self, v: u32) { const idx: u32 = (self.respW as u32) & 0x1F; self.respFifo[idx] = (v & 0xFF) as u8; self.respW = (((self.respW as u32) + 1) & 0xFF) as u8; }
pub fn popResp(self: mut Self) u32 { if (self.respR == self.respW) { return 0; } const idx: u32 = (self.respR as u32) & 0x1F; const v: u8 = self.respFifo[idx]; self.respR = (((self.respR as u32) + 1) & 0xFF) as u8; return v as u32; }
pub fn respEmpty(self: Self) bool { return self.respR == self.respW; }
pub fn clearResp(self: mut Self) { self.respR = 0; self.respW = 0; }
pub fn getStat(self: Self) u32 { var s: u32 = CD_STAT_SPINDLE; if (self.readOngoing != 0) { s = s | CD_STAT_READ; } if ((self.mode as u32 & 0x10) != 0) { s = s | CD_STAT_IDERROR; } if (self.hasDisc == 0) { s = s | CD_STAT_SHELLOPEN; } return s; }
pub fn readStatus(self: Self) u32 { var r: u32 = (self.index as u32) & 0x3; // bit 2 (ADPBUSY): XA-ADPCM streaming active. Set when a ReadN/ReadS // runs in XA mode. if (self.xaPlaying != 0) { r = r | 0x04; } if (self.paramEmpty()) { r = r | 0x08; } if (!self.paramFull()) { r = r | 0x10; } if (!self.respEmpty()) { r = r | 0x20; } // bit 6: data FIFO not empty AND data_req is asserted. if (self.dataRidx < self.dataWidx && self.dataReq != 0) { r = r | 0x40; } if (self.busy != 0) { r = r | 0x80; } return r; }
pub fn read8(self: mut Self, off: u32) u32 { match (off) { 0 { return self.readStatus(); } 1 { return self.popResp(); } 2 { return self.readData(); } 3 { const i: u32 = (self.index as u32) & 0x3; if ((i & 1) != 0) { return 0xE0 | (self.ifr as u32); } return self.ier as u32; } _ { return 0; } } }
pub fn read16(self: mut Self, off: u32) u32 { return self.read8(off) | (self.read8(off + 1) << 8); }
pub fn read32(self: mut Self, off: u32) u32 { return self.read8(off) | (self.read8(off + 1) << 8) | (self.read8(off + 2) << 16) | (self.read8(off + 3) << 24); }
pub fn write8(self: mut Self, off: u32, val: u32) { if (off == 0) { self.index = (val & 0x3) as u8; return; } const bank: u32 = (self.index as u32) & 0x3; const slot: u32 = (bank << 2) | (off & 0x3); match (slot) { // Bank 0 - command byte: schedule state-machine entry. 1 { self.pendingCmd = (val & 0xFF) as u8; self.cmdPending = 1; self.prevState = self.state; self.state = CD_STATE_TX_RESP1 as u8; var delay: u32 = CD_DELAY_FR; if ((val & 0xFF) == CDL_INIT) { delay = CD_DELAY_INIT_FR; } self.delay = delay; } // Push parameter byte. 2 { self.pushParam(val); } // Request register - bit 7 = BFRD (data req). 3 { self.dataReq = ((val >> 7) & 1) as u8; } // Bank 1 6 { self.ier = (val & 0x1F) as u8; } 7 { if ((val & 0x40) != 0) { self.clearParam(); } self.ifr = ((self.ifr as u32) & (~(val & 0x1F))) as u8; } // Bank 2 / 3 - volume registers. Not needed for BIOS boot. _ {} } }
pub fn write16(self: mut Self, off: u32, val: u32) { self.write8(off, val & 0xFF); self.write8(off + 1, (val >> 8) & 0xFF); }
pub fn write32(self: mut Self, off: u32, val: u32) { self.write8(off, val & 0xFF); self.write8(off + 1, (val >> 8) & 0xFF); self.write8(off + 2, (val >> 16) & 0xFF); self.write8(off + 3, (val >> 24) & 0xFF); }
pub fn executeCommand(self: mut Self, disc: *mut[] Disc, ic: *mut[] u32, cop0: *mut[] u32) { const cmd: u32 = self.pendingCmd as u32; self.busy = 0;
// (1) disc-required commands with no disc -> INT5(11h,80h), // (3) wrong parameter count -> INT5(03h,20h), unknown command -> // INT5(03h,40h). (Version checks are omitted - jam models a // single CD-ROM revision.) `pcnt` = parameters queued for this cmd. const pcnt: u32 = ((self.paramW as u32) - (self.paramR as u32)) & 0xFF; const c0: bool = cmd == CDL_GETSTAT || cmd == CDL_FORWARD || cmd == CDL_BACKWARD || cmd == CDL_READN || cmd == CDL_MOTORON || cmd == CDL_STOP || cmd == CDL_PAUSE || cmd == CDL_INIT || cmd == CDL_MUTE || cmd == CDL_DEMUTE || cmd == CDL_GETPARAM || cmd == CDL_GETLOCL || cmd == CDL_GETLOCP || cmd == CDL_GETTN || cmd == CDL_SEEKL || cmd == CDL_SEEKP || cmd == CDL_GETID || cmd == CDL_READS || cmd == CDL_RESET || cmd == CDL_READTOC; const c1: bool = cmd == CDL_SETMODE || cmd == CDL_GETTD || cmd == CDL_TEST; const c2: bool = cmd == CDL_SETFILTER || cmd == CDL_GETQ; const needsDisc: bool = cmd == CDL_SETLOC || cmd == CDL_PLAY || cmd == CDL_FORWARD || cmd == CDL_BACKWARD || cmd == CDL_READN || cmd == CDL_MOTORON || cmd == CDL_STOP || cmd == CDL_PAUSE || cmd == CDL_MUTE || cmd == CDL_DEMUTE || cmd == CDL_SETFILTER || cmd == CDL_GETLOCL || cmd == CDL_GETLOCP || cmd == CDL_SETSESSION || cmd == CDL_GETTN || cmd == CDL_GETTD || cmd == CDL_SEEKL || cmd == CDL_SEEKP || cmd == CDL_GETID || cmd == CDL_READS || cmd == CDL_GETQ; if (self.hasDisc == 0 && needsDisc) { self.errorOut(CD_STAT_SHELLOPEN, CD_ERR_NO_DISC); return; } const known: bool = c0 || c1 || c2 || cmd == CDL_SETLOC || cmd == CDL_PLAY; if (!known) { self.errorOut(CD_STAT_SPINDLE, CD_ERR_INVALID_COMMAND); return; } var pbad: bool = false; if (c0 && pcnt != 0) { pbad = true; } if (c1 && pcnt != 1) { pbad = true; } if (c2 && pcnt != 2) { pbad = true; } if (cmd == CDL_SETLOC && pcnt != 3) { pbad = true; } if (cmd == CDL_PLAY && pcnt > 1) { pbad = true; } if (pbad) { self.errorOut(CD_STAT_SPINDLE, CD_ERR_WRONG_PARAM_CNT); return; }
if (cmd == CDL_TEST) { const subf: u32 = self.popParam(); if (subf == 0x20) { self.ifr = 3; const v: u32 = self.version as u32; self.pushResp(versionByteForIdx(v, 0)); self.pushResp(versionByteForIdx(v, 1)); self.pushResp(versionByteForIdx(v, 2)); self.pushResp(versionByteForIdx(v, 3)); self.state = self.prevState; return; } self.errorOut(CD_STAT_SPINDLE, CD_ERR_INVALID_SUBFUNC); return; } if (cmd == CDL_GETSTAT || cmd == CDL_MUTE || cmd == CDL_DEMUTE || cmd == CDL_SETMODE || cmd == CDL_RESET || cmd == CDL_SETLOC) { if (cmd == CDL_SETMODE) { 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(); const s: u32 = self.popParam(); const f: u32 = self.popParam(); const bcdOk: bool = ((m & 0x0F) <= 9) && ((m >> 4) <= 9) && ((s & 0x0F) <= 9) && ((s >> 4) <= 9) && ((f & 0x0F) <= 9) && ((f >> 4) <= 9); if (!bcdOk || s >= 0x60 || f >= 0x75) { self.errorOut(CD_STAT_SPINDLE, CD_ERR_INVALID_SUBFUNC); return; } const mB: u32 = (m & 0x0F) + ((m >> 4) & 0x0F) * 10; const sB: u32 = (s & 0x0F) + ((s >> 4) & 0x0F) * 10; const fB: u32 = (f & 0x0F) + ((f >> 4) & 0x0F) * 10; self.pendingLba = (mB * 4500) + (sB * 75) + fB; } self.ifr = 3; self.pushResp(self.getStat()); if (cmd == CDL_SETMODE) { self.state = CD_STATE_IDLE as u8; self.prevState = CD_STATE_IDLE as u8; self.readOngoing = 0; self.xaPlaying = 0; return; } self.state = self.prevState; return; } if (cmd == CDL_PAUSE) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; var pauseDelay: u32 = 7000; if (self.readOngoing != 0) { if ((self.mode as u32 & MODE_SPEED) != 0) { pauseDelay = 33869 * 35; } else { pauseDelay = 33869 * 70; } } self.delay = pauseDelay; return; } if (cmd == CDL_INIT) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; self.delay = CD_DELAY_1MS; return; } if (cmd == CDL_SEEKL) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; self.delay = CD_DELAY_1MS; return; } if (cmd == CDL_GETID) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; self.delay = CD_DELAY_FR; return; } if (cmd == CDL_READN || cmd == CDL_READS) { self.ifr = 3; self.pushResp(self.getStat()); self.processSetloc(); if (self.hasDisc != 0) { const lba0: u32 = self.lba; self.discReadAt(disc, lba0); } self.state = CD_STATE_READ as u8; self.prevState = CD_STATE_READ as u8; self.readOngoing = 1; if ((self.mode as u32 & MODE_XA_ADPCM) != 0) { self.xaPlaying = 1; } self.delay = self.readDelay(); return; } if (cmd == CDL_SETFILTER) { self.ifr = 3; self.pushResp(self.getStat()); self.xaFile = self.popParam() as u8; self.xaChannel = self.popParam() as u8; self.state = self.prevState; return; } if (cmd == CDL_GETPARAM) { self.ifr = 3; self.pushResp(self.getStat()); self.pushResp(self.mode as u32); self.pushResp(0); self.pushResp(self.xaFile as u32); self.pushResp(self.xaChannel as u32); self.state = self.prevState; return; } if (cmd == CDL_GETLOCL) { self.ifr = 3; var i: u32 = 0; while (i < 8) { self.pushResp(self.dataBuf[0x0C + i] as u32); i = i + 1; } self.state = self.prevState; return; } if (cmd == CDL_GETLOCP) { self.ifr = 3; const lbaRaw: u32 = self.lba; const track: u32 = discTrackNumber(disc, lbaRaw); const trackLba: u32 = discTrackLba(disc, track); var lba: i32 = lbaRaw as i32; if (self.seekPrec == 0) { lba = lba - 25; } var diff: i32 = lba - (trackLba as i32); if (diff < 0) { diff = -diff; } const du: u32 = diff as u32; const lu: u32 = lba as u32; const rmm: u32 = du / (60 * 75); const rss: u32 = (du % (60 * 75)) / 75; const rff: u32 = (du % (60 * 75)) % 75; const amm: u32 = lu / (60 * 75); const ass: u32 = (lu % (60 * 75)) / 75; const aff: u32 = (lu % (60 * 75)) % 75; var index: u32 = 1; if (discQuery(disc, lbaRaw) == 2) { index = 0; } // TS_PREGAP self.pushResp(bcdEnc(track)); self.pushResp(bcdEnc(index)); self.pushResp(bcdEnc(rmm)); self.pushResp(bcdEnc(rss)); self.pushResp(bcdEnc(rff)); self.pushResp(bcdEnc(amm)); self.pushResp(bcdEnc(ass)); self.pushResp(bcdEnc(aff)); self.state = self.prevState; return; } if (cmd == CDL_GETTN) { self.ifr = 3; self.pushResp(self.getStat()); self.pushResp(1); self.pushResp(bcdEnc(discTrackCount(disc))); self.state = self.prevState; return; } if (cmd == CDL_GETTD) { const bcd: u32 = self.popParam(); if (!bcdValid(bcd)) { self.errorOut(CD_STAT_SPINDLE, CD_ERR_INVALID_SUBFUNC); return; } const track: u32 = (bcd & 0x0F) + ((bcd >> 4) & 0x0F) * 10; const f: u32 = discTrackLba(disc, track); if (f == 0xFFFFFFFF) { self.errorOut(CD_STAT_SPINDLE, CD_ERR_INVALID_SUBFUNC); return; } self.ifr = 3; const mm: u32 = f / (60 * 75); const ss: u32 = (f % (60 * 75)) / 75; self.pushResp(self.getStat()); self.pushResp(bcdEnc(mm)); self.pushResp(bcdEnc(ss)); self.state = self.prevState; return; } if (cmd == CDL_FORWARD) { self.ifr = 3; self.pushResp(self.getStat()); self.lba = self.lba + 35; self.state = self.prevState; return; } if (cmd == CDL_BACKWARD) { self.ifr = 3; self.pushResp(self.getStat()); var lbaCur: u32 = self.lba; if (lbaCur > 35 + 150) { lbaCur = lbaCur - 35; } else { lbaCur = 150; } self.lba = lbaCur; self.state = self.prevState; return; } if (cmd == CDL_MOTORON || cmd == CDL_STOP) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; self.delay = CD_DELAY_FR; return; } if (cmd == CDL_SEEKP) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; self.delay = CD_DELAY_1MS; return; }
if (cmd == CDL_SETSESSION) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; self.delay = CD_DELAY_FR; return; } if (cmd == CDL_READTOC) { self.ifr = 3; self.pushResp(self.getStat()); self.state = CD_STATE_TX_RESP2 as u8; self.delay = CD_DELAY_FR; return; } if (cmd == CDL_PLAY) { self.ifr = 3; self.pushResp(self.getStat()); self.processSetloc(); if (self.paramW != self.paramR) { self.popParam(); } self.state = self.prevState; return; } if (cmd == CDL_GETQ) { self.ifr = 3; self.pushResp(self.getStat()); var i: u32 = 0; while (i < 10) { self.pushResp(0); i = i + 1; } self.state = self.prevState; return; } // Unknown command. self.errorOut(CD_STAT_SPINDLE, CD_ERR_INVALID_COMMAND); }
pub fn processSetloc(self: mut Self) { if (self.pendingLba == 0) { return; } self.lba = self.pendingLba; self.pendingLba = 0; }
pub fn readDelay(self: Self) u32 { if ((self.mode as u32 & MODE_SPEED) != 0) { return CD_DELAY_READ_DS; } return CD_DELAY_READ_SS; }
pub fn executeResp2(self: mut Self, disc: *mut[] Disc) { const cmd: u32 = self.pendingCmd as u32; if (cmd == CDL_INIT) { self.ifr = 2; self.pushResp(self.getStat()); self.state = CD_STATE_IDLE as u8; return; } if (cmd == CDL_SEEKL) { const tsl: u32 = discQuery(disc, self.pendingLba); if (tsl == 3) { self.errorOut(CD_STAT_SPINDLE | CD_STAT_SEEKERROR, CD_ERR_INVALID_SUBFUNC); return; } if (tsl == 1) { self.errorOut(CD_STAT_SPINDLE | CD_STAT_SEEKERROR, CD_ERR_SEEK_FAILED); return; } self.processSetloc(); self.seekPrec = 1; self.ifr = 2; self.pushResp(self.getStat()); if (self.prevState == CD_STATE_READ as u8) { self.state = CD_STATE_READ as u8; } else { self.state = CD_STATE_IDLE as u8; } return; } if (cmd == CDL_SEEKP) { self.ifr = 2; self.pushResp(self.getStat()); self.processSetloc(); self.seekPrec = 0; if (self.prevState == CD_STATE_READ as u8) { self.state = CD_STATE_READ as u8; } else { self.state = CD_STATE_IDLE as u8; } return; } if (cmd == CDL_STOP) { self.pendingLba = 150; self.processSetloc(); self.readOngoing = 0; self.xaPlaying = 0; self.ifr = 2; self.pushResp(self.getStat()); self.state = CD_STATE_IDLE as u8; self.prevState = CD_STATE_IDLE as u8; return; } if (cmd == CDL_MOTORON || cmd == CDL_SETSESSION || cmd == CDL_READTOC) { self.ifr = 2; self.pushResp(self.getStat()); self.state = CD_STATE_IDLE as u8; return; } if (cmd == CDL_PAUSE) { self.ifr = 2; self.pushResp(CD_STAT_SPINDLE); self.state = CD_STATE_IDLE as u8; self.prevState = CD_STATE_IDLE as u8; self.pendingCmd = 0; self.busy = 0; self.readOngoing = 0; self.xaPlaying = 0; return; } if (cmd == CDL_GETID) { if (self.hasDisc != 0) { self.ifr = 2; self.pushResp(0x02); // licensed flag self.pushResp(0x00); self.pushResp(0x20); // disc type = data self.pushResp(0x00); self.pushResp(0x53); // 'S' self.pushResp(0x43); // 'C' self.pushResp(0x45); // 'E' var region: u32 = 0x41; // 'A' if (self.region == 0) { region = 0x49; } // 'I' if (self.region == 2) { region = 0x45; } // 'E' self.pushResp(region); self.state = CD_STATE_IDLE as u8; return; } self.ifr = 5; self.pushResp(0x08); self.pushResp(0x40); self.pushResp(0x00); self.pushResp(0x00); self.pushResp(0x00); self.pushResp(0x00); self.pushResp(0x00); self.pushResp(0x00); self.state = CD_STATE_IDLE as u8; return; } self.state = CD_STATE_IDLE as u8; }
pub fn errorOut(self: mut Self, stat: u32, err: u32) { self.clearParam(); self.clearResp(); self.ifr = 5; var s: u32 = CD_STAT_ERROR | stat; if ((stat & CD_STAT_IDERROR) != 0) { s = stat; } if ((stat & CD_STAT_SEEKERROR) != 0) { s = stat; } self.pushResp(s); self.pushResp(err); self.prevState = CD_STATE_IDLE as u8; self.state = CD_STATE_IDLE as u8; self.pendingCmd = 0; self.busy = 0; }
pub fn update(self: mut Self, disc: *mut[] Disc, spu: *mut[] u8, ic: *mut[] u32, cop0: *mut[] u32, cyc: u32) { var delay: u32 = self.delay; if (delay > 0) { if (delay > cyc) { self.delay = delay - cyc; return; } self.delay = 0; delay = 0; } if (self.state == CD_STATE_IDLE as u8) { return; } if (self.state == CD_STATE_PLAY as u8) { return; } if ((self.ifr as u32 & 0x1F) != 0) { if ((self.ifr as u32 & self.ier as u32 & 0x1F) != 0) { irqRaise(ic, cop0, IC_CDROM); } self.delay = 2; return; } if (self.state == CD_STATE_TX_RESP1 as u8) { self.executeCommand(disc, ic, cop0); if (self.state == CD_STATE_READ as u8) { self.processSetloc(); // first sector after a command uses the ongoing-read delay // (= readDelay + 4ms). The speed-switch resync delay is // computed but then overwritten by this ongoing-read delay, // so it never applies; adding pendingSpeedSwitch (~650ms once) // delayed the first FMV sector. Drop it; just consume it. self.delay = self.readDelay() + 4 * 33869; 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(); // same self.delay = self.readDelay() + 4 * 33869; self.pendingSpeedSwitch = 0; } } else if (self.state == CD_STATE_READ as u8) { self.handleRead(disc, spu); } // Only raise CPU IRQ if the corresponding IER bit is enabled. if ((self.ifr as u32 & self.ier as u32 & 0x1F) == 0) { return; } irqRaise(ic, cop0, IC_CDROM); }
pub fn handleRead(self: mut Self, disc: *mut[] Disc, spu: *mut[] u8) { self.processSetloc(); if (self.hasDisc == 0) { self.errorOut(CD_STAT_SHELLOPEN, CD_ERR_NO_DISC); return; } const lba: u32 = self.lba; if (self.discReadAt(disc, lba) == 0) { self.errorOut(CD_STAT_SPINDLE | CD_STAT_SEEKERROR, CD_ERR_INVALID_SUBFUNC); return; } const submode: u32 = self.dataBuf[18] as u32; const isXaAudio: bool = (submode & 0x04) != 0 && (submode & 0x40) != 0; if ((self.mode as u32 & MODE_XA_ADPCM) != 0 && isXaAudio) { // SPU CD-audio; fall through to deliver INT1 self.tryDecodeXa(spu); }
var rIdx: u32 = 24; var wEnd: u32 = 24 + 0x800; if ((self.mode as u32 & MODE_SECTOR_SIZE) != 0) { rIdx = 12; wEnd = 12 + 0x924; } self.dataRidx = rIdx; self.dataWidx = wEnd;
self.ifr = 1; self.pushResp(self.getStat());
self.pendingLba = lba + 1; self.delay = self.readDelay(); }
pub fn tryDecodeXa(self: mut Self, spu: *mut[] u8) { // TODO: Revist this // Copy the current sector into a scratch buffer because the XA // helpers want a *mut[] u8 / *const[] u8 they can stride // through. var scratch: [2352]u8 = [0; 2352]; var i: u32 = 0; while (i < 2352) { scratch[i] = self.dataBuf[i]; i = i + 1; } if (!xaSectorIsAudio(scratch.asPtr())) { return; } if ((self.mode as u32 & MODE_XA_FILTER) != 0) { if (!xaSectorMatchesFilter(scratch.asPtr(), self.xaFile as u32, self.xaChannel as u32)) { return; } } var leftBuf: [2016]i32 = [0; 2016]; var rightBuf: [2016]i32 = [0; 2016]; var monoBuf: [4032]i32 = [0; 4032]; const stereo: bool = xaSectorIsStereo(scratch.asPtr()); const n: u32 = xaDecodeSector(scratch.asMutPtr(), leftBuf.asMutPtr(), rightBuf.asMutPtr(), monoBuf.asMutPtr(), self.xaLh.asMutPtr(), self.xaRh.asMutPtr()); const f18: bool = xaSectorIs18kHz(scratch.asPtr()); var m: u32 = 6; if (f18) { m = 3; } var resampleCount: u32 = 2352; if (!stereo) { resampleCount = 4704; } if (f18) { resampleCount = resampleCount * 2; } const lsL: i32 = self.xaPrevL; const lsR: i32 = self.xaPrevR; var outL: i32 = lsL; var outR: i32 = lsR; var j: u32 = 0; while (j < resampleCount) { const q: u32 = (j * m) / 7; outL = lsL; outR = lsR; if (q != 0) { var si: u32 = q - 1; if (si >= n) { si = n - 1; } if (stereo) { outL = leftBuf[si]; outR = rightBuf[si]; } else { outL = monoBuf[si]; outR = monoBuf[si]; } } if (outL < -32768) { outL = -32768; } if (outL > 32767) { outL = 32767; } if (outR < -32768) { outR = -32768; } if (outR > 32767) { outR = 32767; } spuPushCdSample(spu, (outL as u32) & 0xFFFF, (outR as u32) & 0xFFFF); j = j + 1; } // Carry this sector's last output sample as the next sector's ls seed. self.xaPrevL = outL; self.xaPrevR = outR; }
pub fn discReadAt(self: Self, disc: *mut[] Disc, lba: u32) i32 { return discRead(disc, lba, self.dataBuf.asMutPtr()); }
pub fn readData(self: mut Self) u32 { if (self.dataReq == 0) { return 0; } var rIdx: u32 = self.dataRidx; const wIdx: u32 = self.dataWidx; if (rIdx >= wIdx) { return 0; } const v: u8 = self.dataBuf[rIdx]; self.dataRidx = rIdx + 1; return v as u32; }};
// Binary-to-BCD encoder. Inputs 0..99.pub fn bcdEnc(v: u32) u32 { const lo: u32 = v % 10; const hi: u32 = (v / 10) % 10; return (hi << 4) | lo;}
// BCD validity check: both nibbles must be 0..9.pub fn bcdValid(b: u32) bool { return ((b & 0x0F) <= 9) && (((b >> 4) & 0x0F) <= 9);}
// CD-ROM controller version-ID table, 4 bytes per version. Index 1 = C0A.pub fn versionByteForIdx(version: u32, n: u32) u32 { if (n == 0) { return 0x94; } if (n == 1) { return 0x09; } if (n == 2) { return 0x19; } return 0xC0;}