diff --git a/src/runtime/audio.rs b/src/runtime/audio.rs index ca25cad..e9953a4 100644 --- a/src/runtime/audio.rs +++ b/src/runtime/audio.rs @@ -1,3 +1,4 @@ +use crate::native_core::apu::ChannelOutputs; use crate::runtime::VideoMode; #[derive(Debug)] @@ -25,15 +26,53 @@ impl AudioMixer { self.sample_accumulator = 0.0; } - pub fn push_cycles(&mut self, cpu_cycles: u8, apu_regs: &[u8; 0x20], out: &mut Vec) { + pub fn push_cycles(&mut self, cpu_cycles: u8, channels: ChannelOutputs, out: &mut Vec) { 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; - // Current core does not expose a final mixed PCM stream yet. - // Use DMC output level as a stable interim signal in [-1.0, 1.0]. - let dmc = apu_regs[0x11] & 0x7F; - let sample = (f32::from(dmc) / 63.5) - 1.0; + 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}"); + } + } +} diff --git a/src/runtime/core.rs b/src/runtime/core.rs index f076a93..468e3c8 100644 --- a/src/runtime/core.rs +++ b/src/runtime/core.rs @@ -109,7 +109,7 @@ impl NesRuntime { self.bus.begin_frame(); while !self.bus.take_frame_complete() { let cycles = self.step_instruction()?; - mixer.push_cycles(cycles, self.bus.apu_registers(), out_samples); + mixer.push_cycles(cycles, self.bus.apu_channel_outputs(), out_samples); } self.frame_number = self.frame_number.saturating_add(1); Ok(())