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 convolution_reverb;
8pub mod basic_reverb;
9pub mod distortion;
10pub mod high_pass;
11pub mod low_pass;
12pub mod compressor;
13pub mod limiter;
14mod biquad;
15
16pub use basic_reverb::{BasicReverbEffect, BasicReverbSettings};
17pub use convolution_reverb::{ConvolutionReverbEffect, ConvolutionReverbSettings};
18pub use distortion::{DistortionEffect, DistortionSettings};
19pub use high_pass::{HighPassFilterEffect, HighPassFilterSettings};
20pub use low_pass::{LowPassFilterEffect, LowPassFilterSettings};
21pub use compressor::{CompressorEffect, CompressorSettings};
22pub use limiter::{LimiterEffect, LimiterSettings};
23
24/// Shared context for preparing and running DSP effects.
25#[derive(Debug, Clone)]
26pub struct EffectContext {
27    pub sample_rate: u32,
28    pub channels: usize,
29    pub container_path: Option<String>,
30    pub impulse_response_spec: Option<ImpulseResponseSpec>,
31    pub impulse_response_tail_db: f32,
32}
33
34/// Configured audio effect that can process interleaved samples.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub enum AudioEffect {
37    #[serde(rename = "BasicReverbSettings")]
38    BasicReverb(BasicReverbEffect),
39    #[serde(rename = "ConvolutionReverbSettings")]
40    ConvolutionReverb(ConvolutionReverbEffect),
41    #[serde(rename = "LowPassFilterSettings")]
42    LowPassFilter(LowPassFilterEffect),
43    #[serde(rename = "HighPassFilterSettings")]
44    HighPassFilter(HighPassFilterEffect),
45    #[serde(rename = "DistortionSettings")]
46    Distortion(DistortionEffect),
47    #[serde(rename = "CompressorSettings")]
48    Compressor(CompressorEffect),
49    #[serde(rename = "LimiterSettings")]
50    Limiter(LimiterEffect),
51}
52
53impl AudioEffect {
54    /// Process the provided samples through the effect.
55    ///
56    /// # Arguments
57    /// - `samples`: Interleaved input samples.
58    /// - `context`: Environment details (sample rate, channels, etc.).
59    /// - `drain`: When true, flush any buffered tail data.
60    ///
61    /// # Returns
62    /// Processed interleaved samples.
63    pub fn process(
64        &mut self,
65        samples: &[f32],
66        context: &EffectContext,
67        drain: bool,
68    ) -> Vec<f32> {
69        match self {
70            AudioEffect::BasicReverb(effect) => effect.process(samples, context, drain),
71            AudioEffect::ConvolutionReverb(effect) => effect.process(samples, context, drain),
72            AudioEffect::LowPassFilter(effect) => effect.process(samples, context, drain),
73            AudioEffect::HighPassFilter(effect) => effect.process(samples, context, drain),
74            AudioEffect::Distortion(effect) => effect.process(samples, context, drain),
75            AudioEffect::Compressor(effect) => effect.process(samples, context, drain),
76            AudioEffect::Limiter(effect) => effect.process(samples, context, drain),
77        }
78    }
79
80    /// Reset any internal state maintained by the effect.
81    pub fn reset_state(&mut self) {
82        match self {
83            AudioEffect::BasicReverb(effect) => effect.reset_state(),
84            AudioEffect::ConvolutionReverb(effect) => effect.reset_state(),
85            AudioEffect::LowPassFilter(effect) => effect.reset_state(),
86            AudioEffect::HighPassFilter(effect) => effect.reset_state(),
87            AudioEffect::Distortion(effect) => effect.reset_state(),
88            AudioEffect::Compressor(effect) => effect.reset_state(),
89            AudioEffect::Limiter(effect) => effect.reset_state(),
90        }
91    }
92
93    /// Mutable access to the convolution reverb effect, if present.
94    pub fn as_convolution_reverb_mut(&mut self) -> Option<&mut ConvolutionReverbEffect> {
95        match self {
96            AudioEffect::ConvolutionReverb(effect) => Some(effect),
97            _ => None,
98        }
99    }
100
101    /// Immutable access to the convolution reverb effect, if present.
102    pub fn as_convolution_reverb(&self) -> Option<&ConvolutionReverbEffect> {
103        match self {
104            AudioEffect::ConvolutionReverb(effect) => Some(effect),
105            _ => None,
106        }
107    }
108
109    /// Mutable access to the basic reverb effect, if present.
110    pub fn as_basic_reverb_mut(&mut self) -> Option<&mut BasicReverbEffect> {
111        match self {
112            AudioEffect::BasicReverb(effect) => Some(effect),
113            _ => None,
114        }
115    }
116
117    /// Immutable access to the basic reverb effect, if present.
118    pub fn as_basic_reverb(&self) -> Option<&BasicReverbEffect> {
119        match self {
120            AudioEffect::BasicReverb(effect) => Some(effect),
121            _ => None,
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn audio_effect_serde_roundtrip_variants() {
132        let effects = vec![
133            AudioEffect::BasicReverb(BasicReverbEffect::default()),
134            AudioEffect::ConvolutionReverb(ConvolutionReverbEffect::default()),
135            AudioEffect::LowPassFilter(LowPassFilterEffect::default()),
136            AudioEffect::HighPassFilter(HighPassFilterEffect::default()),
137            AudioEffect::Distortion(DistortionEffect::default()),
138            AudioEffect::Compressor(CompressorEffect::default()),
139            AudioEffect::Limiter(LimiterEffect::default()),
140        ];
141
142        let json = serde_json::to_string(&effects).expect("serialize effects");
143        let decoded: Vec<AudioEffect> =
144            serde_json::from_str(&json).expect("deserialize effects");
145        assert_eq!(decoded.len(), effects.len());
146    }
147
148    #[test]
149    fn audio_effect_serde_accepts_aliases() {
150        let json = r#"
151        [
152            {"ConvolutionReverbSettings":{"enabled":true,"wet_dry":0.25}},
153            {"BasicReverbSettings":{"enabled":true,"dry_wet":0.5}},
154            {"LowPassFilterSettings":{"enabled":true,"freq":800,"bandwidth":0.7}},
155            {"HighPassFilterSettings":{"enabled":true,"frequency_hz":1200,"q":0.9}},
156            {"DistortionSettings":{"enabled":true,"gain":2.0,"threshold":0.4}},
157            {"CompressorSettings":{"enabled":true,"threshold":-12.0,"ratio":2.0,
158                "attack":5.0,"release":50.0,"makeup_db":3.0}},
159            {"LimiterSettings":{"enabled":true,"threshold_db":-3.0,"knee_width":2.0,
160                "attack_ms":3.0,"release_ms":30.0}}
161        ]
162        "#;
163
164        let decoded: Vec<AudioEffect> =
165            serde_json::from_str(json).expect("deserialize effects");
166        assert_eq!(decoded.len(), 7);
167    }
168}