From f804fede95ccd786d687e056edd49c253b145b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kleines=20Filmr=C3=B6llchen?= Date: Tue, 15 Oct 2024 22:50:00 +0200 Subject: [PATCH] sapemu: TONS of internal state & access sequencing work with some exceptions, this should be everything needed for accessing memory and cpu-exposed registers in a cycle-accurate way. --- sapemu/src/dsp.rs | 827 ++++++++++++++++++++++++++++- sapemu/src/dsp/audio_processing.rs | 9 + sapemu/src/dsp/registers.rs | 171 +++--- sapemu/src/dsp/tables.rs | 2 +- sapemu/src/memory.rs | 2 +- sapemu/src/smp.rs | 22 + sapemu/src/smp/peripherals.rs | 4 +- sapemu/src/test.rs | 62 +-- src/brr/mod.rs | 8 +- 9 files changed, 977 insertions(+), 130 deletions(-) create mode 100644 sapemu/src/dsp/audio_processing.rs diff --git a/sapemu/src/dsp.rs b/sapemu/src/dsp.rs index 7ec8a47..d71aa40 100644 --- a/sapemu/src/dsp.rs +++ b/sapemu/src/dsp.rs @@ -1,22 +1,73 @@ //! S-DSP (Synthesizer) emulator. -use registers::DspRegisters; +#![allow(unused)] +use registers::{DspFlags, DspRegisters, PerVoiceFlag}; +use spcasm::brr; +use tables::RATE_TABLE; + +use crate::memory::Memory; +use crate::trace; + +mod audio_processing; pub mod registers; mod tables; /// State of the S-DSP. -#[derive(Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Dsp { /// Public DSP registers. pub registers: DspRegisters, + + // Main state + /// Main volume for left channel, copied from DSP registers. + main_volume_left: i8, + /// Main volume for right channel, copied from DSP registers. + main_volume_right: i8, + /// Internal voice state, data copied from DSP registers into here as in hardware timings. + voices_state: [VoiceState; 8], + /// Sample directory address. + sample_directory: u16, + /// Noise frequency divider. + noise_frequency: u16, + /// Last stereo sample (left, right) produced by the DSP, for sending to other audio systems. + pub last_sample: (i16, i16), + + // Echo + /// Echo volume for left channel, copied from DSP registers. + echo_volume_left: i8, + /// Echo volume for right channel, copied from DSP registers. + echo_volume_right: i8, + /// Feedback from echo output back to input, copied from DSP registers. + echo_feedback: i8, + /// Amount of samples that the echo buffer delays by. + echo_delay: u16, + /// Base address of in-memory echo ring buffer. + echo_base_address: u16, + /// Disable all writes to the echo buffer (true), copied from DSP registers. + echo_write_disable: bool, + /// Current memory address where the echo buffer data is written or read. + echo_buffer_head: u16, + /// FIR filter coefficients, data copied from DSP registers. + fir_coefficients: [u8; 8], + /// Last few samples that passed through the FIR filter, for the previous samples in the FIR calculation. + fir_buffer: [u16; 8], + /// Echo output or input values, which are first read from memory, then written (with the new output) into memory + /// at the appropriate cycles. + echo_data: (u16, u16), + + // Cycle-accurate emulation + /// Internal sample cycle, used to perform memory accesses at the correct time. + sample_cycle: u8, + /// Whether the next cycles 30 and 31 read the KON and KOFF flags (which are only read every 2 samples). + current_sample_read_kon_koff: bool, } impl Dsp { /// Create a new DSP instance. #[must_use] pub fn new() -> Self { - Self { registers: DspRegisters::default() } + Self::default() } /// Load the register state from a bank of registers (e.g. a memory dump). @@ -26,4 +77,774 @@ impl Dsp { self.registers.write(address as u8, *value); } } + + /// Execute one DSP tick. This may execute up to two memory accesses. + pub fn tick(&mut self, memory: &mut Memory) { + self.sample_cycle = (self.sample_cycle + 1) % 32; + if self.sample_cycle == 0 { + self.current_sample_read_kon_koff = !self.current_sample_read_kon_koff; + } + + self.read_memory(memory); + self.read_write_registers(); + + // Note that we can't run the processing just once per sample cycle due to when hardware registers are written + // back. + self.run_audio_processing(memory); + } + + /// Execute memory reads for this sample cycle. + #[allow(clippy::match_same_arms)] + fn read_memory(&mut self, memory: &mut Memory) { + match self.sample_cycle { + 0 => self.voices_state[0].read_brr_2(memory), + 1 => self.voices_state[1].read_sample_directory(memory, self.sample_directory), + 2 => self.voices_state[1].read_brr_1(memory), + 3 => self.voices_state[1].read_brr_2(memory), + 4 => self.voices_state[2].read_sample_directory(memory, self.sample_directory), + 5 => self.voices_state[2].read_brr_1(memory), + 6 => self.voices_state[2].read_brr_2(memory), + 7 => self.voices_state[3].read_sample_directory(memory, self.sample_directory), + 8 => self.voices_state[3].read_brr_1(memory), + 9 => self.voices_state[3].read_brr_2(memory), + 10 => self.voices_state[4].read_sample_directory(memory, self.sample_directory), + 11 => self.voices_state[4].read_brr_1(memory), + 12 => self.voices_state[4].read_brr_2(memory), + 13 => self.voices_state[5].read_sample_directory(memory, self.sample_directory), + 14 => self.voices_state[5].read_brr_1(memory), + 15 => self.voices_state[5].read_brr_2(memory), + 16 => self.voices_state[6].read_sample_directory(memory, self.sample_directory), + 17 => self.voices_state[6].read_brr_1(memory), + 18 => self.voices_state[6].read_brr_2(memory), + 19 => self.voices_state[7].read_sample_directory(memory, self.sample_directory), + 20 => self.voices_state[7].read_brr_1(memory), + 21 => self.voices_state[7].read_brr_2(memory), + 22 => self.voices_state[0].read_sample_directory(memory, self.sample_directory), + 23 => self.read_echo(memory, true), + 24 => self.read_echo(memory, false), + 25 => {}, + 26 => self.voices_state[0].read_brr_1(memory), + 27 => {}, + 28 => {}, + 29 => {}, + 30 => self.write_echo(memory, true), + 31 => self.write_echo(memory, false), + _ => unreachable!(), + } + } + + /// Execute BRR register reads and writes for this sample cycle. + #[allow(clippy::cast_possible_wrap, clippy::too_many_lines)] + fn read_write_registers(&mut self) { + match self.sample_cycle { + 0 => { + // V0VOLL + self.voices_state[0].volume_left = self.registers.voices[0].volume_left as i8; + // V2SRCN + self.voices_state[2].sample_index = self.registers.voices[2].sample_number; + }, + 1 => { + // V0VOLR + self.voices_state[0].volume_right = self.registers.voices[0].volume_right as i8; + // V1PITCHL + self.voices_state[1].pitch_counter.set_lsb(self.registers.voices[1].pitch_low); + // V1ADSR1 + self.voices_state[1].envelope_settings.set_adsr1(self.registers.voices[1].adsr_low); + // ENDX.0 + self.registers + .voice_end + .set(PerVoiceFlag::ZERO, self.voices_state[0].brr_decoder_step.just_read_end_block()); + }, + 2 => { + // V0ENVX + self.registers.voices[0].envelope_value = self.voices_state[0].envelope_output(); + // V1PITCHH + self.voices_state[1].pitch_counter.set_msb(self.registers.voices[1].pitch_high); + // V1ADSR2 / V1GAIN + self.voices_state[1].envelope_settings.set_adsr2(self.registers.voices[1].adsr_high); + self.voices_state[1].envelope_settings.set_gain(self.registers.voices[1].gain_settings); + // FLG.7 for V1 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[1].soft_reset(); + } + }, + 3 => { + // V0OUTX + self.registers.voices[0].output_value = self.voices_state[0].output(); + // V1VOLL + self.voices_state[1].volume_left = self.registers.voices[1].volume_left as i8; + // V3SRCN + self.voices_state[3].sample_index = self.registers.voices[3].sample_number; + }, + 4 => { + // V1VOLR + self.voices_state[1].volume_right = self.registers.voices[1].volume_right as i8; + // V2PITCHL + self.voices_state[2].pitch_counter.set_lsb(self.registers.voices[2].pitch_low); + // V2ADSR1 + self.voices_state[2].envelope_settings.set_adsr1(self.registers.voices[2].adsr_low); + // ENDX.1 + self.registers + .voice_end + .set(PerVoiceFlag::ONE, self.voices_state[1].brr_decoder_step.just_read_end_block()); + }, + 5 => { + // V1ENVX + self.registers.voices[1].envelope_value = self.voices_state[1].envelope_output(); + // V2PITCHH + self.voices_state[2].pitch_counter.set_msb(self.registers.voices[2].pitch_high); + // V2ADSR2 / V2GAIN + self.voices_state[2].envelope_settings.set_adsr2(self.registers.voices[2].adsr_high); + self.voices_state[2].envelope_settings.set_gain(self.registers.voices[2].gain_settings); + // FLG.7 for V2 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[2].soft_reset(); + } + }, + 6 => { + // V1OUTX + self.registers.voices[1].output_value = self.voices_state[1].output(); + // V2VOLL + self.voices_state[2].volume_left = self.registers.voices[2].volume_left as i8; + // V4SRCN + self.voices_state[4].sample_index = self.registers.voices[4].sample_number; + }, + 7 => { + // V2VOLR + self.voices_state[2].volume_right = self.registers.voices[2].volume_right as i8; + // V3PITCHL + self.voices_state[3].pitch_counter.set_lsb(self.registers.voices[3].pitch_low); + // V3ADSR1 + self.voices_state[3].envelope_settings.set_adsr1(self.registers.voices[3].adsr_low); + // ENDX.2 + self.registers + .voice_end + .set(PerVoiceFlag::TWO, self.voices_state[2].brr_decoder_step.just_read_end_block()); + }, + 8 => { + // V2ENVX + self.registers.voices[2].envelope_value = self.voices_state[2].envelope_output(); + // V3PITCHH + self.voices_state[3].pitch_counter.set_msb(self.registers.voices[3].pitch_high); + // V3ADSR2 / V3GAIN + self.voices_state[3].envelope_settings.set_adsr2(self.registers.voices[3].adsr_high); + self.voices_state[3].envelope_settings.set_gain(self.registers.voices[3].gain_settings); + // FLG.7 for V3 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[3].soft_reset(); + } + }, + 9 => { + // V2OUTX + self.registers.voices[2].output_value = self.voices_state[2].output(); + // V3VOLL + self.voices_state[3].volume_left = self.registers.voices[3].volume_left as i8; + // V5SRCN + self.voices_state[5].sample_index = self.registers.voices[5].sample_number; + }, + 10 => { + // V3VOLR + self.voices_state[3].volume_right = self.registers.voices[3].volume_right as i8; + // V4PITCHL + self.voices_state[4].pitch_counter.set_lsb(self.registers.voices[4].pitch_low); + // V4ADSR1 + self.voices_state[4].envelope_settings.set_adsr1(self.registers.voices[4].adsr_low); + // ENDX.3 + self.registers + .voice_end + .set(PerVoiceFlag::THREE, self.voices_state[3].brr_decoder_step.just_read_end_block()); + }, + 11 => { + // V3ENVX + self.registers.voices[3].envelope_value = self.voices_state[3].envelope_output(); + // V4PITCHH + self.voices_state[4].pitch_counter.set_msb(self.registers.voices[4].pitch_high); + // V4ADSR2 / V4GAIN + self.voices_state[4].envelope_settings.set_adsr2(self.registers.voices[4].adsr_high); + self.voices_state[4].envelope_settings.set_gain(self.registers.voices[4].gain_settings); + // FLG.7 for V4 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[4].soft_reset(); + } + }, + 12 => { + // V3OUTX + self.registers.voices[3].output_value = self.voices_state[3].output(); + // V4VOLL + self.voices_state[4].volume_left = self.registers.voices[4].volume_left as i8; + // V6SRCN + self.voices_state[6].sample_index = self.registers.voices[6].sample_number; + }, + 13 => { + // V4VOLR + self.voices_state[4].volume_right = self.registers.voices[4].volume_right as i8; + // V5PITCHL + self.voices_state[5].pitch_counter.set_lsb(self.registers.voices[5].pitch_low); + // V5ADSR1 + self.voices_state[5].envelope_settings.set_adsr1(self.registers.voices[5].adsr_low); + // ENDX.4 + self.registers + .voice_end + .set(PerVoiceFlag::FOUR, self.voices_state[4].brr_decoder_step.just_read_end_block()); + }, + 14 => { + // V4ENVX + self.registers.voices[4].envelope_value = self.voices_state[4].envelope_output(); + // V5PITCHH + self.voices_state[5].pitch_counter.set_msb(self.registers.voices[5].pitch_high); + // V5ADSR2 / V5GAIN + self.voices_state[5].envelope_settings.set_adsr2(self.registers.voices[5].adsr_high); + self.voices_state[5].envelope_settings.set_gain(self.registers.voices[5].gain_settings); + // FLG.7 for V5 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[5].soft_reset(); + } + }, + 15 => { + // V4OUTX + self.registers.voices[4].output_value = self.voices_state[4].output(); + // V5VOLL + self.voices_state[5].volume_left = self.registers.voices[5].volume_left as i8; + // V7SRCN + self.voices_state[7].sample_index = self.registers.voices[7].sample_number; + }, + 16 => { + // V5VOLR + self.voices_state[5].volume_right = self.registers.voices[5].volume_right as i8; + // V6PITCHL + self.voices_state[6].pitch_counter.set_lsb(self.registers.voices[6].pitch_low); + // V6ADSR1 + self.voices_state[6].envelope_settings.set_adsr1(self.registers.voices[6].adsr_low); + // ENDX.5 + self.registers + .voice_end + .set(PerVoiceFlag::FIVE, self.voices_state[5].brr_decoder_step.just_read_end_block()); + }, + 17 => { + // V5ENVX + self.registers.voices[5].envelope_value = self.voices_state[5].envelope_output(); + // V6PITCHH + self.voices_state[6].pitch_counter.set_msb(self.registers.voices[6].pitch_high); + // V6ADSR2 / V6GAIN + self.voices_state[6].envelope_settings.set_adsr2(self.registers.voices[6].adsr_high); + self.voices_state[6].envelope_settings.set_gain(self.registers.voices[6].gain_settings); + // FLG.7 for V6 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[6].soft_reset(); + } + }, + 18 => { + // V5OUTX + self.registers.voices[5].output_value = self.voices_state[5].output(); + // V6VOLL + self.voices_state[6].volume_left = self.registers.voices[6].volume_left as i8; + // V0SRCN + self.voices_state[0].sample_index = self.registers.voices[0].sample_number; + }, + 19 => { + // V6VOLR + self.voices_state[6].volume_right = self.registers.voices[6].volume_right as i8; + // V7PITCHL + self.voices_state[7].pitch_counter.set_lsb(self.registers.voices[7].pitch_low); + // V7ADSR1 + self.voices_state[7].envelope_settings.set_adsr1(self.registers.voices[7].adsr_low); + // ENDX.6 + self.registers + .voice_end + .set(PerVoiceFlag::SIX, self.voices_state[6].brr_decoder_step.just_read_end_block()); + }, + 20 => { + // V6ENVX + self.registers.voices[6].envelope_value = self.voices_state[6].envelope_output(); + // V7PITCHH + self.voices_state[7].pitch_counter.set_msb(self.registers.voices[7].pitch_high); + // V7ADSR2 / V7GAIN + self.voices_state[7].envelope_settings.set_adsr2(self.registers.voices[7].adsr_high); + self.voices_state[7].envelope_settings.set_gain(self.registers.voices[7].gain_settings); + // FLG.7 for V7 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[7].soft_reset(); + } + }, + 21 => { + // V6OUTX + self.registers.voices[6].output_value = self.voices_state[6].output(); + // V7VOLL + self.voices_state[7].volume_left = self.registers.voices[7].volume_left as i8; + // V1SRCN + self.voices_state[1].sample_index = self.registers.voices[1].sample_number; + }, + 22 => { + // V7VOLR + self.voices_state[7].volume_right = self.registers.voices[7].volume_right as i8; + // V0PITCHL + self.voices_state[0].pitch_counter.set_lsb(self.registers.voices[0].pitch_low); + // V0ADSR1 + self.voices_state[0].envelope_settings.set_adsr1(self.registers.voices[0].adsr_low); + // ENDX.7 + self.registers + .voice_end + .set(PerVoiceFlag::SEVEN, self.voices_state[7].brr_decoder_step.just_read_end_block()); + }, + 23 => { + // V7ENVX + self.registers.voices[7].envelope_value = self.voices_state[7].envelope_output(); + // V0PITCHH + self.voices_state[0].pitch_counter.set_msb(self.registers.voices[0].pitch_high); + // FIR0 + self.fir_coefficients[0] = self.registers.fir_coefficients[0]; + }, + 24 => { + // V7OUTX + self.registers.voices[7].output_value = self.voices_state[7].output(); + // FIR1 + self.fir_coefficients[1] = self.registers.fir_coefficients[1]; + // FIR2 + self.fir_coefficients[2] = self.registers.fir_coefficients[2]; + }, + 25 => { + // FIR3 + self.fir_coefficients[3] = self.registers.fir_coefficients[3]; + // FIR4 + self.fir_coefficients[4] = self.registers.fir_coefficients[4]; + // FIR5 + self.fir_coefficients[5] = self.registers.fir_coefficients[5]; + }, + 26 => { + // FIR6 + self.fir_coefficients[6] = self.registers.fir_coefficients[6]; + // FIR7 + self.fir_coefficients[7] = self.registers.fir_coefficients[7]; + }, + 27 => { + // MVOLL + self.main_volume_left = self.registers.main_volume_left as i8; + // EVOLL + self.echo_volume_left = self.registers.echo_volume_left as i8; + // EFB + self.echo_feedback = self.registers.echo_feedback_volume as i8; + }, + 28 => { + // MVOLR + self.main_volume_left = self.registers.main_volume_left as i8; + // EVOLR + self.echo_volume_left = self.registers.echo_volume_left as i8; + // PMON + self.voices_state[1].pitch_mod_enable = self.registers.pitch_mod_enable.contains(PerVoiceFlag::ONE); + self.voices_state[2].pitch_mod_enable = self.registers.pitch_mod_enable.contains(PerVoiceFlag::TWO); + self.voices_state[3].pitch_mod_enable = self.registers.pitch_mod_enable.contains(PerVoiceFlag::THREE); + self.voices_state[4].pitch_mod_enable = self.registers.pitch_mod_enable.contains(PerVoiceFlag::FOUR); + self.voices_state[5].pitch_mod_enable = self.registers.pitch_mod_enable.contains(PerVoiceFlag::FIVE); + self.voices_state[6].pitch_mod_enable = self.registers.pitch_mod_enable.contains(PerVoiceFlag::SIX); + self.voices_state[7].pitch_mod_enable = self.registers.pitch_mod_enable.contains(PerVoiceFlag::SEVEN); + }, + 29 => { + // NON + self.voices_state[0].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::ZERO); + self.voices_state[1].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::ONE); + self.voices_state[2].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::TWO); + self.voices_state[3].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::THREE); + self.voices_state[4].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::FOUR); + self.voices_state[5].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::FIVE); + self.voices_state[6].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::SIX); + self.voices_state[7].noise_enable = self.registers.noise_enable.contains(PerVoiceFlag::SEVEN); + // EON + self.voices_state[0].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::ZERO); + self.voices_state[1].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::ONE); + self.voices_state[2].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::TWO); + self.voices_state[3].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::THREE); + self.voices_state[4].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::FOUR); + self.voices_state[5].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::FIVE); + self.voices_state[6].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::SIX); + self.voices_state[7].echo_enable = self.registers.echo_enable.contains(PerVoiceFlag::SEVEN); + // DIR + self.sample_directory = self.registers.sample_directory(); + // FLG.5 + self.echo_write_disable = self.registers.flags.contains(DspFlags::ECHO_WRITE_DISABLE); + }, + 30 => { + // EDL + self.echo_delay = self.registers.echo_delay_samples(); + // ESA + self.echo_base_address = self.registers.echo_base_address(); + // Fullsnes says "KON?" but we assume that KON is actually read in cycle 31. + // FLG.5 (again!) + self.echo_write_disable = self.registers.flags.contains(DspFlags::ECHO_WRITE_DISABLE); + }, + 31 => { + // KOFF, KON + if self.current_sample_read_kon_koff { + self.voices_state[0].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::ZERO); + self.voices_state[1].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::ONE); + self.voices_state[2].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::TWO); + self.voices_state[3].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::THREE); + self.voices_state[4].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::FOUR); + self.voices_state[5].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::FIVE); + self.voices_state[6].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::SIX); + self.voices_state[7].was_keyed_on = self.registers.key_on.contains(PerVoiceFlag::SEVEN); + self.voices_state[0].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::ZERO); + self.voices_state[1].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::ONE); + self.voices_state[2].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::TWO); + self.voices_state[3].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::THREE); + self.voices_state[4].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::FOUR); + self.voices_state[5].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::FIVE); + self.voices_state[6].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::SIX); + self.voices_state[7].was_keyed_off = self.registers.key_off.contains(PerVoiceFlag::SEVEN); + } + // V0ADSR2 / V0GAIN + self.voices_state[0].envelope_settings.set_adsr2(self.registers.voices[0].adsr_high); + self.voices_state[0].envelope_settings.set_gain(self.registers.voices[0].gain_settings); + // FLG.0-4 + self.noise_frequency = self.registers.noise_frequency(); + // FLG.7 for V0 + if self.registers.flags.contains(DspFlags::SOFT_RESET) { + self.voices_state[0].soft_reset(); + } + }, + _ => unreachable!(), + } + } + + fn read_echo(&mut self, memory: &Memory, is_left: bool) { + if is_left { + let lsb = memory.read(self.echo_buffer_head, false); + let msb = memory.read(self.echo_buffer_head.wrapping_add(1), false); + self.echo_data.0 = echo_value_from_memory(lsb, msb); + trace!("echo read left: {}", self.echo_data.0); + } else { + let lsb = memory.read(self.echo_buffer_head.wrapping_add(2), false); + let msb = memory.read(self.echo_buffer_head.wrapping_add(3), false); + self.echo_data.1 = echo_value_from_memory(lsb, msb); + trace!("echo read right: {}", self.echo_data.1); + } + } + + fn write_echo(&self, memory: &mut Memory, is_left: bool) { + if is_left { + let (lsb, msb) = echo_value_to_memory(self.echo_data.0); + memory.write(self.echo_buffer_head, lsb); + memory.write(self.echo_buffer_head.wrapping_add(1), msb); + trace!("echo write left: {}", self.echo_data.0); + } else { + let (lsb, msb) = echo_value_to_memory(self.echo_data.1); + memory.write(self.echo_buffer_head.wrapping_add(2), lsb); + memory.write(self.echo_buffer_head.wrapping_add(3), msb); + trace!("echo write right: {}", self.echo_data.1); + } + } +} + +/// Returns an echo sample value from the very weirdly stored 15-bit value in memory. +#[must_use] +pub const fn echo_value_from_memory(lsb: u8, msb: u8) -> u16 { + ((lsb as u16) >> 1) | ((msb as u16) << 7) +} + +/// Converts a 15-bit echo sample to the weird memory format as (lsb, msb). +#[must_use] +#[allow(clippy::cast_possible_truncation)] +pub const fn echo_value_to_memory(sample: u16) -> (u8, u8) { + (((sample & 0x7f) << 1) as u8, ((sample >> 7) as u8)) +} + +/// Size of the internal BRR decode buffer that is used for pitch interpolation. +pub const BRR_DECODE_BUFFER_SIZE: usize = 12; + +/// Internal per-voice state. +#[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] +struct VoiceState { + // Main voice state + /// Pre-channel mixing output value, mirrored in the OUTX register. + output: i16, + /// Left channel volume, copied from hardware register. + volume_left: i8, + /// Right channel volume, copied from hardware register. + volume_right: i8, + /// Whether the voice was just triggered (keyed on) via the KON register. Reset after processing the start of a new + /// sample playback. + was_keyed_on: bool, + /// Whether the voice was just stopped (keyed off) via the KOFF register. Reset after processing the start of a new + /// sample playback. + was_keyed_off: bool, + /// Enable noise output instead of BRR samples. + noise_enable: bool, + /// Enable output to echo. + echo_enable: bool, + + // Pitch handling + /// Pitch scale, copied from hardware registers. + pitch_scale: u16, + /// Internal pitch counter for running the pitch processing. Effectively a fixed-point index into the decoded + /// sample buffer. + pitch_counter: PitchCounter, + /// Enable pitch modulation for this channel. + pitch_mod_enable: bool, + + // BRR state + /// Current BRR sample index, copied from hardware register. Has an effect as soon as KON or a loop point are + /// encountered. + sample_index: u8, + /// At which step the decoder currently is. This mainly determines what the next memory access looks like. + brr_decoder_step: BrrDecoderStep, + /// Current BRR read address. + brr_address: u16, + /// Last 12 decoded samples, for pitch interpolation. + decoded_sample_buffer: [i16; BRR_DECODE_BUFFER_SIZE], + /// Next write index for the sample buffer. 4 samples are decoded at a time (from 2 bytes in memory), so this + /// always advances in steps of 4: 0 -> 4 -> 8 -> 0. + brr_buffer_index: u8, + /// Header of the BRR block currently in processing. + brr_header: brr::Header, + /// Bytes read in from memory during last memory access. This is written by the memory access logic at the + /// appropriate time. + brr_read_bytes: [u8; 3], + /// Whether the channel is playing. + is_playing: bool, + + // Envelope state + /// Current envelope volume (11 bit). + envelope_volume: u16, + /// Current envelope state. + envelope_state: EnvelopeState, + /// Current envelope settings, copied from hardware register. + envelope_settings: EnvelopeSettings, + /// Sample step counter, incremented at 32KHz. Current rate divider from `envelope_settings` determines at what + /// value this is reset and another envelope step is taken. + envelope_step_counter: u16, +} + +macro_rules! brr_read { + ($self:ident, $memory:ident[$data_index:ident]) => { + #[allow(unused_assignments)] + { + $self.brr_read_bytes[$data_index as usize] = + $memory.read($self.brr_address.wrapping_add($data_index), false); + trace!("BRR data read: {}", $self.brr_read_bytes[$data_index as usize]); + $data_index += 1; + } + }; +} + +impl VoiceState { + /// Read the sample directory entry for this voice. This is done if the voice was keyed on or when the last BRR + /// block has hit a block with both loop and end flags (i.e. the sample needs to loop and we need to jump to the + /// loop point given by the directory entry). + pub fn read_sample_directory(&mut self, memory: &mut Memory, sample_directory_address: u16) { + let directory_address = sample_directory_address.wrapping_add(u16::from(self.sample_index) * 4); + if self.was_keyed_on { + // Read address of sample start + self.brr_address = memory.read_word(directory_address, false); + } + if self.will_loop() { + // Read address of loop point + self.brr_address = memory.read_word(directory_address.wrapping_add(2), false); + } + } + + /// Perform part 1 of the BRR data read if necessary. + pub fn read_brr_1(&mut self, memory: &Memory) { + if self.needs_more_brr_data() { + let mut data_index = 0; + if self.brr_decoder_step.will_read_three_bytes() { + brr_read!(self, memory[data_index]); + } + brr_read!(self, memory[data_index]); + self.brr_address = self.brr_address.wrapping_add(data_index); + } + } + + /// Perform part 2 of the BRR data read if necessary. + pub fn read_brr_2(&mut self, memory: &Memory) { + if self.needs_more_brr_data() { + let mut data_index = if self.brr_decoder_step.will_read_three_bytes() { 2 } else { 1 }; + brr_read!(self, memory[data_index]); + self.brr_address = self.brr_address.wrapping_add(1); + } + } + + /// Returns true if we need to jump to the loop point of the current sample to continue processing BRR data. This is + /// therefore only true if the last sample before the loop point has been fully processed and more data is needed. + const fn will_loop(&self) -> bool { + self.needs_more_brr_data() && self.brr_header.flags.will_loop_afterwards() + } + + /// Returns true if more BRR block data is needed from memory. + #[allow(clippy::cast_possible_truncation)] + const fn needs_more_brr_data(&self) -> bool { + // Buffer index has fallen at least 4 samples behind the pitch index, so we can now write there. + let buffer_write_end = (self.brr_buffer_index + 4) % (BRR_DECODE_BUFFER_SIZE as u8); + self.pitch_counter.sample_index() >= buffer_write_end + } + + /// ENVX register value for this voice, which are the upper 7 bits of the 11-bit envelope value. + pub const fn envelope_output(&self) -> u8 { + ((self.envelope_volume >> 4) & 0xff) as u8 + } + + /// OUTX register value for this voice, which are the upper 8 bits of the 16-bit pre-channel mixer output value. + pub const fn output(&self) -> u8 { + self.output.to_le_bytes()[1] + } + + /// Perform soft reset as per reset flag. + pub fn soft_reset(&mut self) { + self.envelope_volume = 0; + self.was_keyed_off = true; + } +} + +/// Fixed-point 16-bit pitch counter. Top 4 bits are sample index, bits 11 to 3 are gauss table index, bits 2 to 0 are +/// not used directly. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] +struct PitchCounter(u16); +impl std::ops::Deref for PitchCounter { + type Target = u16; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for PitchCounter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl PitchCounter { + pub const fn sample_index(self) -> u8 { + (self.0 >> 12) as u8 + } + + pub const fn gauss_table_index(self) -> usize { + ((self.0 >> 3) & 0x1ff) as usize + } + + pub fn set_msb(&mut self, msb: u8) { + let [lsb, _] = self.to_le_bytes(); + self.0 = u16::from_le_bytes([lsb, msb]); + } + + pub fn set_lsb(&mut self, lsb: u8) { + let [_, msb] = self.to_le_bytes(); + self.0 = u16::from_le_bytes([lsb, msb]); + } +} + +/// Internal envelope state, determine how envelope volume proceeds. This is also updated during gain mode, although it +/// does not affect the envelope except for Release mode. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +enum EnvelopeState { + Attack, + Decay, + #[default] + Sustain, + Release, +} + +/// Internal envelope settings, rates are the full 32KHz divider after the rate table is applied. +#[derive(Clone, Copy, Debug)] +enum EnvelopeSettings { + Adsr { attack_rate: u16, decay_rate: u16, sustain_rate: u16, sustain_level: u16 }, + FixedGain { level: u16 }, + CustomGain { rate: u16, mode: GainMode }, +} + +impl Default for EnvelopeSettings { + fn default() -> Self { + Self::Adsr { attack_rate: 0, decay_rate: 0, sustain_rate: 0, sustain_level: 0 } + } +} + +impl EnvelopeSettings { + /// Set the ADSR1 register value. + pub fn set_adsr1(&mut self, adsr1: u8) { + if adsr1 & 0x80 > 1 { + // ADSR mode. + let attack_rate = (adsr1 & 0xf) * 2 + 1; + let decay_rate = ((adsr1 >> 4) & 0x7) * 2 + 16; + + *self = match *self { + Self::Adsr { sustain_rate, sustain_level, .. } => Self::Adsr { + attack_rate: RATE_TABLE[attack_rate as usize], + decay_rate: RATE_TABLE[decay_rate as usize], + sustain_level, + sustain_rate, + }, + _ => Self::Adsr { + attack_rate: RATE_TABLE[attack_rate as usize], + decay_rate: RATE_TABLE[decay_rate as usize], + sustain_level: 0, + sustain_rate: 0, + }, + }; + } else { + // Gain mode. + todo!() + } + } + + /// Set the ADSR2 register value, if applicable. + #[allow(clippy::needless_pass_by_ref_mut)] + pub fn set_adsr2(&mut self, adsr2: u8) { + todo!() + } + + /// Set the GAIN register value, if applicable. + #[allow(clippy::needless_pass_by_ref_mut)] + pub fn set_gain(&mut self, gain: u8) { + todo!() + } +} + +/// Custom gain modes. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +enum GainMode { + #[default] + LinearDecrease, + ExponentialDecrease, + LinearIncrease, + BentIncrease, +} + +/// Internal BRR decoder state machine. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +enum BrrDecoderStep { + /// BRR decoder is done with a block (read 16 samples) and will read a header next. + #[default] + Done, + /// BRR decoder has just read the header and four samples. + ReadHeader, + /// BRR decoder has read four samples, but it's not right after the header read. This is important for handling the + /// ENDX flag write correctly. + Read4Samples, + /// BRR decoder has read 8 samples. + Read8Samples, + /// BRR decoder has read 12 samples. + Read12Samples, +} + +impl BrrDecoderStep { + /// Advances the decoder to the next step once the next pitch counter overflow (across a 4-sample boundary) happened + /// and the memory accesses were executed. + pub fn advance(&mut self) { + *self = match *self { + Self::Done => Self::ReadHeader, + Self::ReadHeader | Self::Read4Samples => Self::Read8Samples, + Self::Read8Samples => Self::Read12Samples, + Self::Read12Samples => Self::Done, + }; + } + + /// Returns whether the decoder will need to read three bytes of memory due to needing to read a BRR header. + pub fn will_read_three_bytes(self) -> bool { + self == Self::Done + } + + /// Returns whether the decoder just read the header of an end block, which should trigger the flag write to the + /// ENDX register. + pub fn just_read_end_block(self) -> bool { + self == Self::ReadHeader + } } diff --git a/sapemu/src/dsp/audio_processing.rs b/sapemu/src/dsp/audio_processing.rs new file mode 100644 index 0000000..169c196 --- /dev/null +++ b/sapemu/src/dsp/audio_processing.rs @@ -0,0 +1,9 @@ +//! Audio processing code for the DSP. + +use super::Dsp; +use crate::memory::Memory; + +impl Dsp { + #[allow(clippy::needless_pass_by_ref_mut, clippy::unused_self)] + pub(super) fn run_audio_processing(&mut self, memory: &mut Memory) {} +} diff --git a/sapemu/src/dsp/registers.rs b/sapemu/src/dsp/registers.rs index 4de23fa..5b42c5a 100644 --- a/sapemu/src/dsp/registers.rs +++ b/sapemu/src/dsp/registers.rs @@ -5,77 +5,79 @@ use std::ops::Deref; use bitflags::bitflags; +use super::tables::RATE_TABLE; + /// All DSP registers exposed to the SMP. #[derive(Copy, Clone, Debug)] pub struct DspRegisters { /// x0-x9, per-voice registers `VxVOLL` - `VxOUTX` - voices: [VoiceRegisters; 8], + pub(super) voices: [VoiceRegisters; 8], /// 0C, MVOLL - main_volume_left: u8, + pub(super) main_volume_left: u8, /// 1C, MVOLR - main_volume_right: u8, + pub(super) main_volume_right: u8, /// 2C, EVOLL - echo_volume_left: u8, + pub(super) echo_volume_left: u8, /// 3C, EVOLR - echo_volume_right: u8, + pub(super) echo_volume_right: u8, /// 4C, KON - key_on: PerVoiceFlag, + pub(super) key_on: PerVoiceFlag, /// 5C, KOFF - key_off: PerVoiceFlag, + pub(super) key_off: PerVoiceFlag, /// 6C, FLG - flags: DspFlags, + pub(super) flags: DspFlags, /// 7C, ENDX - voice_end: PerVoiceFlag, + pub(super) voice_end: PerVoiceFlag, /// 0D, EFB - echo_feedback_volume: u8, + pub(super) echo_feedback_volume: u8, /// 2D, PMON - pitch_mod_enable: PerVoiceFlag, + pub(super) pitch_mod_enable: PerVoiceFlag, /// 3D, NON - noise_enable: PerVoiceFlag, + pub(super) noise_enable: PerVoiceFlag, /// 4D, EON - echo_enable: PerVoiceFlag, + pub(super) echo_enable: PerVoiceFlag, /// 5D, DIR - sample_table_address: u8, + pub(super) sample_directory_index: u8, /// 6D, ESA 🚀 - echo_buffer_address: u8, + pub(super) echo_source: u8, /// 7D, EDL - echo_delay: u8, + pub(super) echo_delay: u8, /// xF, `FIRx` - fir_coefficients: [u8; 8], + pub(super) fir_coefficients: [u8; 8], /// xA, unused - unused_a: [u8; 8], + pub(super) unused_a: [u8; 8], /// xB, unused - unused_b: [u8; 8], + pub(super) unused_b: [u8; 8], /// 1D, unused - unused_1d: u8, + pub(super) unused_1d: u8, /// xE, unused - unused_e: [u8; 8], + pub(super) unused_e: [u8; 8], } impl Default for DspRegisters { fn default() -> Self { Self { - voices: Default::default(), - main_volume_left: Default::default(), - main_volume_right: Default::default(), - echo_volume_left: Default::default(), - echo_volume_right: Default::default(), - key_on: PerVoiceFlag::default(), - key_off: PerVoiceFlag::default(), - flags: DspFlags::default(), - voice_end: PerVoiceFlag(0xff), - echo_feedback_volume: Default::default(), - pitch_mod_enable: PerVoiceFlag::default(), - noise_enable: PerVoiceFlag::default(), - echo_enable: PerVoiceFlag::default(), - sample_table_address: Default::default(), - echo_buffer_address: Default::default(), - echo_delay: Default::default(), - fir_coefficients: Default::default(), - unused_a: Default::default(), - unused_b: Default::default(), - unused_1d: Default::default(), - unused_e: Default::default(), + voices: Default::default(), + main_volume_left: Default::default(), + main_volume_right: Default::default(), + echo_volume_left: Default::default(), + echo_volume_right: Default::default(), + key_on: PerVoiceFlag::default(), + key_off: PerVoiceFlag::default(), + flags: DspFlags::default(), + voice_end: PerVoiceFlag(0xff), + echo_feedback_volume: Default::default(), + pitch_mod_enable: PerVoiceFlag::default(), + noise_enable: PerVoiceFlag::default(), + echo_enable: PerVoiceFlag::default(), + sample_directory_index: Default::default(), + echo_source: Default::default(), + echo_delay: Default::default(), + fir_coefficients: Default::default(), + unused_a: Default::default(), + unused_b: Default::default(), + unused_1d: Default::default(), + unused_e: Default::default(), } } } @@ -105,8 +107,8 @@ impl DspRegisters { (0x2, 0xD) => self.pitch_mod_enable.0, (0x3, 0xD) => self.noise_enable.0, (0x4, 0xD) => self.echo_enable.0, - (0x5, 0xD) => self.sample_table_address, - (0x6, 0xD) => self.echo_buffer_address, + (0x5, 0xD) => self.sample_directory_index, + (0x6, 0xD) => self.echo_source, (0x7, 0xD) => self.echo_delay, (_, 0xF) => self.fir_coefficients[upper_nibble as usize], _ => unreachable!(), @@ -139,34 +141,62 @@ impl DspRegisters { (0x2, 0xD) => self.pitch_mod_enable.0 = value, (0x3, 0xD) => self.noise_enable.0 = value, (0x4, 0xD) => self.echo_enable.0 = value, - (0x5, 0xD) => self.sample_table_address = value, - (0x6, 0xD) => self.echo_buffer_address = value, + (0x5, 0xD) => self.sample_directory_index = value, + (0x6, 0xD) => self.echo_source = value, (0x7, 0xD) => self.echo_delay = value, (_, 0xF) => self.fir_coefficients[upper_nibble as usize] = value, _ => unreachable!(), } } + + /// Returns the actual address of the sample directory. + #[must_use] + pub const fn sample_directory(&self) -> u16 { + self.sample_directory_index as u16 * 0x100 + } + + /// Returns the noise frequency divider from the flags register. + #[must_use] + pub fn noise_frequency(&self) -> u16 { + RATE_TABLE[(self.flags & DspFlags::NOISE_FREQUENCY).0 as usize] + } + + /// Returns the number of samples that the echo buffer delays by. + #[must_use] + pub const fn echo_delay_samples(&self) -> u16 { + self.echo_delay as u16 * 2048 + } + + /// Returns the base address of the echo ring buffer in memory. + #[must_use] + pub const fn echo_base_address(&self) -> u16 { + self.echo_source as u16 * 256 + } } /// Per-voice registers, x0 - xB #[derive(Clone, Copy, Debug, Default)] pub struct VoiceRegisters { /// 0, VOLL - volume_left: u8, + pub(super) volume_left: u8, /// 1, VOLR - volume_right: u8, - /// 2, PITCHL and 3, PITCHH - pitch: u16, + pub(super) volume_right: u8, + /// 2, PITCHL + pub(super) pitch_low: u8, + /// 3, PITCHH + pub(super) pitch_high: u8, /// 4, SRCN - sample_number: u8, - /// 5, ADSR1 (low) and 6, ADSR2 (high) - adsr_settings: AdsrSettings, + pub(super) sample_number: u8, + /// 5, ADSR1 (low) + pub(super) adsr_low: u8, + /// 6, ADSR2 (high) + pub(super) adsr_high: u8, /// 7, GAIN - gain_settings: u8, + pub(super) gain_settings: u8, /// 8, ENVX - envelope_value: u8, + pub(super) envelope_value: u8, /// 9, OUTX - output_value: u8, + pub(super) output_value: u8, } impl VoiceRegisters { @@ -175,11 +205,11 @@ impl VoiceRegisters { match address { 0 => self.volume_left, 1 => self.volume_right, - 2 => self.pitch.to_le_bytes()[0], - 3 => self.pitch.to_le_bytes()[1], + 2 => self.pitch_low, + 3 => self.pitch_high, 4 => self.sample_number, - 5 => self.adsr_settings.to_be_bytes()[0], - 6 => self.adsr_settings.to_be_bytes()[1], + 5 => self.adsr_low, + 6 => self.adsr_high, 7 => self.gain_settings, 8 => self.envelope_value, 9 => self.output_value, @@ -192,11 +222,11 @@ impl VoiceRegisters { match address { 0 => self.volume_left = value, 1 => self.volume_right = value, - 2 => self.pitch = (self.pitch & 0xf0) | u16::from(value), - 3 => self.pitch = (self.pitch & 0xf) | (u16::from(value) << 8), + 2 => self.pitch_low = value, + 3 => self.pitch_high = value, 4 => self.sample_number = value, - 5 => self.adsr_settings.0 = (self.adsr_settings.0 & 0xf0) | u16::from(value), - 6 => self.adsr_settings.0 = (self.adsr_settings.0 & 0xf) | (u16::from(value) << 8), + 5 => self.adsr_low = value, + 6 => self.adsr_high = value, 7 => self.gain_settings = value, 8 => self.envelope_value = value, 9 => self.output_value = value, @@ -210,19 +240,6 @@ impl VoiceRegisters { #[repr(transparent)] pub struct PerVoiceFlag(u8); -/// ADSR settings per voice, a 16-bit field. -#[derive(Clone, Copy, Debug, Default)] -#[repr(transparent)] -pub struct AdsrSettings(u16); - -impl Deref for AdsrSettings { - type Target = u16; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - /// Global DSP flags in the FLG register. #[derive(Clone, Copy, Debug)] #[repr(transparent)] diff --git a/sapemu/src/dsp/tables.rs b/sapemu/src/dsp/tables.rs index 11e59e1..b69986f 100644 --- a/sapemu/src/dsp/tables.rs +++ b/sapemu/src/dsp/tables.rs @@ -39,7 +39,7 @@ pub const GAUSS_TABLE: [u16; 512] = [ ]; /// Envelope rate table. -pub const RATE_TABLE: [usize; 1 << 5] = [ +pub const RATE_TABLE: [u16; 1 << 5] = [ // Rate 0 is a sentinel value since it means no step is ever taken. This is handled elsewhere. 0, 2048, 1536, 1280, 1024, 768, 640, 512, 384, 320, 256, 192, 160, 128, 96, 80, 64, 48, 40, 32, 24, 20, 16, 12, 10, 8, 6, 5, 4, 3, 2, 1, diff --git a/sapemu/src/memory.rs b/sapemu/src/memory.rs index df587be..4b452f6 100644 --- a/sapemu/src/memory.rs +++ b/sapemu/src/memory.rs @@ -62,7 +62,7 @@ impl Memory { /// Performs a 16-bit little endian read from memory at the given address. #[inline] pub fn read_word(&mut self, address: u16, enable_boot_rom: bool) -> u16 { - u16::from(self.read(address, enable_boot_rom)) | (u16::from(self.read(address + 1, enable_boot_rom)) << 8) + u16::from_le_bytes([self.read(address, enable_boot_rom), self.read(address + 1, enable_boot_rom)]) } /// Copies the state of the various memory mapped registers to the hidden memory behind it. This function is used diff --git a/sapemu/src/smp.rs b/sapemu/src/smp.rs index 9d56d10..3300252 100644 --- a/sapemu/src/smp.rs +++ b/sapemu/src/smp.rs @@ -97,6 +97,18 @@ pub const CPUIO3: u16 = 0x00F7; pub const DSPADDR: u16 = 0x00F2; /// DSP register address port. pub const DSPDATA: u16 = 0x00F3; +/// Timer 0 divider. +pub const T0DIV: u16 = 0x00FA; +/// Timer 1 divider. +pub const T1DIV: u16 = 0x00FB; +/// Timer 2 divider. +pub const T2DIV: u16 = 0x00FC; +/// Timer 0 output. +pub const T0OUT: u16 = 0x00FD; +/// Timer 1 output. +pub const T1OUT: u16 = 0x00FE; +/// Timer 2 output. +pub const T2OUT: u16 = 0x00FF; /// Vector for software interrupts. pub const BREAK_VECTOR: u16 = 0xFFDE; @@ -255,6 +267,10 @@ impl Smp { TEST => self.test_write(value), CONTROL => self.control_write(value), CPUIO0 | CPUIO1 | CPUIO2 | CPUIO3 => self.ports.write(address - CPUIO0, value), + T0DIV => self.timers.timer_divisor[0] = value, + T1DIV => self.timers.timer_divisor[1] = value, + T2DIV => self.timers.timer_divisor[2] = value, + // Writes to the timer output registers pass through to memory. DSPDATA => { let dsp_address = memory.read(DSPADDR, self.control.contains(ControlRegister::BootRomEnable)); dsp.write(dsp_address, value); @@ -273,6 +289,12 @@ impl Smp { let dsp_address = memory.read(DSPADDR, self.control.contains(ControlRegister::BootRomEnable)); dsp.read(dsp_address) }, + T0DIV => self.timers.timer_divisor[0], + T1DIV => self.timers.timer_divisor[1], + T2DIV => self.timers.timer_divisor[2], + T0OUT => self.timers.timer_out[0], + T1OUT => self.timers.timer_out[1], + T2OUT => self.timers.timer_out[2], _ => memory.read(address, self.control.contains(ControlRegister::BootRomEnable)), } } diff --git a/sapemu/src/smp/peripherals.rs b/sapemu/src/smp/peripherals.rs index bbccff3..787d5d5 100644 --- a/sapemu/src/smp/peripherals.rs +++ b/sapemu/src/smp/peripherals.rs @@ -129,13 +129,13 @@ impl Timers { const T2_RATE: usize = 64000; const TIMER_CLOCKS_PER_STEP: [usize; 3] = [Self::T01_CLOCKS_PER_STEP, Self::T01_CLOCKS_PER_STEP, Self::T2_CLOCKS_PER_STEP]; - #[allow(unused)] const TIMER_RATES: [usize; 3] = [Self::T01_RATE, Self::T01_RATE, Self::T2_RATE]; /// Create new timers. #[must_use] pub fn new() -> Self { - let mut new = Self { timer_out: [0; 3], timer_divisor: [1; 3], timer_tick_remaining: [0; 3] }; + let mut new = + Self { timer_out: [0; 3], timer_divisor: [0xFF; 3], timer_tick_remaining: [0; 3] }; new.reset_timers_if_necessary(); new } diff --git a/sapemu/src/test.rs b/sapemu/src/test.rs index a0c793c..3051b74 100644 --- a/sapemu/src/test.rs +++ b/sapemu/src/test.rs @@ -24,7 +24,9 @@ use crate::dsp::registers::DspRegisters; use crate::dsp::Dsp; use crate::memory::Memory; use crate::smp::peripherals::{ControlRegister, CpuIOPorts, ProgramStatusWord, TestRegister}; -use crate::smp::{Smp, CONTROL, CPUIO0, CPUIO1, CPUIO2, CPUIO3, DSPADDR, TEST}; +use crate::smp::{ + Smp, CONTROL, CPUIO0, CPUIO1, CPUIO2, CPUIO3, DSPADDR, DSPDATA, T0DIV, T0OUT, T1DIV, T1OUT, T2DIV, T2OUT, TEST, +}; #[derive(Deserialize, Debug, Clone)] struct Test { @@ -115,9 +117,15 @@ impl From<&RamState> for Memory { } } +/// Any test accessing these addresses will be wrong since it doesn't properly account for hardware behavior. +/// See . +const IGNORED_ADDRESSES: &[u16] = &[TEST, CONTROL, DSPADDR, DSPDATA, T0DIV, T0OUT, T1DIV, T1OUT, T2DIV, T2OUT]; + impl PartialEq for RamState { fn eq(&self, other: &Memory) -> bool { - self.0.iter().all(|MemoryCellState { address, value }| other.read(*address, false) == *value) + self.0.iter().all(|MemoryCellState { address, value }| { + IGNORED_ADDRESSES.contains(address) || other.read(*address, false) == *value + }) } } @@ -224,45 +232,6 @@ impl TryFrom<(Option, Option, String)> for Cycle { } } -/// Tests that are not run due to issues with `SingleStepTests`' disregard of hardware properties. -/// See . -const IGNORED_TESTS: &[&str] = &[ - "09 01A8", "39 0295", "59 0146", "3A 0355", "47 02DD", "6E 0344", "99 02BD", "A9 01BF", "C7 000C", "D7 00B9", - "D7 0110", "D7 0111", "DA 0082", // DSP-related - "03 001B", "02 0097", "07 0123", "06 01D9", "04 02A7", "09 026A", "0E 022E", "0B 03E4", "18 0078", "12 03A4", - "13 02F3", "1A 00D4", "1B 0034", "23 00FF", "22 018B", "24 0023", "27 0002", "29 00B4", "2B 00EA", "26 01ED", - "2E 0014", "32 0018", "33 0115", "34 0179", "3B 002F", "3A 0046", "3E 0082", "37 0328", "38 03D5", "42 00C4", - "43 002E", "44 007E", "46 008B", "49 00B0", "47 00A2", "4B 0379", "52 013E", "57 01C8", "54 0200", "53 0272", - "58 005F", "5B 01C0", "5A 02CE", "62 0089", "63 01A0", "64 0135", "67 0200", "66 0296", "69 0179", "6B 009B", - "6E 031A", "74 006C", "79 004C", "78 01B0", "77 0302", "7E 0042", "7B 01BE", "7A 021D", "82 0053", "83 0013", - "84 0176", "89 0212", "8B 0123", "93 001F", "92 023B", "94 01AE", "8F 03C6", "99 00F0", "9B 00CC", "98 031D", - "9A 0332", "A2 00AC", "A3 00AF", "A7 009A", "A6 028E", "A9 01A8", "AB 02F8", "B0 0028", "B4 002D", "AF 02B8", - "B3 022A", "B2 021C", "BA 0087", "B8 0115", "BB 01BF", "B7 0258", "B9 022B", "BF 00A4", "C4 016A", "C2 02F7", - "C6 00A3", "C7 0173", "CB 001F", "CD 013B", "CA 0227", "D2 0024", "CF 022E", "D3 0065", "D4 000A", "D8 00D3", - "D9 00B9", "DB 001D", "D7 025A", "DA 0115", "DE 01E6", "E2 013E", "E3 00FC", "E6 0036", "E4 013F", "E7 015C", - "EB 0158", "F2 0003", "F3 015C", "F9 005A", "F7 025E", "F4 03AA", "FB 0313", "03 01CC", "02 0290", "06 03CF", - "09 0317", "13 02F4", "18 01CF", "1A 0072", "1B 032C", "22 0221", "24 0149", "27 00A6", "23 0301", "26 02D0", - "29 014A", "2B 016B", "2E 018E", "32 01C0", "37 0049", "34 035B", "3B 0160", "3E 0138", "3A 03E1", "43 00BA", - "44 010C", "46 00F4", "47 0116", "49 0148", "4B 0384", "52 020A", "58 00B4", "57 01D0", "5A 0194", "5B 023F", - "62 017F", "64 0282", "63 03CA", "66 039A", "69 0228", "6B 018B", "6E 03AB", "79 0135", "74 03CC", "7A 02C6", - "7B 0339", "82 008B", "7E 02B7", "84 02CE", "8B 0159", "93 018D", "97 0119", "92 02D1", "94 02B7", "9B 032E", - "A2 0209", "A7 0166", "B4 010D", "BA 00DE", "B7 0360", "BB 02E7", "BF 02B9", "C4 0184", "C7 018D", "CB 0162", - "D4 003A", "D2 0261", "DA 01A1", "D7 0267", "D9 0314", "DE 01EA", "E2 021F", "E3 0173", "E7 0056", "E4 0264", - "E6 013A", "EB 03C6", "F7 007F", "F3 02EA", "F4 03C1", "F9 00A3", "FB 0375", "18 01F9", "1A 0179", "1B 03BF", - "27 0176", "24 0309", "23 039E", "29 01DC", "2B 02C4", "2E 0260", "37 0123", "3E 0185", "3B 0351", "43 0225", - "44 0260", "46 0284", "58 02C7", "5B 02E6", "5A 0302", "62 0203", "6B 01FC", "69 03A2", "79 017D", "7A 02D6", - "7E 0384", "82 039B", "8B 03B4", "93 0263", "92 034F", "94 03DC", "97 02BB", "9B 0334", "A2 025C", "A7 0173", - "B4 0137", "BA 00E3", "B7 03CF", "C7 021F", "C4 0393", "CB 01A7", "D4 0081", "D7 036D", "DA 031D", "DE 0347", - "E2 032B", "E3 025C", "E6 01F4", "E7 01A7", "E4 0322", "F3 0376", "F7 03E2", "1A 02CA", "1B 03CE", "27 01D7", - "29 0280", "37 0357", "3E 023A", "44 02DE", "5A 0373", "62 02A5", "79 0213", "8B 03C4", "93 035C", "A7 01C6", - "B4 0279", "BA 02B8", "C7 023A", "CB 026B", "D4 01C3", "D7 03BD", "E6 02E6", "E7 0331", "F3 03D5", "27 020F", - "44 033F", "A7 0295", "B4 02C7", "C7 0251", "D4 033B", "E6 0331", "27 024E", "A7 031E", "B4 034F", "C7 03A6", - "D4 0348", "27 0276", "A7 0339", "19 00CA", "29 02C2", "39 0094", "49 01DE", "59 00C5", "99 0162", "A9 0267", - "B9 02B2", "FA 008F", "19 0278", "29 02CA", "39 015F", "49 036C", "59 012E", "99 0290", "A9 0276", "FA 00D8", - "19 02DF", "29 038A", "59 01EB", "99 02E0", "A9 02D6", "FA 01CB", "19 02E8", "99 032E", "A9 032B", "FA 02A1", - "19 03CE", "A9 0334", -]; - /// rstest limitation: we have to generate all values in advance, unfortunately. /// python: `for i in range(0xff+1): print(hex(i), end=', ')` #[rstest] @@ -302,8 +271,15 @@ fn single_instruction( let mut copy = file_bytes.to_vec(); let test_file: Vec = simd_json::serde::from_slice(&mut copy).expect("couldn't parse test set"); for test in test_file { - if IGNORED_TESTS.contains(&test.name.as_ref()) { - info!("skipping ignored test {}", test.name); + if test + .initial_state + .ram + .0 + .iter() + .chain(test.final_state.ram.0.iter()) + .any(|x| IGNORED_ADDRESSES.contains(&x.address)) + { + info!("skipping test {} since it uses ignored addresses", test.name); continue; } info!("#######################################\nperforming test {}...", test.name); diff --git a/src/brr/mod.rs b/src/brr/mod.rs index e90cf20..84da661 100644 --- a/src/brr/mod.rs +++ b/src/brr/mod.rs @@ -523,7 +523,7 @@ fn merge_nybbles_into_bytes(nybbles: EncodedBlockSamples) -> [u8; 8] { /// * `l` is the loop bit, indicating whether playback should loop to the loop point (specified in the sample table) /// after this block. Note that a `l` bit set without an `e` bit set has no effect. /// * `e` is the end bit, indicating that the sample has ended. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] pub struct Header { /// The real amount of shift this header specifies for the samples in its block. pub real_shift: i8, @@ -563,10 +563,11 @@ impl From
for u8 { /// | 1 | 15/16 ~ 1 | 0 | delta/differential coding | /// | 2 | 61/32 ~ 2 | -15/16 ~ -1 | almost polynomial order 2 predictor | /// | 3 | 115/64 ~ 2 | -13/16 ~ -1 | ??? | -#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, FromPrimitive)] #[repr(u8)] pub enum LPCFilter { /// Filter 0, verbatim samples. + #[default] Zero = 0, /// Filter 1, differential coding. One = 1, @@ -653,10 +654,11 @@ impl std::fmt::Display for LPCFilter { } /// Loop and end flags used in the BRR block header to determine sample end and looping. -#[derive(Clone, Copy, Debug, Eq, FromPrimitive)] +#[derive(Clone, Copy, Debug, Default, Eq, FromPrimitive)] #[repr(u8)] pub enum LoopEndFlags { /// Nothing special happens, this is a normal sample. + #[default] Nothing = 0, /// End the sample playback without looping. EndWithoutLooping = 1,