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!(
42                "duration() requires 1 argument (duration in ms or beats fraction like 1/4)"
43            ));
44        }
45
46        let duration_seconds = match &args[0] {
47            // Number: interpret as milliseconds
48            Value::Number(d) => *d / 1000.0,
49
50            // String or Identifier: try parsing as duration using the parser
51            Value::String(s) | Value::Identifier(s) => {
52                let frac_str = s.trim().trim_matches('"').trim_matches('\'');
53
54                // Try using the duration parser first
55                use crate::language::syntax::parser::driver::duration::parse_duration_token;
56                match parse_duration_token(frac_str) {
57                    Ok(crate::language::syntax::ast::DurationValue::Milliseconds(ms)) => {
58                        ms / 1000.0
59                    }
60                    Ok(crate::language::syntax::ast::DurationValue::Beats(beats)) => {
61                        beats * (60.0 / context.tempo)
62                    }
63                    Ok(crate::language::syntax::ast::DurationValue::Number(ms)) => ms / 1000.0,
64                    Ok(crate::language::syntax::ast::DurationValue::Beat(beat_str)) => {
65                        // Parse the beat string (should be a number)
66                        if let Ok(beats) = beat_str.parse::<f32>() {
67                            beats * (60.0 / context.tempo)
68                        } else {
69                            return Err(anyhow!("Invalid beat value: '{}'", beat_str));
70                        }
71                    }
72                    Ok(crate::language::syntax::ast::DurationValue::Identifier(id)) => {
73                        // This is a temporal identifier like "__temporal__4_beats"
74                        if id.starts_with("__temporal__") {
75                            if let Some(beats_str) = id.strip_prefix("__temporal__") {
76                                if let Ok(beats) =
77                                    beats_str.split('_').next().unwrap_or("").parse::<f32>()
78                                {
79                                    beats * (60.0 / context.tempo)
80                                } else {
81                                    return Err(anyhow!(
82                                        "Invalid temporal duration format: '{}'",
83                                        s
84                                    ));
85                                }
86                            } else {
87                                return Err(anyhow!("Invalid temporal duration format: '{}'", s));
88                            }
89                        } else {
90                            // Unknown identifier
91                            return Err(anyhow!("Unknown duration: '{}'", s));
92                        }
93                    }
94                    Ok(crate::language::syntax::ast::DurationValue::Auto) => {
95                        return Err(anyhow!("duration() cannot use 'auto'"));
96                    }
97                    Err(_) => {
98                        // Parser failed, give a helpful error
99                        return Err(anyhow!(
100                            "Invalid duration format: '{}'. Use: number (ms), 'ms', fraction (1/4), or temporal (1 beat, 2 steps, 3 bar, etc.)",
101                            s
102                        ));
103                    }
104                }
105            }
106
107            _ => {
108                return Err(anyhow!(
109                    "duration() argument must be a number, string, or identifier"
110                ));
111            }
112        };
113
114        // Store duration in ms for consistency with other code
115        let duration_ms = duration_seconds * 1000.0;
116        context.set("duration", Value::Number(duration_ms));
117        context.duration = duration_seconds;
118        Ok(())
119    }
120}
121
122/// Parse fraction string like "1/4" to beats as f32
123fn parse_fraction_beats(s: &str) -> Option<f32> {
124    let mut parts = s.split('/');
125    let numerator: f32 = parts.next()?.trim().parse().ok()?;
126    let denominator: f32 = parts.next()?.trim().parse().ok()?;
127    if denominator.abs() < f32::EPSILON {
128        return None;
129    }
130    Some(numerator / denominator)
131}
132
133/// Pan function: stereo positioning (-1.0 = left, 0.0 = center, 1.0 = right)
134pub struct PanFunction;
135
136impl FunctionExecutor for PanFunction {
137    fn name(&self) -> &str {
138        "pan"
139    }
140
141    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
142        if args.is_empty() {
143            return Err(anyhow!("pan() requires 1 argument (pan value -1.0 to 1.0)"));
144        }
145
146        let pan = match &args[0] {
147            Value::Number(p) => *p,
148            _ => return Err(anyhow!("pan() argument must be a number")),
149        };
150
151        context.set("pan", Value::Number(pan));
152        Ok(())
153    }
154}
155
156/// Detune function: pitch adjustment in cents
157pub struct DetuneFunction;
158
159impl FunctionExecutor for DetuneFunction {
160    fn name(&self) -> &str {
161        "detune"
162    }
163
164    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
165        if args.is_empty() {
166            return Err(anyhow!("detune() requires 1 argument (cents)"));
167        }
168
169        let detune = match &args[0] {
170            Value::Number(d) => *d,
171            _ => return Err(anyhow!("detune() argument must be a number")),
172        };
173
174        context.set("detune", Value::Number(detune));
175        Ok(())
176    }
177}
178
179/// Spread function: stereo spread for chords (0.0 = mono, 1.0 = full stereo)
180pub struct SpreadFunction;
181
182impl FunctionExecutor for SpreadFunction {
183    fn name(&self) -> &str {
184        "spread"
185    }
186
187    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
188        if args.is_empty() {
189            return Err(anyhow!(
190                "spread() requires 1 argument (spread value 0.0 to 1.0)"
191            ));
192        }
193
194        let spread = match &args[0] {
195            Value::Number(s) => *s,
196            _ => return Err(anyhow!("spread() argument must be a number")),
197        };
198
199        context.set("spread", Value::Number(spread));
200        Ok(())
201    }
202}
203
204/// Gain function: volume multiplier
205pub struct GainFunction;
206
207impl FunctionExecutor for GainFunction {
208    fn name(&self) -> &str {
209        "gain"
210    }
211
212    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
213        if args.is_empty() {
214            return Err(anyhow!("gain() requires 1 argument (gain multiplier)"));
215        }
216
217        let gain = match &args[0] {
218            Value::Number(g) => *g,
219            _ => return Err(anyhow!("gain() argument must be a number")),
220        };
221
222        context.set("gain", Value::Number(gain));
223        Ok(())
224    }
225}
226
227/// Attack function: envelope attack time override in ms
228pub struct AttackFunction;
229
230impl FunctionExecutor for AttackFunction {
231    fn name(&self) -> &str {
232        "attack"
233    }
234
235    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
236        if args.is_empty() {
237            return Err(anyhow!("attack() requires 1 argument (attack time in ms)"));
238        }
239
240        let attack = match &args[0] {
241            Value::Number(a) => *a,
242            _ => return Err(anyhow!("attack() argument must be a number")),
243        };
244
245        context.set("attack", Value::Number(attack));
246        Ok(())
247    }
248}
249
250/// Release function: envelope release time override in ms
251pub struct ReleaseFunction;
252
253impl FunctionExecutor for ReleaseFunction {
254    fn name(&self) -> &str {
255        "release"
256    }
257
258    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
259        if args.is_empty() {
260            return Err(anyhow!(
261                "release() requires 1 argument (release time in ms)"
262            ));
263        }
264
265        let release = match &args[0] {
266            Value::Number(r) => *r,
267            _ => return Err(anyhow!("release() argument must be a number")),
268        };
269
270        context.set("release", Value::Number(release));
271        Ok(())
272    }
273}
274
275/// Delay function: echo effect with time in ms (default feedback: 0.3, mix: 0.5)
276/// Usage: -> delay(400) or -> delay(400, 0.5) or -> delay(400, 0.5, 0.7)
277pub struct DelayFunction;
278
279impl FunctionExecutor for DelayFunction {
280    fn name(&self) -> &str {
281        "delay"
282    }
283
284    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
285        if args.is_empty() {
286            return Err(anyhow!(
287                "delay() requires at least 1 argument (delay time in ms)"
288            ));
289        }
290        // Support both positional args: delay(time, feedback?, mix?)
291        // and map-style: delay({ time: 300, feedback: 0.3, mix: 0.5 })
292        let mut time_val: Option<f32> = None;
293        let mut feedback: f32 = 0.3;
294        let mut mix: f32 = 0.5;
295
296        if let Some(Value::Map(params)) = args.first() {
297            if let Some(Value::Number(t)) = params.get("time") {
298                time_val = Some(*t);
299            }
300            if let Some(Value::Number(f)) = params.get("feedback") {
301                feedback = *f;
302            }
303            if let Some(Value::Number(m)) = params.get("mix") {
304                mix = *m;
305            }
306        } else {
307            // Positional
308            // Time in ms (required)
309            time_val = match &args[0] {
310                Value::Number(t) => Some(*t),
311                _ => None,
312            };
313
314            if args.len() > 1 {
315                if let Value::Number(f) = &args[1] {
316                    feedback = *f;
317                } else {
318                    return Err(anyhow!(
319                        "delay() second argument must be a number (feedback)"
320                    ));
321                }
322            }
323
324            if args.len() > 2 {
325                if let Value::Number(m) = &args[2] {
326                    mix = *m;
327                } else {
328                    return Err(anyhow!("delay() third argument must be a number (mix)"));
329                }
330            }
331        }
332
333        let time = if let Some(t) = time_val {
334            t
335        } else {
336            return Err(anyhow!(
337                "delay() requires a time parameter either as first numeric arg or as {{ time: ... }}"
338            ));
339        };
340
341        context.set("delay_time", Value::Number(time));
342        context.set("delay_feedback", Value::Number(feedback));
343        context.set("delay_mix", Value::Number(mix));
344
345        Ok(())
346    }
347}
348
349/// Reverb function: reverb effect with amount (0.0-1.0, default: 0.5)
350/// Usage: -> reverb(0.3) for 30% reverb
351pub struct ReverbFunction;
352
353impl FunctionExecutor for ReverbFunction {
354    fn name(&self) -> &str {
355        "reverb"
356    }
357
358    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
359        // Accept either reverb(0.3) or reverb({ size: 0.9 })
360        let mut amount = 0.5; // default
361
362        if let Some(Value::Map(params)) = args.first() {
363            if let Some(Value::Number(s)) = params.get("size") {
364                amount = *s;
365            } else if let Some(Value::Number(a)) = params.get("amount") {
366                amount = *a;
367            }
368        } else if let Some(Value::Number(a)) = args.get(0) {
369            amount = *a;
370        } else {
371            return Err(anyhow!(
372                "reverb() requires either a numeric argument or a parameter map {{ size: ... }}"
373            ));
374        }
375
376        context.set("reverb_amount", Value::Number(amount));
377        context.set("reverb_size", Value::Number(amount)); // Keep compatibility
378
379        Ok(())
380    }
381}
382
383/// Drive function: tube-style saturation (amount: 0.0-1.0, color: 0.0-1.0 default 0.5)
384/// Usage: -> drive(0.6) or -> drive(0.6, 0.7)
385pub struct DriveFunction;
386
387impl FunctionExecutor for DriveFunction {
388    fn name(&self) -> &str {
389        "drive"
390    }
391
392    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
393        if args.is_empty() {
394            return Err(anyhow!(
395                "drive() requires at least 1 argument (drive amount 0.0-1.0)"
396            ));
397        }
398        // Support both positional: drive(amount, color?) and map: drive({ gain: 2.0, color: 0.5 })
399        let mut amount = 0.5;
400        let mut color = 0.5;
401
402        if let Some(Value::Map(params)) = args.first() {
403            if let Some(Value::Number(g)) = params.get("gain") {
404                amount = *g;
405            } else if let Some(Value::Number(a)) = params.get("amount") {
406                amount = *a;
407            }
408
409            if let Some(Value::Number(c)) = params.get("color") {
410                color = *c;
411            }
412        } else {
413            // Positional
414            amount = match &args[0] {
415                Value::Number(a) => *a,
416                _ => return Err(anyhow!("drive() first argument must be a number (amount)")),
417            };
418
419            if args.len() > 1 {
420                color = match &args[1] {
421                    Value::Number(c) => *c,
422                    _ => return Err(anyhow!("drive() second argument must be a number (color)")),
423                };
424            }
425        }
426
427        context.set("drive_amount", Value::Number(amount));
428        context.set("drive_amp", Value::Number(amount)); // Keep compatibility
429        context.set("drive_color", Value::Number(color));
430
431        Ok(())
432    }
433}
434
435/// Chorus function: multiple detuned voices for richness
436pub struct ChorusFunction;
437
438impl FunctionExecutor for ChorusFunction {
439    fn name(&self) -> &str {
440        "chorus"
441    }
442
443    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
444        // Default params
445        let mut depth = 0.5; // Amount of pitch modulation
446        let mut rate = 1.5; // LFO rate in Hz
447        let mut mix = 0.5; // Dry/wet mix
448
449        // Parse params from map { depth: 0.5, rate: 1.5, mix: 0.5 }
450        if let Some(Value::Map(params)) = args.first() {
451            if let Some(Value::Number(d)) = params.get("depth") {
452                depth = *d;
453            }
454            if let Some(Value::Number(r)) = params.get("rate") {
455                rate = *r;
456            }
457            if let Some(Value::Number(m)) = params.get("mix") {
458                mix = *m;
459            }
460        }
461
462        context.set("chorus_depth", Value::Number(depth));
463        context.set("chorus_rate", Value::Number(rate));
464        context.set("chorus_mix", Value::Number(mix));
465
466        Ok(())
467    }
468}
469
470/// Flanger function: sweeping comb filter effect
471pub struct FlangerFunction;
472
473impl FunctionExecutor for FlangerFunction {
474    fn name(&self) -> &str {
475        "flanger"
476    }
477
478    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
479        // Default params
480        let mut depth = 0.7; // Delay modulation depth
481        let mut rate = 0.5; // LFO rate in Hz
482        let mut feedback = 0.5; // Feedback amount
483        let mut mix = 0.5; // Dry/wet mix
484
485        // Parse params from map
486        if let Some(Value::Map(params)) = args.first() {
487            if let Some(Value::Number(d)) = params.get("depth") {
488                depth = *d;
489            }
490            if let Some(Value::Number(r)) = params.get("rate") {
491                rate = *r;
492            }
493            if let Some(Value::Number(f)) = params.get("feedback") {
494                feedback = *f;
495            }
496            if let Some(Value::Number(m)) = params.get("mix") {
497                mix = *m;
498            }
499        }
500
501        context.set("flanger_depth", Value::Number(depth));
502        context.set("flanger_rate", Value::Number(rate));
503        context.set("flanger_feedback", Value::Number(feedback));
504        context.set("flanger_mix", Value::Number(mix));
505
506        Ok(())
507    }
508}
509
510/// Phaser function: phase shifting effect
511pub struct PhaserFunction;
512
513impl FunctionExecutor for PhaserFunction {
514    fn name(&self) -> &str {
515        "phaser"
516    }
517
518    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
519        // Default params
520        let mut stages = 4.0; // Number of allpass stages (2-12)
521        let mut rate = 0.5; // LFO rate in Hz
522        let mut depth = 0.7; // Modulation depth
523        let mut feedback = 0.5; // Feedback amount
524        let mut mix = 0.5; // Dry/wet mix
525
526        // Parse params from map
527        if let Some(Value::Map(params)) = args.first() {
528            if let Some(Value::Number(s)) = params.get("stages") {
529                stages = *s;
530            }
531            if let Some(Value::Number(r)) = params.get("rate") {
532                rate = *r;
533            }
534            if let Some(Value::Number(d)) = params.get("depth") {
535                depth = *d;
536            }
537            if let Some(Value::Number(f)) = params.get("feedback") {
538                feedback = *f;
539            }
540            if let Some(Value::Number(m)) = params.get("mix") {
541                mix = *m;
542            }
543        }
544
545        context.set("phaser_stages", Value::Number(stages));
546        context.set("phaser_rate", Value::Number(rate));
547        context.set("phaser_depth", Value::Number(depth));
548        context.set("phaser_feedback", Value::Number(feedback));
549        context.set("phaser_mix", Value::Number(mix));
550
551        Ok(())
552    }
553}
554
555/// Compressor function: dynamic range compression
556pub struct CompressorFunction;
557
558impl FunctionExecutor for CompressorFunction {
559    fn name(&self) -> &str {
560        "compressor"
561    }
562
563    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
564        // Default params
565        let mut threshold = -20.0; // Threshold in dB
566        let mut ratio = 4.0; // Compression ratio (1:1 to 20:1)
567        let mut attack = 5.0; // Attack time in ms
568        let mut release = 50.0; // Release time in ms
569        let mut makeup = 0.0; // Makeup gain in dB
570
571        // Parse params from map
572        if let Some(Value::Map(params)) = args.first() {
573            if let Some(Value::Number(t)) = params.get("threshold") {
574                threshold = *t;
575            }
576            if let Some(Value::Number(r)) = params.get("ratio") {
577                ratio = *r;
578            }
579            if let Some(Value::Number(a)) = params.get("attack") {
580                attack = *a;
581            }
582            if let Some(Value::Number(r)) = params.get("release") {
583                release = *r;
584            }
585            if let Some(Value::Number(m)) = params.get("makeup") {
586                makeup = *m;
587            }
588        }
589
590        context.set("compressor_threshold", Value::Number(threshold));
591        context.set("compressor_ratio", Value::Number(ratio));
592        context.set("compressor_attack", Value::Number(attack));
593        context.set("compressor_release", Value::Number(release));
594        context.set("compressor_makeup", Value::Number(makeup));
595
596        Ok(())
597    }
598}
599
600/// Distortion function: enhanced distortion/saturation
601pub struct DistortionFunction;
602
603impl FunctionExecutor for DistortionFunction {
604    fn name(&self) -> &str {
605        "distortion"
606    }
607
608    fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
609        // Default params
610        let mut amount = 0.5; // Distortion amount 0-1
611        let mut tone = 0.5; // Tone control (brightness)
612        let mut mix = 1.0; // Dry/wet mix
613
614        // Parse params from map
615        if let Some(Value::Map(params)) = args.first() {
616            if let Some(Value::Number(a)) = params.get("amount") {
617                amount = *a;
618            }
619            if let Some(Value::Number(t)) = params.get("tone") {
620                tone = *t;
621            }
622            if let Some(Value::Number(m)) = params.get("mix") {
623                mix = *m;
624            }
625        }
626
627        context.set("distortion_amount", Value::Number(amount));
628        context.set("distortion_tone", Value::Number(tone));
629        context.set("distortion_mix", Value::Number(mix));
630
631        Ok(())
632    }
633}