rolled my own downsampler
This commit is contained in:
parent
0459390d78
commit
28240c80c3
|
@ -132,15 +132,15 @@ 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(sample_rate);
|
let (output, mut rx) = AudioOutput::new(sample_rate as f32);
|
||||||
|
|
||||||
let stream = device
|
let stream = device
|
||||||
.build_output_stream(
|
.build_output_stream(
|
||||||
&config.config(),
|
&config.config(),
|
||||||
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
|
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
|
||||||
for v in data {
|
for v in data.chunks_exact_mut(2) {
|
||||||
match executor::block_on(rx.pop()) {
|
match executor::block_on(rx.pop()) {
|
||||||
Some(a) => *v = a,
|
Some(a) => v.copy_from_slice(&a),
|
||||||
None => panic!("Audio queue disconnected!"),
|
None => panic!("Audio queue disconnected!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
samplerate = "0.2.4"
|
|
||||||
async-ringbuf = "0.1.2"
|
async-ringbuf = "0.1.2"
|
||||||
futures = "0.3.26"
|
futures = "0.3.26"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
|
|
|
@ -24,18 +24,18 @@ pub trait Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioOutput {
|
pub struct AudioOutput {
|
||||||
pub sample_rate: u32,
|
pub sample_rate: f32,
|
||||||
pub send_rb: AsyncHeapProducer<f32>,
|
pub send_rb: AsyncHeapProducer<[f32; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioOutput {
|
impl AudioOutput {
|
||||||
pub fn new(sample_rate: u32) -> (Self, AsyncHeapConsumer<f32>) {
|
pub fn new(sample_rate: f32) -> (Self, AsyncHeapConsumer<[f32; 2]>) {
|
||||||
let rb_len = sample_rate as usize / 60;
|
let rb_len = sample_rate as usize / (60 * 2);
|
||||||
|
|
||||||
let rb = AsyncHeapRb::<f32>::new(rb_len);
|
let rb = AsyncHeapRb::<[f32; 2]>::new(rb_len);
|
||||||
let (mut send_rb, rx) = rb.split();
|
let (mut send_rb, rx) = rb.split();
|
||||||
|
|
||||||
executor::block_on(send_rb.push_iter(vec![0.; rb_len - 1].into_iter())).unwrap();
|
executor::block_on(send_rb.push_iter(vec![[0.; 2]; rb_len - 1].into_iter())).unwrap();
|
||||||
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
use self::types::{Channels, DacSample, Mixer, VinEnable, Volume};
|
use self::{
|
||||||
|
downsampler::Downsampler,
|
||||||
|
types::{Channels, DacSample, Mixer, VinEnable, Volume},
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
connect::AudioOutput,
|
connect::AudioOutput,
|
||||||
constants::CLOCK_SPEED,
|
|
||||||
processor::memory::Address,
|
processor::memory::Address,
|
||||||
util::{get_bit, set_or_clear_bit},
|
util::{get_bit, set_or_clear_bit},
|
||||||
};
|
};
|
||||||
use futures::executor;
|
use futures::executor;
|
||||||
use itertools::izip;
|
use itertools::izip;
|
||||||
use samplerate::{ConverterType, Samplerate};
|
|
||||||
|
|
||||||
mod channels;
|
mod channels;
|
||||||
|
mod downsampler;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
impl DacSample {
|
impl DacSample {
|
||||||
fn mixed(&self, mixer: &Mixer) -> Vec<f32> {
|
fn mixed(&self, mixer: &Mixer) -> [f32; 2] {
|
||||||
let left = (self.one * mixer.ch1.left.scale())
|
let left = (self.one * mixer.ch1.left.scale())
|
||||||
+ (self.two * mixer.ch2.left.scale())
|
+ (self.two * mixer.ch2.left.scale())
|
||||||
+ (self.three * mixer.ch3.left.scale())
|
+ (self.three * mixer.ch3.left.scale())
|
||||||
|
@ -22,7 +24,7 @@ impl DacSample {
|
||||||
+ (self.two * mixer.ch2.right.scale())
|
+ (self.two * mixer.ch2.right.scale())
|
||||||
+ (self.three * mixer.ch3.right.scale())
|
+ (self.three * mixer.ch3.right.scale())
|
||||||
+ (self.four * mixer.ch4.right.scale());
|
+ (self.four * mixer.ch4.right.scale());
|
||||||
vec![
|
[
|
||||||
self.mix_channel(left, mixer.vol_left),
|
self.mix_channel(left, mixer.vol_left),
|
||||||
self.mix_channel(right, mixer.vol_right),
|
self.mix_channel(right, mixer.vol_right),
|
||||||
]
|
]
|
||||||
|
@ -40,7 +42,7 @@ pub struct Apu {
|
||||||
mixer: Mixer,
|
mixer: Mixer,
|
||||||
div_apu: u8,
|
div_apu: u8,
|
||||||
buffer: Vec<DacSample>,
|
buffer: Vec<DacSample>,
|
||||||
converter: Samplerate,
|
converter: Downsampler,
|
||||||
output: AudioOutput,
|
output: AudioOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,14 +50,6 @@ const CYCLES_PER_FRAME: usize = 70224;
|
||||||
|
|
||||||
impl Apu {
|
impl Apu {
|
||||||
pub fn new(output: AudioOutput) -> Self {
|
pub fn new(output: AudioOutput) -> Self {
|
||||||
let converter = Samplerate::new(
|
|
||||||
ConverterType::ZeroOrderHold,
|
|
||||||
CLOCK_SPEED as u32,
|
|
||||||
output.sample_rate,
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
apu_enable: true,
|
apu_enable: true,
|
||||||
channels: Channels::default(),
|
channels: Channels::default(),
|
||||||
|
@ -63,7 +57,7 @@ impl Apu {
|
||||||
mixer: Mixer::default(),
|
mixer: Mixer::default(),
|
||||||
div_apu: 0,
|
div_apu: 0,
|
||||||
buffer: vec![],
|
buffer: vec![],
|
||||||
converter,
|
converter: Downsampler::new(output.sample_rate),
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,16 +105,12 @@ impl Apu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_audio(&mut self) {
|
fn next_audio(&mut self) {
|
||||||
let converted = self
|
let converted = self.converter.process(
|
||||||
.converter
|
self.buffer
|
||||||
.process(
|
|
||||||
&self
|
|
||||||
.buffer
|
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.flat_map(|v| v.mixed(&self.mixer))
|
.map(|v| v.mixed(&self.mixer))
|
||||||
.collect::<Vec<f32>>(),
|
.collect::<Vec<[f32; 2]>>(),
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap();
|
executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
29
lib/src/processor/memory/mmio/apu/downsampler.rs
Normal file
29
lib/src/processor/memory/mmio/apu/downsampler.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use crate::constants::CLOCK_SPEED;
|
||||||
|
|
||||||
|
const TIME_PER_CYCLE: f32 = 1. / CLOCK_SPEED as f32;
|
||||||
|
|
||||||
|
pub(super) struct Downsampler {
|
||||||
|
ratio: f32,
|
||||||
|
time_accum: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Downsampler {
|
||||||
|
pub fn new(sample_rate: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
ratio: 1. / sample_rate,
|
||||||
|
time_accum: 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(&mut self, signal: Vec<[f32; 2]>) -> Vec<[f32; 2]> {
|
||||||
|
let mut output = vec![];
|
||||||
|
for val in signal {
|
||||||
|
self.time_accum += TIME_PER_CYCLE;
|
||||||
|
if self.time_accum >= self.ratio {
|
||||||
|
self.time_accum = 0.;
|
||||||
|
output.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue