mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-24 10:36:46 -06:00
Guard all playback output paths against NaN from interpolation overflow
Fixes #918 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
f0d5acb800
commit
072b4ff3da
@@ -363,7 +363,7 @@ impl AudioState {
|
|||||||
let ch = self.pending.get(c);
|
let ch = self.pending.get(c);
|
||||||
let a = ch.and_then(|v| v.get(i0)).copied().unwrap_or(0.0);
|
let a = ch.and_then(|v| v.get(i0)).copied().unwrap_or(0.0);
|
||||||
let b = ch.and_then(|v| v.get(i0 + 1)).copied().unwrap_or(a);
|
let b = ch.and_then(|v| v.get(i0 + 1)).copied().unwrap_or(a);
|
||||||
a + (b - a) * fr
|
a * (1.0 - fr) + b * fr
|
||||||
};
|
};
|
||||||
if channels < nch {
|
if channels < nch {
|
||||||
// Fold surround to stereo first; a mono device averages the two sides.
|
// Fold surround to stereo first; a mono device averages the two sides.
|
||||||
@@ -381,18 +381,15 @@ impl AudioState {
|
|||||||
}
|
}
|
||||||
for (c, o) in frame.iter_mut().enumerate() {
|
for (c, o) in frame.iter_mut().enumerate() {
|
||||||
*o = match c {
|
*o = match c {
|
||||||
0 if channels == 1 => {
|
0 if channels == 1 => out_sample(0.5 * l + 0.5 * r),
|
||||||
let mono = (0.5 * l + 0.5 * r).clamp(-1.0, 1.0);
|
0 => out_sample(l),
|
||||||
if mono.is_finite() { mono } else { 0.0 }
|
1 => out_sample(r),
|
||||||
}
|
|
||||||
0 => l.clamp(-1.0, 1.0),
|
|
||||||
1 => r.clamp(-1.0, 1.0),
|
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (c, o) in frame.iter_mut().enumerate() {
|
for (c, o) in frame.iter_mut().enumerate() {
|
||||||
*o = if c < nch.max(1) && sounding { sample(c).clamp(-1.0, 1.0) } else { 0.0 };
|
*o = if c < nch.max(1) && sounding { out_sample(sample(c)) } else { 0.0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.read += step;
|
self.read += step;
|
||||||
@@ -400,6 +397,12 @@ impl AudioState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clamp to the device range, mapping NaN and infinities to silence.
|
||||||
|
fn out_sample(v: f32) -> f32 {
|
||||||
|
let v = v.clamp(-1.0, 1.0);
|
||||||
|
if v.is_finite() { v } else { 0.0 }
|
||||||
|
}
|
||||||
|
|
||||||
/// UI-thread bookkeeping of the third-party instances the audio thread holds.
|
/// UI-thread bookkeeping of the third-party instances the audio thread holds.
|
||||||
struct Side {
|
struct Side {
|
||||||
/// What was last created for each slot: (plugin id, channels, created ok).
|
/// What was last created for each slot: (plugin id, channels, created ok).
|
||||||
@@ -1253,6 +1256,18 @@ mod tests {
|
|||||||
assert!(out.iter().all(|x| *x == 0.0), "overflow in the stereo fold-down must not send NaN to a mono device");
|
assert!(out.iter().all(|x| *x == 0.0), "overflow in the stereo fold-down must not send NaN to a mono device");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interpolation_overflow_between_huge_samples_never_reaches_the_device() {
|
||||||
|
use soundcraft_model::ChannelFormat;
|
||||||
|
let alternating: Vec<f32> = (0..512).map(|i| if i % 2 == 0 { -3.4e38 } else { 3.4e38 }).collect();
|
||||||
|
for (format, nch, device) in [(ChannelFormat::Stereo, 2, 2), (ChannelFormat::Stereo, 2, 8), (ChannelFormat::Mono, 1, 1)] {
|
||||||
|
let mut st = audio_output_state(format, vec![alternating.clone(); nch]);
|
||||||
|
let mut out = vec![0.5; 64 * device];
|
||||||
|
st.fill(&mut out, device);
|
||||||
|
assert!(out.iter().all(|x| x.is_finite() && (-1.0..=1.0).contains(x)), "{format:?} on {device} channels: {out:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn surround_main_maps_to_device_channels_or_folds_down() {
|
fn surround_main_maps_to_device_channels_or_folds_down() {
|
||||||
use soundcraft_model::{ChannelFormat, Clip, SourceAudio, SourceId, TrackKind};
|
use soundcraft_model::{ChannelFormat, Clip, SourceAudio, SourceId, TrackKind};
|
||||||
|
|||||||
Reference in New Issue
Block a user