From 52dc4f9822775b1f34e5e4ad23d3813120d6c20c Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Tue, 13 Oct 2026 17:50:50 +0200 Subject: [PATCH] Reset clip effect EQ/compressor state on stop and seek (#442) --- crates/mix/src/lib.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/mix/src/lib.rs b/crates/mix/src/lib.rs index e8c4fbad..3d78bde5 100644 --- a/crates/mix/src/lib.rs +++ b/crates/mix/src/lib.rs @@ -515,6 +515,9 @@ impl MixEngine { s.align.clear(); s.align_pre.clear(); s.out_extra.clear(); + for fx in s.clip_fx.values_mut() { + fx.reset(); + } } self.meters.clear(); self.main_meter = StripMeter::default(); @@ -1149,6 +1152,12 @@ impl ClipFx { fn new(sr: f32, ch: usize) -> ClipFx { ClipFx { eq: None, comp: None, gain_db: 0.0, sr, ch } } + /// Clear filter, detector and lookahead history (transport stop / locate). + fn reset(&mut self) { + for p in self.eq.iter_mut().chain(self.comp.iter_mut()) { + p.reset(); + } + } fn make(&self, id: &str) -> Option> { soundcraft_dsp::create(id).map(|mut p| { p.prepare(self.sr, 16_384, self.ch.max(1)); @@ -2902,6 +2911,29 @@ mod tests { assert!(out.iter().flatten().all(|x| *x == 0.0), "old aligned audio after a reset"); } + /// A reset (stop, locate) must clear the cached clip EQ/compressor history too. + #[test] + fn reset_clears_clip_effect_history() { + // Compressor lookahead delay, then EQ high-pass filter state, each primed with DC. + for (param, value) in [("comp.lookahead", 5.0), ("eq.hpf_on", 1.0)] { + let (mut s, t) = session_with_dc(0.5, 9600); + s.edit.click = false; + let cid = s.track(t).unwrap().clips()[0].id.0; + s.edit.values.insert(format!("clip_fx.{cid}.{param}"), value); + let mut out = vec![vec![0.0f32; 512]; 2]; + let mut eng = MixEngine::new(48_000.0, 512); + for b in 0..4 { + eng.render(&s, b * 512, 512, &mut out); + } + eng.reset(); + eng.render(&s, 4096, 512, &mut out); + let mut fresh = MixEngine::new(48_000.0, 512); + let mut expect = vec![vec![0.0f32; 512]; 2]; + fresh.render(&s, 4096, 512, &mut expect); + assert_eq!(out, expect, "{param}: clip effect state survived the reset"); + } + } + fn impulse_at(ch: &[f32]) -> Vec { ch.iter().enumerate().filter(|(_, v)| v.abs() > 1e-4).map(|(i, _)| i).collect() }