test(audio): edge-case unit tests for the noise gate

Covers the previously-untested branches of the NoiseGate envelope/timing:
frame_rms known values, empty-frame transmit-follows-state, disabled gate
parks the envelope open (no fade-in on re-enable), hold-window-then-release
ordering, sustained mid-level refreshes the hold, and a loud signal
re-opening a releasing gate. Gate tests 6 -> 12; test-only, no prod change.

Implemented by Gemini per next-task.md; reviewed against the real diff and
re-verified (build + clippy --all-targets + test all green) by the senior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 16:00:15 -04:00
co-authored by Claude Opus 4.8
parent b36e8f239a
commit fa951e570f
+108
View File
@@ -227,4 +227,112 @@ mod tests {
let mut f = frame(0);
assert!(!g.process(&mut f, 0.05), "an unopened gate on silence must not transmit");
}
#[test]
fn frame_rms_known_values() {
assert_eq!(frame_rms(&[]), 0.0);
assert_eq!(frame_rms(&frame(0)), 0.0);
let f_high = frame(16384);
let rms_high = frame_rms(&f_high);
assert!((rms_high - 0.5).abs() < 1e-4, "rms_high was {}", rms_high);
let f_low = frame(3277);
let rms_low = frame_rms(&f_low);
assert!((rms_low - 0.1).abs() < 1e-3, "rms_low was {}", rms_low);
}
#[test]
fn empty_frame_transmit_follows_gate_state() {
let mut g = NoiseGate::new(SR);
// fresh gate (never opened)
assert!(!g.process(&mut [], 0.05));
// open it with loud signals
for _ in 0..3 {
let mut f = frame(10000);
assert!(g.process(&mut f, 0.05));
}
// now empty frame should transmit
assert!(g.process(&mut [], 0.05));
}
#[test]
fn disabled_parks_envelope_so_reenable_has_no_fade_in() {
let mut g = NoiseGate::new(SR);
let mut f1 = frame(5000);
assert!(g.process(&mut f1, 0.0)); // disabled
let mut f2 = frame(10000);
assert!(g.process(&mut f2, 0.05)); // enabled
assert!(f2[0].abs() > 9000, "expected first sample of enabled frame to have no fade-in, got {}", f2[0]);
}
#[test]
fn hold_keeps_open_through_window_then_releases_to_closed() {
let mut g = NoiseGate::new(SR);
// open it
for _ in 0..3 {
let mut f = frame(10000);
g.process(&mut f, 0.05);
}
let mut results = Vec::new();
for _ in 0..25 {
let mut f = frame(0);
results.push(g.process(&mut f, 0.05));
}
assert!(results[4], "should still transmit at the 5th silent frame");
assert!(!results[19], "should not transmit at the 20th silent frame");
}
#[test]
fn sustained_mid_level_refreshes_hold_and_stays_open() {
let mut g = NoiseGate::new(SR);
// open it loud
for _ in 0..3 {
let mut f = frame(10000);
g.process(&mut f, 0.05);
}
// feed 30x frame(1310) (rms ~0.04, between close 0.03 and open 0.05)
for _ in 0..30 {
let mut f = frame(1310);
assert!(g.process(&mut f, 0.05));
}
assert!(g.open, "gate must stay open (hold refreshed by mid-level input)");
}
#[test]
fn loud_signal_reopens_a_releasing_gate() {
let mut g = NoiseGate::new(SR);
// open it
for _ in 0..3 {
let mut f = frame(10000);
g.process(&mut f, 0.05);
}
// feed silent frames to fully close
let mut closed = false;
for _ in 0..40 {
let mut f = frame(0);
if !g.process(&mut f, 0.05) {
closed = true;
break;
}
}
assert!(closed);
// feed frame(10000) @ 0.05 a few times
let mut last_peak = 0;
for _ in 0..3 {
let mut f = frame(10000);
assert!(g.process(&mut f, 0.05));
last_peak = peak(&f);
}
assert!(g.open);
assert!(last_peak >= 9900, "peak of the 3rd reopened frame must be >= 9900, got {}", last_peak);
}
}