From d8545dd8cdeb47d8227e84fa35c772c6a8f48978 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 28 Dec 2019 15:59:35 +0200 Subject: [PATCH] sdl2: Use callbacks with ring buffer instead of audio queue. Better latencies, and also fast-forward (aka Turbo mode) sounds normal this way. Former-commit-id: d1075087e847c765a871157a7973c897575ef4d7 --- Cargo.lock | 18 +++++++++++ Cargo.toml | 2 ++ src/core/gpu/mod.rs | 7 +--- src/core/sound/dsp.rs | 29 ++++++++--------- src/core/sound/fifo.rs | 1 - src/core/sound/mod.rs | 55 ++++++++++++++++++------------- src/lib.rs | 7 +++- src/plat/sdl2/audio.rs | 73 +++++++++++++++++++++++++++++++++++------- src/plat/sdl2/main.rs | 7 ++-- 9 files changed, 137 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a233448..c2aff19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,6 +212,15 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "debug_stub_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dirs" version = "2.0.2" @@ -669,6 +678,11 @@ name = "rgb" version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ringbuf" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustboyadvance-ng" version = "0.1.0" @@ -682,12 +696,14 @@ dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "hexdump 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "minifb 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)", "spin_sleep 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1007,6 +1023,7 @@ dependencies = [ "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" +"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" "checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd" @@ -1060,6 +1077,7 @@ dependencies = [ "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" "checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92" +"checksum ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7c2b29d87cfbdce39849012bb5020fff88b8f01f4f5b55846a0b6ef360774eae" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67e12e40e0240de07f0dab4f4dd01bdb15d74dc977026d4ba91666c41c679ade" diff --git a/Cargo.toml b/Cargo.toml index 1584362..c0738d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ zip = "0.5.3" ctrlc = "3.1.3" spin_sleep="0.3.7" bit-set = "0.5.1" +ringbuf = "0.2.1" +debug_stub_derive = "0.3.0" [[bin]] name = "rba-sdl2" diff --git a/src/core/gpu/mod.rs b/src/core/gpu/mod.rs index 9e5d911..59effbc 100644 --- a/src/core/gpu/mod.rs +++ b/src/core/gpu/mod.rs @@ -467,12 +467,7 @@ impl Gpu { } // Returns the new gpu state - pub fn step( - &mut self, - cycles: usize, - sb: &mut SysBus, - irqs: &mut IrqBitmask, - ) { + pub fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) { self.cycles += cycles; match self.state { diff --git a/src/core/sound/dsp.rs b/src/core/sound/dsp.rs index f841bfa..cc36761 100644 --- a/src/core/sound/dsp.rs +++ b/src/core/sound/dsp.rs @@ -1,37 +1,34 @@ -pub type Sample = (i16, i16); +use crate::{AudioInterface, StereoSample}; + const PI: f32 = std::f32::consts::PI; pub trait Resampler { - fn push_sample(&mut self, s: Sample, output: &mut Vec); + fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface); } +#[derive(Debug)] pub struct CosineResampler { - last_in_sample: Sample, + last_in_sample: StereoSample, 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; +fn cosine_interpolation(y1: i16, y2: i16, phase: f32) -> i16 { + let y1 = y1 as i32 as f32; + let y2 = y2 as i32 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, - ) + (y2 * (1.0 - mu2) + y1 * mu2) as i16 } impl Resampler for CosineResampler { - fn push_sample(&mut self, s: Sample, output: &mut Vec) { + fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface) { while self.phase < 1.0 { - let x = cosine_interpolation(self.last_in_sample, s, self.phase); - output.push(x.0); - output.push(x.1); + let left = cosine_interpolation(self.last_in_sample.0, s.0, self.phase); + let right = cosine_interpolation(self.last_in_sample.1, s.1, self.phase); + audio.push_sample((left, right)); self.phase += self.in_freq / self.out_freq; } self.phase = self.phase - 1.0; diff --git a/src/core/sound/fifo.rs b/src/core/sound/fifo.rs index 9fb403d..d4afc15 100644 --- a/src/core/sound/fifo.rs +++ b/src/core/sound/fifo.rs @@ -1,5 +1,4 @@ // TODO write tests or replace with a crate - const SOUND_FIFO_CAPACITY: usize = 32; #[derive(Debug)] diff --git a/src/core/sound/mod.rs b/src/core/sound/mod.rs index a8a4d2b..621b8f3 100644 --- a/src/core/sound/mod.rs +++ b/src/core/sound/mod.rs @@ -31,10 +31,20 @@ struct DmaSoundChannel { fifo: SoundFifo, } +impl DmaSoundChannel { + fn is_stereo_channel_enabled(&self, channel: usize) -> bool { + match channel { + 0 => self.enable_left, + 1 => self.enable_right, + _ => unreachable!(), + } + } +} + impl Default for DmaSoundChannel { fn default() -> DmaSoundChannel { DmaSoundChannel { - volume_shift: 1, + volume_shift: 0, value: 0, enable_right: false, enable_left: false, @@ -50,8 +60,12 @@ 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; +type AudioDeviceRcRefCell = Rc>; + +#[derive(DebugStub)] pub struct SoundController { - audio_device: Rc>, + #[debug_stub = "AudioDeviceRcRefCell"] + audio_device: AudioDeviceRcRefCell, 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. @@ -90,7 +104,7 @@ pub struct SoundController { } impl SoundController { - pub fn new(audio_device: Rc>) -> SoundController { + pub fn new(audio_device: AudioDeviceRcRefCell) -> SoundController { let resampler = CosineResampler::new(32768_f32, audio_device.borrow().get_sample_rate() as f32); SoundController { @@ -250,13 +264,13 @@ impl SoundController { } REG_FIFO_A_L | REG_FIFO_A_H => { - self.dma_sound[0].fifo.write(((value >> 8) & 0xff) as i8); self.dma_sound[0].fifo.write((value & 0xff) as i8); + self.dma_sound[0].fifo.write(((value >> 8) & 0xff) as i8); } REG_FIFO_B_L | REG_FIFO_B_H => { - self.dma_sound[1].fifo.write(((value >> 8) & 0xff) as i8); self.dma_sound[1].fifo.write((value & 0xff) as i8); + self.dma_sound[1].fifo.write(((value >> 8) & 0xff) as i8); } REG_SOUNDBIAS => self.sound_bias = value & 0xc3fe, @@ -283,11 +297,11 @@ impl SoundController { const FIFO_INDEX_TO_REG: [u32; 2] = [REG_FIFO_A, REG_FIFO_B]; for fifo in 0..2 { - let channel = &mut self.dma_sound[fifo]; + let dma = &mut self.dma_sound[fifo]; - if timer_id == channel.timer_select { - channel.value = channel.fifo.read(); - if channel.fifo.count() <= 16 { + if timer_id == dma.timer_select { + dma.value = dma.fifo.read(); + if dma.fifo.count() <= 16 { dmac.notify_sound_fifo(FIFO_INDEX_TO_REG[fifo]); } } @@ -310,22 +324,19 @@ impl SoundController { // 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) >> channel.volume_shift; - } - if channel.enable_right { - sample.1 += ((channel.value as i16) << 8) >> channel.volume_shift; + let mut sample = [0; 2]; + + for channel in 0..=1 { + for dma in &mut self.dma_sound { + if dma.is_stereo_channel_enabled(channel) { + sample[channel] += dma.value as i16; + } } } - 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(); - } + let mut audio = self.audio_device.borrow_mut(); + self.resampler + .push_sample((sample[0], sample[1]), &mut *audio); } } } diff --git a/src/lib.rs b/src/lib.rs index c14e289..850cebe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,9 @@ #![feature(core_intrinsics)] #![feature(exclusive_range_pattern)] +#[macro_use] +extern crate debug_stub_derive; + #[macro_use] extern crate enum_primitive_derive; extern crate num; @@ -34,11 +37,13 @@ pub trait VideoInterface { fn render(&mut self, buffer: &[u32]); } +pub type StereoSample = (i16, i16); + pub trait AudioInterface { fn get_sample_rate(&self) -> i32; #[allow(unused_variables)] - fn play(&mut self, samples: &[i16]) {} + fn push_sample(&mut self, samples: StereoSample) {} } pub trait InputInterface { diff --git a/src/plat/sdl2/audio.rs b/src/plat/sdl2/audio.rs index 55451e6..7603139 100644 --- a/src/plat/sdl2/audio.rs +++ b/src/plat/sdl2/audio.rs @@ -1,40 +1,89 @@ use sdl2; -use sdl2::audio::{AudioQueue, AudioSpecDesired}; +use sdl2::audio::{AudioCallback, AudioDevice, AudioSpec, AudioSpecDesired}; -use rustboyadvance_ng::AudioInterface; +use rustboyadvance_ng::{AudioInterface, StereoSample}; + +extern crate ringbuf; +use ringbuf::{Consumer, Producer, RingBuffer}; + +struct GbaAudioCallback { + consumer: Consumer, + spec: AudioSpec, +} pub struct Sdl2AudioPlayer { - pub device: AudioQueue, + _device: AudioDevice, + producer: Producer, freq: i32, } +impl AudioCallback for GbaAudioCallback { + type Channel = i16; + + fn callback(&mut self, out_samples: &mut [i16]) { + let sample_count = out_samples.len() / 2; + + for i in 0..sample_count { + if let Some((left, right)) = self.consumer.pop() { + out_samples[2 * i] = left * (1 << 4); + out_samples[2 * i + 1] = right * (1 << 4); + } else { + out_samples[2 * i] = self.spec.silence as i16; + out_samples[2 * i + 1] = self.spec.silence as i16; + } + } + } +} + impl AudioInterface for Sdl2AudioPlayer { fn get_sample_rate(&self) -> i32 { self.freq } - fn play(&mut self, samples: &[i16]) { - self.device.queue(&samples); + fn push_sample(&mut self, sample: StereoSample) { + #![allow(unused_must_use)] + self.producer.push(sample); } } pub fn create_audio_player(sdl: &sdl2::Sdl) -> Sdl2AudioPlayer { - let audio_subsystem = sdl.audio().unwrap(); - let desired_spec = AudioSpecDesired { freq: Some(44_100), channels: Some(2), // stereo samples: None, }; + let audio_subsystem = sdl.audio().unwrap(); + + let mut freq = 0; + + let mut producer: Option> = None; + let device = audio_subsystem - .open_queue::(None, &desired_spec) + .open_playback(None, &desired_spec, |spec| { + println!("Found audio device: {:?}", spec); + freq = spec.freq; + + // Create a thread-safe SPSC fifo + let ringbuf_size = (spec.samples as usize) * 2; + let rb = RingBuffer::::new(ringbuf_size); + let (prod, cons) = rb.split(); + + // move producer to the outer scope + producer = Some(prod); + + GbaAudioCallback { + consumer: cons, + spec, + } + }) .unwrap(); - println!("Found audio device: {:?}", device.spec()); - - let freq = device.spec().freq; device.resume(); - Sdl2AudioPlayer { device, freq } + Sdl2AudioPlayer { + _device: device, + freq, + producer: producer.unwrap(), + } } diff --git a/src/plat/sdl2/main.rs b/src/plat/sdl2/main.rs index 352f605..e967651 100644 --- a/src/plat/sdl2/main.rs +++ b/src/plat/sdl2/main.rs @@ -17,9 +17,9 @@ mod audio; mod input; mod video; -use audio::{create_audio_player, Sdl2AudioPlayer}; -use input::{create_input, Sdl2Input}; -use video::{create_video_interface, Sdl2Video}; +use audio::create_audio_player; +use input::create_input; +use video::create_video_interface; extern crate rustboyadvance_ng; use rustboyadvance_ng::prelude::*; @@ -85,7 +85,6 @@ fn main() { keycode: Some(Keycode::Space), .. } => { - audio.borrow_mut().device.clear(); // clear audio queue frame_limiter = true; } Event::KeyDown {