apu.rsannotatedapu.rssource341 lines · 10.8 KB · raw
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}