1//! Sega CD CD-ROM drive
2
3pub mod cdc;
4pub mod cdd;
5
6use crate::api::SegaCdLoadResult;
7use crate::cddrive::cdc::RchipDmaArgs;
8use crate::cddrive::cdd::CdModel;
9use crate::rf5c164::Rf5c164;
10use crate::wordram::WordRam;
11use crate::{api, memory};
12use bincode::{Decode, Encode};
13use cdc::Rchip;
14use cdd::CdDrive;
15use cdrom::reader::CdRom;
16use genesis_config::{GenesisRegion, SegaCdEmulatorConfig};
17use jgenesis_proc_macros::PartialClone;
18use std::array;
19
20const SEGA_CD_MCLK_FREQUENCY: u64 = api::SEGA_CD_MASTER_CLOCK_RATE;
21const CD_DA_FREQUENCY: u64 = 44100;
22
23// The CD drive's master clock is not actually derived from the Sega CD's master clock, but it's
24// much easier to emulate by pretending that it is
25#[derive(Debug, Clone, Encode, Decode)]
26struct CdPrescaler {
27    sega_cd_mclk_cycles: u64,
28    drive_cycle_product: u64,
29}
30
31impl CdPrescaler {
32    fn new() -> Self {
33        Self { sega_cd_mclk_cycles: 0, drive_cycle_product: 0 }
34    }
35
36    fn tick(
37        &mut self,
38        sega_cd_mclk_cycles: u64,
39        mut callback: impl FnMut() -> SegaCdLoadResult<()>,
40    ) -> SegaCdLoadResult<()> {
41        self.drive_cycle_product += sega_cd_mclk_cycles * CD_DA_FREQUENCY;
42
43        while self.drive_cycle_product >= SEGA_CD_MCLK_FREQUENCY {
44            self.drive_cycle_product -= SEGA_CD_MCLK_FREQUENCY;
45            callback()?;
46        }
47
48        Ok(())
49    }
50}
51
52#[derive(Debug, Encode, Decode, PartialClone)]
53pub struct CdController {
54    #[partial_clone(partial)]
55    drive: CdDrive,
56    rchip: Rchip,
57    sector_buffer: Box<[u8; cdrom::BYTES_PER_SECTOR as usize]>,
58    prescaler: CdPrescaler,
59}
60
61impl CdController {
62    pub fn new(disc: Option<CdRom>, model: CdModel, config: &SegaCdEmulatorConfig) -> Self {
63        Self {
64            drive: CdDrive::new(disc, model, config),
65            rchip: Rchip::new(),
66            sector_buffer: Box::new(array::from_fn(|_| 0)),
67            prescaler: CdPrescaler::new(),
68        }
69    }
70
71    pub fn tick(
72        &mut self,
73        mclk_cycles: u64,
74        word_ram: &mut WordRam,
75        prg_ram: &mut [u16; memory::PRG_RAM_LEN_WORDS],
76        prg_ram_accessible: bool,
77        pcm: &mut Rf5c164,
78        mut audio_callback: impl FnMut(f64, f64),
79    ) -> SegaCdLoadResult<()> {
80        self.prescaler.tick(mclk_cycles, || {
81            let (sample_l, sample_r) = self.drive.clock_44100hz(
82                &mut self.rchip,
83                RchipDmaArgs { word_ram, prg_ram, prg_ram_accessible, pcm },
84            )?;
85
86            audio_callback(sample_l, sample_r);
87
88            Ok(())
89        })
90    }
91
92    pub fn cdd(&self) -> &CdDrive {
93        &self.drive
94    }
95
96    pub fn cdd_mut(&mut self) -> &mut CdDrive {
97        &mut self.drive
98    }
99
100    pub fn cdc(&self) -> &Rchip {
101        &self.rchip
102    }
103
104    pub fn cdc_mut(&mut self) -> &mut Rchip {
105        &mut self.rchip
106    }
107
108    pub fn disc_title(&mut self, region: GenesisRegion) -> SegaCdLoadResult<Option<String>> {
109        self.drive.disc_title(region)
110    }
111
112    pub fn take_disc(&mut self) -> Option<CdRom> {
113        self.drive.take_disc()
114    }
115
116    pub fn take_disc_from(&mut self, other: &mut Self) {
117        self.drive.take_disc_from(&mut other.drive);
118    }
119
120    pub fn reset(&mut self) {
121        self.drive.reset();
122        self.rchip.reset();
123    }
124}