From 903cfacad3676071f917ff0aaa2baf1f436146d5 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Wed, 28 Jul 2021 02:01:04 -0500 Subject: [PATCH] fix(apu): replace mpsc with spsc ringbuffer --- Cargo.lock | 17 +++++++- Cargo.toml | 2 +- src/apu.rs | 77 +++++++++++++------------------- src/apu/gen.rs | 106 ++++++++++++++++++++++----------------------- src/bus.rs | 4 -- src/cpu.rs | 10 ----- src/instruction.rs | 1 - src/lib.rs | 2 +- src/main.rs | 16 ++++--- 9 files changed, 111 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cce05e1..000ad49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,6 +178,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + [[package]] name = "calloop" version = "0.6.5" @@ -684,10 +690,10 @@ dependencies = [ "anyhow", "bitfield", "clap", - "crossbeam-channel", "gilrs", "pixels", "rodio", + "rtrb", "winit", "winit_input_helper", ] @@ -1677,6 +1683,15 @@ dependencies = [ "petgraph", ] +[[package]] +name = "rtrb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "318256ac02f7e11a48a10339ba5dca8bd7eb17496abf384e8ea909bb2ae5275f" +dependencies = [ + "cache-padded", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index c5e287a..bd10d11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ pixels = "^0.5" winit = "^0.25" winit_input_helper = "^0.10" rodio = "^0.14" -crossbeam-channel = "^0.5" +rtrb = "^0.1.4" [profile.release] debug = true diff --git a/src/apu.rs b/src/apu.rs index 9c7fa7d..da09ed7 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -1,6 +1,6 @@ use crate::bus::BusIo; use crate::emu::SM83_CLOCK_SPEED; -use gen::{AudioBuffer, AudioSender}; +use gen::SampleProducer; use types::ch1::{Sweep, SweepDirection}; use types::ch3::Volume as Ch3Volume; use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter}; @@ -10,13 +10,9 @@ use types::{ChannelControl, FrameSequencerState, SoundOutput}; pub mod gen; mod types; +const SAMPLE_INCREMENT: u64 = gen::SAMPLE_RATE as u64; const WAVE_PATTERN_RAM_LEN: usize = 0x10; -const SAMPLE_RATE: u32 = 48000; // Hz -const AUDIO_BUFFER_LEN: usize = 512; -const CHANNEL_COUNT: usize = 2; -const SAMPLE_INCREMENT: u64 = SAMPLE_RATE as u64; - #[derive(Default, Debug)] pub struct Apu { ctrl: SoundControl, @@ -33,15 +29,14 @@ pub struct Apu { frame_seq_state: FrameSequencerState, div_prev: Option, - sender: Option>, + prod: Option>, sample_counter: u64, - - buffer: AudioBuffer<(f32, f32)>, } impl BusIo for Apu { fn read_byte(&self, addr: u16) -> u8 { match addr & 0x00FF { + 0x10 => self.ch1.sweep(), 0x11 => self.ch1.duty(), 0x12 => self.ch1.envelope(), 0x14 => self.ch1.freq_hi(), @@ -136,55 +131,45 @@ impl Apu { self.div_prev = Some(bit_5); - if self.sender.is_some() && self.sample_counter >= SM83_CLOCK_SPEED { - self.sample_counter %= SM83_CLOCK_SPEED; - // Sample the APU + if let Some(ref mut prod) = self.prod { + if self.sample_counter >= SM83_CLOCK_SPEED { + self.sample_counter %= SM83_CLOCK_SPEED; - let ch1_amplitude = self.ch1.amplitude(); - let ch1_left = self.ctrl.output.ch1_left() as u8 as f32 * ch1_amplitude; - let ch1_right = self.ctrl.output.ch1_right() as u8 as f32 * ch1_amplitude; + // Sample the APU + let ch1_amplitude = self.ch1.amplitude(); + let ch1_left = self.ctrl.output.ch1_left() as u8 as f32 * ch1_amplitude; + let ch1_right = self.ctrl.output.ch1_right() as u8 as f32 * ch1_amplitude; - let ch2_amplitude = self.ch2.amplitude(); - let ch2_left = self.ctrl.output.ch2_left() as u8 as f32 * ch2_amplitude; - let ch2_right = self.ctrl.output.ch2_right() as u8 as f32 * ch2_amplitude; + let ch2_amplitude = self.ch2.amplitude(); + let ch2_left = self.ctrl.output.ch2_left() as u8 as f32 * ch2_amplitude; + let ch2_right = self.ctrl.output.ch2_right() as u8 as f32 * ch2_amplitude; - let ch3_amplitude = self.ch3.amplitude(); - let ch3_left = self.ctrl.output.ch3_left() as u8 as f32 * ch3_amplitude; - let ch3_right = self.ctrl.output.ch3_right() as u8 as f32 * ch3_amplitude; + let ch3_amplitude = self.ch3.amplitude(); + let ch3_left = self.ctrl.output.ch3_left() as u8 as f32 * ch3_amplitude; + let ch3_right = self.ctrl.output.ch3_right() as u8 as f32 * ch3_amplitude; - let ch4_amplitude = self.ch4.amplitude(); - let ch4_left = self.ctrl.output.ch4_left() as u8 as f32 * ch4_amplitude; - let ch4_right = self.ctrl.output.ch4_right() as u8 as f32 * ch4_amplitude; + let ch4_amplitude = self.ch4.amplitude(); + let ch4_left = self.ctrl.output.ch4_left() as u8 as f32 * ch4_amplitude; + let ch4_right = self.ctrl.output.ch4_right() as u8 as f32 * ch4_amplitude; - let left = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0; - let right = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0; + let left = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0; + let right = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0; - self.buffer.push_back((left, right)); + prod.push(left) + .and(prod.push(right)) + .expect("Add samples to ring buffer"); + } } } - pub fn set_audio_sender(&mut self, sender: AudioSender) { - self.sender = Some(sender); + pub fn set_producer(&mut self, prod: SampleProducer) { + self.prod = Some(prod); } pub(crate) fn is_full(&self) -> bool { - self.buffer.len() >= AUDIO_BUFFER_LEN * CHANNEL_COUNT - } - - pub(crate) fn flush_samples(&mut self) { - if let Some(sender) = self.sender.as_ref() { - while self.buffer.len() >= CHANNEL_COUNT { - match self.buffer.pop_front() { - Some((left, right)) => { - sender - .send_samples(left, right) - .expect("Successfully sent samples across threads"); - } - None => unreachable!( - "While loop ensures that there are at least two items in AudioBuffer" - ), - } - } + match self.prod.as_ref() { + Some(prod) => prod.is_full(), + None => false, } } diff --git a/src/apu/gen.rs b/src/apu/gen.rs index 701821b..2a2c3b4 100644 --- a/src/apu/gen.rs +++ b/src/apu/gen.rs @@ -1,47 +1,74 @@ -use super::{AUDIO_BUFFER_LEN, CHANNEL_COUNT, SAMPLE_RATE}; -use crossbeam_channel::{Receiver, SendError, Sender}; use rodio::Source; -use std::collections::VecDeque; +use rtrb::{Consumer, Producer, PushError, RingBuffer}; -pub struct AudioMPSC; +pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz +const CHANNEL_COUNT: usize = 2; +const AUDIO_BUFFER_LEN: usize = 4096; -impl AudioMPSC { - pub fn init() -> (AudioSender, AudioReceiver) { - // TODO: Can we provide an upper limit for this? - // The larger this channel is, the more lag there is between the Audio and - // Emulator - let (send, recv) = crossbeam_channel::unbounded(); +pub struct AudioSPSC { + inner: RingBuffer, +} - (AudioSender { inner: send }, AudioReceiver { inner: recv }) +impl Default for AudioSPSC { + fn default() -> Self { + Self { + inner: RingBuffer::new(AUDIO_BUFFER_LEN * CHANNEL_COUNT), + } } } -#[derive(Debug)] -pub struct AudioSender { - inner: Sender, -} +impl AudioSPSC { + pub fn new(capacity: usize) -> Self { + Self { + inner: RingBuffer::new(capacity), + } + } -impl AudioSender { - pub(crate) fn send_samples(&self, left: T, right: T) -> Result<(), SendError> { - self.inner.send(left).and(self.inner.send(right))?; - Ok(()) + pub fn init(self) -> (SampleProducer, SampleConsumer) { + let (prod, cons) = self.inner.split(); + + ( + SampleProducer { inner: prod }, + SampleConsumer { inner: cons }, + ) } } -pub struct AudioReceiver { - inner: Receiver, +pub struct SampleProducer { + inner: Producer, } -impl Iterator for AudioReceiver { - type Item = T; +impl SampleProducer { + pub(crate) fn push(&mut self, value: T) -> Result<(), PushError> { + self.inner.push(value) + } + + pub(crate) fn is_full(&self) -> bool { + self.inner.is_full() + } +} + +impl std::fmt::Debug for SampleProducer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(&format!("SampleProducer<{}>", std::any::type_name::())) + .finish_non_exhaustive() + } +} + +pub struct SampleConsumer { + inner: Consumer, +} + +impl Iterator for SampleConsumer { + type Item = f32; fn next(&mut self) -> Option { - // TODO: Should this never return none? - self.inner.recv().ok() + // As of 2021-07-28, PopError can only be Empty + Some(self.inner.pop().unwrap_or_default()) } } -impl Source for AudioReceiver { +impl Source for SampleConsumer { fn current_frame_len(&self) -> Option { // A frame changes when the samples rate or // number of channels change. This will never happen, so @@ -63,30 +90,3 @@ impl Source for AudioReceiver { None } } - -#[derive(Debug)] -pub(crate) struct AudioBuffer { - inner: VecDeque, -} - -impl Default for AudioBuffer { - fn default() -> Self { - Self { - inner: VecDeque::with_capacity(AUDIO_BUFFER_LEN * CHANNEL_COUNT), - } - } -} - -impl AudioBuffer { - pub(crate) fn push_back(&mut self, value: T) { - self.inner.push_back(value) - } - - pub(crate) fn pop_front(&mut self) -> Option { - self.inner.pop_front() - } - - pub(crate) fn len(&self) -> usize { - self.inner.len() - } -} diff --git a/src/bus.rs b/src/bus.rs index e0395fd..ac33c2e 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -70,10 +70,6 @@ impl Bus { &self.apu } - pub(crate) fn apu_mut(&mut self) -> &mut Apu { - &mut self.apu - } - pub(crate) fn clock(&mut self) { self.ppu.clock(); self.timer.clock(); diff --git a/src/cpu.rs b/src/cpu.rs index fbe74a1..2810f38 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -4,7 +4,6 @@ use crate::instruction::{Cycle, Instruction}; use crate::interrupt::{InterruptEnable, InterruptFlag}; use crate::joypad::Joypad; use crate::ppu::Ppu; -use crate::timer::Timer; use bitfield::bitfield; use std::fmt::{Display, Formatter, Result as FmtResult}; @@ -127,8 +126,6 @@ impl Cpu { if !self.bus.apu().is_full() { self.bus.clock(); elapsed += 1; - } else { - self.bus.apu_mut().flush_samples(); } } @@ -178,10 +175,6 @@ impl Cpu { &mut self.bus.joypad } - pub(crate) fn timer(&self) -> &Timer { - &self.bus.timer - } - fn check_ime(&mut self) { match self.ime { ImeState::Pending => { @@ -292,7 +285,6 @@ impl Cpu { E => self.reg.e = value, H => self.reg.h = value, L => self.reg.l = value, - Flag => self.flags = value.into(), } } @@ -307,7 +299,6 @@ impl Cpu { E => self.reg.e, H => self.reg.h, L => self.reg.l, - Flag => self.flags.into(), } } @@ -409,7 +400,6 @@ pub(crate) enum Register { E, H, L, - Flag, } #[derive(Debug, Clone, Copy)] diff --git a/src/instruction.rs b/src/instruction.rs index 197d97b..5d622e1 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1887,7 +1887,6 @@ impl TryFrom for InstrRegister { Register::E => Ok(Self::E), Register::H => Ok(Self::H), Register::L => Ok(Self::L), - Register::Flag => Err("Can not convert Register::Flag to InstrRegister"), } } } diff --git a/src/lib.rs b/src/lib.rs index 3025d0c..6ba0645 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub use apu::gen::AudioMPSC; +pub use apu::gen::AudioSPSC; pub use instruction::Cycle; pub const GB_WIDTH: usize = 160; diff --git a/src/main.rs b/src/main.rs index bbe846e..9f7df3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ use anyhow::{anyhow, Result}; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; -use gb::{AudioMPSC, Cycle, GB_HEIGHT, GB_WIDTH}; +use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; use gilrs::Gilrs; use pixels::{PixelsBuilder, SurfaceTexture}; -use rodio::OutputStream; +use rodio::{OutputStream, Sink}; use std::time::Instant; use winit::dpi::LogicalSize; use winit::event::{Event, VirtualKeyCode}; @@ -65,16 +65,18 @@ fn main() -> Result<()> { .build()? }; - let (send, recv) = AudioMPSC::init(); - game_boy.apu_mut().set_audio_sender(send); + let spsc: AudioSPSC = Default::default(); + let (prod, cons) = spsc.init(); + + game_boy.apu_mut().set_producer(prod); // Initialize Audio let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); + let sink = Sink::try_new(&stream_handle)?; + sink.append(cons); std::thread::spawn(move || { - stream_handle - .play_raw(recv) - .expect("Failed to play Audio Source"); + sink.sleep_until_end(); }); let mut now = Instant::now();