Skip to main content

proteus_lib/dsp/effects/
mod.rs

1//! Chainable DSP effect modules.
2
3use serde::{Deserialize, Serialize};
4
5use crate::dsp::effects::convolution_reverb::ImpulseResponseSpec;
6
7pub mod basic_reverb;
8mod biquad;
9pub mod compressor;
10pub mod convolution_reverb;
11pub mod diffusion_reverb;
12pub mod distortion;
13pub mod gain;
14pub mod high_pass;
15mod level;
16pub mod limiter;
17pub mod low_pass;
18pub mod multiband_eq;
19
20#[allow(deprecated)]
21#[deprecated(note = "Use DelayReverbEffect instead.")]
22pub use basic_reverb::BasicReverbEffect;
23#[allow(deprecated)]
24#[deprecated(note = "Use DelayReverbSettings instead.")]
25pub use basic_reverb::BasicReverbSettings;
26pub use basic_reverb::{DelayReverbEffect, DelayReverbSettings};
27pub use compressor::{CompressorEffect, CompressorSettings};
28pub use convolution_reverb::{ConvolutionReverbEffect, ConvolutionReverbSettings};
29pub use diffusion_reverb::{DiffusionReverbEffect, DiffusionReverbSettings};
30pub use distortion::{DistortionEffect, DistortionSettings};
31pub use gain::{GainEffect, GainSettings};
32pub use high_pass::{HighPassFilterEffect, HighPassFilterSettings};
33pub use limiter::{LimiterEffect, LimiterSettings};
34pub use low_pass::{LowPassFilterEffect, LowPassFilterSettings};
35pub use multiband_eq::{
36    EqPointSettings, HighEdgeFilterSettings, LowEdgeFilterSettings, MultibandEqEffect,
37    MultibandEqSettings,
38};
39
40/// Shared context for preparing and running DSP effects.
41#[derive(Debug, Clone)]
42pub struct EffectContext {
43    pub sample_rate: u32,
44    pub channels: usize,
45    pub container_path: Option<String>,
46    pub impulse_response_spec: Option<ImpulseResponseSpec>,
47    pub impulse_response_tail_db: f32,
48}
49
50/// Configured audio effect that can process interleaved samples.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub enum AudioEffect {
53    #[serde(rename = "DelayReverbSettings")]
54    DelayReverb(DelayReverbEffect),
55    #[deprecated(note = "Use AudioEffect::DelayReverb instead.")]
56    #[serde(rename = "BasicReverbSettings")]
57    BasicReverb(DelayReverbEffect),
58    #[serde(rename = "DiffusionReverbSettings")]
59    DiffusionReverb(DiffusionReverbEffect),
60    #[serde(rename = "ConvolutionReverbSettings")]
61    ConvolutionReverb(ConvolutionReverbEffect),
62    #[serde(rename = "LowPassFilterSettings")]
63    LowPassFilter(LowPassFilterEffect),
64    #[serde(rename = "HighPassFilterSettings")]
65    HighPassFilter(HighPassFilterEffect),
66    #[serde(rename = "DistortionSettings")]
67    Distortion(DistortionEffect),
68    #[serde(rename = "GainSettings")]
69    Gain(GainEffect),
70    #[serde(rename = "CompressorSettings")]
71    Compressor(CompressorEffect),
72    #[serde(rename = "LimiterSettings")]
73    Limiter(LimiterEffect),
74    #[serde(rename = "MultibandEqSettings")]
75    MultibandEq(MultibandEqEffect),
76}
77
78impl AudioEffect {
79    /// Process the provided samples through the effect.
80    ///
81    /// # Arguments
82    /// - `samples`: Interleaved input samples.
83    /// - `context`: Environment details (sample rate, channels, etc.).
84    /// - `drain`: When true, flush any buffered tail data.
85    ///
86    /// # Returns
87    /// Processed interleaved samples.
88    #[allow(deprecated)]
89    pub fn process(&mut self, samples: &[f32], context: &EffectContext, drain: bool) -> Vec<f32> {
90        match self {
91            AudioEffect::BasicReverb(effect) => effect.process(samples, context, drain),
92            AudioEffect::DelayReverb(effect) => effect.process(samples, context, drain),
93            AudioEffect::DiffusionReverb(effect) => effect.process(samples, context, drain),
94            AudioEffect::ConvolutionReverb(effect) => effect.process(samples, context, drain),
95            AudioEffect::LowPassFilter(effect) => effect.process(samples, context, drain),
96            AudioEffect::HighPassFilter(effect) => effect.process(samples, context, drain),
97            AudioEffect::Distortion(effect) => effect.process(samples, context, drain),
98            AudioEffect::Gain(effect) => effect.process(samples, context, drain),
99            AudioEffect::Compressor(effect) => effect.process(samples, context, drain),
100            AudioEffect::Limiter(effect) => effect.process(samples, context, drain),
101            AudioEffect::MultibandEq(effect) => effect.process(samples, context, drain),
102        }
103    }
104
105    /// Reset any internal state maintained by the effect.
106    #[allow(deprecated)]
107    pub fn reset_state(&mut self) {
108        match self {
109            AudioEffect::BasicReverb(effect) => effect.reset_state(),
110            AudioEffect::DelayReverb(effect) => effect.reset_state(),
111            AudioEffect::DiffusionReverb(effect) => effect.reset_state(),
112            AudioEffect::ConvolutionReverb(effect) => effect.reset_state(),
113            AudioEffect::LowPassFilter(effect) => effect.reset_state(),
114            AudioEffect::HighPassFilter(effect) => effect.reset_state(),
115            AudioEffect::Distortion(effect) => effect.reset_state(),
116            AudioEffect::Gain(effect) => effect.reset_state(),
117            AudioEffect::Compressor(effect) => effect.reset_state(),
118            AudioEffect::Limiter(effect) => effect.reset_state(),
119            AudioEffect::MultibandEq(effect) => effect.reset_state(),
120        }
121    }
122
123    /// Ensure any internal state (e.g., convolution IR) is initialized.
124    #[allow(deprecated)]
125    pub fn warm_up(&mut self, context: &EffectContext) {
126        match self {
127            AudioEffect::BasicReverb(_) => {}
128            AudioEffect::DelayReverb(_) => {}
129            AudioEffect::DiffusionReverb(_) => {}
130            AudioEffect::ConvolutionReverb(effect) => {
131                let _ = effect.process(&[], context, false);
132            }
133            AudioEffect::LowPassFilter(_) => {}
134            AudioEffect::HighPassFilter(_) => {}
135            AudioEffect::Distortion(_) => {}
136            AudioEffect::Gain(_) => {}
137            AudioEffect::Compressor(_) => {}
138            AudioEffect::Limiter(_) => {}
139            AudioEffect::MultibandEq(_) => {}
140        }
141    }
142
143    /// Mutable access to the convolution reverb effect, if present.
144    pub fn as_convolution_reverb_mut(&mut self) -> Option<&mut ConvolutionReverbEffect> {
145        match self {
146            AudioEffect::ConvolutionReverb(effect) => Some(effect),
147            _ => None,
148        }
149    }
150
151    /// Immutable access to the convolution reverb effect, if present.
152    pub fn as_convolution_reverb(&self) -> Option<&ConvolutionReverbEffect> {
153        match self {
154            AudioEffect::ConvolutionReverb(effect) => Some(effect),
155            _ => None,
156        }
157    }
158
159    /// Mutable access to the diffusion reverb effect, if present.
160    pub fn as_diffusion_reverb_mut(&mut self) -> Option<&mut DiffusionReverbEffect> {
161        match self {
162            AudioEffect::DiffusionReverb(effect) => Some(effect),
163            _ => None,
164        }
165    }
166
167    /// Immutable access to the diffusion reverb effect, if present.
168    pub fn as_diffusion_reverb(&self) -> Option<&DiffusionReverbEffect> {
169        match self {
170            AudioEffect::DiffusionReverb(effect) => Some(effect),
171            _ => None,
172        }
173    }
174
175    /// Mutable access to the delay reverb effect, if present.
176    #[allow(deprecated)]
177    pub fn as_delay_reverb_mut(&mut self) -> Option<&mut DelayReverbEffect> {
178        match self {
179            AudioEffect::DelayReverb(effect) => Some(effect),
180            AudioEffect::BasicReverb(effect) => Some(effect),
181            _ => None,
182        }
183    }
184
185    /// Immutable access to the delay reverb effect, if present.
186    #[allow(deprecated)]
187    pub fn as_delay_reverb(&self) -> Option<&DelayReverbEffect> {
188        match self {
189            AudioEffect::DelayReverb(effect) => Some(effect),
190            AudioEffect::BasicReverb(effect) => Some(effect),
191            _ => None,
192        }
193    }
194
195    /// Mutable access to the basic reverb effect, if present.
196    #[deprecated(note = "Use as_delay_reverb_mut instead.")]
197    #[allow(deprecated)]
198    pub fn as_basic_reverb_mut(&mut self) -> Option<&mut BasicReverbEffect> {
199        self.as_delay_reverb_mut()
200    }
201
202    /// Immutable access to the basic reverb effect, if present.
203    #[deprecated(note = "Use as_delay_reverb instead.")]
204    #[allow(deprecated)]
205    pub fn as_basic_reverb(&self) -> Option<&BasicReverbEffect> {
206        self.as_delay_reverb()
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn audio_effect_serde_roundtrip_variants() {
216        let effects = vec![
217            AudioEffect::DelayReverb(DelayReverbEffect::default()),
218            AudioEffect::DiffusionReverb(DiffusionReverbEffect::default()),
219            AudioEffect::ConvolutionReverb(ConvolutionReverbEffect::default()),
220            AudioEffect::LowPassFilter(LowPassFilterEffect::default()),
221            AudioEffect::HighPassFilter(HighPassFilterEffect::default()),
222            AudioEffect::Distortion(DistortionEffect::default()),
223            AudioEffect::Gain(GainEffect::default()),
224            AudioEffect::Compressor(CompressorEffect::default()),
225            AudioEffect::Limiter(LimiterEffect::default()),
226            AudioEffect::MultibandEq(MultibandEqEffect::default()),
227        ];
228
229        let json = serde_json::to_string(&effects).expect("serialize effects");
230        let decoded: Vec<AudioEffect> = serde_json::from_str(&json).expect("deserialize effects");
231        assert_eq!(decoded.len(), effects.len());
232    }
233
234    #[test]
235    fn audio_effect_serde_accepts_aliases() {
236        let json = r#"
237        [
238            {"ConvolutionReverbSettings":{"enabled":true,"wet_dry":0.25}},
239            {"DelayReverbSettings":{"enabled":true,"dry_wet":0.5}},
240            {"BasicReverbSettings":{"enabled":true,"dry_wet":0.5}},
241            {"DiffusionReverbSettings":{"enabled":true,"dry_wet":0.35}},
242            {"LowPassFilterSettings":{"enabled":true,"freq":800,"bandwidth":0.7}},
243            {"HighPassFilterSettings":{"enabled":true,"frequency_hz":1200,"q":0.9}},
244            {"DistortionSettings":{"enabled":true,"gain":2.0,"threshold":0.4}},
245            {"GainSettings":{"enabled":true,"gain":1.25}},
246            {"CompressorSettings":{"enabled":true,"threshold":-12.0,"ratio":2.0,
247                "attack":5.0,"release":50.0,"makeup_db":3.0}},
248            {"LimiterSettings":{"enabled":true,"threshold_db":-3.0,"knee_width":2.0,
249                "attack_ms":3.0,"release_ms":30.0}},
250            {"MultibandEqSettings":{
251                "enabled":true,
252                "points":[
253                    {"freq_hz":120,"q":0.8,"gain_db":3.0},
254                    {"freq_hz":1000,"q":1.0,"gain_db":-2.0},
255                    {"freq_hz":8000,"q":0.8,"gain_db":2.5}
256                ],
257                "low_edge":{"type":"high_pass","freq_hz":60,"q":0.7},
258                "high_edge":{"type":"high_shelf","freq_hz":10000,"q":0.8,"gain_db":1.5}
259            }}
260        ]
261        "#;
262
263        let decoded: Vec<AudioEffect> = serde_json::from_str(json).expect("deserialize effects");
264        assert_eq!(decoded.len(), 11);
265    }
266}