fix: stabilize desktop audio playback

This commit is contained in:
2026-03-13 18:58:07 +03:00
parent f86e3c2284
commit 260e7ff9db
10 changed files with 399 additions and 67 deletions

View File

@@ -6,6 +6,7 @@ pub struct AudioMixer {
sample_rate: u32,
samples_per_cpu_cycle: f64,
sample_accumulator: f64,
last_output_sample: f32,
}
impl AudioMixer {
@@ -15,6 +16,7 @@ impl AudioMixer {
sample_rate,
samples_per_cpu_cycle: sample_rate as f64 / cpu_hz,
sample_accumulator: 0.0,
last_output_sample: 0.0,
}
}
@@ -24,10 +26,11 @@ impl AudioMixer {
pub fn reset(&mut self) {
self.sample_accumulator = 0.0;
self.last_output_sample = 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);
pub fn push_cycles(&mut self, cpu_cycles: u32, channels: ChannelOutputs, out: &mut Vec<f32>) {
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;
@@ -35,10 +38,23 @@ impl AudioMixer {
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;
let sample = pulse_out + tnd_out;
out.extend(std::iter::repeat_n(sample, samples));
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;
}
}
@@ -47,14 +63,14 @@ mod tests {
use super::*;
#[test]
fn mixer_silent_channels_produce_negative_one() {
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 - (-1.0)).abs() < 1e-6, "expected -1.0, got {s}");
assert!(s.abs() < 1e-6, "expected 0.0, got {s}");
}
}
@@ -75,4 +91,34 @@ mod tests {
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]
);
}
}