diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs index 38fdb01..9249d0c 100644 --- a/src/apu/dmc.rs +++ b/src/apu/dmc.rs @@ -1,10 +1,26 @@ + +pub const SAMPLE_RATES: [u16; 16] = [428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54]; + #[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct DMC { pub sample: u16, pub enabled: bool, pub interrupt: bool, + pub loop_flag: bool, + pub rate_index: usize, pub length_counter: usize, - pub bytes_remaining: usize, + pub cpu_cycles_left: u16, + + // Memory reader + sample_byte: u8, // passed in every APU clock cycle, need to think of a better way to read CPU from APU + sample_buffer: Option, // buffer that the output unit draws into its shift register + sample_address: usize, // start of sample in memory + sample_length: usize, + pub current_address: usize, // address of the next byte of the sample to play + pub bytes_remaining: usize, // bytes left in the sample + + // Output unit + } impl DMC { @@ -13,17 +29,65 @@ impl DMC { sample: 0, enabled: false, interrupt: false, - bytes_remaining: 0, + loop_flag: false, + rate_index: 0, length_counter: 0, + cpu_cycles_left: 0, + sample_byte: 0, + sample_buffer: None, + sample_address: 0, + sample_length: 0, + current_address: 0, + bytes_remaining: 0, } } - pub fn clock(&mut self) { - + pub fn clock(&mut self, sample_byte: u8) { + // self.sample_byte = sample_byte; + self.clock_memory_reader(sample_byte); + } + + fn clock_memory_reader(&mut self, sample_byte: u8) { + // When the sample buffer is emptied, the memory reader fills the sample buffer + // with the next byte from the currently playing sample. It has an address counter and a bytes remaining counter. + // if self.sample_buffer.is_none() { + // self.sample_buffer = Some(sample_byte); + // } + // When a sample is (re)started, the current address is set to the sample address, and bytes remaining is set to the sample length. + if self.bytes_remaining == 0 && self.loop_flag { + self.current_address = self.sample_address; + self.bytes_remaining = self.sample_length; + } + // Any time the sample buffer is in an empty state and bytes remaining is not zero (including just after a write to $4015 that enables the channel, + // regardless of where that write occurs relative to the bit counter mentioned below), the following occur: + if self.sample_buffer.is_none() && self.bytes_remaining != 0 { + // The CPU is stalled for up to 4 CPU cycles to allow the longest possible write (the return address and write after an IRQ) to finish. + // If OAM DMA is in progress, it is paused for two cycles. The sample fetch always occurs on an even CPU cycle due to its alignment with the APU. + // Specific delay cases: + // 4 cycles if it falls on a CPU read cycle. + // 3 cycles if it falls on a single CPU write cycle (or the second write of a double CPU write). + // 4 cycles if it falls on the first write of a double CPU write cycle.[4] + // 2 cycles if it occurs during an OAM DMA, or on the $4014 write cycle that triggers the OAM DMA. + // 1 cycle if it occurs on the second-last OAM DMA cycle. + // 3 cycles if it occurs on the last OAM DMA cycle. + + // The sample buffer is filled with the next sample byte read from the current address, subject to whatever mapping hardware is present. + self.sample_buffer = Some(sample_byte); + // The address is incremented; if it exceeds $FFFF, it is wrapped around to $8000. + if self.current_address == 0xFFFF { + self.current_address = 0x8000 + } else { + self.current_address += 1; + } + // The bytes remaining counter is decremented; if it becomes zero and the loop flag is set, the sample is restarted (see above); otherwise, if the bytes remaining counter becomes zero and the IRQ enabled flag is set, the interrupt flag is set. + self.bytes_remaining -= 1; + // At any time, if the interrupt flag is set, the CPU's IRQ line is continuously asserted until the interrupt flag is cleared. + // The processor will continue on from where it was stalled. } pub fn write_control(&mut self, value: u8) { - + // $4010 IL--.RRRR Flags and Rate (write) + } pub fn direct_load(&mut self, value: u8) { diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 8ae4340..ece99ae 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -26,7 +26,7 @@ pub struct Apu { square2: Square, triangle: Triangle, noise: Noise, - dmc: DMC, + pub dmc: DMC, square_table: Vec, tnd_table: Vec, @@ -62,14 +62,14 @@ impl Apu { } } - pub fn clock(&mut self) -> f32 { + pub fn clock(&mut self, sample_byte: u8) -> f32 { // Clock each channel self.square1.clock(); self.square2.clock(); self.triangle.clock(); self.triangle.clock(); // hacky. clocking triangle twice because it runs every CPU cycle self.noise.clock(); - self.dmc.clock(); + self.dmc.clock(sample_byte); // Step frame counter if necessary if FRAME_COUNTER_STEPS.contains(&self.cycle) { @@ -80,6 +80,15 @@ impl Apu { self.cycle = 0; } + // Clock sampler if it's been the correct number of CPU cycles + if self.dmc.cpu_cycles_left == 0 { + self.dmc.clock(sample_byte); + } + self.dmc.cpu_cycles_left -= 2; // APU runs every other CPU cycle + if self.dmc.cpu_cycles_left == 0 { + self.dmc.cpu_cycles_left = dmc::SAMPLE_RATES[self.dmc.rate_index]; + } + // Send all samples to buffer, let the SDL2 audio callback take what it needs self.mix() } diff --git a/src/main.rs b/src/main.rs index 0a07f5e..acae884 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,7 +136,9 @@ fn run_game( } } for _ in 0..apu_cycles { - temp_buffer.push(cpu.apu.clock()); + // can't read CPU from APU so have to pass byte in here + let sample_byte = cpu.read(cpu.apu.dmc.current_address); + temp_buffer.push(cpu.apu.clock(sample_byte)); } // clock PPU three times for every CPU cycle for _ in 0..cpu_cycles * 3 {