Skip to content
Snippets Groups Projects
Verified Commit 9ad5064e authored by João Magalhães's avatar João Magalhães :rocket:
Browse files

feat: initial working prototype for CH4

The implemenation seems correct but is yet to be tested.
The coding of LFSR still needs auditing.
parent 912ab991
No related branches found
No related tags found
1 merge request!21Initial working version of Audio CH4
Pipeline #2448 passed
......@@ -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 {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment