devalang_wasm/engine/audio/effects/
mod.rs1pub mod chain;
3pub mod processors;
4pub mod registry;
5
6use crate::language::syntax::ast::Value;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum EffectAvailability {
12 SynthOnly,
13 TriggerOnly,
14 Both,
15}
16
17#[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
86pub 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 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 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 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
126pub 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
145pub 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
164pub 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 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
192pub 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}