From 579d13d0ef561e05e47f75948d6da51d47e26a72 Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Wed, 15 Feb 2023 15:10:22 +1100 Subject: [PATCH] render pulse channels --- Cargo.lock | 19 ++++++ Cargo.toml | 1 + src/processor/gpu.rs | 1 + src/processor/memory/mmio/apu.rs | 47 +++++++++++++- src/processor/memory/mmio/apu/channels.rs | 77 +++++++++++++++++++++-- src/processor/timer.rs | 3 +- 6 files changed, 140 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d36f93..5bad05a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,6 +248,7 @@ dependencies = [ "gilrs", "minifb", "rand", + "samplerate", "spin_sleep", ] @@ -385,6 +386,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "libsamplerate-sys" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28853b399f78f8281cd88d333b54a63170c4275f6faea66726a2bea5cca72e0d" +dependencies = [ + "cmake", +] + [[package]] name = "libudev-sys" version = "0.1.4" @@ -675,6 +685,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "samplerate" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e032b2b24715c4f982f483ea3abdb3c9ba444d9f63e87b2843d6f998f5ba2698" +dependencies = [ + "libsamplerate-sys", +] + [[package]] name = "scoped-tls" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index e00d7f7..a1d3604 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ spin_sleep = "1.1.1" minifb = "0.23" rand = "0.8.5" gilrs = "0.10.1" +samplerate = "0.2.4" diff --git a/src/processor/gpu.rs b/src/processor/gpu.rs index 237bfb1..c1423c8 100644 --- a/src/processor/gpu.rs +++ b/src/processor/gpu.rs @@ -160,6 +160,7 @@ impl Cpu { } self.gpu.mode = DrawMode::VBlank; self.render_window(); + self.next_audio(); self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0)); } diff --git a/src/processor/memory/mmio/apu.rs b/src/processor/memory/mmio/apu.rs index 260267e..29eb132 100644 --- a/src/processor/memory/mmio/apu.rs +++ b/src/processor/memory/mmio/apu.rs @@ -1,5 +1,11 @@ +use samplerate::{convert, ConverterType}; + use crate::{ - processor::memory::{masked_update, Address}, + processor::{ + memory::{masked_update, Address}, + timer::CLOCK_SPEED, + Cpu, + }, util::{get_bit, set_or_clear_bit}, }; @@ -46,6 +52,7 @@ pub struct Apu { vol_left: u8, vol_right: u8, div_apu: u8, + buffer: Vec, } impl Default for Apu { @@ -58,10 +65,13 @@ impl Default for Apu { vol_left: 7, vol_right: 7, div_apu: 0, + buffer: vec![], } } } +const SAMPLE_RATE: u32 = 44100; + impl Apu { pub fn div_apu_tick(&mut self) { self.div_apu = self.div_apu.wrapping_add(1); @@ -76,6 +86,31 @@ impl Apu { } } + fn tick(&mut self, steps: usize) { + self.buffer.append( + &mut self + .channels + .one + .tick(steps) + .into_iter() + .zip(self.channels.two.tick(steps).into_iter()) + .map(|(one, two)| (one + two) / 2.) + .collect(), + ); + } + + fn next_audio(&mut self) { + let _converted = convert( + CLOCK_SPEED as u32, + SAMPLE_RATE, + 1, + ConverterType::Linear, + &self.buffer, + ) + .unwrap(); + self.buffer.clear(); + } + pub fn get_register(&self, addr: Address) -> u8 { if addr == 0xFF26 || addr == 0xFF11 @@ -189,3 +224,13 @@ impl Apu { self.mem[addr_el] = masked_update(self.mem[addr_el], data, mask); } } + +impl Cpu { + pub fn advance_apu_clock(&mut self, steps: usize) { + self.memory.apu.tick(steps); + } + + pub fn next_audio(&mut self) { + self.memory.apu.next_audio(); + } +} diff --git a/src/processor/memory/mmio/apu/channels.rs b/src/processor/memory/mmio/apu/channels.rs index ae5848c..95165cc 100644 --- a/src/processor/memory/mmio/apu/channels.rs +++ b/src/processor/memory/mmio/apu/channels.rs @@ -39,32 +39,80 @@ impl Default for Envelope { } } +enum DutyCycle { + // 0b00 + TwelvePointFive, + // 0b01 + TwentyFive, + // 0b10 + Fifty, + // 0b11 + SeventyFive, +} + +impl From for DutyCycle { + fn from(value: u8) -> Self { + match value & 0b11 { + 0b00 => Self::TwelvePointFive, + 0b01 => Self::TwentyFive, + 0b10 => Self::Fifty, + 0b11 => Self::SeventyFive, + _ => panic!("bad match!"), + } + } +} + +impl DutyCycle { + fn as_waveform(&self) -> [u8; 8] { + match self { + DutyCycle::TwelvePointFive => [0, 0, 0, 0, 0, 0, 0, 1], + DutyCycle::TwentyFive => [1, 0, 0, 0, 0, 0, 0, 1], + DutyCycle::Fifty => [1, 0, 0, 0, 0, 1, 1, 1], + DutyCycle::SeventyFive => [0, 1, 1, 1, 1, 1, 1, 0], + } + } + + fn as_u8(&self) -> u8 { + match self { + DutyCycle::TwelvePointFive => 0b00, + DutyCycle::TwentyFive => 0b01, + DutyCycle::Fifty => 0b10, + DutyCycle::SeventyFive => 0b11, + } + } +} + pub(super) struct PwmChannel { pub(super) enabled: bool, pub(super) pan_left: bool, pub(super) pan_right: bool, sweep: Sweep, - duty_cycle: u8, + duty_cycle: DutyCycle, length_enable: bool, length_timer: u8, envelope: Envelope, queued_envelope: Envelope, wavelength: u16, + wave_timer: u16, + wave_position: usize, } impl PwmChannel { pub(super) fn new(enabled: bool, pan_left: bool, pan_right: bool) -> Self { + let wavelength = 0x700; Self { enabled, pan_left, pan_right, sweep: Sweep::default(), - duty_cycle: 2, + duty_cycle: DutyCycle::Fifty, length_enable: false, length_timer: 63, envelope: Envelope::default(), queued_envelope: Envelope::default(), - wavelength: 0x700, + wavelength, + wave_timer: set_wave_timer(wavelength), + wave_position: 0, } } @@ -73,6 +121,19 @@ impl PwmChannel { self.envelope = self.queued_envelope; } + pub fn tick(&mut self, steps: usize) -> Vec { + (0..steps) + .map(|_| { + let b; + (self.wave_timer, b) = self.wave_timer.overflowing_sub(1); + if b { + self.wave_position = (self.wave_position + 1) % 8; + } + (self.duty_cycle.as_waveform()[self.wave_position] as f32 * (-2.)) + 1. + }) + .collect() + } + pub(super) fn update_sweep(&mut self, pace: u8, mode: EnvelopeMode, slope: u8) { self.sweep.pace = pace; self.sweep.mode = mode; @@ -88,12 +149,12 @@ impl PwmChannel { } pub(super) fn update_length_timer_and_duty_cycle(&mut self, data: u8) { - self.length_timer = (data & 0b11000000) >> 6; - self.duty_cycle = data & 0b11111; + self.duty_cycle = DutyCycle::from(data >> 6); + self.length_timer = data & !(0b11 << 6); } pub(super) fn get_length_timer_and_duty_cycle(&self) -> u8 { - ((self.duty_cycle & 0b11) << 6) | 0b11111 + (self.duty_cycle.as_u8() << 6) | !(0b11 << 6) } pub(super) fn update_volume_and_envelope(&mut self, data: u8) { @@ -134,6 +195,10 @@ impl PwmChannel { } } +fn set_wave_timer(wavelength: u16) -> u16 { + (2048 - wavelength) * 4 +} + pub(super) struct WaveChannel { pub(super) enabled: bool, pub(super) pan_left: bool, diff --git a/src/processor/timer.rs b/src/processor/timer.rs index b5e83aa..b6d9c0c 100644 --- a/src/processor/timer.rs +++ b/src/processor/timer.rs @@ -20,7 +20,7 @@ impl Timers { } // Hz -const CLOCK_SPEED: usize = 4194304; +pub const CLOCK_SPEED: usize = 4194304; // this will need to change when cgb mode is implemented // as it uses bit 5 in double speed mode const AUDIO_BIT: u8 = 4; @@ -30,6 +30,7 @@ impl Cpu { let clock_cycles = (machine_cycles as usize) * 4; self.advance_gpu_clock(clock_cycles); + self.advance_apu_clock(clock_cycles); self.timers.div_counter += clock_cycles; let mut div_diff = (self.timers.div_counter / 256) as u8;