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;
The CD drive's master clock is not actually derived from the Sega CD's master clock, but it's much easier to emulate by pretending that it is
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}