Files
nesemu/src/runtime/audio.rs

79 lines
2.3 KiB
Rust

use crate::native_core::apu::ChannelOutputs;
use crate::runtime::VideoMode;
#[derive(Debug)]
pub struct AudioMixer {
sample_rate: u32,
samples_per_cpu_cycle: f64,
sample_accumulator: f64,
}
impl AudioMixer {
pub fn new(sample_rate: u32, mode: VideoMode) -> Self {
let cpu_hz = mode.cpu_hz();
Self {
sample_rate,
samples_per_cpu_cycle: sample_rate as f64 / cpu_hz,
sample_accumulator: 0.0,
}
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn reset(&mut self) {
self.sample_accumulator = 0.0;
}
pub fn push_cycles(&mut self, cpu_cycles: u8, channels: ChannelOutputs, out: &mut Vec<f32>) {
self.sample_accumulator += self.samples_per_cpu_cycle * f64::from(cpu_cycles);
let samples = self.sample_accumulator.floor() as usize;
self.sample_accumulator -= samples as f64;
let pulse_out = 0.00752 * (f32::from(channels.pulse1) + f32::from(channels.pulse2));
let tnd_out = 0.00851 * f32::from(channels.triangle)
+ 0.00494 * f32::from(channels.noise)
+ 0.00335 * f32::from(channels.dmc);
let mixed = pulse_out + tnd_out;
let sample = mixed * 2.0 - 1.0;
out.extend(std::iter::repeat_n(sample, samples));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mixer_silent_channels_produce_negative_one() {
let mut mixer = AudioMixer::new(44_100, VideoMode::Ntsc);
let channels = ChannelOutputs::default();
let mut out = Vec::new();
mixer.push_cycles(50, channels, &mut out);
assert!(!out.is_empty());
for &s in &out {
assert!((s - (-1.0)).abs() < 1e-6, "expected -1.0, got {s}");
}
}
#[test]
fn mixer_max_channels_produce_positive() {
let mut mixer = AudioMixer::new(44_100, VideoMode::Ntsc);
let channels = ChannelOutputs {
pulse1: 15,
pulse2: 15,
triangle: 15,
noise: 15,
dmc: 127,
};
let mut out = Vec::new();
mixer.push_cycles(50, channels, &mut out);
assert!(!out.is_empty());
for &s in &out {
assert!(s > 0.0, "expected positive sample, got {s}");
}
}
}