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 futures::executor;
use gb_emu_lib::{ use gb_emu_lib::{
connect::{AudioOutput, EmulatorMessage, JoypadState, Renderer, RomFile}, connect::{AudioOutput, DownsampleType, EmulatorMessage, JoypadState, Renderer, RomFile},
util::scale_buffer, util::scale_buffer,
EmulatorCore, 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) { fn create_audio_output() -> (AudioOutput, Stream) {
let host = cpal::default_host(); let host = cpal::default_host();
@ -132,7 +135,8 @@ fn create_audio_output() -> (AudioOutput, Stream) {
let sample_rate = config.sample_rate().0; 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 let stream = device
.build_output_stream( .build_output_stream(

View file

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

View file

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

View file

@ -59,13 +59,13 @@ impl Apu {
div_apu: 0, div_apu: 0,
buffer: vec![], buffer: vec![],
out_buffer: vec![], out_buffer: vec![],
converter: Downsampler::new(output.sample_rate), converter: Downsampler::new(output.sample_rate, output.downsample_type),
output, output,
} }
} }
pub fn replace_output(&mut self, new: AudioOutput) { 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; 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; const TIME_PER_CYCLE: f32 = 1. / CLOCK_SPEED as f32;
@ -42,15 +42,18 @@ impl Averager {
pub(super) struct Downsampler { pub(super) struct Downsampler {
ratio: f32, ratio: f32,
time_accum: f32, time_accum: f32,
average: Averager, average: Option<Averager>,
} }
impl Downsampler { impl Downsampler {
pub fn new(sample_rate: f32) -> Self { pub fn new(sample_rate: f32, algo: DownsampleType) -> Self {
Self { Self {
ratio: 1. / sample_rate, ratio: 1. / sample_rate,
time_accum: 0., 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![]; let mut output = vec![];
for ref val in signal { for ref val in signal {
self.time_accum += TIME_PER_CYCLE; 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 { if self.time_accum >= self.ratio {
self.time_accum = 0.; self.time_accum = 0.;
output.push(self.average.finish()); output.push(if let Some(ref mut averager) = self.average {
averager.finish()
} else {
*val
});
} }
} }
output output