diff --git a/src/apu.rs b/src/apu.rs index 77a24ef925c7f379a54e5650e71ef14b923701d8..229eb4084845176b9351014060e4562a50531a3e 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -9,6 +9,8 @@ const DUTY_TABLE: [[u8; 8]; 4] = [ [0, 1, 1, 1, 1, 1, 1, 0], ]; +const CH4_DIVISORS: [u8; 8] = [8, 16, 32, 48, 64, 80, 96, 112]; + pub enum Channel { Ch1, Ch2, @@ -60,13 +62,18 @@ pub struct Apu { ch3_enabled: bool, ch4_timer: i16, + ch4_envelope_sequence: u8, + ch4_envelope_enabled: bool, ch4_output: u8, ch4_length_timer: u8, ch4_pace: u8, ch4_direction: u8, ch4_volume: u8, ch4_output_level: u8, - ch4_wave_length: u16, + ch4_divisor: u8, + ch4_width_mode: bool, + ch4_clock_shift: u8, + ch4_lfsr: u16, ch4_length_stop: bool, ch4_enabled: bool, @@ -129,13 +136,18 @@ impl Apu { ch3_enabled: false, ch4_timer: 0, + ch4_envelope_sequence: 0, + ch4_envelope_enabled: false, ch4_output: 0, ch4_length_timer: 0x0, ch4_pace: 0x0, ch4_direction: 0x0, ch4_volume: 0x0, ch4_output_level: 0x0, - ch4_wave_length: 0x0, + ch4_divisor: 0x0, + ch4_width_mode: false, + ch4_clock_shift: 0x0, + ch4_lfsr: 0x0, ch4_length_stop: false, ch4_enabled: false, @@ -208,10 +220,18 @@ impl Apu { self.ch3_enabled = false; self.ch4_timer = 0; + self.ch4_envelope_sequence = 0; + self.ch4_envelope_enabled = false; self.ch4_output = 0; self.ch4_length_timer = 0x0; + self.ch4_pace = 0x0; + self.ch4_direction = 0x0; + self.ch4_volume = 0x0; self.ch4_output_level = 0x0; - self.ch4_wave_length = 0x0; + self.ch4_divisor = 0x0; + self.ch4_width_mode = false; + self.ch4_clock_shift = 0x0; + self.ch4_lfsr = 0x0; self.ch4_length_stop = false; self.ch4_enabled = false; @@ -278,7 +298,8 @@ impl Apu { } // @TODO the CPU clock is hardcoded here, we must handle situations - // where there's some kind of overclock + // where there's some kind of overclock, and for that to happen the + // current CPU clock must be propagated here self.output_timer += (4194304.0 / self.sampling_rate as f32) as i16; } } @@ -383,12 +404,19 @@ impl Apu { } // 0xFF22 — NR43: Channel 4 frequency & randomness 0xff22 => { - //@TODO need to implement this one! + self.ch4_divisor = value & 0x07; + self.ch4_width_mode = value & 0x08 == 0x08; + self.ch4_clock_shift = (value & 0xf0) >> 4; } // 0xFF23 — NR44: Channel 4 control 0xff23 => { self.ch4_length_stop |= value & 0x40 == 0x40; self.ch4_enabled |= value & 0x80 == 0x80; + if value & 0x80 == 0x80 { + self.ch4_timer = + (CH4_DIVISORS[self.ch4_divisor as usize] << self.ch4_clock_shift) as i16; + self.ch4_lfsr = 0x7ff1; + } } // 0xFF30-0xFF3F — Wave pattern RAM @@ -416,6 +444,10 @@ impl Apu { self.ch3_output } + pub fn ch4_output(&self) -> u8 { + self.ch4_output + } + pub fn audio_buffer(&self) -> &VecDeque<u8> { &self.audio_buffer } @@ -463,7 +495,13 @@ impl Apu { self.ch3_length_timer = 0; } } - Channel::Ch4 => (), + Channel::Ch4 => { + self.ch4_length_timer = self.ch4_length_timer.saturating_add(1); + if self.ch4_length_timer >= 64 { + self.ch4_enabled = !self.ch4_length_stop; + self.ch4_length_timer = 0; + } + } } } @@ -510,7 +548,23 @@ impl Apu { } } Channel::Ch3 => (), - Channel::Ch4 => (), + Channel::Ch4 => { + if !self.ch4_enabled || !self.ch4_envelope_enabled { + return; + } + self.ch4_envelope_sequence += 1; + if self.ch4_envelope_sequence >= self.ch4_pace { + if self.ch4_direction == 0x01 { + self.ch4_volume = self.ch4_volume.saturating_add(1); + } else { + self.ch4_volume = self.ch4_volume.saturating_sub(1); + } + if self.ch4_volume == 0 || self.ch4_volume == 15 { + self.ch4_envelope_enabled = false; + } + self.ch4_envelope_sequence = 0; + } + } } } @@ -541,6 +595,7 @@ impl Apu { self.tick_ch1(cycles); self.tick_ch2(cycles); self.tick_ch3(cycles); + self.tick_ch4(cycles); } #[inline(always)] @@ -615,6 +670,38 @@ impl Apu { self.ch3_timer += ((2048 - self.ch3_wave_length) << 1) as i16; self.ch3_position = (self.ch3_position + 1) & 31; } + + #[inline(always)] + fn tick_ch4(&mut self, cycles: u8) { + self.ch4_timer = self.ch4_timer.saturating_sub(cycles as i16); + if self.ch4_timer > 0 { + return; + } + + if self.ch4_enabled { + // obtains the current value of the LFSR based as + // the XOR of the 1st and 2nd bit of the LFSR + let result = ((self.ch4_lfsr & 0x0001) ^ ((self.ch4_lfsr >> 1) & 0x0001)) == 0x0001; + + // shifts the LFSR to the right and in case the + // value is positive sets the 15th bit to 1 + self.ch4_lfsr >>= 1; + self.ch4_lfsr |= if result { 0x0001 << 14 } else { 0x0 }; + + // in case the short width mode (7 bits) is set then + // the 6th bit will be set to value of the 15th bit + if self.ch4_width_mode { + self.ch4_lfsr &= 0xbf; + self.ch4_lfsr |= if result { 0x40 } else { 0x00 }; + } + + self.ch4_output = if result { self.ch4_volume } else { 0 }; + } else { + self.ch4_output = 0; + } + + self.ch4_timer += (CH4_DIVISORS[self.ch4_divisor as usize] << self.ch4_clock_shift) as i16; + } } impl Default for Apu {