devalang_wasm/engine/functions/
effects.rs

1/// Additional arrow call functions: velocity, duration, pan, detune, spread, gain, attack, release, delay, reverb, drive
2use super::{FunctionContext, FunctionExecutor};
3use crate::language::syntax::ast::nodes::Value;
4use anyhow::{Result, anyhow};
5
6/// Velocity function: modifies note velocity (0-127)
7pub struct VelocityFunction;
8
9impl FunctionExecutor for VelocityFunction {
10    fn name(&self) -> &str {
11        "velocity"
12    }
13
14    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
15        if args.is_empty() {
16            return Err(anyhow!(
17                "velocity() requires 1 argument (velocity value 0-127)"
18            ));
19        }
20
21        let velocity = match &args[0] {
22            Value::Number(v) => *v,
23            _ => return Err(anyhow!("velocity() argument must be a number")),
24        };
25
26        context.set("velocity", Value::Number(velocity));
27        Ok(())
28    }
29}
30
31/// Duration function: modifies note duration
32pub struct DurationFunction;
33
34impl FunctionExecutor for DurationFunction {
35    fn name(&self) -> &str {
36        "duration"
37    }
38
39    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
40        if args.is_empty() {
41            return Err(anyhow!("duration() requires 1 argument (duration in ms)"));
42        }
43
44        let duration_ms = match &args[0] {
45            Value::Number(d) => *d,
46            _ => return Err(anyhow!("duration() argument must be a number")),
47        };
48
49        context.set("duration", Value::Number(duration_ms));
50        context.duration = duration_ms / 1000.0; // Convert to seconds
51        Ok(())
52    }
53}
54
55/// Pan function: stereo positioning (-1.0 = left, 0.0 = center, 1.0 = right)
56pub struct PanFunction;
57
58impl FunctionExecutor for PanFunction {
59    fn name(&self) -> &str {
60        "pan"
61    }
62
63    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
64        if args.is_empty() {
65            return Err(anyhow!("pan() requires 1 argument (pan value -1.0 to 1.0)"));
66        }
67
68        let pan = match &args[0] {
69            Value::Number(p) => *p,
70            _ => return Err(anyhow!("pan() argument must be a number")),
71        };
72
73        context.set("pan", Value::Number(pan));
74        Ok(())
75    }
76}
77
78/// Detune function: pitch adjustment in cents
79pub struct DetuneFunction;
80
81impl FunctionExecutor for DetuneFunction {
82    fn name(&self) -> &str {
83        "detune"
84    }
85
86    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
87        if args.is_empty() {
88            return Err(anyhow!("detune() requires 1 argument (cents)"));
89        }
90
91        let detune = match &args[0] {
92            Value::Number(d) => *d,
93            _ => return Err(anyhow!("detune() argument must be a number")),
94        };
95
96        context.set("detune", Value::Number(detune));
97        Ok(())
98    }
99}
100
101/// Spread function: stereo spread for chords (0.0 = mono, 1.0 = full stereo)
102pub struct SpreadFunction;
103
104impl FunctionExecutor for SpreadFunction {
105    fn name(&self) -> &str {
106        "spread"
107    }
108
109    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
110        if args.is_empty() {
111            return Err(anyhow!(
112                "spread() requires 1 argument (spread value 0.0 to 1.0)"
113            ));
114        }
115
116        let spread = match &args[0] {
117            Value::Number(s) => *s,
118            _ => return Err(anyhow!("spread() argument must be a number")),
119        };
120
121        context.set("spread", Value::Number(spread));
122        Ok(())
123    }
124}
125
126/// Gain function: volume multiplier
127pub struct GainFunction;
128
129impl FunctionExecutor for GainFunction {
130    fn name(&self) -> &str {
131        "gain"
132    }
133
134    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
135        if args.is_empty() {
136            return Err(anyhow!("gain() requires 1 argument (gain multiplier)"));
137        }
138
139        let gain = match &args[0] {
140            Value::Number(g) => *g,
141            _ => return Err(anyhow!("gain() argument must be a number")),
142        };
143
144        context.set("gain", Value::Number(gain));
145        Ok(())
146    }
147}
148
149/// Attack function: envelope attack time override in ms
150pub struct AttackFunction;
151
152impl FunctionExecutor for AttackFunction {
153    fn name(&self) -> &str {
154        "attack"
155    }
156
157    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
158        if args.is_empty() {
159            return Err(anyhow!("attack() requires 1 argument (attack time in ms)"));
160        }
161
162        let attack = match &args[0] {
163            Value::Number(a) => *a,
164            _ => return Err(anyhow!("attack() argument must be a number")),
165        };
166
167        context.set("attack", Value::Number(attack));
168        Ok(())
169    }
170}
171
172/// Release function: envelope release time override in ms
173pub struct ReleaseFunction;
174
175impl FunctionExecutor for ReleaseFunction {
176    fn name(&self) -> &str {
177        "release"
178    }
179
180    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
181        if args.is_empty() {
182            return Err(anyhow!(
183                "release() requires 1 argument (release time in ms)"
184            ));
185        }
186
187        let release = match &args[0] {
188            Value::Number(r) => *r,
189            _ => return Err(anyhow!("release() argument must be a number")),
190        };
191
192        context.set("release", Value::Number(release));
193        Ok(())
194    }
195}
196
197/// Delay function: echo effect with time in ms (default feedback: 0.3, mix: 0.5)
198/// Usage: -> delay(400) or -> delay(400, 0.5) or -> delay(400, 0.5, 0.7)
199pub struct DelayFunction;
200
201impl FunctionExecutor for DelayFunction {
202    fn name(&self) -> &str {
203        "delay"
204    }
205
206    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
207        if args.is_empty() {
208            return Err(anyhow!(
209                "delay() requires at least 1 argument (delay time in ms)"
210            ));
211        }
212
213        // Time in ms (required)
214        let time = match &args[0] {
215            Value::Number(t) => *t,
216            _ => {
217                return Err(anyhow!(
218                    "delay() first argument must be a number (time in ms)"
219                ));
220            }
221        };
222
223        // Feedback (optional, default 0.3)
224        let feedback = if args.len() > 1 {
225            match &args[1] {
226                Value::Number(f) => *f,
227                _ => {
228                    return Err(anyhow!(
229                        "delay() second argument must be a number (feedback)"
230                    ));
231                }
232            }
233        } else {
234            0.3
235        };
236
237        // Mix (optional, default 0.5)
238        let mix = if args.len() > 2 {
239            match &args[2] {
240                Value::Number(m) => *m,
241                _ => return Err(anyhow!("delay() third argument must be a number (mix)")),
242            }
243        } else {
244            0.5
245        };
246
247        context.set("delay_time", Value::Number(time));
248        context.set("delay_feedback", Value::Number(feedback));
249        context.set("delay_mix", Value::Number(mix));
250
251        Ok(())
252    }
253}
254
255/// Reverb function: reverb effect with amount (0.0-1.0, default: 0.5)
256/// Usage: -> reverb(0.3) for 30% reverb
257pub struct ReverbFunction;
258
259impl FunctionExecutor for ReverbFunction {
260    fn name(&self) -> &str {
261        "reverb"
262    }
263
264    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
265        if args.is_empty() {
266            return Err(anyhow!(
267                "reverb() requires 1 argument (reverb amount 0.0-1.0)"
268            ));
269        }
270
271        let amount = match &args[0] {
272            Value::Number(a) => *a,
273            _ => return Err(anyhow!("reverb() argument must be a number")),
274        };
275
276        context.set("reverb_amount", Value::Number(amount));
277        context.set("reverb_size", Value::Number(amount)); // Keep compatibility
278
279        Ok(())
280    }
281}
282
283/// Drive function: tube-style saturation (amount: 0.0-1.0, color: 0.0-1.0 default 0.5)
284/// Usage: -> drive(0.6) or -> drive(0.6, 0.7)
285pub struct DriveFunction;
286
287impl FunctionExecutor for DriveFunction {
288    fn name(&self) -> &str {
289        "drive"
290    }
291
292    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
293        if args.is_empty() {
294            return Err(anyhow!(
295                "drive() requires at least 1 argument (drive amount 0.0-1.0)"
296            ));
297        }
298
299        // Amount (required)
300        let amount = match &args[0] {
301            Value::Number(a) => *a,
302            _ => return Err(anyhow!("drive() first argument must be a number (amount)")),
303        };
304
305        // Color/tone (optional, default 0.5)
306        let color = if args.len() > 1 {
307            match &args[1] {
308                Value::Number(c) => *c,
309                _ => return Err(anyhow!("drive() second argument must be a number (color)")),
310            }
311        } else {
312            0.5
313        };
314
315        context.set("drive_amount", Value::Number(amount));
316        context.set("drive_amp", Value::Number(amount)); // Keep compatibility
317        context.set("drive_color", Value::Number(color));
318
319        Ok(())
320    }
321}
322
323/// Chorus function: multiple detuned voices for richness
324pub struct ChorusFunction;
325
326impl FunctionExecutor for ChorusFunction {
327    fn name(&self) -> &str {
328        "chorus"
329    }
330
331    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
332        // Default params
333        let mut depth = 0.5; // Amount of pitch modulation
334        let mut rate = 1.5; // LFO rate in Hz
335        let mut mix = 0.5; // Dry/wet mix
336
337        // Parse params from map { depth: 0.5, rate: 1.5, mix: 0.5 }
338        if let Some(Value::Map(params)) = args.first() {
339            if let Some(Value::Number(d)) = params.get("depth") {
340                depth = *d;
341            }
342            if let Some(Value::Number(r)) = params.get("rate") {
343                rate = *r;
344            }
345            if let Some(Value::Number(m)) = params.get("mix") {
346                mix = *m;
347            }
348        }
349
350        context.set("chorus_depth", Value::Number(depth));
351        context.set("chorus_rate", Value::Number(rate));
352        context.set("chorus_mix", Value::Number(mix));
353
354        Ok(())
355    }
356}
357
358/// Flanger function: sweeping comb filter effect
359pub struct FlangerFunction;
360
361impl FunctionExecutor for FlangerFunction {
362    fn name(&self) -> &str {
363        "flanger"
364    }
365
366    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
367        // Default params
368        let mut depth = 0.7; // Delay modulation depth
369        let mut rate = 0.5; // LFO rate in Hz
370        let mut feedback = 0.5; // Feedback amount
371        let mut mix = 0.5; // Dry/wet mix
372
373        // Parse params from map
374        if let Some(Value::Map(params)) = args.first() {
375            if let Some(Value::Number(d)) = params.get("depth") {
376                depth = *d;
377            }
378            if let Some(Value::Number(r)) = params.get("rate") {
379                rate = *r;
380            }
381            if let Some(Value::Number(f)) = params.get("feedback") {
382                feedback = *f;
383            }
384            if let Some(Value::Number(m)) = params.get("mix") {
385                mix = *m;
386            }
387        }
388
389        context.set("flanger_depth", Value::Number(depth));
390        context.set("flanger_rate", Value::Number(rate));
391        context.set("flanger_feedback", Value::Number(feedback));
392        context.set("flanger_mix", Value::Number(mix));
393
394        Ok(())
395    }
396}
397
398/// Phaser function: phase shifting effect
399pub struct PhaserFunction;
400
401impl FunctionExecutor for PhaserFunction {
402    fn name(&self) -> &str {
403        "phaser"
404    }
405
406    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
407        // Default params
408        let mut stages = 4.0; // Number of allpass stages (2-12)
409        let mut rate = 0.5; // LFO rate in Hz
410        let mut depth = 0.7; // Modulation depth
411        let mut feedback = 0.5; // Feedback amount
412        let mut mix = 0.5; // Dry/wet mix
413
414        // Parse params from map
415        if let Some(Value::Map(params)) = args.first() {
416            if let Some(Value::Number(s)) = params.get("stages") {
417                stages = *s;
418            }
419            if let Some(Value::Number(r)) = params.get("rate") {
420                rate = *r;
421            }
422            if let Some(Value::Number(d)) = params.get("depth") {
423                depth = *d;
424            }
425            if let Some(Value::Number(f)) = params.get("feedback") {
426                feedback = *f;
427            }
428            if let Some(Value::Number(m)) = params.get("mix") {
429                mix = *m;
430            }
431        }
432
433        context.set("phaser_stages", Value::Number(stages));
434        context.set("phaser_rate", Value::Number(rate));
435        context.set("phaser_depth", Value::Number(depth));
436        context.set("phaser_feedback", Value::Number(feedback));
437        context.set("phaser_mix", Value::Number(mix));
438
439        Ok(())
440    }
441}
442
443/// Compressor function: dynamic range compression
444pub struct CompressorFunction;
445
446impl FunctionExecutor for CompressorFunction {
447    fn name(&self) -> &str {
448        "compressor"
449    }
450
451    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
452        // Default params
453        let mut threshold = -20.0; // Threshold in dB
454        let mut ratio = 4.0; // Compression ratio (1:1 to 20:1)
455        let mut attack = 5.0; // Attack time in ms
456        let mut release = 50.0; // Release time in ms
457        let mut makeup = 0.0; // Makeup gain in dB
458
459        // Parse params from map
460        if let Some(Value::Map(params)) = args.first() {
461            if let Some(Value::Number(t)) = params.get("threshold") {
462                threshold = *t;
463            }
464            if let Some(Value::Number(r)) = params.get("ratio") {
465                ratio = *r;
466            }
467            if let Some(Value::Number(a)) = params.get("attack") {
468                attack = *a;
469            }
470            if let Some(Value::Number(r)) = params.get("release") {
471                release = *r;
472            }
473            if let Some(Value::Number(m)) = params.get("makeup") {
474                makeup = *m;
475            }
476        }
477
478        context.set("compressor_threshold", Value::Number(threshold));
479        context.set("compressor_ratio", Value::Number(ratio));
480        context.set("compressor_attack", Value::Number(attack));
481        context.set("compressor_release", Value::Number(release));
482        context.set("compressor_makeup", Value::Number(makeup));
483
484        Ok(())
485    }
486}
487
488/// Distortion function: enhanced distortion/saturation
489pub struct DistortionFunction;
490
491impl FunctionExecutor for DistortionFunction {
492    fn name(&self) -> &str {
493        "distortion"
494    }
495
496    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
497        // Default params
498        let mut amount = 0.5; // Distortion amount 0-1
499        let mut tone = 0.5; // Tone control (brightness)
500        let mut mix = 1.0; // Dry/wet mix
501
502        // Parse params from map
503        if let Some(Value::Map(params)) = args.first() {
504            if let Some(Value::Number(a)) = params.get("amount") {
505                amount = *a;
506            }
507            if let Some(Value::Number(t)) = params.get("tone") {
508                tone = *t;
509            }
510            if let Some(Value::Number(m)) = params.get("mix") {
511                mix = *m;
512            }
513        }
514
515        context.set("distortion_amount", Value::Number(amount));
516        context.set("distortion_tone", Value::Number(tone));
517        context.set("distortion_mix", Value::Number(mix));
518
519        Ok(())
520    }
521}