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(map) = value {
12 if map.contains_key("waveform") || map.contains_key("_plugin_ref") {
13 let mut is_plugin = false;
15 let mut plugin_author: Option<String> = None;
16 let mut plugin_name: Option<String> = None;
17 let mut plugin_export: Option<String> = None;
18
19 if let Some(Value::String(plugin_ref)) = map.get("_plugin_ref") {
20 let parts: Vec<&str> = plugin_ref.split('.').collect();
21 if parts.len() == 2 {
22 let (var_name, prop_name) = (parts[0], parts[1]);
23 if let Some(var_value) = interpreter.variables.get(var_name) {
24 if let Value::Map(var_map) = var_value {
25 if let Some(Value::String(resolved_plugin)) = var_map.get(prop_name) {
26 if resolved_plugin.starts_with("plugin:") {
27 let ref_parts: Vec<&str> =
28 resolved_plugin["plugin:".len()..].split(':').collect();
29 if ref_parts.len() == 2 {
30 let full_plugin_name = ref_parts[0];
31 let export_name = ref_parts[1];
32 let plugin_parts: Vec<&str> =
33 full_plugin_name.split('.').collect();
34 if plugin_parts.len() == 2 {
35 plugin_author = Some(plugin_parts[0].to_string());
36 plugin_name = Some(plugin_parts[1].to_string());
37 plugin_export = Some(export_name.to_string());
38 is_plugin = true;
39 }
40 }
41 }
42 } else if let Some(Value::Map(export_map)) = var_map.get(prop_name) {
43 if let (
44 Some(Value::String(author)),
45 Some(Value::String(name)),
46 Some(Value::String(export)),
47 ) = (
48 export_map.get("_plugin_author"),
49 export_map.get("_plugin_name"),
50 export_map.get("_export_name"),
51 ) {
52 plugin_author = Some(author.clone());
53 plugin_name = Some(name.clone());
54 plugin_export = Some(export.clone());
55 is_plugin = true;
56 }
57 }
58 }
59 }
60 }
61 }
62
63 let waveform = crate::engine::audio::events::extract_string(map, "waveform", "sine");
64 let attack = crate::engine::audio::events::extract_number(map, "attack", 0.01);
65 let decay = crate::engine::audio::events::extract_number(map, "decay", 0.1);
66 let sustain = crate::engine::audio::events::extract_number(map, "sustain", 0.7);
67 let release = crate::engine::audio::events::extract_number(map, "release", 0.2);
68
69 let synth_type = if let Some(Value::String(t)) = map.get("type") {
70 let clean = t.trim_matches('"').trim_matches('\'');
71 if clean.is_empty() || clean == "synth" {
72 None
73 } else {
74 Some(clean.to_string())
75 }
76 } else {
77 None
78 };
79
80 let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
81 crate::engine::audio::events::extract_filters(filters_arr)
82 } else {
83 Vec::new()
84 };
85
86 let lfo = if let Some(Value::Map(lfo_map)) = map.get("lfo") {
88 use crate::engine::audio::lfo::{LfoParams, LfoRate, LfoTarget, LfoWaveform};
89
90 let rate_str = if let Some(Value::Number(n)) = lfo_map.get("rate") {
92 n.to_string()
93 } else if let Some(Value::String(s)) = lfo_map.get("rate") {
94 s.clone()
95 } else {
96 "5.0".to_string() };
98 let rate = LfoRate::from_value(&rate_str);
99
100 let depth = if let Some(Value::Number(n)) = lfo_map.get("depth") {
102 (*n).clamp(0.0, 1.0)
103 } else {
104 0.5 };
106
107 let waveform_str = if let Some(Value::String(s)) = lfo_map.get("shape") {
109 s.clone()
110 } else if let Some(Value::String(s)) = lfo_map.get("waveform") {
111 s.clone()
112 } else {
113 "sine".to_string() };
115 let waveform = LfoWaveform::from_str(&waveform_str);
116
117 let target = if let Some(Value::String(s)) = lfo_map.get("target") {
119 LfoTarget::from_str(s).unwrap_or(LfoTarget::Volume)
120 } else {
121 LfoTarget::Volume };
123
124 let phase = if let Some(Value::Number(n)) = lfo_map.get("phase") {
126 (*n).fract().abs() } else {
128 0.0 };
130
131 Some(LfoParams {
132 rate,
133 depth,
134 waveform,
135 target,
136 phase,
137 })
138 } else {
139 None
140 };
141
142 let mut options = std::collections::HashMap::new();
143 let reserved_keys = if is_plugin {
144 vec![
145 "attack",
146 "decay",
147 "sustain",
148 "release",
149 "type",
150 "filters",
151 "_plugin_ref",
152 "lfo",
153 ]
154 } else {
155 vec![
156 "waveform",
157 "attack",
158 "decay",
159 "sustain",
160 "release",
161 "type",
162 "filters",
163 "_plugin_ref",
164 "lfo",
165 ]
166 };
167
168 for (key, val) in map.iter() {
169 if !reserved_keys.contains(&key.as_str()) {
170 match val {
171 Value::Number(n) => {
172 options.insert(key.clone(), *n);
173 }
174 Value::String(s) => {
175 if is_plugin && key == "waveform" {
176 let waveform_id = match s
177 .trim_matches('"')
178 .trim_matches('\'')
179 .to_lowercase()
180 .as_str()
181 {
182 "sine" => 0.0,
183 "saw" => 1.0,
184 "square" => 2.0,
185 "triangle" => 3.0,
186 _ => 1.0,
187 };
188 options.insert(key.clone(), waveform_id);
189 }
190 }
191 _ => {}
192 }
193 }
194 }
195
196 if is_plugin && map.contains_key("decay") {
197 options.insert("decay".to_string(), decay);
198 }
199
200 let final_waveform = if is_plugin {
201 "plugin".to_string()
202 } else {
203 waveform
204 };
205
206 let synth_def = crate::engine::audio::events::SynthDefinition {
207 waveform: final_waveform,
208 attack,
209 decay,
210 sustain,
211 release,
212 synth_type,
213 filters,
214 options,
215 plugin_author,
216 plugin_name,
217 plugin_export,
218 lfo,
219 };
220
221 interpreter.events.add_synth(name.to_string(), synth_def);
222 }
223 }
224
225 interpreter
226 .variables
227 .insert(name.to_string(), value.clone());
228 Ok(())
229}
230
231pub fn handle_call(interpreter: &mut AudioInterpreter, name: &str) -> Result<()> {
232 if let Some(pattern_value) = interpreter.variables.get(name).cloned() {
243 if let Value::Statement(stmt_box) = pattern_value {
244 if let StatementKind::Pattern { target, .. } = &stmt_box.kind {
245 if let Some(tgt) = target.as_ref() {
246 let (pattern_str, options) = interpreter.extract_pattern_data(&stmt_box.value);
247 if let Some(pat) = pattern_str {
248 interpreter.execute_pattern(tgt.as_str(), &pat, options)?;
249 return Ok(());
250 }
251 }
252 }
253 }
254 }
255
256 if let Some(body) = interpreter.groups.get(name).cloned() {
257 super::collector::collect_events(interpreter, &body)?;
258 } else {
259 println!("⚠️ Warning: Group or pattern '{}' not found", name);
260 }
261
262 Ok(())
263}
264
265pub fn execute_print(interpreter: &AudioInterpreter, value: &Value) -> Result<()> {
266 let message = match value {
267 Value::String(s) => {
268 if s.contains('{') && s.contains('}') {
269 interpreter.interpolate_string(s)
270 } else {
271 s.clone()
272 }
273 }
274 Value::Identifier(id) => {
275 if let Some(v) = interpreter.variables.get(id) {
277 match v {
278 Value::String(s) => s.clone(),
279 Value::Number(n) => n.to_string(),
280 Value::Boolean(b) => b.to_string(),
281 Value::Array(arr) => format!("{:?}", arr),
282 Value::Map(map) => format!("{:?}", map),
283 _ => format!("{:?}", v),
284 }
285 } else {
286 format!("Identifier(\"{}\")", id)
287 }
288 }
289 Value::Number(n) => n.to_string(),
290 Value::Boolean(b) => b.to_string(),
291 Value::Array(arr) => format!("{:?}", arr),
292 Value::Map(map) => format!("{:?}", map),
293 _ => format!("{:?}", value),
294 };
295
296 println!("💬 {}", message);
297 Ok(())
298}
299
300pub fn execute_if(
301 interpreter: &mut AudioInterpreter,
302 condition: &Value,
303 body: &[Statement],
304 else_body: &Option<Vec<Statement>>,
305) -> Result<()> {
306 let condition_result = interpreter.evaluate_condition(condition)?;
307
308 if condition_result {
309 super::collector::collect_events(interpreter, body)?;
310 } else if let Some(else_stmts) = else_body {
311 super::collector::collect_events(interpreter, else_stmts)?;
312 }
313
314 Ok(())
315}
316
317pub fn execute_event_handlers(interpreter: &mut AudioInterpreter, event_name: &str) -> Result<()> {
318 let handlers = interpreter.event_registry.get_handlers_matching(event_name);
319
320 for (index, handler) in handlers.iter().enumerate() {
321 if handler.once
322 && !interpreter
323 .event_registry
324 .should_execute_once(event_name, index)
325 {
326 continue;
327 }
328
329 let body_clone = handler.body.clone();
330 super::collector::collect_events(interpreter, &body_clone)?;
331 }
332
333 Ok(())
334}
335
336pub fn handle_assign(
337 interpreter: &mut AudioInterpreter,
338 target: &str,
339 property: &str,
340 value: &Value,
341) -> Result<()> {
342 if let Some(var) = interpreter.variables.get_mut(target) {
343 if let Value::Map(map) = var {
344 map.insert(property.to_string(), value.clone());
345
346 if interpreter.events.synths.contains_key(target) {
347 let map_clone = map.clone();
348 let updated_def = interpreter.extract_synth_def_from_map(&map_clone)?;
349 interpreter
350 .events
351 .synths
352 .insert(target.to_string(), updated_def);
353 }
354 } else {
355 return Err(anyhow::anyhow!(
356 "Cannot assign property '{}' to non-map variable '{}'",
357 property,
358 target
359 ));
360 }
361 } else {
362 return Err(anyhow::anyhow!("Variable '{}' not found", target));
363 }
364
365 Ok(())
366}
367
368pub fn extract_synth_def_from_map(
369 _interpreter: &AudioInterpreter,
370 map: &HashMap<String, Value>,
371) -> Result<crate::engine::audio::events::SynthDefinition> {
372 use crate::engine::audio::events::extract_filters;
373 use crate::engine::audio::lfo::{LfoParams, LfoRate, LfoTarget, LfoWaveform};
374
375 let waveform = crate::engine::audio::events::extract_string(map, "waveform", "sine");
376 let attack = crate::engine::audio::events::extract_number(map, "attack", 0.01);
377 let decay = crate::engine::audio::events::extract_number(map, "decay", 0.1);
378 let sustain = crate::engine::audio::events::extract_number(map, "sustain", 0.7);
379 let release = crate::engine::audio::events::extract_number(map, "release", 0.2);
380
381 let synth_type = if let Some(Value::String(t)) = map.get("type") {
382 let clean = t.trim_matches('"').trim_matches('\'');
383 if clean.is_empty() || clean == "synth" {
384 None
385 } else {
386 Some(clean.to_string())
387 }
388 } else {
389 None
390 };
391
392 let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
393 extract_filters(filters_arr)
394 } else {
395 Vec::new()
396 };
397
398 let plugin_author = if let Some(Value::String(s)) = map.get("plugin_author") {
399 Some(s.clone())
400 } else {
401 None
402 };
403 let plugin_name = if let Some(Value::String(s)) = map.get("plugin_name") {
404 Some(s.clone())
405 } else {
406 None
407 };
408 let plugin_export = if let Some(Value::String(s)) = map.get("plugin_export") {
409 Some(s.clone())
410 } else {
411 None
412 };
413
414 let lfo = if let Some(Value::Map(lfo_map)) = map.get("lfo") {
416 let rate_str = if let Some(Value::Number(n)) = lfo_map.get("rate") {
418 n.to_string()
419 } else if let Some(Value::String(s)) = lfo_map.get("rate") {
420 s.clone()
421 } else {
422 "5.0".to_string() };
424 let rate = LfoRate::from_value(&rate_str);
425
426 let depth = if let Some(Value::Number(n)) = lfo_map.get("depth") {
428 (*n).clamp(0.0, 1.0)
429 } else {
430 0.5 };
432
433 let waveform_str = if let Some(Value::String(s)) = lfo_map.get("shape") {
435 s.clone()
436 } else if let Some(Value::String(s)) = lfo_map.get("waveform") {
437 s.clone()
438 } else {
439 "sine".to_string() };
441 let waveform = LfoWaveform::from_str(&waveform_str);
442
443 let target = if let Some(Value::String(s)) = lfo_map.get("target") {
445 LfoTarget::from_str(s).unwrap_or(LfoTarget::Volume)
446 } else {
447 LfoTarget::Volume };
449
450 let phase = if let Some(Value::Number(n)) = lfo_map.get("phase") {
452 (*n).fract().abs() } else {
454 0.0 };
456
457 Some(LfoParams {
458 rate,
459 depth,
460 waveform,
461 target,
462 phase,
463 })
464 } else {
465 None
466 };
467
468 let mut options = HashMap::new();
469 for (key, val) in map.iter() {
470 if ![
471 "waveform",
472 "attack",
473 "decay",
474 "sustain",
475 "release",
476 "type",
477 "filters",
478 "plugin_author",
479 "plugin_name",
480 "plugin_export",
481 "lfo",
482 ]
483 .contains(&key.as_str())
484 {
485 if let Value::Number(n) = val {
486 options.insert(key.clone(), *n);
487 } else if let Value::String(s) = val {
488 if key == "waveform" || key.starts_with("_") {
489 continue;
490 }
491 if let Ok(n) = s.parse::<f32>() {
492 options.insert(key.clone(), n);
493 }
494 }
495 }
496 }
497
498 Ok(crate::engine::audio::events::SynthDefinition {
499 waveform,
500 attack,
501 decay,
502 sustain,
503 release,
504 synth_type,
505 filters,
506 options,
507 plugin_author,
508 plugin_name,
509 plugin_export,
510 lfo,
511 })
512}
513
514pub fn handle_load(interpreter: &mut AudioInterpreter, source: &str, alias: &str) -> Result<()> {
515 use std::path::Path;
516
517 let path = Path::new(source);
518 if let Some(ext) = path
520 .extension()
521 .and_then(|s| s.to_str())
522 .map(|s| s.to_lowercase())
523 {
524 match ext.as_str() {
525 "mid" | "midi" => {
526 use crate::engine::audio::midi::load_midi_file;
527 let midi_data = load_midi_file(path)?;
528 interpreter.variables.insert(alias.to_string(), midi_data);
529 Ok(())
531 }
532 "wav" | "flac" | "mp3" | "ogg" => {
533 #[cfg(feature = "cli")]
535 {
536 use crate::engine::audio::samples;
537 let registered = samples::register_sample_from_path(path)?;
538 interpreter
540 .variables
541 .insert(alias.to_string(), Value::String(registered.clone()));
542 return Ok(());
544 }
545
546 #[cfg(not(feature = "cli"))]
548 {
549 interpreter
550 .variables
551 .insert(alias.to_string(), Value::String(source.to_string()));
552 return Ok(());
553 }
554 }
555 _ => Err(anyhow::anyhow!("Unsupported file type for @load: {}", ext)),
556 }
557 } else {
558 Err(anyhow::anyhow!(
559 "Cannot determine file extension for {}",
560 source
561 ))
562 }
563}
564
565pub fn handle_bind(
566 interpreter: &mut AudioInterpreter,
567 source: &str,
568 target: &str,
569 options: &Value,
570) -> Result<()> {
571 use std::collections::HashMap as StdHashMap;
572
573 if source.starts_with("mapping.") || target.starts_with("mapping.") {
580 let opts_map: StdHashMap<String, Value> = if let Value::Map(m) = options {
582 m.clone()
583 } else {
584 StdHashMap::new()
585 };
586
587 fn create_and_insert(
589 path: &str,
590 opts_map: &StdHashMap<String, Value>,
591 interpreter: &mut AudioInterpreter,
592 ) -> Option<(String, String)> {
593 let parts: Vec<&str> = path.split('.').collect();
594 if parts.len() >= 3 {
595 let direction = parts[1]; let device = parts[2];
597
598 let mut map = StdHashMap::new();
599 map.insert(
600 "_type".to_string(),
601 Value::String("midi_mapping".to_string()),
602 );
603 map.insert(
604 "direction".to_string(),
605 Value::String(direction.to_string()),
606 );
607 map.insert("device".to_string(), Value::String(device.to_string()));
608
609 for (k, v) in opts_map.iter() {
611 map.insert(k.clone(), v.clone());
612 }
613
614 interpreter
615 .variables
616 .insert(path.to_string(), Value::Map(map.clone()));
617
618 let note_on = format!("mapping.{}.{}.noteOn", direction, device);
620 let note_off = format!("mapping.{}.{}.noteOff", direction, device);
621 let rest = format!("mapping.{}.{}.rest", direction, device);
622 interpreter
623 .variables
624 .insert(note_on.clone(), Value::String(note_on.clone()));
625 interpreter
626 .variables
627 .insert(note_off.clone(), Value::String(note_off.clone()));
628 interpreter
629 .variables
630 .insert(rest.clone(), Value::String(rest.clone()));
631
632 return Some((direction.to_string(), device.to_string()));
633 }
634 None
635 }
636
637 if source.starts_with("mapping.") {
639 if let Some((direction, device)) = create_and_insert(source, &opts_map, interpreter) {
640 if !target.starts_with("mapping.") {
642 let mut bmap = StdHashMap::new();
643 bmap.insert("instrument".to_string(), Value::String(target.to_string()));
644 bmap.insert("direction".to_string(), Value::String(direction.clone()));
645 bmap.insert("device".to_string(), Value::String(device.clone()));
646 for (k, v) in opts_map.iter() {
647 bmap.insert(k.clone(), v.clone());
648 }
649 interpreter
650 .variables
651 .insert(format!("__mapping_bind::{}", source), Value::Map(bmap));
652
653 #[cfg(feature = "cli")]
655 if let Some(manager) = &mut interpreter.midi_manager {
656 if let Some(Value::Number(port_num)) = opts_map.get("port") {
657 let idx = *port_num as usize;
658 if let Ok(mut mgr) = manager.lock() {
659 let _ = mgr.open_input_by_index(idx, &device);
661 }
662 }
663 }
664 }
665 }
666 }
667
668 if target.starts_with("mapping.") {
670 if let Some((direction, device)) = create_and_insert(target, &opts_map, interpreter) {
671 if !source.starts_with("mapping.") {
672 let mut bmap = StdHashMap::new();
673 bmap.insert("source".to_string(), Value::String(source.to_string()));
674 bmap.insert("direction".to_string(), Value::String(direction.clone()));
675 bmap.insert("device".to_string(), Value::String(device.clone()));
676 for (k, v) in opts_map.iter() {
677 bmap.insert(k.clone(), v.clone());
678 }
679 interpreter
680 .variables
681 .insert(format!("__mapping_bind::{}", target), Value::Map(bmap));
682
683 #[cfg(feature = "cli")]
685 if let Some(manager) = &mut interpreter.midi_manager {
686 if let Some(Value::Number(port_num)) = opts_map.get("port") {
687 let idx = *port_num as usize;
688 if let Ok(mut mgr) = manager.lock() {
689 let _ = mgr.open_output_by_name(&device, idx);
690 }
691 }
692 }
693 }
694 }
695 }
696
697 return Ok(());
702 }
703
704 let midi_data = interpreter
706 .variables
707 .get(source)
708 .ok_or_else(|| anyhow::anyhow!("MIDI source '{}' not found", source))?
709 .clone();
710
711 if let Value::Map(midi_map) = &midi_data {
712 let notes = midi_map
713 .get("notes")
714 .ok_or_else(|| anyhow::anyhow!("MIDI data has no notes"))?;
715
716 if let Value::Array(notes_array) = notes {
717 let _synth_def = interpreter
718 .events
719 .synths
720 .get(target)
721 .ok_or_else(|| anyhow::anyhow!("Synth '{}' not found", target))?
722 .clone();
723
724 let default_velocity = 100;
725 let mut velocity = default_velocity;
726
727 if let Value::Map(opts) = options {
728 if let Some(Value::Number(v)) = opts.get("velocity") {
729 velocity = *v as u8;
730 }
731 }
732
733 let midi_bpm =
736 crate::engine::audio::events::extract_number(midi_map, "bpm", interpreter.bpm);
737
738 for note_val in notes_array {
739 if let Value::Map(note_map) = note_val {
740 let time = crate::engine::audio::events::extract_number(note_map, "time", 0.0);
741 let note =
742 crate::engine::audio::events::extract_number(note_map, "note", 60.0) as u8;
743 let note_velocity = crate::engine::audio::events::extract_number(
744 note_map,
745 "velocity",
746 velocity as f32,
747 ) as u8;
748 let duration_ms =
750 crate::engine::audio::events::extract_number(note_map, "duration", 500.0);
751
752 use crate::engine::audio::events::AudioEvent;
753 let synth_def = interpreter
754 .events
755 .get_synth(target)
756 .cloned()
757 .unwrap_or_default();
758 let interp_bpm = interpreter.bpm;
761 let factor = if interp_bpm > 0.0 {
762 midi_bpm / interp_bpm
763 } else {
764 1.0
765 };
766
767 let start_time_s = (time / 1000.0) * factor;
768 let duration_s = (duration_ms / 1000.0) * factor;
769
770 let event = AudioEvent::Note {
771 midi: note,
772 start_time: start_time_s,
773 duration: duration_s,
774 velocity: note_velocity as f32,
775 synth_id: target.to_string(),
776 synth_def,
777 pan: 0.0,
778 detune: 0.0,
779 gain: 1.0,
780 attack: None,
781 release: None,
782 delay_time: None,
783 delay_feedback: None,
784 delay_mix: None,
785 reverb_amount: None,
786 drive_amount: None,
787 drive_color: None,
788 use_per_note_automation: false,
789 };
790
791 interpreter.events.events.push(event);
794 }
795 }
796
797 }
799 }
800
801 Ok(())
802}
803
804#[cfg(feature = "cli")]
805pub fn handle_use_plugin(
806 interpreter: &mut AudioInterpreter,
807 author: &str,
808 name: &str,
809 alias: &str,
810) -> Result<()> {
811 use crate::engine::plugin::loader::load_plugin;
812
813 match load_plugin(author, name) {
814 Ok((info, _wasm_bytes)) => {
815 let mut plugin_map = HashMap::new();
816 plugin_map.insert("_type".to_string(), Value::String("plugin".to_string()));
817 plugin_map.insert("_author".to_string(), Value::String(info.author.clone()));
818 plugin_map.insert("_name".to_string(), Value::String(info.name.clone()));
819
820 if let Some(version) = &info.version {
821 plugin_map.insert("_version".to_string(), Value::String(version.clone()));
822 }
823
824 for export in &info.exports {
825 let mut export_map = HashMap::new();
826 export_map.insert(
827 "_plugin_author".to_string(),
828 Value::String(info.author.clone()),
829 );
830 export_map.insert("_plugin_name".to_string(), Value::String(info.name.clone()));
831 export_map.insert(
832 "_export_name".to_string(),
833 Value::String(export.name.clone()),
834 );
835 export_map.insert(
836 "_export_kind".to_string(),
837 Value::String(export.kind.clone()),
838 );
839
840 plugin_map.insert(export.name.clone(), Value::Map(export_map));
841 }
842
843 interpreter
844 .variables
845 .insert(alias.to_string(), Value::Map(plugin_map));
846 }
847 Err(e) => {
848 eprintln!("❌ Failed to load plugin {}.{}: {}", author, name, e);
849 return Err(anyhow::anyhow!("Failed to load plugin: {}", e));
850 }
851 }
852
853 Ok(())
854}
855
856#[cfg(not(feature = "cli"))]
857pub fn handle_use_plugin(
858 interpreter: &mut AudioInterpreter,
859 author: &str,
860 name: &str,
861 alias: &str,
862) -> Result<()> {
863 let mut plugin_map = HashMap::new();
865 plugin_map.insert(
866 "_type".to_string(),
867 Value::String("plugin_stub".to_string()),
868 );
869 plugin_map.insert("_author".to_string(), Value::String(author.to_string()));
870 plugin_map.insert("_name".to_string(), Value::String(name.to_string()));
871 interpreter
872 .variables
873 .insert(alias.to_string(), Value::Map(plugin_map));
874 Ok(())
875}
876
877pub fn handle_bank(
878 interpreter: &mut AudioInterpreter,
879 name: &str,
880 alias: &Option<String>,
881) -> Result<()> {
882 let target_alias = alias
883 .clone()
884 .unwrap_or_else(|| name.split('.').last().unwrap_or(name).to_string());
885
886 if let Some(existing_value) = interpreter.variables.get(name) {
887 interpreter
888 .variables
889 .insert(target_alias.clone(), existing_value.clone());
890 } else {
891 #[cfg(feature = "wasm")]
892 {
893 use crate::web::registry::banks::REGISTERED_BANKS;
894 REGISTERED_BANKS.with(|banks| {
895 for bank in banks.borrow().iter() {
896 if bank.full_name == *name {
897 if let Some(Value::Map(bank_map)) = interpreter.variables.get(&bank.alias) {
898 interpreter
899 .variables
900 .insert(target_alias.clone(), Value::Map(bank_map.clone()));
901 }
902 }
903 }
904 });
905 }
906
907 #[cfg(not(feature = "wasm"))]
908 {
909 if let Ok(current_dir) = std::env::current_dir() {
910 match interpreter.banks.register_bank(
911 target_alias.clone(),
912 &name,
913 ¤t_dir,
914 ¤t_dir,
915 ) {
916 Ok(_) => {
917 let mut bank_map = HashMap::new();
918 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
919 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
920 interpreter
921 .variables
922 .insert(target_alias.clone(), Value::Map(bank_map));
923 }
924 Err(e) => {
925 eprintln!("⚠️ Failed to register bank '{}': {}", name, e);
926 let mut bank_map = HashMap::new();
927 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
928 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
929 interpreter
930 .variables
931 .insert(target_alias.clone(), Value::Map(bank_map));
932 }
933 }
934 } else {
935 let mut bank_map = HashMap::new();
936 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
937 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
938 interpreter
939 .variables
940 .insert(target_alias.clone(), Value::Map(bank_map));
941 eprintln!(
942 "⚠️ Could not determine cwd to register bank '{}', registered minimal alias.",
943 name
944 );
945 }
946 }
947 }
948
949 Ok(())
950}
951
952pub fn handle_trigger(interpreter: &mut AudioInterpreter, entity: &str) -> Result<()> {
953 let resolved_entity = if entity.starts_with('.') {
954 &entity[1..]
955 } else {
956 entity
957 };
958
959 if resolved_entity.contains('.') {
960 let parts: Vec<&str> = resolved_entity.split('.').collect();
961 if parts.len() == 2 {
962 let (var_name, property) = (parts[0], parts[1]);
963
964 if let Some(Value::Map(map)) = interpreter.variables.get(var_name) {
965 if let Some(Value::String(sample_uri)) = map.get(property) {
966 let uri = sample_uri.trim_matches('"').trim_matches('\'');
967 interpreter
968 .events
969 .add_sample_event(uri, interpreter.cursor_time, 1.0);
970 let beat_duration = interpreter.beat_duration();
971 interpreter.cursor_time += beat_duration;
972 } else {
973 #[cfg(not(feature = "wasm"))]
974 {
975 let resolved_uri = interpreter.resolve_sample_uri(resolved_entity);
977 if resolved_uri != resolved_entity {
978 interpreter.events.add_sample_event(
979 &resolved_uri,
980 interpreter.cursor_time,
981 1.0,
982 );
983 let beat_duration = interpreter.beat_duration();
984 interpreter.cursor_time += beat_duration;
985 } else if let Some(pathbuf) =
986 interpreter.banks.resolve_trigger(var_name, property)
987 {
988 if let Some(path_str) = pathbuf.to_str() {
989 interpreter.events.add_sample_event(
990 path_str,
991 interpreter.cursor_time,
992 1.0,
993 );
994 let beat_duration = interpreter.beat_duration();
995 interpreter.cursor_time += beat_duration;
996 } else {
997 println!(
998 "⚠️ Resolution failed for {}.{} (invalid path)",
999 var_name, property
1000 );
1001 }
1002 } else {
1003 println!("⚠️ No path found for {} via BankRegistry", resolved_entity);
1004 }
1005 }
1006 }
1007 }
1008 }
1009 } else {
1010 if let Some(Value::String(sample_uri)) = interpreter.variables.get(resolved_entity) {
1011 let uri = sample_uri.trim_matches('"').trim_matches('\'');
1012 interpreter
1013 .events
1014 .add_sample_event(uri, interpreter.cursor_time, 1.0);
1015 let beat_duration = interpreter.beat_duration();
1016 interpreter.cursor_time += beat_duration;
1017 }
1018 }
1019
1020 Ok(())
1024}
1025
1026pub fn extract_pattern_data(
1027 _interpreter: &AudioInterpreter,
1028 value: &Value,
1029) -> (Option<String>, Option<HashMap<String, f32>>) {
1030 match value {
1031 Value::String(pattern) => (Some(pattern.clone()), None),
1032 Value::Map(map) => {
1033 let pattern = map.get("pattern").and_then(|v| {
1034 if let Value::String(s) = v {
1035 Some(s.clone())
1036 } else {
1037 None
1038 }
1039 });
1040
1041 let mut options = HashMap::new();
1042 for (key, val) in map.iter() {
1043 if key != "pattern" {
1044 if let Value::Number(num) = val {
1045 options.insert(key.clone(), *num);
1046 }
1047 }
1048 }
1049
1050 let opts = if options.is_empty() {
1051 None
1052 } else {
1053 Some(options)
1054 };
1055 (pattern, opts)
1056 }
1057 _ => (None, None),
1058 }
1059}
1060
1061pub fn execute_pattern(
1062 interpreter: &mut AudioInterpreter,
1063 target: &str,
1064 pattern: &str,
1065 options: Option<HashMap<String, f32>>,
1066) -> Result<()> {
1067 use crate::engine::audio::events::AudioEvent;
1068
1069 let swing = options
1070 .as_ref()
1071 .and_then(|o| o.get("swing").copied())
1072 .unwrap_or(0.0);
1073 let humanize = options
1074 .as_ref()
1075 .and_then(|o| o.get("humanize").copied())
1076 .unwrap_or(0.0);
1077 let velocity_mult = options
1078 .as_ref()
1079 .and_then(|o| o.get("velocity").copied())
1080 .unwrap_or(1.0);
1081 let tempo_override = options.as_ref().and_then(|o| o.get("tempo").copied());
1082
1083 let effective_bpm = tempo_override.unwrap_or(interpreter.bpm);
1084
1085 let resolved_uri = resolve_sample_uri(interpreter, target);
1086
1087 let pattern_chars: Vec<char> = pattern.chars().filter(|c| !c.is_whitespace()).collect();
1088 let step_count = pattern_chars.len() as f32;
1089 if step_count == 0.0 {
1090 return Ok(());
1091 }
1092
1093 let bar_duration = (60.0 / effective_bpm) * 4.0;
1094 let step_duration = bar_duration / step_count;
1095
1096 for (i, &ch) in pattern_chars.iter().enumerate() {
1097 if ch == 'x' || ch == 'X' {
1098 let mut time = interpreter.cursor_time + (i as f32 * step_duration);
1099 if swing > 0.0 && i % 2 == 1 {
1100 time += step_duration * swing;
1101 }
1102
1103 #[cfg(any(feature = "cli", feature = "wasm"))]
1104 if humanize > 0.0 {
1105 use rand::Rng;
1106 let mut rng = rand::thread_rng();
1107 let offset = rng.gen_range(-humanize..humanize);
1108 time += offset;
1109 }
1110
1111 let event = AudioEvent::Sample {
1112 uri: resolved_uri.clone(),
1113 start_time: time,
1114 velocity: velocity_mult, };
1116 interpreter.events.events.push(event);
1117 }
1118 }
1119
1120 interpreter.cursor_time += bar_duration;
1121 Ok(())
1122}
1123
1124pub fn resolve_sample_uri(interpreter: &AudioInterpreter, target: &str) -> String {
1125 if let Some(dot_pos) = target.find('.') {
1126 let bank_alias = &target[..dot_pos];
1127 let trigger_name = &target[dot_pos + 1..];
1128 if let Some(Value::Map(bank_map)) = interpreter.variables.get(bank_alias) {
1129 if let Some(Value::String(bank_name)) = bank_map.get("_name") {
1130 return format!("devalang://bank/{}/{}", bank_name, trigger_name);
1131 }
1132 }
1133 }
1134 target.to_string()
1135}