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