1use anyhow::Result;
3use std::collections::HashMap;
4
5use crate::language::syntax::ast::{Statement, StatementKind, Value};
6
7use super::AudioInterpreter;
8
9pub fn handle_let(interpreter: &mut AudioInterpreter, name: &str, value: &Value) -> Result<()> {
10 if let Value::Map(orig_map) = value {
12 let mut map = orig_map.clone();
14
15 if let Some(synth_val) = map.get("synth_type").cloned() {
18 let should_replace = match map.get("type") {
19 Some(Value::String(s)) => {
20 let clean = s.trim_matches('"').trim_matches('\'');
21 clean == "synth" || clean.is_empty()
22 }
23 Some(Value::Identifier(id)) => {
24 let clean = id.trim_matches('"').trim_matches('\'');
25 clean == "synth" || clean.is_empty()
26 }
27 None => true,
28 _ => false,
29 };
30 if should_replace {
31 map.insert("type".to_string(), synth_val.clone());
32 map.remove("synth_type");
33 } else {
34 }
36 }
37
38 let chain_keys = ["params", "adsr", "envelope"];
40 for key in &chain_keys {
41 if let Some(Value::Map(submap)) = map.get(*key) {
42 let to_insert: Vec<(String, Value)> =
44 submap.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
45 for (k, v) in to_insert {
46 map.insert(k, v);
47 }
48 map.remove(*key);
49 }
50 }
51
52 map.entry("volume".to_string())
56 .or_insert(Value::Number(1.0));
57 map.entry("gain".to_string()).or_insert(Value::Number(1.0));
58 map.entry("pan".to_string()).or_insert(Value::Number(0.0));
59 map.entry("detune".to_string())
60 .or_insert(Value::Number(0.0));
61 map.entry("type".to_string())
63 .or_insert(Value::String("synth".to_string()));
64
65 crate::utils::props::ensure_default_properties(&mut map, Some("synth"));
67
68 if map.contains_key("waveform") || map.contains_key("_plugin_ref") {
69 let mut is_plugin = false;
71 let mut plugin_author: Option<String> = None;
72 let mut plugin_name: Option<String> = None;
73 let mut plugin_export: Option<String> = None;
74
75 if let Some(Value::String(plugin_ref)) = map.get("_plugin_ref") {
76 let parts: Vec<&str> = plugin_ref.split('.').collect();
77 if parts.len() == 2 {
78 let (var_name, prop_name) = (parts[0], parts[1]);
79 if let Some(var_value) = interpreter.variables.get(var_name) {
80 if let Value::Map(var_map) = var_value {
81 if let Some(Value::String(resolved_plugin)) = var_map.get(prop_name) {
82 if resolved_plugin.starts_with("plugin:") {
83 let ref_parts: Vec<&str> =
84 resolved_plugin["plugin:".len()..].split(':').collect();
85 if ref_parts.len() == 2 {
86 let full_plugin_name = ref_parts[0];
87 let export_name = ref_parts[1];
88 let plugin_parts: Vec<&str> =
89 full_plugin_name.split('.').collect();
90 if plugin_parts.len() == 2 {
91 plugin_author = Some(plugin_parts[0].to_string());
92 plugin_name = Some(plugin_parts[1].to_string());
93 plugin_export = Some(export_name.to_string());
94 is_plugin = true;
95 }
96 }
97 }
98 } else if let Some(Value::Map(export_map)) = var_map.get(prop_name) {
99 if let (
100 Some(Value::String(author)),
101 Some(Value::String(name)),
102 Some(Value::String(export)),
103 ) = (
104 export_map.get("_plugin_author"),
105 export_map.get("_plugin_name"),
106 export_map.get("_export_name"),
107 ) {
108 plugin_author = Some(author.clone());
109 plugin_name = Some(name.clone());
110 plugin_export = Some(export.clone());
111 is_plugin = true;
112 }
113 }
114 }
115 }
116 }
117 }
118
119 let waveform = crate::engine::audio::events::extract_string(&map, "waveform", "sine");
120 let attack = crate::engine::audio::events::extract_number(&map, "attack", 0.01);
121 let decay = crate::engine::audio::events::extract_number(&map, "decay", 0.1);
122 let sustain = crate::engine::audio::events::extract_number(&map, "sustain", 0.7);
123 let release = crate::engine::audio::events::extract_number(&map, "release", 0.2);
124
125 let synth_type = if let Some(v) = map.get("type") {
127 match v {
128 Value::String(t) => {
129 let clean = t.trim_matches('"').trim_matches('\'');
130 if clean.is_empty() || clean == "synth" {
131 None
132 } else {
133 Some(clean.to_string())
134 }
135 }
136 Value::Identifier(id) => {
137 let clean = id.trim_matches('"').trim_matches('\'');
138 if clean.is_empty() || clean == "synth" {
139 None
140 } else {
141 Some(clean.to_string())
142 }
143 }
144 _ => None,
145 }
146 } else {
147 None
148 };
149
150 let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
151 crate::engine::audio::events::extract_filters(filters_arr)
152 } else {
153 Vec::new()
154 };
155
156 let lfo = if let Some(Value::Map(lfo_map)) = map.get("lfo") {
158 use crate::engine::audio::lfo::{LfoParams, LfoRate, LfoTarget, LfoWaveform};
159
160 let rate_str = if let Some(Value::Number(n)) = lfo_map.get("rate") {
162 n.to_string()
163 } else if let Some(Value::String(s)) = lfo_map.get("rate") {
164 s.clone()
165 } else {
166 "5.0".to_string() };
168 let rate = LfoRate::from_value(&rate_str);
169
170 let depth = if let Some(Value::Number(n)) = lfo_map.get("depth") {
172 (*n).clamp(0.0, 1.0)
173 } else {
174 0.5 };
176
177 let waveform_str = if let Some(Value::String(s)) = lfo_map.get("shape") {
179 s.clone()
180 } else if let Some(Value::String(s)) = lfo_map.get("waveform") {
181 s.clone()
182 } else {
183 "sine".to_string() };
185 let waveform = LfoWaveform::from_str(&waveform_str);
186
187 let target = if let Some(Value::String(s)) = lfo_map.get("target") {
189 LfoTarget::from_str(s).unwrap_or(LfoTarget::Volume)
190 } else {
191 LfoTarget::Volume };
193
194 let phase = if let Some(Value::Number(n)) = lfo_map.get("phase") {
196 (*n).fract().abs() } else {
198 0.0 };
200
201 Some(LfoParams {
202 rate,
203 depth,
204 waveform,
205 target,
206 phase,
207 })
208 } else {
209 None
210 };
211
212 let mut options = std::collections::HashMap::new();
213 let reserved_keys = if is_plugin {
214 vec![
215 "attack",
216 "decay",
217 "sustain",
218 "release",
219 "type",
220 "filters",
221 "_plugin_ref",
222 "lfo",
223 ]
224 } else {
225 vec![
226 "waveform",
227 "attack",
228 "decay",
229 "sustain",
230 "release",
231 "type",
232 "filters",
233 "_plugin_ref",
234 "lfo",
235 ]
236 };
237
238 for (key, val) in map.iter() {
239 if !reserved_keys.contains(&key.as_str()) {
240 match val {
241 Value::Number(n) => {
242 options.insert(key.clone(), *n);
243 }
244 Value::String(s) => {
245 if is_plugin && key == "waveform" {
246 let waveform_id = match s
247 .trim_matches('"')
248 .trim_matches('\'')
249 .to_lowercase()
250 .as_str()
251 {
252 "sine" => 0.0,
253 "saw" => 1.0,
254 "square" => 2.0,
255 "triangle" => 3.0,
256 _ => 1.0,
257 };
258 options.insert(key.clone(), waveform_id);
259 }
260 }
261 _ => {}
262 }
263 }
264 }
265
266 if is_plugin && map.contains_key("decay") {
267 options.insert("decay".to_string(), decay);
268 }
269
270 let final_waveform = if is_plugin {
271 "plugin".to_string()
272 } else {
273 waveform
274 };
275
276 let synth_def = crate::engine::audio::events::SynthDefinition {
277 waveform: final_waveform,
278 attack,
279 decay,
280 sustain,
281 release,
282 synth_type,
283 filters,
284 options,
285 plugin_author,
286 plugin_name,
287 plugin_export,
288 lfo,
289 };
290
291 interpreter.events.add_synth(name.to_string(), synth_def);
292 }
293
294 interpreter
297 .variables
298 .insert(name.to_string(), Value::Map(map.clone()));
299 return Ok(());
300 }
301
302 if let Value::Statement(stmt_box) = value {
307 if let StatementKind::Trigger {
308 entity,
309 duration,
310 effects,
311 } = &stmt_box.kind
312 {
313 let mut map = HashMap::new();
314 map.insert("kind".to_string(), Value::String("Trigger".to_string()));
315 map.insert("entity".to_string(), Value::String(entity.clone()));
316 map.insert("duration".to_string(), Value::Duration(duration.clone()));
317 if let Some(eff) = effects {
318 map.insert("effects".to_string(), eff.clone());
319 }
320
321 crate::utils::props::ensure_default_properties(&mut map, Some("trigger"));
323
324 interpreter
325 .variables
326 .insert(name.to_string(), Value::Map(map));
327 return Ok(());
328 }
329 }
330
331 interpreter
332 .variables
333 .insert(name.to_string(), value.clone());
334 Ok(())
335}
336
337pub fn handle_call(interpreter: &mut AudioInterpreter, name: &str, args: &[Value]) -> Result<()> {
338 if let Some(var_val) = interpreter.variables.get(name).cloned() {
348 if let Value::Statement(stmt_box) = var_val {
349 if let StatementKind::Function {
350 name: _fname,
351 parameters,
352 body,
353 } = &stmt_box.kind
354 {
355 let vars_snapshot = interpreter.variables.clone();
357
358 for (i, param) in parameters.iter().enumerate() {
360 let bound = args.get(i).cloned().unwrap_or(Value::Null);
361 let bound_val = match bound {
363 Value::Identifier(ref id) => {
364 interpreter.resolve_value(&Value::Identifier(id.clone()))?
365 }
366 other => other,
367 };
368 interpreter.variables.insert(param.clone(), bound_val);
369 }
370
371 interpreter.function_call_depth += 1;
374 let exec_result = super::collector::collect_events(interpreter, body);
375 interpreter.function_call_depth = interpreter.function_call_depth.saturating_sub(1);
377 exec_result?;
378
379 let mut captured_return: Option<Value> = None;
381 if interpreter.returning_flag {
382 captured_return = interpreter.return_value.clone();
383 interpreter.returning_flag = false;
385 interpreter.return_value = None;
386 }
387
388 interpreter.variables = vars_snapshot;
391
392 if let Some(rv) = captured_return {
396 interpreter.variables.insert("__return".to_string(), rv);
397 }
398
399 return Ok(());
400 }
401 if let StatementKind::Pattern { target, .. } = &stmt_box.kind {
403 if let Some(tgt) = target.as_ref() {
404 let (pattern_str, options) = interpreter.extract_pattern_data(&stmt_box.value);
405 if let Some(pat) = pattern_str {
406 interpreter.execute_pattern(tgt.as_str(), &pat, options)?;
407 return Ok(());
408 }
409 }
410 }
411 }
412 }
413
414 if let Some(body) = interpreter.groups.get(name).cloned() {
416 super::collector::collect_events(interpreter, &body)?;
417 return Ok(());
418 }
419
420 println!(
421 "⚠️ Warning: Group, pattern or function '{}' not found",
422 name
423 );
424 Ok(())
425}
426
427pub fn call_function(
431 interpreter: &mut AudioInterpreter,
432 name: &str,
433 args: &[Value],
434) -> Result<Value> {
435 if let Some(var_val) = interpreter.variables.get(name).cloned() {
437 if let Value::Statement(stmt_box) = var_val {
438 if let StatementKind::Function {
439 name: _fname,
440 parameters,
441 body,
442 } = &stmt_box.kind
443 {
444 let vars_snapshot = interpreter.variables.clone();
446
447 for (i, param) in parameters.iter().enumerate() {
449 let bound = args.get(i).cloned().unwrap_or(Value::Null);
450 let bound_val = match bound {
452 Value::Identifier(ref id) => {
453 interpreter.resolve_value(&Value::Identifier(id.clone()))?
454 }
455 other => other,
456 };
457 interpreter.variables.insert(param.clone(), bound_val);
458 }
459
460 interpreter.function_call_depth += 1;
462
463 let exec_result = super::collector::collect_events(interpreter, body);
464 interpreter.function_call_depth = interpreter.function_call_depth.saturating_sub(1);
465 exec_result?;
466
467 let mut captured_return: Option<Value> = None;
469 if interpreter.returning_flag {
470 captured_return = interpreter.return_value.clone();
471 interpreter.returning_flag = false;
472 interpreter.return_value = None;
473 }
474
475 interpreter.variables = vars_snapshot;
477
478 if let Some(rv) = captured_return {
479 return Ok(rv);
480 }
481
482 return Ok(Value::Null);
483 }
484 if let StatementKind::Pattern { target, .. } = &stmt_box.kind {
486 if let Some(tgt) = target.as_ref() {
487 let (pattern_str, options) = interpreter.extract_pattern_data(&stmt_box.value);
488 if let Some(pat) = pattern_str {
489 interpreter.execute_pattern(tgt.as_str(), &pat, options)?;
490 return Ok(Value::Null);
491 }
492 }
493 }
494 }
495 }
496
497 if let Some(body) = interpreter.groups.get(name).cloned() {
499 super::collector::collect_events(interpreter, &body)?;
500 return Ok(Value::Null);
501 }
502
503 println!(
504 "⚠️ Warning: Group, pattern or function '{}' not found",
505 name
506 );
507 Ok(Value::Null)
508}
509
510pub fn execute_print(interpreter: &mut AudioInterpreter, value: &Value) -> Result<()> {
511 let message = match value {
512 Value::Call { name: _, args: _ } => {
513 let resolved = interpreter.resolve_value(value)?;
515 interpreter.value_to_string(&resolved)
516 }
517
518 Value::String(s) => {
519 if s.contains('{') && s.contains('}') {
520 interpreter.interpolate_string(s)
521 } else {
522 s.clone()
523 }
524 }
525 Value::Identifier(id) => {
526 if id.ends_with("++") {
529 let varname = id[..id.len() - 2].trim();
530 let cur = match interpreter.variables.get(varname) {
532 Some(Value::Number(n)) => *n as isize,
533 _ => 0,
534 };
535 interpreter
537 .variables
538 .insert(varname.to_string(), Value::Number((cur + 1) as f32));
539 cur.to_string()
540 } else {
541 let resolved = interpreter.resolve_value(&Value::Identifier(id.clone()))?;
544 match resolved {
545 Value::String(s) => s.clone(),
546 Value::Number(n) => n.to_string(),
547 Value::Boolean(b) => b.to_string(),
548 Value::Array(arr) => format!("{:?}", arr),
549 Value::Map(map) => format!("{:?}", map),
550 Value::Null => format!("Identifier(\"{}\")", id),
551 other => format!("{:?}", other),
552 }
553 }
554 }
555 Value::Number(n) => n.to_string(),
556 Value::Boolean(b) => b.to_string(),
557 Value::Array(arr) => {
558 let mut parts = Vec::new();
560 for v in arr.iter() {
561 if let Value::Identifier(idtok) = v {
563 if idtok.ends_with("++") {
564 let varname = idtok[..idtok.len() - 2].trim();
565 let cur = match interpreter.variables.get(varname) {
566 Some(Value::Number(n)) => *n as isize,
567 _ => 0,
568 };
569 interpreter
570 .variables
571 .insert(varname.to_string(), Value::Number((cur + 1) as f32));
572 parts.push(cur.to_string());
573 continue;
574 }
575 }
576 let resolved = interpreter.resolve_value(v)?;
577 parts.push(interpreter.value_to_string(&resolved));
578 }
579 parts.join("")
580 }
581 Value::Map(map) => format!("{:?}", map),
582 _ => format!("{:?}", value),
583 };
584 let log_message = message.clone();
587 interpreter
589 .events
590 .add_log_event(log_message.clone(), interpreter.cursor_time);
591 if !interpreter.suppress_print {
596 #[cfg(feature = "cli")]
600 {
601 crate::tools::logger::Logger::new().print(message.clone());
602 }
603
604 #[cfg(not(feature = "cli"))]
607 {
608 if let Some(tx) = &interpreter.realtime_print_tx {
609 let _ = tx.send((interpreter.cursor_time, log_message.clone()));
610 }
611 }
612 } else {
613 if let Some(tx) = &interpreter.realtime_print_tx {
618 let _ = tx.send((interpreter.cursor_time, log_message.clone()));
620 }
621 }
622 Ok(())
623}
624
625pub fn execute_if(
626 interpreter: &mut AudioInterpreter,
627 condition: &Value,
628 body: &[Statement],
629 else_body: &Option<Vec<Statement>>,
630) -> Result<()> {
631 let condition_result = interpreter.evaluate_condition(condition)?;
632
633 if condition_result {
634 super::collector::collect_events(interpreter, body)?;
635 } else if let Some(else_stmts) = else_body {
636 super::collector::collect_events(interpreter, else_stmts)?;
637 }
638
639 Ok(())
640}
641
642pub fn execute_event_handlers(interpreter: &mut AudioInterpreter, event_name: &str) -> Result<()> {
643 let handlers = interpreter.event_registry.get_handlers_matching(event_name);
644
645 for (index, handler) in handlers.iter().enumerate() {
646 if let Some(args) = &handler.args {
648 if let Some(num_val) = args.iter().find(|v| matches!(v, Value::Number(_))) {
649 if let Value::Number(n) = num_val {
650 let interval = (*n as usize).max(1);
651 if event_name == "beat" {
652 let cur = interpreter.special_vars.current_beat.floor() as usize;
653 if cur % interval != 0 {
654 continue;
655 }
656 } else if event_name == "bar" {
657 let cur = interpreter.special_vars.current_bar.floor() as usize;
658 if cur % interval != 0 {
659 continue;
660 }
661 }
662 }
663 }
664 }
665
666 if handler.once
667 && !interpreter
668 .event_registry
669 .should_execute_once(event_name, index)
670 {
671 continue;
672 }
673
674 let body_clone = handler.body.clone();
675 super::collector::collect_events(interpreter, &body_clone)?;
676 }
677
678 Ok(())
679}
680
681pub fn handle_assign(
682 interpreter: &mut AudioInterpreter,
683 target: &str,
684 property: &str,
685 value: &Value,
686) -> Result<()> {
687 if target.contains('.') {
690 let parts: Vec<&str> = target.split('.').collect();
691 let root = parts[0];
692 if let Some(root_val) = interpreter.variables.get_mut(root) {
693 let mut current = root_val;
695 for seg in parts.iter().skip(1) {
696 match current {
697 Value::Map(map) => {
698 if !map.contains_key(*seg) {
699 map.insert((*seg).to_string(), Value::Map(HashMap::new()));
701 }
702 current = map.get_mut(*seg).unwrap();
703 }
704 _ => {
705 return Err(anyhow::anyhow!(
706 "Cannot traverse into non-map segment '{}' when assigning to '{}'",
707 seg,
708 target
709 ));
710 }
711 }
712 }
713
714 if let Value::Map(map) = current {
716 map.insert(property.to_string(), value.clone());
717
718 if interpreter.events.synths.contains_key(root) {
720 if let Some(Value::Map(root_map)) = interpreter.variables.get(root) {
721 let map_clone = root_map.clone();
722 let updated_def = interpreter.extract_synth_def_from_map(&map_clone)?;
723 interpreter
724 .events
725 .synths
726 .insert(root.to_string(), updated_def);
727 }
728 }
729 } else {
730 return Err(anyhow::anyhow!(
731 "Cannot assign property '{}' to non-map target '{}'",
732 property,
733 target
734 ));
735 }
736 } else {
737 return Err(anyhow::anyhow!("Variable '{}' not found", root));
738 }
739 } else {
740 if let Some(var) = interpreter.variables.get_mut(target) {
741 if let Value::Map(map) = var {
742 map.insert(property.to_string(), value.clone());
743
744 if interpreter.events.synths.contains_key(target) {
745 let map_clone = map.clone();
746 let updated_def = interpreter.extract_synth_def_from_map(&map_clone)?;
747 interpreter
748 .events
749 .synths
750 .insert(target.to_string(), updated_def);
751 }
752 } else {
753 return Err(anyhow::anyhow!(
754 "Cannot assign property '{}' to non-map variable '{}'",
755 property,
756 target
757 ));
758 }
759 } else {
760 return Err(anyhow::anyhow!("Variable '{}' not found", target));
761 }
762 }
763
764 Ok(())
765}
766
767pub fn extract_synth_def_from_map(
768 _interpreter: &AudioInterpreter,
769 map: &HashMap<String, Value>,
770) -> Result<crate::engine::audio::events::SynthDefinition> {
771 use crate::engine::audio::events::extract_filters;
772 use crate::engine::audio::lfo::{LfoParams, LfoRate, LfoTarget, LfoWaveform};
773
774 let waveform = crate::engine::audio::events::extract_string(map, "waveform", "sine");
775 let attack = crate::engine::audio::events::extract_number(map, "attack", 0.01);
776 let decay = crate::engine::audio::events::extract_number(map, "decay", 0.1);
777 let sustain = crate::engine::audio::events::extract_number(map, "sustain", 0.7);
778 let release = crate::engine::audio::events::extract_number(map, "release", 0.2);
779
780 let synth_type = if let Some(v) = map.get("type") {
782 match v {
783 Value::String(t) => {
784 let clean = t.trim_matches('"').trim_matches('\'');
785 if clean.is_empty() || clean == "synth" {
786 None
787 } else {
788 Some(clean.to_string())
789 }
790 }
791 Value::Identifier(id) => {
792 let clean = id.trim_matches('"').trim_matches('\'');
793 if clean.is_empty() || clean == "synth" {
794 None
795 } else {
796 Some(clean.to_string())
797 }
798 }
799 _ => None,
800 }
801 } else if let Some(v2) = map.get("synth_type") {
802 match v2 {
803 Value::String(t2) => {
804 let clean = t2.trim_matches('"').trim_matches('\'');
805 if clean.is_empty() || clean == "synth" {
806 None
807 } else {
808 Some(clean.to_string())
809 }
810 }
811 Value::Identifier(id2) => {
812 let clean = id2.trim_matches('"').trim_matches('\'');
813 if clean.is_empty() || clean == "synth" {
814 None
815 } else {
816 Some(clean.to_string())
817 }
818 }
819 _ => None,
820 }
821 } else {
822 None
823 };
824
825 let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
826 extract_filters(filters_arr)
827 } else {
828 Vec::new()
829 };
830
831 let plugin_author = if let Some(Value::String(s)) = map.get("plugin_author") {
832 Some(s.clone())
833 } else {
834 None
835 };
836 let plugin_name = if let Some(Value::String(s)) = map.get("plugin_name") {
837 Some(s.clone())
838 } else {
839 None
840 };
841 let plugin_export = if let Some(Value::String(s)) = map.get("plugin_export") {
842 Some(s.clone())
843 } else {
844 None
845 };
846
847 let lfo = if let Some(Value::Map(lfo_map)) = map.get("lfo") {
849 let rate_str = if let Some(Value::Number(n)) = lfo_map.get("rate") {
851 n.to_string()
852 } else if let Some(Value::String(s)) = lfo_map.get("rate") {
853 s.clone()
854 } else {
855 "5.0".to_string() };
857 let rate = LfoRate::from_value(&rate_str);
858
859 let depth = if let Some(Value::Number(n)) = lfo_map.get("depth") {
861 (*n).clamp(0.0, 1.0)
862 } else {
863 0.5 };
865
866 let waveform_str = if let Some(Value::String(s)) = lfo_map.get("shape") {
868 s.clone()
869 } else if let Some(Value::String(s)) = lfo_map.get("waveform") {
870 s.clone()
871 } else {
872 "sine".to_string() };
874 let waveform = LfoWaveform::from_str(&waveform_str);
875
876 let target = if let Some(Value::String(s)) = lfo_map.get("target") {
878 LfoTarget::from_str(s).unwrap_or(LfoTarget::Volume)
879 } else {
880 LfoTarget::Volume };
882
883 let phase = if let Some(Value::Number(n)) = lfo_map.get("phase") {
885 (*n).fract().abs() } else {
887 0.0 };
889
890 Some(LfoParams {
891 rate,
892 depth,
893 waveform,
894 target,
895 phase,
896 })
897 } else {
898 None
899 };
900
901 let mut options = HashMap::new();
902 for (key, val) in map.iter() {
903 if ![
904 "waveform",
905 "attack",
906 "decay",
907 "sustain",
908 "release",
909 "type",
910 "filters",
911 "plugin_author",
912 "plugin_name",
913 "plugin_export",
914 "lfo",
915 ]
916 .contains(&key.as_str())
917 {
918 if let Value::Number(n) = val {
919 options.insert(key.clone(), *n);
920 } else if let Value::String(s) = val {
921 if key == "waveform" || key.starts_with("_") {
922 continue;
923 }
924 if let Ok(n) = s.parse::<f32>() {
925 options.insert(key.clone(), n);
926 }
927 }
928 }
929 }
930
931 Ok(crate::engine::audio::events::SynthDefinition {
932 waveform,
933 attack,
934 decay,
935 sustain,
936 release,
937 synth_type,
938 filters,
939 options,
940 plugin_author,
941 plugin_name,
942 plugin_export,
943 lfo,
944 })
945}
946
947pub fn handle_load(interpreter: &mut AudioInterpreter, source: &str, alias: &str) -> Result<()> {
948 use std::path::Path;
949
950 let path = Path::new(source);
951 if let Some(ext) = path
953 .extension()
954 .and_then(|s| s.to_str())
955 .map(|s| s.to_lowercase())
956 {
957 match ext.as_str() {
958 "mid" | "midi" => {
959 use crate::engine::audio::midi::load_midi_file;
960 let midi_data = load_midi_file(path)?;
961 interpreter.variables.insert(alias.to_string(), midi_data);
962 Ok(())
964 }
965 "wav" | "flac" | "mp3" | "ogg" => {
966 #[cfg(feature = "cli")]
968 {
969 use crate::engine::audio::samples;
970 let registered = samples::register_sample_from_path(path)?;
971 interpreter
973 .variables
974 .insert(alias.to_string(), Value::String(registered.clone()));
975 return Ok(());
977 }
978
979 #[cfg(not(feature = "cli"))]
981 {
982 interpreter
983 .variables
984 .insert(alias.to_string(), Value::String(source.to_string()));
985 return Ok(());
986 }
987 }
988 _ => Err(anyhow::anyhow!("Unsupported file type for @load: {}", ext)),
989 }
990 } else {
991 Err(anyhow::anyhow!(
992 "Cannot determine file extension for {}",
993 source
994 ))
995 }
996}
997
998pub fn handle_bind(
999 interpreter: &mut AudioInterpreter,
1000 source: &str,
1001 target: &str,
1002 options: &Value,
1003) -> Result<()> {
1004 use std::collections::HashMap as StdHashMap;
1005
1006 if source.starts_with("mapping.") || target.starts_with("mapping.") {
1013 let opts_map: StdHashMap<String, Value> = if let Value::Map(m) = options {
1015 m.clone()
1016 } else {
1017 StdHashMap::new()
1018 };
1019
1020 fn create_and_insert(
1022 path: &str,
1023 opts_map: &StdHashMap<String, Value>,
1024 interpreter: &mut AudioInterpreter,
1025 ) -> Option<(String, String)> {
1026 let parts: Vec<&str> = path.split('.').collect();
1027 if parts.len() >= 3 {
1028 let direction = parts[1]; let device = parts[2];
1030
1031 let mut map = StdHashMap::new();
1032 map.insert(
1033 "_type".to_string(),
1034 Value::String("midi_mapping".to_string()),
1035 );
1036 map.insert(
1037 "direction".to_string(),
1038 Value::String(direction.to_string()),
1039 );
1040 map.insert("device".to_string(), Value::String(device.to_string()));
1041
1042 for (k, v) in opts_map.iter() {
1044 map.insert(k.clone(), v.clone());
1045 }
1046
1047 crate::utils::props::ensure_default_properties(&mut map, Some("mapping"));
1049
1050 interpreter
1051 .variables
1052 .insert(path.to_string(), Value::Map(map.clone()));
1053
1054 let note_on = format!("mapping.{}.{}.noteOn", direction, device);
1056 let note_off = format!("mapping.{}.{}.noteOff", direction, device);
1057 let rest = format!("mapping.{}.{}.rest", direction, device);
1058 interpreter
1059 .variables
1060 .insert(note_on.clone(), Value::String(note_on.clone()));
1061 interpreter
1062 .variables
1063 .insert(note_off.clone(), Value::String(note_off.clone()));
1064 interpreter
1065 .variables
1066 .insert(rest.clone(), Value::String(rest.clone()));
1067
1068 return Some((direction.to_string(), device.to_string()));
1069 }
1070 None
1071 }
1072
1073 if source.starts_with("mapping.") {
1075 if let Some((direction, device)) = create_and_insert(source, &opts_map, interpreter) {
1076 if !target.starts_with("mapping.") {
1078 let mut bmap = StdHashMap::new();
1079 bmap.insert("instrument".to_string(), Value::String(target.to_string()));
1080 bmap.insert("direction".to_string(), Value::String(direction.clone()));
1081 bmap.insert("device".to_string(), Value::String(device.clone()));
1082 for (k, v) in opts_map.iter() {
1083 bmap.insert(k.clone(), v.clone());
1084 }
1085 interpreter
1086 .variables
1087 .insert(format!("__mapping_bind::{}", source), Value::Map(bmap));
1088
1089 #[cfg(feature = "cli")]
1091 if let Some(manager) = &mut interpreter.midi_manager {
1092 if let Some(Value::Number(port_num)) = opts_map.get("port") {
1093 let idx = *port_num as usize;
1094 if let Ok(mut mgr) = manager.lock() {
1095 let _ = mgr.open_input_by_index(idx, &device);
1097 }
1098 }
1099 }
1100 }
1101 }
1102 }
1103
1104 if target.starts_with("mapping.") {
1106 if let Some((direction, device)) = create_and_insert(target, &opts_map, interpreter) {
1107 if !source.starts_with("mapping.") {
1108 let mut bmap = StdHashMap::new();
1109 bmap.insert("source".to_string(), Value::String(source.to_string()));
1110 bmap.insert("direction".to_string(), Value::String(direction.clone()));
1111 bmap.insert("device".to_string(), Value::String(device.clone()));
1112 for (k, v) in opts_map.iter() {
1113 bmap.insert(k.clone(), v.clone());
1114 }
1115 interpreter
1116 .variables
1117 .insert(format!("__mapping_bind::{}", target), Value::Map(bmap));
1118
1119 #[cfg(feature = "cli")]
1121 if let Some(manager) = &mut interpreter.midi_manager {
1122 if let Some(Value::Number(port_num)) = opts_map.get("port") {
1123 let idx = *port_num as usize;
1124 if let Ok(mut mgr) = manager.lock() {
1125 let _ = mgr.open_output_by_name(&device, idx);
1126 }
1127 }
1128 }
1129 }
1130 }
1131 }
1132
1133 return Ok(());
1138 }
1139
1140 let midi_data = interpreter
1142 .variables
1143 .get(source)
1144 .ok_or_else(|| anyhow::anyhow!("MIDI source '{}' not found", source))?
1145 .clone();
1146
1147 if let Value::Map(midi_map) = &midi_data {
1148 let notes = midi_map
1149 .get("notes")
1150 .ok_or_else(|| anyhow::anyhow!("MIDI data has no notes"))?;
1151
1152 if let Value::Array(notes_array) = notes {
1153 let _synth_def = interpreter
1154 .events
1155 .synths
1156 .get(target)
1157 .ok_or_else(|| anyhow::anyhow!("Synth '{}' not found", target))?
1158 .clone();
1159
1160 let default_velocity = 100;
1161 let mut velocity = default_velocity;
1162
1163 if let Value::Map(opts) = options {
1164 if let Some(Value::Number(v)) = opts.get("velocity") {
1165 velocity = *v as u8;
1166 }
1167 }
1168
1169 let midi_bpm =
1172 crate::engine::audio::events::extract_number(midi_map, "bpm", interpreter.bpm);
1173
1174 for note_val in notes_array {
1175 if let Value::Map(note_map) = note_val {
1176 let time = crate::engine::audio::events::extract_number(note_map, "time", 0.0);
1177 let note =
1178 crate::engine::audio::events::extract_number(note_map, "note", 60.0) as u8;
1179 let note_velocity = crate::engine::audio::events::extract_number(
1180 note_map,
1181 "velocity",
1182 velocity as f32,
1183 ) as u8;
1184 let duration_ms =
1186 crate::engine::audio::events::extract_number(note_map, "duration", 500.0);
1187
1188 use crate::engine::audio::events::AudioEvent;
1189 let synth_def = interpreter
1190 .events
1191 .get_synth(target)
1192 .cloned()
1193 .unwrap_or_default();
1194 let interp_bpm = interpreter.bpm;
1197 let factor = if interp_bpm > 0.0 {
1198 midi_bpm / interp_bpm
1199 } else {
1200 1.0
1201 };
1202
1203 let start_time_s = (time / 1000.0) * factor;
1204 let duration_s = (duration_ms / 1000.0) * factor;
1205
1206 let event = AudioEvent::Note {
1207 midi: note,
1208 start_time: start_time_s,
1209 duration: duration_s,
1210 velocity: note_velocity as f32,
1211 synth_id: target.to_string(),
1212 synth_def,
1213 pan: 0.0,
1214 detune: 0.0,
1215 gain: 1.0,
1216 attack: None,
1217 release: None,
1218 delay_time: None,
1219 delay_feedback: None,
1220 delay_mix: None,
1221 reverb_amount: None,
1222 drive_amount: None,
1223 drive_color: None,
1224 effects: None,
1225 use_per_note_automation: false,
1226 };
1227
1228 interpreter.events.events.push(event);
1230 }
1231 }
1232
1233 }
1235 }
1236
1237 Ok(())
1238}
1239
1240#[cfg(feature = "cli")]
1241pub fn handle_use_plugin(
1242 interpreter: &mut AudioInterpreter,
1243 author: &str,
1244 name: &str,
1245 alias: &str,
1246) -> Result<()> {
1247 use crate::engine::plugin::loader::load_plugin;
1248
1249 match load_plugin(author, name) {
1250 Ok((info, _wasm_bytes)) => {
1251 let mut plugin_map = HashMap::new();
1252 plugin_map.insert("_type".to_string(), Value::String("plugin".to_string()));
1253 plugin_map.insert("_author".to_string(), Value::String(info.author.clone()));
1254 plugin_map.insert("_name".to_string(), Value::String(info.name.clone()));
1255
1256 if let Some(version) = &info.version {
1257 plugin_map.insert("_version".to_string(), Value::String(version.clone()));
1258 }
1259
1260 for export in &info.exports {
1261 let mut export_map = HashMap::new();
1262 export_map.insert(
1263 "_plugin_author".to_string(),
1264 Value::String(info.author.clone()),
1265 );
1266 export_map.insert("_plugin_name".to_string(), Value::String(info.name.clone()));
1267 export_map.insert(
1268 "_export_name".to_string(),
1269 Value::String(export.name.clone()),
1270 );
1271 export_map.insert(
1272 "_export_kind".to_string(),
1273 Value::String(export.kind.clone()),
1274 );
1275
1276 plugin_map.insert(export.name.clone(), Value::Map(export_map));
1277 }
1278 crate::utils::props::ensure_default_properties(&mut plugin_map, Some("plugin"));
1280
1281 interpreter
1282 .variables
1283 .insert(alias.to_string(), Value::Map(plugin_map));
1284 }
1285 Err(e) => {
1286 eprintln!("❌ Failed to load plugin {}.{}: {}", author, name, e);
1287 return Err(anyhow::anyhow!("Failed to load plugin: {}", e));
1288 }
1289 }
1290
1291 Ok(())
1292}
1293
1294#[cfg(not(feature = "cli"))]
1295pub fn handle_use_plugin(
1296 interpreter: &mut AudioInterpreter,
1297 author: &str,
1298 name: &str,
1299 alias: &str,
1300) -> Result<()> {
1301 let mut plugin_map = HashMap::new();
1303 plugin_map.insert(
1304 "_type".to_string(),
1305 Value::String("plugin_stub".to_string()),
1306 );
1307 plugin_map.insert("_author".to_string(), Value::String(author.to_string()));
1308 plugin_map.insert("_name".to_string(), Value::String(name.to_string()));
1309 crate::utils::props::ensure_default_properties(&mut plugin_map, Some("plugin"));
1311
1312 interpreter
1313 .variables
1314 .insert(alias.to_string(), Value::Map(plugin_map));
1315 Ok(())
1316}
1317
1318pub fn handle_bank(
1319 interpreter: &mut AudioInterpreter,
1320 name: &str,
1321 alias: &Option<String>,
1322) -> Result<()> {
1323 let target_alias = alias
1324 .clone()
1325 .unwrap_or_else(|| name.split('.').last().unwrap_or(name).to_string());
1326
1327 if let Some(existing_value) = interpreter.variables.get(name) {
1328 interpreter
1329 .variables
1330 .insert(target_alias.clone(), existing_value.clone());
1331 } else {
1332 #[cfg(feature = "wasm")]
1333 {
1334 use crate::web::registry::banks::REGISTERED_BANKS;
1335 REGISTERED_BANKS.with(|banks| {
1336 for bank in banks.borrow().iter() {
1337 if bank.full_name == *name {
1338 if let Some(Value::Map(bank_map)) = interpreter.variables.get(&bank.alias) {
1339 interpreter
1340 .variables
1341 .insert(target_alias.clone(), Value::Map(bank_map.clone()));
1342 }
1343 }
1344 }
1345 });
1346 }
1347
1348 #[cfg(not(feature = "wasm"))]
1349 {
1350 if let Ok(current_dir) = std::env::current_dir() {
1351 match interpreter.banks.register_bank(
1352 target_alias.clone(),
1353 &name,
1354 ¤t_dir,
1355 ¤t_dir,
1356 ) {
1357 Ok(_) => {
1358 let mut bank_map = HashMap::new();
1359 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
1360 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
1361 interpreter
1362 .variables
1363 .insert(target_alias.clone(), Value::Map(bank_map));
1364 }
1365 Err(e) => {
1366 eprintln!("⚠️ Failed to register bank '{}': {}", name, e);
1367 let mut bank_map = HashMap::new();
1368 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
1369 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
1370 interpreter
1371 .variables
1372 .insert(target_alias.clone(), Value::Map(bank_map));
1373 }
1374 }
1375 } else {
1376 let mut bank_map = HashMap::new();
1377 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
1378 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
1379 interpreter
1380 .variables
1381 .insert(target_alias.clone(), Value::Map(bank_map));
1382 eprintln!(
1383 "⚠️ Could not determine cwd to register bank '{}', registered minimal alias.",
1384 name
1385 );
1386 }
1387 }
1388 }
1389
1390 Ok(())
1391}
1392
1393pub fn handle_trigger(
1394 interpreter: &mut AudioInterpreter,
1395 entity: &str,
1396 effects: Option<&crate::language::syntax::ast::Value>,
1397) -> Result<()> {
1398 let resolved_entity = if entity.starts_with('.') {
1399 &entity[1..]
1400 } else {
1401 entity
1402 };
1403
1404 if let Some(var_val) = interpreter.variables.get(resolved_entity).cloned() {
1407 if let crate::language::syntax::ast::Value::Statement(stmt_box) = var_val {
1408 if let crate::language::syntax::ast::StatementKind::Trigger {
1409 entity: inner_entity,
1410 duration: _,
1411 effects: stored_effects,
1412 } = &stmt_box.kind
1413 {
1414 if inner_entity != resolved_entity {
1416 let chosen_effects = stored_effects.as_ref().or(effects);
1418 return handle_trigger(interpreter, inner_entity, chosen_effects);
1419 } else {
1420 return Ok(());
1421 }
1422 }
1423 }
1424 }
1425
1426 if resolved_entity.contains('.') {
1427 let parts: Vec<&str> = resolved_entity.split('.').collect();
1428 if parts.len() == 2 {
1429 let (var_name, property) = (parts[0], parts[1]);
1430
1431 if let Some(Value::Map(map)) = interpreter.variables.get(var_name) {
1432 if let Some(Value::String(sample_uri)) = map.get(property) {
1433 let uri = sample_uri.trim_matches('"').trim_matches('\'');
1434 interpreter.events.add_sample_event_with_effects(
1436 uri,
1437 interpreter.cursor_time,
1438 1.0,
1439 effects.cloned(),
1440 );
1441 let beat_duration = interpreter.beat_duration();
1442 interpreter.cursor_time += beat_duration;
1443 } else {
1444 #[cfg(not(feature = "wasm"))]
1445 {
1446 let resolved_uri = interpreter.resolve_sample_uri(resolved_entity);
1448 if resolved_uri != resolved_entity {
1449 interpreter.events.add_sample_event_with_effects(
1451 &resolved_uri,
1452 interpreter.cursor_time,
1453 1.0,
1454 effects.cloned(),
1455 );
1456 let beat_duration = interpreter.beat_duration();
1457 interpreter.cursor_time += beat_duration;
1458 } else if let Some(pathbuf) =
1459 interpreter.banks.resolve_trigger(var_name, property)
1460 {
1461 if let Some(path_str) = pathbuf.to_str() {
1462 interpreter.events.add_sample_event_with_effects(
1464 path_str,
1465 interpreter.cursor_time,
1466 1.0,
1467 effects.cloned(),
1468 );
1469 let beat_duration = interpreter.beat_duration();
1470 interpreter.cursor_time += beat_duration;
1471 } else {
1472 println!(
1473 "⚠️ Resolution failed for {}.{} (invalid path)",
1474 var_name, property
1475 );
1476 }
1477 } else {
1478 }
1480 }
1481 }
1482 }
1483 }
1484 } else {
1485 if let Some(Value::String(sample_uri)) = interpreter.variables.get(resolved_entity) {
1486 let uri = sample_uri.trim_matches('"').trim_matches('\'');
1487 interpreter.events.add_sample_event_with_effects(
1488 uri,
1489 interpreter.cursor_time,
1490 1.0,
1491 effects.cloned(),
1492 );
1493 let beat_duration = interpreter.beat_duration();
1494 interpreter.cursor_time += beat_duration;
1495 }
1496 }
1497
1498 Ok(())
1502}
1503
1504pub fn extract_pattern_data(
1505 _interpreter: &AudioInterpreter,
1506 value: &Value,
1507) -> (Option<String>, Option<HashMap<String, f32>>) {
1508 match value {
1509 Value::String(pattern) => (Some(pattern.clone()), None),
1510 Value::Map(map) => {
1511 let pattern = map.get("pattern").and_then(|v| {
1512 if let Value::String(s) = v {
1513 Some(s.clone())
1514 } else {
1515 None
1516 }
1517 });
1518
1519 let mut options = HashMap::new();
1520 for (key, val) in map.iter() {
1521 if key != "pattern" {
1522 if let Value::Number(num) = val {
1523 options.insert(key.clone(), *num);
1524 }
1525 }
1526 }
1527
1528 let opts = if options.is_empty() {
1529 None
1530 } else {
1531 Some(options)
1532 };
1533 (pattern, opts)
1534 }
1535 _ => (None, None),
1536 }
1537}
1538
1539pub fn execute_pattern(
1540 interpreter: &mut AudioInterpreter,
1541 target: &str,
1542 pattern: &str,
1543 options: Option<HashMap<String, f32>>,
1544) -> Result<()> {
1545 use crate::engine::audio::events::AudioEvent;
1546
1547 let swing = options
1548 .as_ref()
1549 .and_then(|o| o.get("swing").copied())
1550 .unwrap_or(0.0);
1551 let humanize = options
1552 .as_ref()
1553 .and_then(|o| o.get("humanize").copied())
1554 .unwrap_or(0.0);
1555 let velocity_mult = options
1556 .as_ref()
1557 .and_then(|o| o.get("velocity").copied())
1558 .unwrap_or(1.0);
1559 let tempo_override = options.as_ref().and_then(|o| o.get("tempo").copied());
1560
1561 let effective_bpm = tempo_override.unwrap_or(interpreter.bpm);
1562
1563 let resolved_uri = resolve_sample_uri(interpreter, target);
1564
1565 let pattern_chars: Vec<char> = pattern.chars().filter(|c| !c.is_whitespace()).collect();
1566 let step_count = pattern_chars.len() as f32;
1567 if step_count == 0.0 {
1568 return Ok(());
1569 }
1570
1571 let bar_duration = (60.0 / effective_bpm) * 4.0;
1572 let step_duration = bar_duration / step_count;
1573
1574 for (i, &ch) in pattern_chars.iter().enumerate() {
1575 if ch == 'x' || ch == 'X' {
1576 let mut time = interpreter.cursor_time + (i as f32 * step_duration);
1577 if swing > 0.0 && i % 2 == 1 {
1578 time += step_duration * swing;
1579 }
1580
1581 #[cfg(any(feature = "cli", feature = "wasm"))]
1582 if humanize > 0.0 {
1583 use rand::Rng;
1584 let mut rng = rand::thread_rng();
1585 let offset = rng.gen_range(-humanize..humanize);
1586 time += offset;
1587 }
1588
1589 let event = AudioEvent::Sample {
1590 uri: resolved_uri.clone(),
1591 start_time: time,
1592 velocity: velocity_mult, effects: None,
1594 };
1595 interpreter.events.events.push(event);
1596 }
1597 }
1598
1599 interpreter.cursor_time += bar_duration;
1600 Ok(())
1601}
1602
1603pub fn resolve_sample_uri(interpreter: &AudioInterpreter, target: &str) -> String {
1604 if let Some(dot_pos) = target.find('.') {
1605 let bank_alias = &target[..dot_pos];
1606 let trigger_name = &target[dot_pos + 1..];
1607 if let Some(Value::Map(bank_map)) = interpreter.variables.get(bank_alias) {
1608 if let Some(Value::String(bank_name)) = bank_map.get("_name") {
1609 return format!("devalang://bank/{}/{}", bank_name, trigger_name);
1610 }
1611 }
1612 }
1613 target.to_string()
1614}