different downsample types

This commit is contained in:
Alex Janka 2023-03-09 10:50:18 +11:00
parent bfb895ef4c
commit 659fc4869a
5 changed files with 53 additions and 16 deletions

View file

@ -9,7 +9,7 @@ use cpal::{
};
use futures::executor;
use gb_emu_lib::{
connect::{AudioOutput, EmulatorMessage, JoypadState, Renderer, RomFile},
connect::{AudioOutput, DownsampleType, EmulatorMessage, JoypadState, Renderer, RomFile},
util::scale_buffer,
EmulatorCore,
};
@ -115,6 +115,9 @@ fn main() {
}
}
const FRAMES_TO_BUFFER: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
fn create_audio_output() -> (AudioOutput, Stream) {
let host = cpal::default_host();
@ -132,7 +135,8 @@ fn create_audio_output() -> (AudioOutput, Stream) {
let sample_rate = config.sample_rate().0;
let (output, mut rx) = AudioOutput::new_unfilled(sample_rate as f32, true, 1);
let (output, mut rx) =
AudioOutput::new_unfilled(sample_rate as f32, true, FRAMES_TO_BUFFER, DOWNSAMPLE_TYPE);
let stream = device
.build_output_stream(

View file

@ -1,7 +1,7 @@
use async_ringbuf::AsyncHeapConsumer;
use futures::executor;
use gb_emu_lib::{
connect::{AudioOutput, EmulatorMessage, JoypadButtons, RomFile},
connect::{AudioOutput, DownsampleType, EmulatorMessage, JoypadButtons, RomFile},
EmulatorCore,
};
use nih_plug::prelude::*;
@ -34,6 +34,7 @@ type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>;
const FRAMES_TO_BUFFER: usize = 1;
const INCLUDE_BOOTROM: bool = false;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
impl Plugin for GameboyEmu {
const NAME: &'static str = "Gameboy";
@ -128,16 +129,24 @@ impl Plugin for GameboyEmu {
};
if let Some(ref mut vars) = self.vars {
let (output, rx) =
AudioOutput::new_unfilled(buffer_config.sample_rate, false, FRAMES_TO_BUFFER);
let (output, rx) = AudioOutput::new_unfilled(
buffer_config.sample_rate,
false,
FRAMES_TO_BUFFER,
DOWNSAMPLE_TYPE,
);
vars.emulator_core.replace_output(output);
vars.rx = rx;
} else {
let (sender, receiver) = channel::<EmulatorMessage>();
let (output, rx) =
AudioOutput::new_unfilled(buffer_config.sample_rate, false, FRAMES_TO_BUFFER);
let (output, rx) = AudioOutput::new_unfilled(
buffer_config.sample_rate,
false,
FRAMES_TO_BUFFER,
DOWNSAMPLE_TYPE,
);
let (renderer, frame_receiver, key_handler) = EmulatorRenderer::new();

View file

@ -8,6 +8,12 @@ pub enum EmulatorMessage {
Stop,
}
#[derive(Clone, Copy)]
pub enum DownsampleType {
Linear,
ZeroOrderHold,
}
pub enum RomFile {
Path(String),
Raw(Vec<u8>),
@ -43,6 +49,7 @@ pub struct AudioOutput {
pub sample_rate: f32,
pub send_rb: AsyncHeapProducer<[f32; 2]>,
pub wait_for_output: bool,
pub downsample_type: DownsampleType,
}
impl AudioOutput {
@ -50,8 +57,14 @@ impl AudioOutput {
sample_rate: f32,
wait_for_output: bool,
frames_to_buffer: usize,
downsample_type: DownsampleType,
) -> (Self, AsyncHeapConsumer<[f32; 2]>) {
let (mut output, rx) = Self::new_unfilled(sample_rate, wait_for_output, frames_to_buffer);
let (mut output, rx) = Self::new_unfilled(
sample_rate,
wait_for_output,
frames_to_buffer,
downsample_type,
);
executor::block_on(
output
@ -67,6 +80,7 @@ impl AudioOutput {
sample_rate: f32,
wait_for_output: bool,
frames_to_buffer: usize,
downsample_type: DownsampleType,
) -> (Self, AsyncHeapConsumer<[f32; 2]>) {
let rb_len = (sample_rate as usize / 60) * frames_to_buffer;
@ -78,6 +92,7 @@ impl AudioOutput {
sample_rate,
send_rb,
wait_for_output,
downsample_type,
},
rx,
)

View file

@ -59,13 +59,13 @@ impl Apu {
div_apu: 0,
buffer: vec![],
out_buffer: vec![],
converter: Downsampler::new(output.sample_rate),
converter: Downsampler::new(output.sample_rate, output.downsample_type),
output,
}
}
pub fn replace_output(&mut self, new: AudioOutput) {
self.converter = Downsampler::new(new.sample_rate);
self.converter = Downsampler::new(new.sample_rate, new.downsample_type);
self.output = new;
}

View file

@ -1,4 +1,4 @@
use crate::constants::CLOCK_SPEED;
use crate::{connect::DownsampleType, constants::CLOCK_SPEED};
const TIME_PER_CYCLE: f32 = 1. / CLOCK_SPEED as f32;
@ -42,15 +42,18 @@ impl Averager {
pub(super) struct Downsampler {
ratio: f32,
time_accum: f32,
average: Averager,
average: Option<Averager>,
}
impl Downsampler {
pub fn new(sample_rate: f32) -> Self {
pub fn new(sample_rate: f32, algo: DownsampleType) -> Self {
Self {
ratio: 1. / sample_rate,
time_accum: 0.,
average: Averager::default(),
average: match algo {
DownsampleType::Linear => Some(Averager::default()),
DownsampleType::ZeroOrderHold => None,
},
}
}
@ -58,10 +61,16 @@ impl Downsampler {
let mut output = vec![];
for ref val in signal {
self.time_accum += TIME_PER_CYCLE;
self.average.push(val);
if let Some(ref mut averager) = self.average {
averager.push(val);
}
if self.time_accum >= self.ratio {
self.time_accum = 0.;
output.push(self.average.finish());
output.push(if let Some(ref mut averager) = self.average {
averager.finish()
} else {
*val
});
}
}
output