1//! SNES APU (audio processing unit) 2//! 3//! The APU consists of two primary components: an SPC700 CPU and a DSP that can play back ADPCM samples 4 5mod bootrom; 6mod dsp; 7mod timer; 8 9use crate::api::SnesEmulatorConfig; 10use crate::apu::dsp::AudioDsp; 11use crate::apu::timer::{FastTimer, SlowTimer}; 12use crate::constants; 13use bincode::{Decode, Encode}; 14use jgenesis_common::frontend::TimingMode; 15use jgenesis_common::num::GetBit; 16use spc700_emu::Spc700; 17use spc700_emu::traits::BusInterface; 18 19const AUDIO_RAM_LEN: usize = 64 * 1024; 20 21// The APU frequency is 32000 Hz on paper, but hardware tends to run slightly faster than that 22pub const OUTPUT_FREQUENCY: u64 = 32040; 23 24// Roughly 24.607 MHz 25const ACTUAL_APU_MASTER_CLOCK_FREQUENCY: u64 = OUTPUT_FREQUENCY * 768; 26 27// APU master clock rate increased such that audio signal is timed to 60Hz for NTSC (and slightly under 50Hz for PAL) 28// Specifically, (actual_mclk_rate * 60.099 / 60.0) 29const ADJUSTED_APU_MASTER_CLOCK_FREQUENCY: u64 = ACTUAL_APU_MASTER_CLOCK_FREQUENCY * 60099 / 60000; 30 31// APU outputs a sample every 24 * 32 master clocks 32const SAMPLE_DIVIDER: u8 = 32; 33 34type AudioRam = [u8; AUDIO_RAM_LEN]; 35 36#[derive(Debug, Clone, Encode, Decode)] 37struct ApuRegisters { 38 boot_rom_mapped: bool, 39 main_cpu_communication: [u8; 4], 40 spc700_communication: [u8; 4], 41 port_01_reset: bool, 42 port_23_reset: bool, 43 timer_0: SlowTimer, 44 timer_1: SlowTimer, 45 timer_2: FastTimer, 46 auxio4: u8, 47 auxio5: u8, 48} 49 50impl ApuRegisters { 51 fn new() -> Self { 52 Self { 53 boot_rom_mapped: true, 54 main_cpu_communication: [0; 4], 55 spc700_communication: [0; 4], 56 port_01_reset: false, 57 port_23_reset: false, 58 timer_0: SlowTimer::new(), 59 timer_1: SlowTimer::new(), 60 timer_2: FastTimer::new(), 61 auxio4: 0, 62 auxio5: 0, 63 } 64 } 65 66 fn read(&mut self, register: u16, dsp: &AudioDsp) -> u8 { 67 log::trace!("SPC700 register read: {register}"); 68 69 match register { 70 0 => { 71 log::warn!("Unimplemented APU test register was read"); 72 0x00 73 } 74 1 => { 75 // Control register 76 u8::from(self.timer_0.enabled()) 77 | (u8::from(self.timer_1.enabled()) << 1) 78 | (u8::from(self.timer_2.enabled()) << 2) 79 | (u8::from(self.boot_rom_mapped) << 7) 80 } 81 2 => dsp.read_address(), 82 3 => dsp.read_register(), 83 4 => self.main_cpu_communication[0], 84 5 => self.main_cpu_communication[1], 85 6 => self.main_cpu_communication[2], 86 7 => self.main_cpu_communication[3], 87 8 => self.auxio4, 88 9 => self.auxio5, 89 10 => self.timer_0.divider(), 90 11 => self.timer_1.divider(), 91 12 => self.timer_2.divider(), 92 13 => self.timer_0.read_output(), 93 14 => self.timer_1.read_output(), 94 15 => self.timer_2.read_output(), 95 _ => panic!("invalid APU register: {register}"), 96 } 97 } 98 99 fn write(&mut self, register: u16, value: u8, dsp: &mut AudioDsp) { 100 log::trace!("SPC700 register write: {register} {value:02X}"); 101 102 #[allow(clippy::match_same_arms)] 103 match register { 104 0 => { 105 log::warn!("Unimplemented APU test register was written with value {value:02X}"); 106 } 107 1 => { 108 // Control register 109 self.timer_0.set_enabled(value.bit(0)); 110 self.timer_1.set_enabled(value.bit(1)); 111 self.timer_2.set_enabled(value.bit(2)); 112 113 if value.bit(4) { 114 self.main_cpu_communication[0] = 0; 115 self.main_cpu_communication[1] = 0; 116 self.port_01_reset = true; 117 } 118 119 if value.bit(5) { 120 self.main_cpu_communication[2] = 0; 121 self.main_cpu_communication[3] = 0; 122 self.port_23_reset = true; 123 } 124 125 self.boot_rom_mapped = value.bit(7); 126 127 log::trace!("APU control register write: {value:02X}"); 128 log::trace!(" Timer 0 enabled: {}", value.bit(0)); 129 log::trace!(" Timer 1 enabled: {}", value.bit(1)); 130 log::trace!(" Timer 2 enabled: {}", value.bit(2)); 131 log::trace!(" Boot ROM mapped: {}", self.boot_rom_mapped); 132 } 133 2 => { 134 dsp.write_address(value); 135 } 136 3 => { 137 dsp.write_register(value); 138 } 139 4 => { 140 self.spc700_communication[0] = value; 141 } 142 5 => { 143 self.spc700_communication[1] = value; 144 } 145 6 => { 146 self.spc700_communication[2] = value; 147 } 148 7 => { 149 self.spc700_communication[3] = value; 150 } 151 8 => { 152 // AUXIO4 register; acts as R/W memory 153 self.auxio4 = value; 154 } 155 9 => { 156 // AUXIO5 register; acts as R/W memory 157 self.auxio5 = value; 158 } 159 10 => { 160 self.timer_0.set_divider(value); 161 log::trace!("Timer 0 divider: {value:02X}"); 162 } 163 11 => { 164 self.timer_1.set_divider(value); 165 log::trace!("Timer 1 divider: {value:02X}"); 166 } 167 12 => { 168 self.timer_2.set_divider(value); 169 log::trace!("Timer 2 divider: {value:02X}"); 170 } 171 13..=15 => { 172 // Timer outputs; writes do nothing 173 } 174 _ => panic!("invalid APU register: {register}"), 175 } 176 } 177} 178 179struct Spc700Bus<'a> { 180 dsp: &'a mut AudioDsp, 181 audio_ram: &'a mut Box<AudioRam>, 182 registers: &'a mut ApuRegisters, 183} 184 185impl BusInterface for Spc700Bus<'_> { 186 #[inline] 187 fn read(&mut self, address: u16) -> u8 { 188 log::trace!("SPC700 bus read: {address:04X}"); 189 190 match address { 191 0x0000..=0x00EF | 0x0100..=0xFFBF => self.audio_ram[address as usize], 192 0x00F0..=0x00FF => self.registers.read(address & 0xF, self.dsp), 193 0xFFC0..=0xFFFF => { 194 if self.registers.boot_rom_mapped { 195 bootrom::SPC700_BOOT_ROM[(address & 0x003F) as usize] 196 } else { 197 self.audio_ram[address as usize] 198 } 199 } 200 } 201 } 202 203 #[inline] 204 fn write(&mut self, address: u16, value: u8) { 205 log::trace!("SPC700 bus write: {address:04X} {value:02X}"); 206 207 match address { 208 0x0000..=0x00EF | 0x0100..=0xFFFF => { 209 self.audio_ram[address as usize] = value; 210 } 211 0x00F0..=0x00FF => { 212 self.registers.write(address & 0xF, value, self.dsp); 213 self.audio_ram[address as usize] = value; 214 } 215 } 216 } 217 218 #[inline] 219 fn idle(&mut self) {} 220} 221 222#[derive(Debug, Clone, Copy, PartialEq)] 223pub enum ApuTickEffect { 224 None, 225 OutputSample(f64, f64), 226} 227 228#[derive(Debug, Clone, Encode, Decode)] 229pub struct Apu { 230 spc700: Spc700, 231 dsp: AudioDsp, 232 audio_ram: Box<AudioRam>, 233 registers: ApuRegisters, 234 main_master_clock_frequency: u64, 235 master_cycles_product: u64, 236 sample_divider: u8, 237 enable_audio_60hz_hack: bool, 238} 239 240macro_rules! new_spc700_bus { 241 ($self:expr) => { 242 Spc700Bus { 243 dsp: &mut $self.dsp, 244 audio_ram: &mut $self.audio_ram, 245 registers: &mut $self.registers, 246 } 247 }; 248} 249 250impl Apu { 251 pub fn new(timing_mode: TimingMode, config: SnesEmulatorConfig) -> Self { 252 let main_master_clock_frequency = match timing_mode { 253 TimingMode::Ntsc => constants::NTSC_MASTER_CLOCK_FREQUENCY, 254 TimingMode::Pal => constants::PAL_MASTER_CLOCK_FREQUENCY, 255 }; 256 257 let mut apu = Self { 258 spc700: Spc700::new(), 259 dsp: AudioDsp::new(config.audio_interpolation), 260 audio_ram: vec![0; AUDIO_RAM_LEN].into_boxed_slice().try_into().unwrap(), 261 registers: ApuRegisters::new(), 262 main_master_clock_frequency, 263 master_cycles_product: 0, 264 sample_divider: SAMPLE_DIVIDER, 265 enable_audio_60hz_hack: config.audio_60hz_hack, 266 }; 267 268 apu.spc700.reset(&mut new_spc700_bus!(apu)); 269 270 apu 271 } 272 273 #[must_use] 274 pub fn tick(&mut self, main_master_cycles: u64) -> ApuTickEffect { 275 let apu_master_clock_frequency = if self.enable_audio_60hz_hack { 276 ADJUSTED_APU_MASTER_CLOCK_FREQUENCY 277 } else { 278 ACTUAL_APU_MASTER_CLOCK_FREQUENCY 279 }; 280 self.master_cycles_product += main_master_cycles * apu_master_clock_frequency; 281 282 while self.master_cycles_product >= 24 * self.main_master_clock_frequency { 283 self.master_cycles_product -= 24 * self.main_master_clock_frequency; 284 self.clock(); 285 286 self.sample_divider -= 1; 287 if self.sample_divider == 0 { 288 self.sample_divider = SAMPLE_DIVIDER; 289 290 let (sample_l, sample_r) = self.dsp.clock(&mut self.audio_ram); 291 let sample_l = f64::from(sample_l) / -f64::from(i16::MIN); 292 let sample_r = f64::from(sample_r) / -f64::from(i16::MIN); 293 return ApuTickEffect::OutputSample(sample_l, sample_r); 294 } 295 } 296 297 ApuTickEffect::None 298 } 299 300 fn clock(&mut self) { 301 self.registers.port_01_reset = false; 302 self.registers.port_23_reset = false; 303 304 self.spc700.tick(&mut new_spc700_bus!(self)); 305 306 self.registers.timer_0.tick(); 307 self.registers.timer_1.tick(); 308 self.registers.timer_2.tick(); 309 } 310 311 pub fn read_port(&mut self, address: u32) -> u8 { 312 self.registers.spc700_communication[(address & 0x3) as usize] 313 } 314 315 pub fn write_port(&mut self, address: u32, value: u8) { 316 let port_idx = address & 0x3; 317 318 if (self.registers.port_01_reset && port_idx <= 1) 319 || (self.registers.port_23_reset && port_idx >= 2) 320 { 321 // Discard 65816 communication port writes that occur on the same cycle as a latch 322 // reset via the SPC700 writing bits 4/5 in $F1. 323 // This fixes Kishin Douji Zenki: Tenchi Meidou failing to boot due to a livelock between 324 // the two CPUs 325 return; 326 } 327 328 self.registers.main_cpu_communication[port_idx as usize] = value; 329 } 330 331 pub fn reset(&mut self) { 332 self.registers.boot_rom_mapped = true; 333 self.spc700.reset(&mut new_spc700_bus!(self)); 334 self.dsp.reset(); 335 } 336 337 pub fn update_config(&mut self, config: SnesEmulatorConfig) { 338 self.dsp.update_audio_interpolation(config.audio_interpolation); 339 self.enable_audio_60hz_hack = config.audio_60hz_hack; 340 } 341}