devalang_wasm/engine/audio/effects/
mod.rs

1/// Audio effects module - Processor and effect type management
2pub mod chain;
3pub mod processors;
4pub mod registry;
5
6use crate::language::syntax::ast::Value;
7use std::collections::HashMap;
8
9/// Effect availability enum - defines where an effect can be used
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum EffectAvailability {
12    SynthOnly,
13    TriggerOnly,
14    Both,
15}
16
17/// Effect parameters extracted from a map
18#[derive(Debug, Clone)]
19pub struct EffectParams {
20    pub gain: f32,
21    pub pan: f32,
22    pub fade_in: f32,
23    pub fade_out: f32,
24    pub pitch: f32,
25    pub drive: DriveParams,
26    pub reverb: ReverbParams,
27    pub delay: DelayParams,
28    pub speed: f32,
29    pub reverse: bool,
30}
31
32#[derive(Debug, Clone)]
33pub struct DriveParams {
34    pub amount: f32,
35    pub mix: f32,
36    pub tone: f32,
37    pub color: f32,
38}
39
40#[derive(Debug, Clone)]
41pub struct ReverbParams {
42    pub size: f32,
43    pub mix: f32,
44    pub decay: f32,
45    pub damping: f32,
46}
47
48#[derive(Debug, Clone)]
49pub struct DelayParams {
50    pub time: f32,
51    pub feedback: f32,
52    pub mix: f32,
53}
54
55impl Default for EffectParams {
56    fn default() -> Self {
57        Self {
58            gain: 1.0,
59            pan: 0.0,
60            fade_in: 0.0,
61            fade_out: 0.0,
62            pitch: 1.0,
63            drive: DriveParams {
64                amount: 0.7,
65                mix: 0.5,
66                tone: 0.5,
67                color: 0.5,
68            },
69            reverb: ReverbParams {
70                size: 0.5,
71                mix: 0.3,
72                decay: 0.5,
73                damping: 0.5,
74            },
75            delay: DelayParams {
76                time: 250.0,
77                feedback: 0.4,
78                mix: 0.3,
79            },
80            speed: 1.0,
81            reverse: false,
82        }
83    }
84}
85
86/// Extract effect parameters from a Value map
87pub fn extract_effect_params(effects: &Option<Value>) -> EffectParams {
88    let mut params = EffectParams::default();
89
90    if let Some(Value::Map(map)) = effects {
91        params.gain = param_as_f32(map, &["gain", "volume", "vol"], 1.0);
92        params.pan = param_as_f32(map, &["pan", "panning"], 0.0);
93        params.fade_in = param_as_f32(map, &["fadeIn", "fadein", "fade_in"], 0.0);
94        params.fade_out = param_as_f32(map, &["fadeOut", "fadeout", "fade_out"], 0.0);
95        params.pitch = param_as_f32(map, &["pitch", "tune"], 1.0);
96        params.speed = param_as_f32(map, &["speed", "rate"], 1.0);
97        params.reverse = param_as_bool(map, &["reverse", "rev"], false);
98
99        // Extract drive parameters
100        if let Some(Value::Map(drive_map)) = map.get("drive") {
101            params.drive.amount = param_as_f32(drive_map, &["amount", "drive"], 0.7);
102            params.drive.mix = param_as_f32(drive_map, &["mix", "wet"], 0.5);
103            params.drive.tone = param_as_f32(drive_map, &["tone"], 0.5);
104            params.drive.color = param_as_f32(drive_map, &["color", "tone"], 0.5);
105        }
106
107        // Extract reverb parameters
108        if let Some(Value::Map(reverb_map)) = map.get("reverb") {
109            params.reverb.size = param_as_f32(reverb_map, &["size", "room"], 0.5);
110            params.reverb.mix = param_as_f32(reverb_map, &["mix", "wet"], 0.3);
111            params.reverb.damping = param_as_f32(reverb_map, &["damping", "damp"], 0.5);
112            params.reverb.decay = param_as_f32(reverb_map, &["decay", "tail"], 0.5);
113        }
114
115        // Extract delay parameters
116        if let Some(Value::Map(delay_map)) = map.get("delay") {
117            params.delay.time = param_as_f32(delay_map, &["time", "length"], 250.0);
118            params.delay.feedback = param_as_f32(delay_map, &["feedback", "fb"], 0.4);
119            params.delay.mix = param_as_f32(delay_map, &["mix", "wet"], 0.3);
120        }
121    }
122
123    params
124}
125
126/// Read a numeric parameter from a map with multiple possible key names
127pub fn param_as_f32(params: &HashMap<String, Value>, names: &[&str], default: f32) -> f32 {
128    for name in names.iter() {
129        if let Some(v) = params.get(*name) {
130            match v {
131                Value::Number(n) => return *n,
132                Value::String(s) | Value::Identifier(s) => {
133                    if let Ok(parsed) = s.parse::<f32>() {
134                        return parsed;
135                    }
136                }
137                Value::Boolean(b) => return if *b { 1.0 } else { 0.0 },
138                _ => {}
139            }
140        }
141    }
142    default
143}
144
145/// Read a boolean parameter from a map with multiple possible key names
146pub fn param_as_bool(params: &HashMap<String, Value>, names: &[&str], default: bool) -> bool {
147    for name in names.iter() {
148        if let Some(v) = params.get(*name) {
149            match v {
150                Value::Boolean(b) => return *b,
151                Value::Number(n) => return *n != 0.0,
152                Value::String(s) | Value::Identifier(s) => {
153                    if let Ok(parsed) = s.parse::<bool>() {
154                        return parsed;
155                    }
156                }
157                _ => {}
158            }
159        }
160    }
161    default
162}
163
164/// Normalize effects map into per-effect parameter structure
165pub fn normalize_effects(effects: &Option<Value>) -> HashMap<String, HashMap<String, Value>> {
166    let mut out: HashMap<String, HashMap<String, Value>> = HashMap::new();
167
168    if let Some(Value::Map(map)) = effects {
169        // DEPRECATION: legacy map-style effect parameters are deprecated.
170        // Prefer chained params (-> effect(...)) in scripts. Keep compatibility for now but warn.
171        eprintln!(
172            "DEPRECATION: effect param map support is deprecated — use chained params instead. This will be removed in a future version."
173        );
174        for (k, v) in map.iter() {
175            match v {
176                Value::Number(_) | Value::Boolean(_) | Value::String(_) | Value::Identifier(_) => {
177                    let mut sub = HashMap::new();
178                    sub.insert("value".to_string(), v.clone());
179                    out.insert(k.clone(), sub);
180                }
181                Value::Map(sm) => {
182                    out.insert(k.clone(), sm.clone());
183                }
184                _ => {}
185            }
186        }
187    }
188
189    out
190}
191
192/// Merge an effect entry into a flat effects map
193pub fn merge_effect_entry(map: &mut HashMap<String, Value>, key: &str, val: &Value) {
194    match val {
195        Value::Number(_) | Value::Boolean(_) | Value::String(_) | Value::Identifier(_) => {
196            map.insert(key.to_string(), val.clone());
197        }
198        Value::Map(m) => {
199            map.insert(key.to_string(), Value::Map(m.clone()));
200        }
201        _ => {}
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_extract_effect_params_defaults() {
211        let params = extract_effect_params(&None);
212        assert!((params.gain - 1.0).abs() < f32::EPSILON);
213        assert!((params.pan - 0.0).abs() < f32::EPSILON);
214    }
215
216    #[test]
217    fn test_extract_effect_params_with_values() {
218        let mut map = HashMap::new();
219        map.insert("gain".to_string(), Value::Number(0.5));
220        map.insert("pan".to_string(), Value::Number(-0.3));
221
222        let params = extract_effect_params(&Some(Value::Map(map)));
223        assert!((params.gain - 0.5).abs() < f32::EPSILON);
224        assert!((params.pan + 0.3).abs() < f32::EPSILON);
225    }
226
227    #[test]
228    fn test_param_as_f32_with_aliases() {
229        let mut map = HashMap::new();
230        map.insert("volume".to_string(), Value::Number(0.8));
231
232        let result = param_as_f32(&map, &["gain", "volume", "vol"], 1.0);
233        assert!((result - 0.8).abs() < f32::EPSILON);
234    }
235}