no triply buffered audio

This commit is contained in:
Alex Janka 2023-05-03 14:54:25 +10:00
parent 429e3b4085
commit fb0dbf2f33
5 changed files with 37 additions and 89 deletions

View file

@ -5,7 +5,7 @@ use cpal::{
use futures::executor; use futures::executor;
use gb_emu_lib::connect::{AudioOutput, DownsampleType}; use gb_emu_lib::connect::{AudioOutput, DownsampleType};
const FRAMES_TO_BUFFER: usize = 1; const BUFFERS_PER_FRAME: usize = 100;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold; const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
pub fn create_output(muted: bool) -> (AudioOutput, Stream) { pub fn create_output(muted: bool) -> (AudioOutput, Stream) {
@ -25,8 +25,7 @@ pub fn create_output(muted: bool) -> (AudioOutput, Stream) {
let sample_rate = config.sample_rate().0; let sample_rate = config.sample_rate().0;
let (output, mut rx) = let (output, mut rx) = AudioOutput::new(sample_rate as f32, BUFFERS_PER_FRAME, DOWNSAMPLE_TYPE);
AudioOutput::new(sample_rate as f32, true, FRAMES_TO_BUFFER, DOWNSAMPLE_TYPE);
let stream = if muted { let stream = if muted {
device device

View file

@ -67,8 +67,8 @@ pub struct GameboyEmu {
type FrameReceiver = Mutex<Option<Receiver<Vec<[u8; 4]>>>>; type FrameReceiver = Mutex<Option<Receiver<Vec<[u8; 4]>>>>;
type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>; type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>;
const FRAMES_TO_BUFFER: usize = 1; const BUFFERS_PER_FRAME: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold; const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::Linear;
const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB.gb"); const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB.gb");
@ -218,8 +218,7 @@ impl Plugin for GameboyEmu {
if let Some(ref mut vars) = self.vars { if let Some(ref mut vars) = self.vars {
let (output, rx) = AudioOutput::new( let (output, rx) = AudioOutput::new(
buffer_config.sample_rate, buffer_config.sample_rate,
false, BUFFERS_PER_FRAME,
FRAMES_TO_BUFFER,
DOWNSAMPLE_TYPE, DOWNSAMPLE_TYPE,
); );
@ -232,8 +231,7 @@ impl Plugin for GameboyEmu {
let (output, rx) = AudioOutput::new( let (output, rx) = AudioOutput::new(
buffer_config.sample_rate, buffer_config.sample_rate,
false, BUFFERS_PER_FRAME,
FRAMES_TO_BUFFER,
DOWNSAMPLE_TYPE, DOWNSAMPLE_TYPE,
); );

View file

@ -42,18 +42,16 @@ pub trait Renderer<Format: From<Colour>> {
pub struct AudioOutput { pub struct AudioOutput {
pub sample_rate: f32, pub sample_rate: f32,
pub send_rb: AsyncHeapProducer<[f32; 2]>, pub send_rb: AsyncHeapProducer<[f32; 2]>,
pub wait_for_output: bool,
pub downsample_type: DownsampleType, pub downsample_type: DownsampleType,
} }
impl AudioOutput { impl AudioOutput {
pub fn new( pub fn new(
sample_rate: f32, sample_rate: f32,
wait_for_output: bool, buffers_per_frame: usize,
frames_to_buffer: usize,
downsample_type: DownsampleType, downsample_type: DownsampleType,
) -> (Self, AsyncHeapConsumer<[f32; 2]>) { ) -> (Self, AsyncHeapConsumer<[f32; 2]>) {
let rb_len = (sample_rate as usize / 60) * frames_to_buffer; let rb_len = (sample_rate as usize / 60) / buffers_per_frame;
let rb = AsyncHeapRb::<[f32; 2]>::new(rb_len); let rb = AsyncHeapRb::<[f32; 2]>::new(rb_len);
let (send_rb, rx) = rb.split(); let (send_rb, rx) = rb.split();
@ -62,7 +60,6 @@ impl AudioOutput {
Self { Self {
sample_rate, sample_rate,
send_rb, send_rb,
wait_for_output,
downsample_type, downsample_type,
}, },
rx, rx,

View file

@ -42,8 +42,6 @@ pub struct Apu {
vin: VinEnable, vin: VinEnable,
mixer: Mixer, mixer: Mixer,
div_apu: u8, div_apu: u8,
buffer: Vec<DacSample>,
out_buffer: Vec<[f32; 2]>,
converter: Downsampler, converter: Downsampler,
output: AudioOutput, output: AudioOutput,
} }
@ -55,8 +53,6 @@ pub struct ApuSaveState {
vin: VinEnable, vin: VinEnable,
mixer: Mixer, mixer: Mixer,
div_apu: u8, div_apu: u8,
buffer: Vec<DacSample>,
out_buffer: Vec<[f32; 2]>,
} }
impl ApuSaveState { impl ApuSaveState {
@ -67,14 +63,10 @@ impl ApuSaveState {
vin: apu.vin, vin: apu.vin,
mixer: apu.mixer, mixer: apu.mixer,
div_apu: apu.div_apu, div_apu: apu.div_apu,
buffer: apu.buffer.clone(),
out_buffer: apu.out_buffer.clone(),
} }
} }
} }
const CYCLES_PER_FRAME: usize = 70224;
impl Apu { impl Apu {
pub fn new(output: AudioOutput) -> Self { pub fn new(output: AudioOutput) -> Self {
Self { Self {
@ -83,8 +75,6 @@ impl Apu {
vin: VinEnable::default(), vin: VinEnable::default(),
mixer: Mixer::default(), mixer: Mixer::default(),
div_apu: 0, div_apu: 0,
buffer: vec![],
out_buffer: vec![],
converter: Downsampler::new(output.sample_rate, output.downsample_type), converter: Downsampler::new(output.sample_rate, output.downsample_type),
output, output,
} }
@ -97,8 +87,6 @@ impl Apu {
vin: state.vin, vin: state.vin,
mixer: state.mixer, mixer: state.mixer,
div_apu: state.div_apu, div_apu: state.div_apu,
buffer: state.buffer,
out_buffer: state.out_buffer,
converter: Downsampler::new(output.sample_rate, output.downsample_type), converter: Downsampler::new(output.sample_rate, output.downsample_type),
output, output,
} }
@ -132,8 +120,7 @@ impl Apu {
pub fn tick(&mut self, steps: usize, output: bool) { pub fn tick(&mut self, steps: usize, output: bool) {
if output { if output {
self.buffer.append( for s in izip!(
&mut izip!(
self.channels.one.tick(steps).into_iter(), self.channels.one.tick(steps).into_iter(),
self.channels.two.tick(steps).into_iter(), self.channels.two.tick(steps).into_iter(),
self.channels.three.tick(steps).into_iter(), self.channels.three.tick(steps).into_iter(),
@ -144,13 +131,10 @@ impl Apu {
two, two,
three, three,
four, four,
}) }) {
.collect(), if let Some(next) = self.converter.push(s.mixed(&self.mixer)) {
); executor::block_on(self.output.send_rb.push(next)).unwrap();
if self.buffer.len() >= CYCLES_PER_FRAME { }
self.next_audio();
} else if !self.out_buffer.is_empty() {
self.push_audio();
} }
} else { } else {
self.channels.one.tick(steps); self.channels.one.tick(steps);
@ -168,34 +152,6 @@ impl Apu {
(self.channels.one.last & 0xF) | ((self.channels.two.last & 0xF0) << 4) (self.channels.one.last & 0xF) | ((self.channels.two.last & 0xF0) << 4)
} }
fn next_audio(&mut self) {
self.out_buffer.append(
&mut self.converter.process(
self.buffer
.drain(..)
.map(|v| v.mixed(&self.mixer))
.collect::<Vec<[f32; 2]>>(),
),
);
self.push_audio();
}
fn push_audio(&mut self) {
let length = if self.output.wait_for_output {
self.out_buffer.len()
} else {
self.out_buffer.len().min(self.output.send_rb.free_len())
};
executor::block_on(
self.output
.send_rb
.push_slice(&self.out_buffer.drain(..length).collect::<Vec<[f32; 2]>>()),
)
.unwrap();
}
pub fn is_buffer_full(&self) -> bool { pub fn is_buffer_full(&self) -> bool {
self.output.send_rb.is_full() self.output.send_rb.is_full()
} }

View file

@ -55,22 +55,20 @@ impl Downsampler {
} }
} }
pub fn process(&mut self, signal: Vec<[f32; 2]>) -> Vec<[f32; 2]> { pub fn push(&mut self, signal: [f32; 2]) -> Option<[f32; 2]> {
let mut output = vec![];
for ref val in signal {
self.time_accum += 1.; self.time_accum += 1.;
if let Some(ref mut averager) = self.average { if let Some(ref mut averager) = self.average {
averager.push(val); averager.push(&signal);
} }
if self.time_accum >= self.ratio { if self.time_accum >= self.ratio {
self.time_accum -= self.ratio; self.time_accum -= self.ratio;
output.push(if let Some(ref mut averager) = self.average { Some(if let Some(ref mut averager) = self.average {
averager.finish() averager.finish()
} else { } else {
*val signal
}); })
} else {
None
} }
} }
output
}
} }