diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index 8cd7607d61f4985969f4a9ca24579149cf0f3d84..4dcb15e35a8bf98e919c8381840c1239cc30e998 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -22,6 +22,10 @@ const SCREEN_SCALE: f32 = 2.0; /// The base title to be used in the window. static TITLE: &str = "Boytacean"; +/// Base audio volume to be used as the basis of the +/// amplification level of the volume +static VOLUME: f32 = 100.0; + pub struct Benchmark { count: usize, } @@ -312,7 +316,7 @@ impl Emulator { .system .audio_buffer() .iter() - .map(|v| *v as f32 / 14.0) + .map(|v| *v as f32 / VOLUME) .collect::<Vec<f32>>(); audio.device.queue_audio(&audio_buffer).unwrap(); } diff --git a/src/apu.rs b/src/apu.rs index 588c1e4e145564f616e0e8c30087664cca522acb..3c934c3d3b6139bb9945ba83594feb02974b6632 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -7,9 +7,17 @@ const DUTY_TABLE: [[u8; 8]; 4] = [ [0, 1, 1, 1, 1, 1, 1, 0], ]; +pub enum Channel { + Ch1, + Ch2, + Ch3, + Ch4, +} + pub struct Apu { ch1_timer: u16, ch1_sequence: u8, + ch1_sweep_sequence: u8, ch1_output: u8, ch1_sweep_slope: u8, ch1_sweep_increase: bool, @@ -48,6 +56,9 @@ pub struct Apu { wave_ram: [u8; 16], + sampling_frequency: u16, + sequencer: u16, + sequencer_step: u8, output_timer: u16, audio_buffer: Vec<u8>, } @@ -57,6 +68,7 @@ impl Apu { Self { ch1_timer: 0, ch1_sequence: 0, + ch1_sweep_sequence: 0, ch1_output: 0, ch1_sweep_slope: 0x0, ch1_sweep_increase: false, @@ -95,6 +107,12 @@ impl Apu { wave_ram: [0u8; 16], + sampling_frequency: 44100, + + /// Internal sequencer counter that runs at 512Hz + /// used for the activation of the tick actions. + sequencer: 0, + sequencer_step: 0, output_timer: 0, audio_buffer: Vec::new(), } @@ -103,7 +121,7 @@ impl Apu { pub fn clock(&mut self, cycles: u8) { // @TODO the performance here requires improvement for _ in 0..cycles { - self.cycle(); + self.tick(); } } @@ -118,8 +136,8 @@ impl Apu { match addr { // 0xFF10 — NR10: Channel 1 sweep 0xff10 => { - self.ch1_sweep_slope = value & 0x03; - self.ch1_sweep_increase = value & 0x04 == 0x04; + self.ch1_sweep_slope = value & 0x07; + self.ch1_sweep_increase = value & 0x08 == 0x00; self.ch1_sweep_pace = (value & 0x70) >> 4; } // 0xFF11 — NR11: Channel 1 length timer & duty cycle @@ -206,8 +224,57 @@ impl Apu { } } + pub fn output(&self) -> u8 { + self.ch1_output + self.ch2_output + } + + pub fn audio_buffer(&self) -> &Vec<u8> { + &self.audio_buffer + } + + pub fn audio_buffer_mut(&mut self) -> &mut Vec<u8> { + &mut self.audio_buffer + } + + pub fn clear_audio_buffer(&mut self) { + self.audio_buffer.clear(); + } + #[inline(always)] - pub fn cycle(&mut self) { + fn tick(&mut self) { + self.sequencer += 1; + if self.sequencer >= 8192 { + // each of these steps runs at 518/8 Hz = 64Hz + match self.sequencer_step { + 0 => { + self.tick_length_all(); + } + 1 => (), + 2 => { + self.tick_ch1_sweep(); + self.tick_length_all(); + } + 3 => (), + 4 => { + self.tick_length_all(); + } + 5 => (), + 6 => { + self.tick_ch1_sweep(); + self.tick_length_all(); + } + 7 => { + //channels[0]->envelope_clock(); + //channels[1]->envelope_clock(); + //channels[3]->envelope_clock(); + } + _ => (), + } + + self.sequencer = 0; + self.sequencer_step = (self.sequencer_step + 1) & 7; + } + self.ch1_timer = self.ch1_timer.saturating_sub(1); if self.ch1_timer == 0 { self.ch1_timer = (2048 - self.ch1_wave_length) << 2; @@ -246,24 +313,57 @@ impl Apu { if self.output_timer == 0 { self.audio_buffer.push(self.output()); // @TODO target sampling rate is hardcoded, need to softcode this - self.output_timer = (4194304.0 / 44100.0) as u16; + self.output_timer = (4194304.0 / self.sampling_frequency as f32) as u16; } } - pub fn output(&self) -> u8 { - self.ch1_output + self.ch2_output - } - - pub fn audio_buffer(&self) -> &Vec<u8> { - &self.audio_buffer + fn tick_length_all(&mut self) { + self.tick_length(Channel::Ch1); + self.tick_length(Channel::Ch2); + self.tick_length(Channel::Ch3); + self.tick_length(Channel::Ch4); } - pub fn audio_buffer_mut(&mut self) -> &mut Vec<u8> { - &mut self.audio_buffer + fn tick_length(&mut self, channel: Channel) { + match channel { + Channel::Ch1 => { + self.ch1_length_timer = self.ch1_length_timer.saturating_add(1); + if self.ch1_length_timer >= 64 { + self.ch1_enabled = false; + self.ch1_length_timer = 0; + } + } + Channel::Ch2 => { + self.ch2_length_timer = self.ch2_length_timer.saturating_add(1); + if self.ch2_length_timer >= 64 { + self.ch2_enabled = false; + self.ch2_length_timer = 0; + } + } + Channel::Ch3 => (), + Channel::Ch4 => (), + } } - pub fn clear_audio_buffer(&mut self) { - self.audio_buffer.clear(); + fn tick_ch1_sweep(&mut self) { + if self.ch1_sweep_pace == 0x0 { + return; + } + self.ch1_sweep_sequence += 1; + if self.ch1_sweep_sequence >= self.ch1_sweep_pace { + let divisor = (1 as u16) << self.ch1_sweep_slope as u16; + let delta = (self.ch1_wave_length as f32 / divisor as f32) as u16; + if self.ch1_sweep_increase { + self.ch1_wave_length = self.ch1_wave_length.saturating_add(delta); + } else { + self.ch1_wave_length = self.ch1_wave_length.saturating_sub(delta); + } + if self.ch1_wave_length > 0x07ff { + self.ch1_enabled = false; + self.ch1_wave_length = 0x07ff; + } + self.ch1_sweep_sequence = 0; + } } }