devalang_wasm/engine/audio/effects/
mod.rs

1/// Audio effects module - parameter extraction and normalization
2pub mod chain;
3pub mod processors;
4
5use crate::language::syntax::ast::Value;
6use std::collections::HashMap;
7
8/// Effect parameters extracted from a map
9#[derive(Debug, Clone)]
10pub struct EffectParams {
11    pub gain: f32,
12    pub pan: f32,
13    pub fade_in: f32,
14    pub fade_out: f32,
15    pub pitch: f32,
16    pub drive: f32,
17    pub reverb: f32,
18    pub delay: f32,
19}
20
21impl Default for EffectParams {
22    fn default() -> Self {
23        Self {
24            gain: 1.0,
25            pan: 0.0,
26            fade_in: 0.0,
27            fade_out: 0.0,
28            pitch: 1.0,
29            drive: 0.0,
30            reverb: 0.0,
31            delay: 0.0,
32        }
33    }
34}
35
36/// Extract effect parameters from a Value map
37pub fn extract_effect_params(effects: &Option<Value>) -> EffectParams {
38    let mut params = EffectParams::default();
39
40    if let Some(Value::Map(map)) = effects {
41        params.gain = param_as_f32(map, &["gain", "volume", "vol"], 1.0);
42        params.pan = param_as_f32(map, &["pan", "panning"], 0.0);
43        params.fade_in = param_as_f32(map, &["fadeIn", "fadein", "fade_in"], 0.0);
44        params.fade_out = param_as_f32(map, &["fadeOut", "fadeout", "fade_out"], 0.0);
45        params.pitch = param_as_f32(map, &["pitch", "tune"], 1.0);
46        params.drive = param_as_f32(map, &["drive", "distortion"], 0.0);
47        params.reverb = param_as_f32(map, &["reverb", "verb"], 0.0);
48        params.delay = param_as_f32(map, &["delay", "echo"], 0.0);
49    }
50
51    params
52}
53
54/// Read a numeric parameter from a map with multiple possible key names
55pub fn param_as_f32(params: &HashMap<String, Value>, names: &[&str], default: f32) -> f32 {
56    for name in names.iter() {
57        if let Some(v) = params.get(*name) {
58            match v {
59                Value::Number(n) => return *n,
60                Value::String(s) | Value::Identifier(s) => {
61                    if let Ok(parsed) = s.parse::<f32>() {
62                        return parsed;
63                    }
64                }
65                Value::Boolean(b) => return if *b { 1.0 } else { 0.0 },
66                _ => {}
67            }
68        }
69    }
70    default
71}
72
73/// Normalize effects map into per-effect parameter structure
74pub fn normalize_effects(effects: &Option<Value>) -> HashMap<String, HashMap<String, Value>> {
75    let mut out: HashMap<String, HashMap<String, Value>> = HashMap::new();
76
77    if let Some(Value::Map(map)) = effects {
78        for (k, v) in map.iter() {
79            match v {
80                Value::Number(_) | Value::Boolean(_) | Value::String(_) | Value::Identifier(_) => {
81                    let mut sub = HashMap::new();
82                    sub.insert("value".to_string(), v.clone());
83                    out.insert(k.clone(), sub);
84                }
85                Value::Map(sm) => {
86                    out.insert(k.clone(), sm.clone());
87                }
88                _ => {}
89            }
90        }
91    }
92
93    out
94}
95
96/// Merge an effect entry into a flat effects map
97pub fn merge_effect_entry(map: &mut HashMap<String, Value>, key: &str, val: &Value) {
98    match val {
99        Value::Number(_) | Value::Boolean(_) | Value::String(_) | Value::Identifier(_) => {
100            map.insert(key.to_string(), val.clone());
101        }
102        Value::Map(m) => {
103            map.insert(key.to_string(), Value::Map(m.clone()));
104        }
105        _ => {}
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_extract_effect_params_defaults() {
115        let params = extract_effect_params(&None);
116        assert!((params.gain - 1.0).abs() < f32::EPSILON);
117        assert!((params.pan - 0.0).abs() < f32::EPSILON);
118    }
119
120    #[test]
121    fn test_extract_effect_params_with_values() {
122        let mut map = HashMap::new();
123        map.insert("gain".to_string(), Value::Number(0.5));
124        map.insert("pan".to_string(), Value::Number(-0.3));
125
126        let params = extract_effect_params(&Some(Value::Map(map)));
127        assert!((params.gain - 0.5).abs() < f32::EPSILON);
128        assert!((params.pan + 0.3).abs() < f32::EPSILON);
129    }
130
131    #[test]
132    fn test_param_as_f32_with_aliases() {
133        let mut map = HashMap::new();
134        map.insert("volume".to_string(), Value::Number(0.8));
135
136        let result = param_as_f32(&map, &["gain", "volume", "vol"], 1.0);
137        assert!((result - 0.8).abs() < f32::EPSILON);
138    }
139}