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