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 gb_emu_lib::connect::{AudioOutput, DownsampleType};
const FRAMES_TO_BUFFER: usize = 1;
const BUFFERS_PER_FRAME: usize = 100;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
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 (output, mut rx) =
AudioOutput::new(sample_rate as f32, true, FRAMES_TO_BUFFER, DOWNSAMPLE_TYPE);
let (output, mut rx) = AudioOutput::new(sample_rate as f32, BUFFERS_PER_FRAME, DOWNSAMPLE_TYPE);
let stream = if muted {
device

View file

@ -67,8 +67,8 @@ pub struct GameboyEmu {
type FrameReceiver = Mutex<Option<Receiver<Vec<[u8; 4]>>>>;
type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>;
const FRAMES_TO_BUFFER: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
const BUFFERS_PER_FRAME: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::Linear;
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 {
let (output, rx) = AudioOutput::new(
buffer_config.sample_rate,
false,
FRAMES_TO_BUFFER,
BUFFERS_PER_FRAME,
DOWNSAMPLE_TYPE,
);
@ -232,8 +231,7 @@ impl Plugin for GameboyEmu {
let (output, rx) = AudioOutput::new(
buffer_config.sample_rate,
false,
FRAMES_TO_BUFFER,
BUFFERS_PER_FRAME,
DOWNSAMPLE_TYPE,
);

View file

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

View file

@ -42,8 +42,6 @@ pub struct Apu {
vin: VinEnable,
mixer: Mixer,
div_apu: u8,
buffer: Vec<DacSample>,
out_buffer: Vec<[f32; 2]>,
converter: Downsampler,
output: AudioOutput,
}
@ -55,8 +53,6 @@ pub struct ApuSaveState {
vin: VinEnable,
mixer: Mixer,
div_apu: u8,
buffer: Vec<DacSample>,
out_buffer: Vec<[f32; 2]>,
}
impl ApuSaveState {
@ -67,14 +63,10 @@ impl ApuSaveState {
vin: apu.vin,
mixer: apu.mixer,
div_apu: apu.div_apu,
buffer: apu.buffer.clone(),
out_buffer: apu.out_buffer.clone(),
}
}
}
const CYCLES_PER_FRAME: usize = 70224;
impl Apu {
pub fn new(output: AudioOutput) -> Self {
Self {
@ -83,8 +75,6 @@ impl Apu {
vin: VinEnable::default(),
mixer: Mixer::default(),
div_apu: 0,
buffer: vec![],
out_buffer: vec![],
converter: Downsampler::new(output.sample_rate, output.downsample_type),
output,
}
@ -97,8 +87,6 @@ impl Apu {
vin: state.vin,
mixer: state.mixer,
div_apu: state.div_apu,
buffer: state.buffer,
out_buffer: state.out_buffer,
converter: Downsampler::new(output.sample_rate, output.downsample_type),
output,
}
@ -132,25 +120,21 @@ impl Apu {
pub fn tick(&mut self, steps: usize, output: bool) {
if output {
self.buffer.append(
&mut izip!(
self.channels.one.tick(steps).into_iter(),
self.channels.two.tick(steps).into_iter(),
self.channels.three.tick(steps).into_iter(),
self.channels.four.tick(steps).into_iter()
)
.map(|(one, two, three, four)| DacSample {
one,
two,
three,
four,
})
.collect(),
);
if self.buffer.len() >= CYCLES_PER_FRAME {
self.next_audio();
} else if !self.out_buffer.is_empty() {
self.push_audio();
for s in izip!(
self.channels.one.tick(steps).into_iter(),
self.channels.two.tick(steps).into_iter(),
self.channels.three.tick(steps).into_iter(),
self.channels.four.tick(steps).into_iter()
)
.map(|(one, two, three, four)| DacSample {
one,
two,
three,
four,
}) {
if let Some(next) = self.converter.push(s.mixed(&self.mixer)) {
executor::block_on(self.output.send_rb.push(next)).unwrap();
}
}
} else {
self.channels.one.tick(steps);
@ -168,34 +152,6 @@ impl Apu {
(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 {
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]> {
let mut output = vec![];
for ref val in signal {
self.time_accum += 1.;
if let Some(ref mut averager) = self.average {
averager.push(val);
}
if self.time_accum >= self.ratio {
self.time_accum -= self.ratio;
output.push(if let Some(ref mut averager) = self.average {
averager.finish()
} else {
*val
});
}
pub fn push(&mut self, signal: [f32; 2]) -> Option<[f32; 2]> {
self.time_accum += 1.;
if let Some(ref mut averager) = self.average {
averager.push(&signal);
}
if self.time_accum >= self.ratio {
self.time_accum -= self.ratio;
Some(if let Some(ref mut averager) = self.average {
averager.finish()
} else {
signal
})
} else {
None
}
output
}
}