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, last_output_sample: f32, } 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, last_output_sample: 0.0, } } pub fn sample_rate(&self) -> u32 { self.sample_rate } pub fn reset(&mut self) { self.sample_accumulator = 0.0; self.last_output_sample = 0.0; } pub fn push_cycles(&mut self, cpu_cycles: u32, channels: ChannelOutputs, out: &mut Vec) { self.sample_accumulator += self.samples_per_cpu_cycle * cpu_cycles as f64; 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 sample = pulse_out + tnd_out; if samples == 0 { return; } let start = self.last_output_sample; if samples == 1 { out.push(sample); } else { let denom = samples as f32; for idx in 0..samples { let t = (idx + 1) as f32 / denom; out.push(start + (sample - start) * t); } } self.last_output_sample = sample; } } #[cfg(test)] mod tests { use super::*; #[test] fn mixer_silent_channels_produce_zero() { 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.abs() < 1e-6, "expected 0.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}"); } } #[test] fn mixer_smooths_transition_between_batches() { let mut mixer = AudioMixer::new(44_100, VideoMode::Ntsc); let mut out = Vec::new(); mixer.push_cycles(200, ChannelOutputs::default(), &mut out); let before = out.len(); mixer.push_cycles( 200, ChannelOutputs { pulse1: 15, pulse2: 15, triangle: 15, noise: 15, dmc: 127, }, &mut out, ); let transition = &out[before..]; assert!(transition.len() > 1); assert!(transition[0] < *transition.last().expect("transition sample")); assert!( transition[0] > 0.0, "expected smoothed ramp start, got {}", transition[0] ); } }