fix(apu): replace mpsc with spsc ringbuffer
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
9d0e099a97
commit
903cfacad3
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
47
src/apu.rs
47
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<u8>,
|
||||
|
||||
sender: Option<AudioSender<f32>>,
|
||||
prod: Option<SampleProducer<f32>>,
|
||||
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,10 +131,11 @@ impl Apu {
|
|||
|
||||
self.div_prev = Some(bit_5);
|
||||
|
||||
if self.sender.is_some() && self.sample_counter >= SM83_CLOCK_SPEED {
|
||||
if let Some(ref mut prod) = self.prod {
|
||||
if self.sample_counter >= SM83_CLOCK_SPEED {
|
||||
self.sample_counter %= SM83_CLOCK_SPEED;
|
||||
// Sample the APU
|
||||
|
||||
// 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;
|
||||
|
@ -159,32 +155,21 @@ impl Apu {
|
|||
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<f32>) {
|
||||
self.sender = Some(sender);
|
||||
pub fn set_producer(&mut self, prod: SampleProducer<f32>) {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
106
src/apu/gen.rs
106
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<f32>, AudioReceiver<f32>) {
|
||||
// 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<T> {
|
||||
inner: RingBuffer<T>,
|
||||
}
|
||||
|
||||
(AudioSender { inner: send }, AudioReceiver { inner: recv })
|
||||
impl<T> Default for AudioSPSC<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: RingBuffer::new(AUDIO_BUFFER_LEN * CHANNEL_COUNT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AudioSender<T> {
|
||||
inner: Sender<T>,
|
||||
}
|
||||
impl<T> AudioSPSC<T> {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: RingBuffer::new(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AudioSender<T> {
|
||||
pub(crate) fn send_samples(&self, left: T, right: T) -> Result<(), SendError<T>> {
|
||||
self.inner.send(left).and(self.inner.send(right))?;
|
||||
Ok(())
|
||||
pub fn init(self) -> (SampleProducer<T>, SampleConsumer<T>) {
|
||||
let (prod, cons) = self.inner.split();
|
||||
|
||||
(
|
||||
SampleProducer { inner: prod },
|
||||
SampleConsumer { inner: cons },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioReceiver<T> {
|
||||
inner: Receiver<T>,
|
||||
pub struct SampleProducer<T> {
|
||||
inner: Producer<T>,
|
||||
}
|
||||
|
||||
impl<T> Iterator for AudioReceiver<T> {
|
||||
type Item = T;
|
||||
impl<T> SampleProducer<T> {
|
||||
pub(crate) fn push(&mut self, value: T) -> Result<(), PushError<T>> {
|
||||
self.inner.push(value)
|
||||
}
|
||||
|
||||
pub(crate) fn is_full(&self) -> bool {
|
||||
self.inner.is_full()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for SampleProducer<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct(&format!("SampleProducer<{}>", std::any::type_name::<T>()))
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SampleConsumer<T> {
|
||||
inner: Consumer<T>,
|
||||
}
|
||||
|
||||
impl Iterator for SampleConsumer<f32> {
|
||||
type Item = f32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// 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<T: rodio::Sample> Source for AudioReceiver<T> {
|
||||
impl Source for SampleConsumer<f32> {
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
// A frame changes when the samples rate or
|
||||
// number of channels change. This will never happen, so
|
||||
|
@ -63,30 +90,3 @@ impl<T: rodio::Sample> Source for AudioReceiver<T> {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AudioBuffer<T> {
|
||||
inner: VecDeque<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for AudioBuffer<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: VecDeque::with_capacity(AUDIO_BUFFER_LEN * CHANNEL_COUNT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AudioBuffer<T> {
|
||||
pub(crate) fn push_back(&mut self, value: T) {
|
||||
self.inner.push_back(value)
|
||||
}
|
||||
|
||||
pub(crate) fn pop_front(&mut self) -> Option<T> {
|
||||
self.inner.pop_front()
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
10
src/cpu.rs
10
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)]
|
||||
|
|
|
@ -1887,7 +1887,6 @@ impl TryFrom<Register> 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub use apu::gen::AudioMPSC;
|
||||
pub use apu::gen::AudioSPSC;
|
||||
pub use instruction::Cycle;
|
||||
|
||||
pub const GB_WIDTH: usize = 160;
|
||||
|
|
16
src/main.rs
16
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<f32> = 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();
|
||||
|
|
Loading…
Reference in New Issue