From b026ad4ed3f014e5c0cbeaf95fef8d6d45256613 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Mon, 23 Dec 2019 01:30:36 +0200 Subject: [PATCH] Add simple resampling using cosine interpolation, seems to work~ish. Still have clicks & delays in my audio output. Former-commit-id: 46707fc773d8083fbba1ec614e3e0a3e53866b98 --- src/core/gba.rs | 7 ++--- src/core/iodev.rs | 5 ++-- src/core/sound/dsp.rs | 51 +++++++++++++++++++++++++++++++++++ src/core/sound/mod.rs | 60 ++++++++++++++++++++++++++---------------- src/core/sysbus.rs | 1 - src/plat/sdl2/audio.rs | 15 +++-------- src/plat/sdl2/main.rs | 1 + 7 files changed, 99 insertions(+), 41 deletions(-) create mode 100644 src/core/sound/dsp.rs diff --git a/src/core/gba.rs b/src/core/gba.rs index 8cc68b5..87e33e4 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -7,6 +7,7 @@ use super::cartridge::Cartridge; use super::gpu::*; use super::interrupt::*; use super::iodev::*; +use super::sound::SoundController; use super::sysbus::SysBus; use super::super::{AudioInterface, InputInterface, VideoInterface}; @@ -28,7 +29,8 @@ impl GameBoyAdvance { audio_device: Rc>, input_device: Rc>, ) -> GameBoyAdvance { - let io = IoDevices::new(); + let sound_controller = SoundController::new(audio_device.clone()); + let io = IoDevices::new(sound_controller); GameBoyAdvance { cpu: cpu, sysbus: Box::new(SysBus::new(io, bios_rom, gamepak)), @@ -119,7 +121,6 @@ impl GameBoyAdvance { } io.intc.request_irqs(irqs); - let mut audio_device = self.audio_device.borrow_mut(); - io.sound.update(self.cpu.cycles, &mut (*audio_device)); + io.sound.update(self.cpu.cycles); } } diff --git a/src/core/iodev.rs b/src/core/iodev.rs index a220724..4a33c70 100644 --- a/src/core/iodev.rs +++ b/src/core/iodev.rs @@ -17,7 +17,6 @@ pub enum HaltState { Stop, // In Stop mode, most of the hardware including sound and video are paused } -#[derive(Debug)] pub struct IoDevices { pub intc: InterruptController, pub gpu: Gpu, @@ -33,10 +32,10 @@ pub struct IoDevices { } impl IoDevices { - pub fn new() -> IoDevices { + pub fn new(sound_controller: SoundController) -> IoDevices { IoDevices { gpu: Gpu::new(), - sound: SoundController::new(), + sound: sound_controller, timers: Timers::new(), dmac: DmaController::new(), intc: InterruptController::new(), diff --git a/src/core/sound/dsp.rs b/src/core/sound/dsp.rs new file mode 100644 index 0000000..f841bfa --- /dev/null +++ b/src/core/sound/dsp.rs @@ -0,0 +1,51 @@ +pub type Sample = (i16, i16); +const PI: f32 = std::f32::consts::PI; + +pub trait Resampler { + fn push_sample(&mut self, s: Sample, output: &mut Vec); +} + +pub struct CosineResampler { + last_in_sample: Sample, + phase: f32, + pub in_freq: f32, + out_freq: f32, +} + +fn cosine_interpolation(y1: Sample, y2: Sample, phase: f32) -> Sample { + let y1_left = y1.0 as f32; + let y1_right = y1.1 as f32; + let y2_left = y2.0 as f32; + let y2_right = y2.1 as f32; + + let mu2 = (1.0 - (PI * phase).cos()) / 2.0; + + ( + (y2_left * (1.0 - mu2) + y1_left * mu2) as i16, + (y2_right * (1.0 - mu2) + y1_right * mu2) as i16, + ) +} + +impl Resampler for CosineResampler { + fn push_sample(&mut self, s: Sample, output: &mut Vec) { + while self.phase < 1.0 { + let x = cosine_interpolation(self.last_in_sample, s, self.phase); + output.push(x.0); + output.push(x.1); + self.phase += self.in_freq / self.out_freq; + } + self.phase = self.phase - 1.0; + self.last_in_sample = s; + } +} + +impl CosineResampler { + pub fn new(in_freq: f32, out_freq: f32) -> CosineResampler { + CosineResampler { + last_in_sample: Default::default(), + phase: 0.0, + in_freq: in_freq, + out_freq: out_freq, + } + } +} diff --git a/src/core/sound/mod.rs b/src/core/sound/mod.rs index fcab0a6..a8a4d2b 100644 --- a/src/core/sound/mod.rs +++ b/src/core/sound/mod.rs @@ -1,3 +1,6 @@ +use std::cell::RefCell; +use std::rc::Rc; + use bit::BitIndex; use super::dma::DmaController; @@ -8,8 +11,10 @@ use crate::AudioInterface; mod fifo; use fifo::SoundFifo; +mod dsp; +use dsp::{CosineResampler, Resampler}; + const DMG_RATIOS: [f32; 4] = [0.25, 0.5, 1.0, 0.0]; -const DMA_RATIOS: [f32; 2] = [0.5, 1.0]; const DMA_TIMERS: [usize; 2] = [0, 1]; const DUTY_RATIOS: [f32; 4] = [0.125, 0.25, 0.5, 0.75]; @@ -19,7 +24,7 @@ struct NoiseChannel {} #[derive(Debug)] struct DmaSoundChannel { value: i8, - volume: f32, + volume_shift: i16, enable_right: bool, enable_left: bool, timer_select: usize, @@ -29,7 +34,7 @@ struct DmaSoundChannel { impl Default for DmaSoundChannel { fn default() -> DmaSoundChannel { DmaSoundChannel { - volume: DMA_RATIOS[0], + volume_shift: 1, value: 0, enable_right: false, enable_left: false, @@ -45,8 +50,9 @@ const REG_FIFO_A_H: u32 = REG_FIFO_A + 2; const REG_FIFO_B_L: u32 = REG_FIFO_B; const REG_FIFO_B_H: u32 = REG_FIFO_B + 2; -#[derive(Debug)] pub struct SoundController { + audio_device: Rc>, + sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample? last_sample_cycles: usize, // cycles count when we last provided a new sample. @@ -79,12 +85,17 @@ pub struct SoundController { dma_sound: [DmaSoundChannel; 2], - pub output_buffer: Vec, + resampler: CosineResampler, + pub output_buffer: Vec, } impl SoundController { - pub fn new() -> SoundController { + pub fn new(audio_device: Rc>) -> SoundController { + let resampler = + CosineResampler::new(32768_f32, audio_device.borrow().get_sample_rate() as f32); SoundController { + audio_device: audio_device, + sample_rate_to_cpu_freq: 12345, last_sample_cycles: 0, mse: false, @@ -110,7 +121,8 @@ impl SoundController { sound_bias: 0x200, dma_sound: [Default::default(), Default::default()], - output_buffer: Vec::with_capacity(32), + resampler: resampler, + output_buffer: Vec::with_capacity(10000), } } @@ -135,14 +147,8 @@ impl SoundController { .iter() .position(|&f| f == self.dmg_volume_ratio) .expect("bad dmg_volume_ratio!") as u16 - | DMA_RATIOS - .iter() - .position(|&f| f == self.dma_sound[0].volume) - .unwrap() as u16 - | DMA_RATIOS - .iter() - .position(|&f| f == self.dma_sound[1].volume) - .unwrap() as u16 + | cbit(2, self.dma_sound[0].volume_shift == 1) + | cbit(3, self.dma_sound[1].volume_shift == 1) | cbit(8, self.dma_sound[0].enable_right) | cbit(9, self.dma_sound[0].enable_left) | cbit(10, self.dma_sound[0].timer_select != 0) @@ -210,8 +216,8 @@ impl SoundController { REG_SOUNDCNT_H => { self.dmg_volume_ratio = DMG_RATIOS[value.bit_range(0..1) as usize]; - self.dma_sound[0].volume = DMA_RATIOS[value.bit(2) as usize]; - self.dma_sound[1].volume = DMA_RATIOS[value.bit(3) as usize]; + self.dma_sound[0].volume_shift = value.bit(2) as i16; + self.dma_sound[1].volume_shift = value.bit(3) as i16; self.dma_sound[0].enable_right = value.bit(8); self.dma_sound[0].enable_left = value.bit(9); self.dma_sound[0].timer_select = DMA_TIMERS[value.bit(10) as usize]; @@ -274,7 +280,6 @@ impl SoundController { if !self.mse { return; } - // TODO - play sound ? const FIFO_INDEX_TO_REG: [u32; 2] = [REG_FIFO_A, REG_FIFO_B]; for fifo in 0..2 { @@ -294,24 +299,33 @@ impl SoundController { (32768 << resolution) as i32 } - pub fn update(&mut self, cycles: usize, audio_device: &mut dyn AudioInterface) { + pub fn update(&mut self, cycles: usize) { let resolution = self.sound_bias.bit_range(14..16) as usize; let cycles_per_sample = 512 >> resolution; + + self.resampler.in_freq = self.sample_rate() as f32; + while cycles - self.last_sample_cycles >= cycles_per_sample { self.last_sample_cycles += cycles_per_sample; - let mut sample = (0, 0); + // time to push a new sample! + let mut sample = (0i16, 0i16); for i in 0..2 { let channel = &self.dma_sound[i]; if channel.enable_left { - sample.0 += (channel.value as i16) << 8; + sample.0 += ((channel.value as i16) << 8) >> channel.volume_shift; } if channel.enable_right { - sample.1 += (channel.value as i16) << 8; + sample.1 += ((channel.value as i16) << 8) >> channel.volume_shift; } } - audio_device.play(&[sample.0, sample.1]); + + self.resampler.push_sample(sample, &mut self.output_buffer); + if self.output_buffer.len() >= 10000 { + self.audio_device.borrow_mut().play(&self.output_buffer); + self.output_buffer.clear(); + } } } } diff --git a/src/core/sysbus.rs b/src/core/sysbus.rs index 871cd5c..730b9bc 100644 --- a/src/core/sysbus.rs +++ b/src/core/sysbus.rs @@ -138,7 +138,6 @@ impl Bus for DummyBus { fn write_8(&mut self, _addr: Addr, _value: u8) {} } -#[derive(Debug)] pub struct SysBus { pub io: IoDevices, diff --git a/src/plat/sdl2/audio.rs b/src/plat/sdl2/audio.rs index c3f696c..55451e6 100644 --- a/src/plat/sdl2/audio.rs +++ b/src/plat/sdl2/audio.rs @@ -1,10 +1,10 @@ use sdl2; -use sdl2::audio::{AudioDevice, AudioQueue, AudioSpec, AudioSpecDesired, AudioStatus}; +use sdl2::audio::{AudioQueue, AudioSpecDesired}; use rustboyadvance_ng::AudioInterface; pub struct Sdl2AudioPlayer { - device: AudioQueue, + pub device: AudioQueue, freq: i32, } @@ -27,15 +27,7 @@ pub fn create_audio_player(sdl: &sdl2::Sdl) -> Sdl2AudioPlayer { samples: None, }; - // let mut device = audio_subsystem - // .open_playback(None, &desired_spec, |spec| { - // println!("Obtained {:?}", spec); - - // GbaAudioCallback { spec: spec } - // }) - // .unwrap(); - - let mut device = audio_subsystem + let device = audio_subsystem .open_queue::(None, &desired_spec) .unwrap(); @@ -43,5 +35,6 @@ pub fn create_audio_player(sdl: &sdl2::Sdl) -> Sdl2AudioPlayer { let freq = device.spec().freq; device.resume(); + Sdl2AudioPlayer { device, freq } } diff --git a/src/plat/sdl2/main.rs b/src/plat/sdl2/main.rs index c78285c..352f605 100644 --- a/src/plat/sdl2/main.rs +++ b/src/plat/sdl2/main.rs @@ -85,6 +85,7 @@ fn main() { keycode: Some(Keycode::Space), .. } => { + audio.borrow_mut().device.clear(); // clear audio queue frame_limiter = true; } Event::KeyDown {