From 96ea49c7f63b6afdc45a82fb74a18fbcef6873ff Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Mon, 12 Oct 2026 14:44:57 +0200 Subject: [PATCH] fix(dsp): chorus and flanger tails follow their feedback (#242) --- crates/dsp/src/plugins/modulation.rs | 10 +++++++++- crates/dsp/tests/plugins.rs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/dsp/src/plugins/modulation.rs b/crates/dsp/src/plugins/modulation.rs index 38284f39..962f57de 100644 --- a/crates/dsp/src/plugins/modulation.rs +++ b/crates/dsp/src/plugins/modulation.rs @@ -316,7 +316,15 @@ impl Plugin for ModFx { } fn tail_samples(&self) -> usize { - ms_to_samples(200.0, self.sr) as usize + // Time for feedback to fall by 60 dB at the longest delay of the sweep, as for the delay + // above; never shorter than the 200 ms this used to report. + let longest = match self.kind { + ModKind::Chorus => self.p.v(2) + self.p.v(1) / 100.0 * 6.0, + ModKind::Flanger => self.p.v(2), + }; + let fb = (self.p.v(3).abs() / 100.0).clamp(0.0, 0.99); + let repeats = if fb < 1e-3 { 1.0 } else { (-60.0 / (20.0 * fb.log10())).min(500.0) + 1.0 }; + ms_to_samples((longest * repeats).max(200.0), self.sr) as usize } } diff --git a/crates/dsp/tests/plugins.rs b/crates/dsp/tests/plugins.rs index 5d71f6f3..04aed491 100644 --- a/crates/dsp/tests/plugins.rs +++ b/crates/dsp/tests/plugins.rs @@ -329,6 +329,23 @@ fn delay_repeats_at_time() { assert!((pos as i64 - 4800).abs() <= 2, "echo at {pos}"); } +#[test] +fn modulation_tail_covers_feedback_echoes() { + for (id, delay, feedback) in [("chorus", 30.0, 90.0), ("flanger", 10.0, 95.0), ("flanger", 10.0, -95.0)] { + let mut p = prepared(id); + for (param, v) in [("delay", delay), ("feedback", feedback), ("depth", 0.0), ("mix", 100.0)] { + assert!(p.set_param(param, v)); + } + p.reset(); + let mut x = vec![0.0f32; 48_000 * 5]; + x[0] = 0.5; + let out = run(p.as_mut(), &[x]); + let floor = peak(&out[0]) * 1e-3; + let last = out[0].iter().rposition(|v| v.abs() > floor).unwrap(); + assert!(last < p.tail_samples(), "{id} feedback {feedback}: echo at {last}, tail {}", p.tail_samples()); + } +} + #[test] fn trim_and_invert() { let mut p = prepared("invert");