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