devalang_wasm/engine/audio/effects/
chain.rs

1/// Effect chain module - sequential processing of multiple effects
2use super::processors::{
3    ChorusProcessor, CompressorProcessor, DistortionProcessor, EffectProcessor, FlangerProcessor,
4    PhaserProcessor,
5};
6use crate::language::syntax::ast::Value;
7use std::collections::HashMap;
8
9/// Effect chain - processes audio through multiple effects in sequence
10#[derive(Debug)]
11pub struct EffectChain {
12    effects: Vec<Box<dyn EffectProcessor>>,
13}
14
15impl EffectChain {
16    pub fn new() -> Self {
17        Self {
18            effects: Vec::new(),
19        }
20    }
21
22    /// Add an effect to the chain
23    pub fn add_effect(&mut self, effect: Box<dyn EffectProcessor>) {
24        self.effects.push(effect);
25    }
26
27    /// Process audio samples through all effects in the chain
28    pub fn process(&mut self, samples: &mut [f32], sample_rate: u32) {
29        for effect in &mut self.effects {
30            effect.process(samples, sample_rate);
31        }
32    }
33
34    /// Reset all effects in the chain
35    pub fn reset(&mut self) {
36        for effect in &mut self.effects {
37            effect.reset();
38        }
39    }
40
41    /// Get number of effects in the chain
42    pub fn len(&self) -> usize {
43        self.effects.len()
44    }
45
46    /// Check if chain is empty
47    pub fn is_empty(&self) -> bool {
48        self.effects.is_empty()
49    }
50}
51
52impl Default for EffectChain {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58/// Build an effect chain from a Value::Array of effect definitions
59pub fn build_effect_chain(effects_array: &[Value]) -> EffectChain {
60    let mut chain = EffectChain::new();
61
62    for effect_value in effects_array {
63        if let Some(processor) = build_effect_processor(effect_value) {
64            chain.add_effect(processor);
65        }
66    }
67
68    chain
69}
70
71/// Build a single effect processor from a Value definition
72fn build_effect_processor(value: &Value) -> Option<Box<dyn EffectProcessor>> {
73    match value {
74        Value::Map(map) => {
75            // Get effect type
76            let effect_type =
77                map.get("type")
78                    .or_else(|| map.get("effect"))
79                    .and_then(|v| match v {
80                        Value::String(s) | Value::Identifier(s) => Some(s.clone()),
81                        _ => None,
82                    })?;
83
84            match effect_type.to_lowercase().as_str() {
85                "chorus" => {
86                    let depth = get_f32_param(map, "depth", 0.7);
87                    let rate = get_f32_param(map, "rate", 0.5);
88                    let mix = get_f32_param(map, "mix", 0.5);
89                    Some(Box::new(ChorusProcessor::new(depth, rate, mix)))
90                }
91                "flanger" => {
92                    let depth = get_f32_param(map, "depth", 0.7);
93                    let rate = get_f32_param(map, "rate", 0.5);
94                    let feedback = get_f32_param(map, "feedback", 0.5);
95                    let mix = get_f32_param(map, "mix", 0.5);
96                    Some(Box::new(FlangerProcessor::new(depth, rate, feedback, mix)))
97                }
98                "phaser" => {
99                    let stages = get_f32_param(map, "stages", 4.0) as usize;
100                    let rate = get_f32_param(map, "rate", 0.5);
101                    let depth = get_f32_param(map, "depth", 0.7);
102                    let feedback = get_f32_param(map, "feedback", 0.5);
103                    let mix = get_f32_param(map, "mix", 0.5);
104                    Some(Box::new(PhaserProcessor::new(
105                        stages, rate, depth, feedback, mix,
106                    )))
107                }
108                "compressor" => {
109                    let threshold = get_f32_param(map, "threshold", -20.0);
110                    let ratio = get_f32_param(map, "ratio", 4.0);
111                    let attack = get_f32_param(map, "attack", 0.005);
112                    let release = get_f32_param(map, "release", 0.1);
113                    Some(Box::new(CompressorProcessor::new(
114                        threshold, ratio, attack, release,
115                    )))
116                }
117                "distortion" => {
118                    let drive = get_f32_param(map, "drive", 10.0);
119                    let mix = get_f32_param(map, "mix", 0.5);
120                    Some(Box::new(DistortionProcessor::new(drive, mix)))
121                }
122                _ => None,
123            }
124        }
125        Value::String(s) | Value::Identifier(s) => {
126            // Simple effect name without parameters - use defaults
127            match s.to_lowercase().as_str() {
128                "chorus" => Some(Box::new(ChorusProcessor::default())),
129                "flanger" => Some(Box::new(FlangerProcessor::default())),
130                "phaser" => Some(Box::new(PhaserProcessor::default())),
131                "compressor" => Some(Box::new(CompressorProcessor::default())),
132                "distortion" => Some(Box::new(DistortionProcessor::default())),
133                _ => None,
134            }
135        }
136        _ => None,
137    }
138}
139
140/// Helper to extract f32 parameter from map
141fn get_f32_param(map: &HashMap<String, Value>, key: &str, default: f32) -> f32 {
142    map.get(key)
143        .and_then(|v| match v {
144            Value::Number(n) => Some(*n),
145            Value::String(s) => s.parse::<f32>().ok(),
146            _ => None,
147        })
148        .unwrap_or(default)
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_effect_chain_creation() {
157        let mut chain = EffectChain::new();
158        assert_eq!(chain.len(), 0);
159        assert!(chain.is_empty());
160
161        chain.add_effect(Box::new(ChorusProcessor::default()));
162        assert_eq!(chain.len(), 1);
163        assert!(!chain.is_empty());
164    }
165
166    #[test]
167    fn test_build_effect_from_string() {
168        let value = Value::String("chorus".to_string());
169        let processor = build_effect_processor(&value);
170        assert!(processor.is_some());
171    }
172
173    #[test]
174    fn test_build_effect_from_map() {
175        let mut map = HashMap::new();
176        map.insert("type".to_string(), Value::String("chorus".to_string()));
177        map.insert("depth".to_string(), Value::Number(0.8));
178        map.insert("rate".to_string(), Value::Number(1.0));
179
180        let value = Value::Map(map);
181        let processor = build_effect_processor(&value);
182        assert!(processor.is_some());
183    }
184}