Skip to main content

proteus_lib/dsp/effects/basic_reverb/
mod.rs

1//! Basic reverb effect using a simple feedback delay line.
2
3use log::info;
4use serde::{Deserialize, Serialize};
5
6use super::EffectContext;
7
8const DEFAULT_DURATION_MS: u64 = 100;
9const MAX_AMPLITUDE: f32 = 0.8;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(default)]
13pub struct BasicReverbSettings {
14    pub duration_ms: u64,
15    pub amplitude: f32,
16}
17
18impl BasicReverbSettings {
19    pub fn new(duration_ms: u64, amplitude: f32) -> Self {
20        Self {
21            duration_ms: duration_ms.clamp(0, u64::MAX),
22            amplitude: amplitude.clamp(0.0, MAX_AMPLITUDE),
23        }
24    }
25
26    fn amplitude(&self) -> f32 {
27        self.amplitude.clamp(0.0, MAX_AMPLITUDE)
28    }
29}
30
31impl Default for BasicReverbSettings {
32    fn default() -> Self {
33        Self {
34            duration_ms: DEFAULT_DURATION_MS,
35            amplitude: 0.7,
36        }
37    }
38}
39
40/// Basic reverb effect (feedback delay + mix).
41#[derive(Clone, Serialize, Deserialize)]
42#[serde(default)]
43pub struct BasicReverbEffect {
44    pub enabled: bool,
45    #[serde(alias = "dry_wet", alias = "wet_dry")]
46    pub mix: f32,
47    #[serde(flatten)]
48    pub settings: BasicReverbSettings,
49    #[serde(skip)]
50    state: Option<BasicReverbState>,
51}
52
53impl Default for BasicReverbEffect {
54    fn default() -> Self {
55        Self {
56            enabled: true,
57            mix: 0.0,
58            settings: BasicReverbSettings::default(),
59            state: None,
60        }
61    }
62}
63
64impl std::fmt::Debug for BasicReverbEffect {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        f.debug_struct("BasicReverbEffect")
67            .field("enabled", &self.enabled)
68            .field("mix", &self.mix)
69            .field("settings", &self.settings)
70            .finish()
71    }
72}
73
74impl BasicReverbEffect {
75    /// Create a new basic reverb effect.
76    pub fn new(mix: f32) -> Self {
77        Self {
78            mix: mix.clamp(0.0, 1.0),
79            ..Default::default()
80        }
81    }
82
83    /// Process interleaved samples through a feedback delay line.
84    pub fn process(&mut self, samples: &[f32], context: &EffectContext, _drain: bool) -> Vec<f32> {
85        self.ensure_state(context);
86        if !self.enabled || self.mix <= 0.0 {
87            return samples.to_vec();
88        }
89
90        // If an impulse response is configured, skip basic reverb in favor of convolution.
91        if context.impulse_response_spec.is_some() {
92            return samples.to_vec();
93        }
94
95        let Some(state) = self.state.as_mut() else {
96            return samples.to_vec();
97        };
98
99        let amplitude = if self.mix > 0.0 {
100            self.mix.clamp(0.0, MAX_AMPLITUDE)
101        } else {
102            self.settings.amplitude()
103        };
104
105        if samples.is_empty() {
106            if _drain {
107                return state.drain_tail(amplitude);
108            }
109            return Vec::new();
110        }
111
112        let mut output = Vec::with_capacity(samples.len());
113        state.process_samples(samples, amplitude, &mut output);
114        output
115    }
116
117    /// Reset any internal state (none for basic reverb).
118    pub fn reset_state(&mut self) {
119        if let Some(state) = self.state.as_mut() {
120            state.reset();
121        }
122        self.state = None;
123    }
124
125    /// Mutable access to settings.
126    pub fn settings_mut(&mut self) -> &mut BasicReverbSettings {
127        &mut self.settings
128    }
129
130    fn ensure_state(&mut self, context: &EffectContext) {
131        let delay_samples = delay_samples(
132            context.sample_rate,
133            context.channels,
134            self.settings.duration_ms,
135        );
136        let needs_reset = self
137            .state
138            .as_ref()
139            .map(|state| state.delay_samples != delay_samples)
140            .unwrap_or(true);
141        if needs_reset {
142            self.state = Some(BasicReverbState::new(delay_samples));
143        }
144    }
145}
146
147#[derive(Clone)]
148struct BasicReverbState {
149    delay_samples: usize,
150    delay_line: Vec<f32>,
151    write_pos: usize,
152}
153
154impl BasicReverbState {
155    fn new(delay_samples: usize) -> Self {
156        info!("Using Basic Reverb!");
157        Self {
158            delay_samples,
159            delay_line: vec![0.0; delay_samples.max(1)],
160            write_pos: 0,
161        }
162    }
163
164    fn reset(&mut self) {
165        self.delay_line.fill(0.0);
166        self.write_pos = 0;
167    }
168
169    fn process_samples(&mut self, samples: &[f32], amplitude: f32, out: &mut Vec<f32>) {
170        if self.delay_samples == 0 {
171            out.extend_from_slice(samples);
172            return;
173        }
174
175        let delay_len = self.delay_line.len();
176        for &sample in samples {
177            let delayed = self.delay_line[self.write_pos];
178            let output = sample + (delayed * amplitude);
179            out.push(output);
180
181            // Feedback delay for smoother tails.
182            self.delay_line[self.write_pos] = sample + (delayed * amplitude);
183            self.write_pos += 1;
184            if self.write_pos >= delay_len {
185                self.write_pos = 0;
186            }
187        }
188    }
189
190    fn drain_tail(&mut self, amplitude: f32) -> Vec<f32> {
191        if self.delay_samples == 0 {
192            return Vec::new();
193        }
194
195        let delay_len = self.delay_line.len();
196        let mut out = Vec::with_capacity(delay_len);
197        for _ in 0..delay_len {
198            let delayed = self.delay_line[self.write_pos];
199            let output = delayed * amplitude;
200            out.push(output);
201
202            // Feed silence to decay the tail.
203            self.delay_line[self.write_pos] = delayed * amplitude;
204            self.write_pos += 1;
205            if self.write_pos >= delay_len {
206                self.write_pos = 0;
207            }
208        }
209
210        out
211    }
212}
213
214fn delay_samples(sample_rate: u32, channels: usize, duration_ms: u64) -> usize {
215    if duration_ms == 0 {
216        return 0;
217    }
218    let ns = duration_ms.saturating_mul(1_000_000);
219    let samples = ns.saturating_mul(sample_rate as u64) / 1_000_000_000 * channels as u64;
220    samples as usize
221}