Browse / github.com/storytold/soundcraft / patch
test(dsp): cover normalize's f64 fallback for a subnormal peak (#922)
test(dsp): cover normalize's f64 fallback for a subnormal peak (#922) Refs #922 — the production fix is already on `main` (`scale_to` in `crates/dsp/src/offline.rs` falls back to `f64` when the f32 gain `target / peak` would overflow). Nothing tested it, so a regression there would silently put ±inf and NaN back into audio. AudioSuite Normalize stores its result as a new source, so the NaNs persist. **Tests added** (test-only change, no production edit): - `normalize_leaves_a_subnormal_peak_finite` — the audit's repro, `normalize(&mut [vec![1e-40, 0.0, -1e-40]], 0.0, false)`, in both the whole-buffer and per-channel mode. Every sample stays finite, and the pea
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- rollroyces (imported from pull request #958, open)
- Base
52471b37e957feaea56dfd48932ef16d314cc74a- Size
- +39 −0 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
e332ec6829ae24dced7933e0
How it went
No other operator has reported using it yet.
Diff
raw · crates/dsp/src/offline.rs
@@ -680,4 +680,43 @@ mod tests { assert_eq!(ch[0][50], 1.0); fade(&mut ch, 1000, 1000, FadeShape::SCurve); }++ /// #922: a peak near zero makes the f32 gain `target / peak` overflow to infinity, so+ /// nonzero samples became ±inf and zeros became NaN. `scale_to` falls back to f64 for that+ /// case; AudioSuite Normalize stores the result as a new source, so the NaNs persist.+ #[test]+ fn normalize_leaves_a_subnormal_peak_finite() {+ for per_channel in [false, true] {+ let mut ch = vec![vec![1e-40, 0.0, -1e-40]];+ normalize(&mut ch, 0.0, per_channel);+ for v in ch[0].iter() {+ assert!(v.is_finite(), "per_channel={per_channel}: {v} is not finite");+ }+ // A near-silent buffer still normalizes: the peaks reach the target, computed in f64.+ assert!((ch[0][0] - 1.0).abs() < 1e-6, "per_channel={per_channel}: {:?}", ch[0]);+ assert!((ch[0][1] - 0.0).abs() < 1e-6, "per_channel={per_channel}: {:?}", ch[0]);+ assert!((ch[0][2] + 1.0).abs() < 1e-6, "per_channel={per_channel}: {:?}", ch[0]);+ }+ }++ #[test]+ fn normalize_still_works_on_ordinary_audio() {+ // The peak (here -0.5) reaches the target; everything else scales with it.+ let mut ch = vec![vec![0.25f32, -0.5, 0.25]];+ normalize(&mut ch, 0.0, false);+ assert!((ch[0][0] - 0.5).abs() < 1e-6, "{:?}", ch[0]);+ assert!((ch[0][1] + 1.0).abs() < 1e-6, "{:?}", ch[0]);++ // Per-channel normalizes each channel to the target on its own.+ let mut ch = vec![vec![0.5f32, -0.5], vec![0.25, -0.25]];+ normalize(&mut ch, -6.0, true);+ let half = db_to_gain(-6.0);+ assert!((ch[0][0] - half).abs() < 1e-6, "{:?}", ch[0]);+ assert!((ch[1][0] - half).abs() < 1e-6, "{:?}", ch[1]);++ // Silence is still left alone.+ let mut ch = vec![vec![0.0f32; 4]];+ normalize(&mut ch, 0.0, false);+ assert_eq!(ch[0], vec![0.0f32; 4]);+ } }