devalang_wasm/engine/audio/interpreter/driver/mod.rs
1use crate::engine::audio::events::AudioEventList;
2use crate::engine::audio::events::SynthDefinition;
3#[cfg(feature = "cli")]
4use crate::engine::audio::midi_native::MidiManager;
5use crate::engine::events::EventRegistry;
6use crate::engine::functions::FunctionRegistry;
7use crate::engine::special_vars::{SpecialVarContext, is_special_var, resolve_special_var};
8#[cfg(feature = "cli")]
9use crate::language::addons::registry::BankRegistry;
10use crate::language::syntax::ast::{Statement, Value};
11
12#[cfg(not(feature = "cli"))]
13#[allow(dead_code)]
14#[derive(Clone)]
15pub struct BankRegistry;
16
17#[cfg(not(feature = "cli"))]
18impl BankRegistry {
19 pub fn new() -> Self {
20 BankRegistry
21 }
22 pub fn list_banks(&self) -> Vec<(String, StubBank)> {
23 Vec::new()
24 }
25 pub fn resolve_trigger(&self, _var: &str, _prop: &str) -> Option<std::path::PathBuf> {
26 None
27 }
28 pub fn register_bank(
29 &self,
30 _alias: String,
31 _name: &str,
32 _cwd: &std::path::Path,
33 _cwd2: &std::path::Path,
34 ) -> Result<(), anyhow::Error> {
35 Ok(())
36 }
37}
38
39#[cfg(not(feature = "cli"))]
40#[allow(dead_code)]
41#[derive(Clone)]
42pub struct StubBank;
43
44#[cfg(not(feature = "cli"))]
45impl StubBank {
46 pub fn list_triggers(&self) -> Vec<String> {
47 Vec::new()
48 }
49}
50
51/// Context for note-mode automations: contains templates and their temporal bounds
52#[derive(Clone, Debug)]
53pub struct NoteAutomationContext {
54 pub templates: Vec<crate::engine::audio::automation::AutomationParamTemplate>,
55 pub start_time: f32,
56 pub end_time: f32,
57}
58
59impl NoteAutomationContext {
60 /// Calculate the total duration of this automation block
61 pub fn duration(&self) -> f32 {
62 (self.end_time - self.start_time).max(0.001) // Avoid division by zero
63 }
64
65 /// Calculate global progress (0.0 to 1.0) for a given time point within this block
66 pub fn progress_at_time(&self, time: f32) -> f32 {
67 ((time - self.start_time) / self.duration()).clamp(0.0, 1.0)
68 }
69}
70
71/// Audio interpreter driver - main execution loop
72use anyhow::Result;
73use std::collections::HashMap;
74
75pub mod collector;
76pub mod extractor;
77pub mod handler;
78pub mod renderer;
79
80pub struct AudioInterpreter {
81 pub sample_rate: u32,
82 pub bpm: f32,
83 pub function_registry: FunctionRegistry,
84 pub events: AudioEventList,
85 pub variables: HashMap<String, Value>,
86 pub groups: HashMap<String, Vec<Statement>>,
87 pub banks: BankRegistry,
88 /// Registered global automations
89 pub automation_registry: crate::engine::audio::automation::AutomationRegistry,
90 /// Per-target note-mode automation contexts (including templates and timing info)
91 pub note_automation_templates: std::collections::HashMap<String, NoteAutomationContext>,
92 pub cursor_time: f32,
93 pub special_vars: SpecialVarContext,
94 pub event_registry: EventRegistry,
95 #[cfg(feature = "cli")]
96 pub midi_manager: Option<std::sync::Arc<std::sync::Mutex<MidiManager>>>,
97 /// Track current statement location for better error reporting
98 current_statement_location: Option<(usize, usize)>, // (line, column)
99 /// Internal guard to avoid re-entrant beat emission during handler execution
100 pub suppress_beat_emit: bool,
101 /// Internal guard to suppress printing during simulated/local interpreter runs
102 pub suppress_print: bool,
103 /// Flag used by 'break' statement to request breaking out of loops
104 pub break_flag: bool,
105 /// Background worker channel sender/receiver (threads send AudioEventList here)
106 pub background_event_tx:
107 Option<std::sync::mpsc::Sender<crate::engine::audio::events::AudioEventList>>,
108 pub background_event_rx:
109 Option<std::sync::mpsc::Receiver<crate::engine::audio::events::AudioEventList>>,
110 /// Holds join handles for background workers (optional, can be left running)
111 pub background_workers: Vec<std::thread::JoinHandle<()>>,
112 /// Optional channel used to replay prints in realtime during offline rendering.
113 pub realtime_print_tx: Option<std::sync::mpsc::Sender<(f32, String)>>,
114 /// Depth of active function calls. Used to validate 'return' usage (only valid inside functions).
115 pub function_call_depth: usize,
116 /// Function return state: when executing a function, a `return` statement sets
117 /// this flag and stores the returned value here so callers can inspect it.
118 pub returning_flag: bool,
119 pub return_value: Option<Value>,
120}
121
122impl AudioInterpreter {
123 pub fn new(sample_rate: u32) -> Self {
124 Self {
125 sample_rate,
126 bpm: 120.0,
127 function_registry: FunctionRegistry::new(),
128 events: AudioEventList::new(),
129 variables: HashMap::new(),
130 groups: HashMap::new(),
131 banks: BankRegistry::new(),
132 automation_registry: crate::engine::audio::automation::AutomationRegistry::new(),
133 note_automation_templates: std::collections::HashMap::new(),
134 cursor_time: 0.0,
135 special_vars: SpecialVarContext::new(120.0, sample_rate),
136 event_registry: EventRegistry::new(),
137 #[cfg(feature = "cli")]
138 midi_manager: None,
139 current_statement_location: None,
140 suppress_beat_emit: false,
141 suppress_print: false,
142 break_flag: false,
143 background_event_tx: None,
144 background_event_rx: None,
145 background_workers: Vec::new(),
146 realtime_print_tx: None,
147 function_call_depth: 0,
148 returning_flag: false,
149 return_value: None,
150 }
151 }
152
153 /// Handle a trigger statement (e.g., .kit.kick or kit.kick)
154 fn handle_trigger(&mut self, entity: &str) -> Result<()> {
155 // Delegate detailed trigger handling to the handler module
156 handler::handle_trigger(self, entity, None)
157 }
158
159 /// Helper to print banks and triggers for debugging
160 fn debug_list_banks(&self) {
161 // helper intentionally silent in production builds
162 }
163
164 pub fn interpret(&mut self, statements: &[Statement]) -> Result<Vec<f32>> {
165 // Initialize special vars context
166 let total_duration = self.calculate_total_duration(statements)?;
167 self.special_vars.total_duration = total_duration;
168 self.special_vars.update_bpm(self.bpm);
169
170 // Phase 1: Collect events
171 self.collect_events(statements)?;
172
173 // If background 'pass' workers were spawned, drain their produced batches
174 // until we've received events covering the estimated total duration. This
175 // ensures offline/non-live renders include asynchronous loop-pass events
176 // (which are produced by background workers) before rendering audio.
177 if self.background_event_rx.is_some() {
178 use std::sync::mpsc::RecvTimeoutError;
179 use std::time::{Duration, Instant};
180
181 let rx = self.background_event_rx.as_mut().unwrap();
182 let start = Instant::now();
183 // Hard cap to avoid waiting forever: allow up to total_duration + 5s
184 let hard_limit = Duration::from_secs_f32(total_duration + 5.0);
185 loop {
186 match rx.recv_timeout(Duration::from_millis(200)) {
187 Ok(events) => {
188 self.events.merge(events);
189 }
190 Err(RecvTimeoutError::Timeout) => {
191 // If we've already got events extending to the target duration, stop draining
192 if self.events.total_duration() >= (total_duration - 0.001) {
193 break;
194 }
195 if start.elapsed() > hard_limit {
196 // give up after hard limit
197 break;
198 }
199 // else continue waiting
200 }
201 Err(RecvTimeoutError::Disconnected) => {
202 // Sender(s) dropped; nothing more will arrive
203 break;
204 }
205 }
206 }
207 }
208
209 // Phase 2: Render audio
210
211 // Phase 2: Render audio
212 self.render_audio()
213 }
214
215 /// Get reference to collected audio events (for MIDI export)
216 pub fn events(&self) -> &AudioEventList {
217 &self.events
218 }
219
220 /// Get current statement location for error reporting
221 pub fn current_statement_location(&self) -> Option<(usize, usize)> {
222 self.current_statement_location
223 }
224
225 /// Calculate approximate total duration by scanning statements
226 pub fn calculate_total_duration(&self, _statements: &[Statement]) -> Result<f32> {
227 // For now, return a default duration (will be updated during collect_events)
228 Ok(60.0) // Default 60 seconds
229 }
230
231 pub fn collect_events(&mut self, statements: &[Statement]) -> Result<()> {
232 // Delegate to the collector child module
233 collector::collect_events(self, statements)
234 }
235 pub fn handle_let(&mut self, name: &str, value: &Value) -> Result<()> {
236 handler::handle_let(self, name, value)
237 }
238
239 pub fn extract_audio_event(
240 &mut self,
241 target: &str,
242 context: &crate::engine::functions::FunctionContext,
243 ) -> Result<()> {
244 // Delegate to extractor child module
245 extractor::extract_audio_event(self, target, context)
246 }
247
248 pub fn render_audio(&self) -> Result<Vec<f32>> {
249 // Delegate to renderer child module
250 renderer::render_audio(self)
251 }
252
253 pub fn set_bpm(&mut self, bpm: f32) {
254 self.bpm = bpm.max(1.0).min(999.0);
255 // Keep special vars in sync so $beat/$bar calculations use the updated BPM
256 self.special_vars.update_bpm(self.bpm);
257 }
258
259 pub fn samples_per_beat(&self) -> usize {
260 ((60.0 / self.bpm) * self.sample_rate as f32) as usize
261 }
262
263 /// Get duration of one beat in seconds
264 pub fn beat_duration(&self) -> f32 {
265 60.0 / self.bpm
266 }
267
268 /// Execute print statement with variable interpolation
269 /// Supports {variable_name} syntax
270 pub fn execute_print(&mut self, value: &Value) -> Result<()> {
271 handler::execute_print(self, value)
272 }
273
274 /// Interpolate variables in a string
275 /// Replaces {variable_name} with the variable's value
276 pub fn interpolate_string(&self, template: &str) -> String {
277 let mut result = template.to_string();
278
279 // Find all {variable} patterns
280 let re = regex::Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap();
281
282 for cap in re.captures_iter(template) {
283 let full_match = &cap[0]; // {variable_name}
284 let var_name = &cap[1]; // variable_name
285
286 if let Some(value) = self.variables.get(var_name) {
287 let replacement = self.value_to_string(value);
288 result = result.replace(full_match, &replacement);
289 } else {
290 // Variable not found, leave placeholder or show error
291 result = result.replace(full_match, &format!("<undefined:{}>", var_name));
292 }
293 }
294
295 result
296 }
297
298 /// Convert a Value to a displayable string
299 fn value_to_string(&self, value: &Value) -> String {
300 match value {
301 Value::String(s) => {
302 // Remove surrounding quotes if present
303 s.trim_matches('"').trim_matches('\'').to_string()
304 }
305 Value::Number(n) => {
306 // Format nicely: remove trailing zeros
307 if n.fract() == 0.0 {
308 format!("{:.0}", n)
309 } else {
310 format!("{}", n)
311 }
312 }
313 Value::Boolean(b) => b.to_string(),
314 Value::Array(arr) => {
315 let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
316 format!("[{}]", items.join(", "))
317 }
318 Value::Identifier(id) => id.clone(),
319 _ => format!("{:?}", value),
320 }
321 }
322
323 /// Execute if statement with condition evaluation
324 pub fn execute_if(
325 &mut self,
326 condition: &Value,
327 body: &[Statement],
328 else_body: &Option<Vec<Statement>>,
329 ) -> Result<()> {
330 handler::execute_if(self, condition, body, else_body)
331 }
332
333 /// Evaluate a condition to a boolean
334 /// Supports: ==, !=, <, >, <=, >=
335 pub fn evaluate_condition(&mut self, condition: &Value) -> Result<bool> {
336 // Condition is stored as a Map with operator and operands
337 if let Value::Map(map) = condition {
338 let operator = map
339 .get("operator")
340 .and_then(|v| {
341 if let Value::String(s) = v {
342 Some(s.as_str())
343 } else {
344 None
345 }
346 })
347 .unwrap_or("==");
348
349 let left = map
350 .get("left")
351 .ok_or_else(|| anyhow::anyhow!("Missing left operand"))?;
352 let right = map
353 .get("right")
354 .ok_or_else(|| anyhow::anyhow!("Missing right operand"))?;
355
356 // Resolve values (replace identifiers with their values)
357 let left_val = self.resolve_value(left)?;
358 let right_val = self.resolve_value(right)?;
359
360 // Compare based on operator
361 match operator {
362 "==" => Ok(self.values_equal(&left_val, &right_val)),
363 "!=" => Ok(!self.values_equal(&left_val, &right_val)),
364 "<" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less),
365 ">" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater),
366 "<=" => {
367 let less =
368 self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less)?;
369 let equal = self.values_equal(&left_val, &right_val);
370 Ok(less || equal)
371 }
372 ">=" => {
373 let greater =
374 self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater)?;
375 let equal = self.values_equal(&left_val, &right_val);
376 Ok(greater || equal)
377 }
378 _ => Err(anyhow::anyhow!("Unknown operator: {}", operator)),
379 }
380 } else {
381 // Direct boolean value or identifier: resolve identifiers first
382 let resolved = self.resolve_value(condition)?;
383 match resolved {
384 Value::Boolean(b) => Ok(b),
385 Value::Number(n) => Ok(n != 0.0),
386 Value::String(s) => Ok(!s.is_empty()),
387 Value::Identifier(id) => {
388 // Resolve further if needed (shouldn't usually happen)
389 let further = self.resolve_value(&Value::Identifier(id))?;
390 match further {
391 Value::Boolean(b2) => Ok(b2),
392 Value::Number(n2) => Ok(n2 != 0.0),
393 Value::String(s2) => Ok(!s2.is_empty()),
394 _ => Ok(false),
395 }
396 }
397 _ => Ok(false),
398 }
399 }
400 }
401
402 /// Resolve a value (replace identifiers with their values from variables)
403 pub fn resolve_value(&mut self, value: &Value) -> Result<Value> {
404 match value {
405 Value::Identifier(name) => {
406 // Check if it's a special variable
407 if is_special_var(name) {
408 if let Some(special_val) = resolve_special_var(name, &self.special_vars) {
409 return Ok(special_val);
410 }
411 }
412
413 // Support combined property/index access like `obj.prop` and `arr[0]` or mixed `arr[0].prop`
414 if name.contains('.') || name.contains('[') {
415 // parse segments: start with initial identifier, then .prop or [index]
416 let mut chars = name.chars().peekable();
417 // read initial identifier
418 let mut ident = String::new();
419 while let Some(&c) = chars.peek() {
420 if c == '.' || c == '[' {
421 break;
422 }
423 ident.push(c);
424 chars.next();
425 }
426
427 let mut current: Option<Value> = if ident.is_empty() {
428 None
429 } else if is_special_var(&ident) {
430 resolve_special_var(&ident, &self.special_vars)
431 } else {
432 self.variables.get(&ident).cloned()
433 };
434
435 if current.is_none() {
436 return Ok(Value::Null);
437 }
438
439 // iterate segments
440 while let Some(&c) = chars.peek() {
441 if c == '.' {
442 // consume '.'
443 chars.next();
444 // read property name
445 let mut prop = String::new();
446 while let Some(&nc) = chars.peek() {
447 if nc == '.' || nc == '[' {
448 break;
449 }
450 prop.push(nc);
451 chars.next();
452 }
453 // descend into map
454 match current {
455 Some(Value::Map(ref map)) => {
456 current = map.get(&prop).cloned();
457 }
458 _ => {
459 return Ok(Value::Null);
460 }
461 }
462 if current.is_none() {
463 return Ok(Value::Null);
464 }
465 } else if c == '[' {
466 // consume '['
467 chars.next();
468 // read until ']'
469 let mut idx_tok = String::new();
470 while let Some(&nc) = chars.peek() {
471 if nc == ']' {
472 break;
473 }
474 idx_tok.push(nc);
475 chars.next();
476 }
477 // consume ']'
478 if let Some(&nc) = chars.peek() {
479 if nc == ']' {
480 chars.next();
481 } else {
482 return Ok(Value::Null); // malformed
483 }
484 } else {
485 return Ok(Value::Null); // malformed
486 }
487
488 let idx_tok_trim = idx_tok.trim();
489
490 // resolve index token: it can be a number or identifier
491 let index_value = if let Ok(n) = idx_tok_trim.parse::<usize>() {
492 Value::Number(n as f32)
493 } else {
494 Value::Identifier(idx_tok_trim.to_string())
495 };
496
497 // apply index to current
498 match current {
499 Some(Value::Array(ref arr)) => {
500 // resolve index value to number. Support simple expressions like
501 // `i + 1` and post-increment `i++` (treated as i+1 without mutation).
502 // Evaluate index token. Support mutation for `i++` (post-increment):
503 // When encountering `ident++`, mutate the variable in-place and use the old value.
504 let resolved_idx = if idx_tok_trim.ends_with("++") {
505 // post-increment: mutate var and return old value
506 let varname = idx_tok_trim[..idx_tok_trim.len() - 2].trim();
507 let cur = match self.resolve_value(&Value::Identifier(
508 varname.to_string(),
509 ))? {
510 Value::Number(n2) => n2 as isize,
511 _ => return Ok(Value::Null),
512 };
513 self.variables.insert(
514 varname.to_string(),
515 Value::Number((cur + 1) as f32),
516 );
517 cur
518 } else if idx_tok_trim.ends_with("--") {
519 // post-decrement: mutate var and return old value
520 let varname = idx_tok_trim[..idx_tok_trim.len() - 2].trim();
521 let cur = match self.resolve_value(&Value::Identifier(
522 varname.to_string(),
523 ))? {
524 Value::Number(n2) => n2 as isize,
525 _ => return Ok(Value::Null),
526 };
527 self.variables.insert(
528 varname.to_string(),
529 Value::Number((cur - 1) as f32),
530 );
531 cur
532 } else if idx_tok_trim.contains('+') {
533 let parts: Vec<&str> =
534 idx_tok_trim.splitn(2, '+').collect();
535 let left = parts[0].trim();
536 let right = parts[1].trim();
537 let left_val = if let Ok(n) = left.parse::<isize>() {
538 n
539 } else {
540 match self.resolve_value(&Value::Identifier(
541 left.to_string(),
542 ))? {
543 Value::Number(n2) => n2 as isize,
544 _ => return Ok(Value::Null),
545 }
546 };
547 let right_val = if let Ok(n) = right.parse::<isize>() {
548 n
549 } else {
550 match self.resolve_value(&Value::Identifier(
551 right.to_string(),
552 ))? {
553 Value::Number(n2) => n2 as isize,
554 _ => return Ok(Value::Null),
555 }
556 };
557 left_val + right_val
558 } else if idx_tok_trim.contains('*') {
559 let parts: Vec<&str> =
560 idx_tok_trim.splitn(2, '*').collect();
561 let left = parts[0].trim();
562 let right = parts[1].trim();
563 let left_val = if let Ok(n) = left.parse::<isize>() {
564 n
565 } else {
566 match self.resolve_value(&Value::Identifier(
567 left.to_string(),
568 ))? {
569 Value::Number(n2) => n2 as isize,
570 _ => return Ok(Value::Null),
571 }
572 };
573 let right_val = if let Ok(n) = right.parse::<isize>() {
574 n
575 } else {
576 match self.resolve_value(&Value::Identifier(
577 right.to_string(),
578 ))? {
579 Value::Number(n2) => n2 as isize,
580 _ => return Ok(Value::Null),
581 }
582 };
583 left_val * right_val
584 } else if idx_tok_trim.contains('/') {
585 let parts: Vec<&str> =
586 idx_tok_trim.splitn(2, '/').collect();
587 let left = parts[0].trim();
588 let right = parts[1].trim();
589 let left_val = if let Ok(n) = left.parse::<isize>() {
590 n
591 } else {
592 match self.resolve_value(&Value::Identifier(
593 left.to_string(),
594 ))? {
595 Value::Number(n2) => n2 as isize,
596 _ => return Ok(Value::Null),
597 }
598 };
599 let right_val = if let Ok(n) = right.parse::<isize>() {
600 n
601 } else {
602 match self.resolve_value(&Value::Identifier(
603 right.to_string(),
604 ))? {
605 Value::Number(n2) => n2 as isize,
606 _ => return Ok(Value::Null),
607 }
608 };
609 if right_val == 0 {
610 return Err(anyhow::anyhow!(
611 "Division by zero in index expression"
612 ));
613 }
614 left_val / right_val
615 } else if idx_tok_trim.contains('%') {
616 let parts: Vec<&str> =
617 idx_tok_trim.splitn(2, '%').collect();
618 let left = parts[0].trim();
619 let right = parts[1].trim();
620 let left_val = if let Ok(n) = left.parse::<isize>() {
621 n
622 } else {
623 match self.resolve_value(&Value::Identifier(
624 left.to_string(),
625 ))? {
626 Value::Number(n2) => n2 as isize,
627 _ => return Ok(Value::Null),
628 }
629 };
630 let right_val = if let Ok(n) = right.parse::<isize>() {
631 n
632 } else {
633 match self.resolve_value(&Value::Identifier(
634 right.to_string(),
635 ))? {
636 Value::Number(n2) => n2 as isize,
637 _ => return Ok(Value::Null),
638 }
639 };
640 if right_val == 0 {
641 return Err(anyhow::anyhow!(
642 "Modulo by zero in index expression"
643 ));
644 }
645 left_val % right_val
646 } else if idx_tok_trim.contains('-') {
647 let parts: Vec<&str> =
648 idx_tok_trim.splitn(2, '-').collect();
649 let left = parts[0].trim();
650 let right = parts[1].trim();
651 let left_val = if let Ok(n) = left.parse::<isize>() {
652 n
653 } else {
654 match self.resolve_value(&Value::Identifier(
655 left.to_string(),
656 ))? {
657 Value::Number(n2) => n2 as isize,
658 _ => return Ok(Value::Null),
659 }
660 };
661 let right_val = if let Ok(n) = right.parse::<isize>() {
662 n
663 } else {
664 match self.resolve_value(&Value::Identifier(
665 right.to_string(),
666 ))? {
667 Value::Number(n2) => n2 as isize,
668 _ => return Ok(Value::Null),
669 }
670 };
671 left_val - right_val
672 } else {
673 match index_value {
674 Value::Number(n) => n as isize,
675 Value::Identifier(ref id) => match self
676 .resolve_value(&Value::Identifier(id.clone()))?
677 {
678 Value::Number(n2) => n2 as isize,
679 _ => return Ok(Value::Null),
680 },
681 _ => return Ok(Value::Null),
682 }
683 };
684
685 if resolved_idx < 0 {
686 return Ok(Value::Null);
687 }
688 let ui = resolved_idx as usize;
689 if ui < arr.len() {
690 let mut elem = arr[ui].clone();
691 // If the element is a bare Identifier (like C4) and there is no
692 // variable with that name, treat it as a String token for convenience
693 if let Value::Identifier(ref id) = elem {
694 if !is_special_var(id)
695 && self.variables.get(id).is_none()
696 {
697 elem = Value::String(id.clone());
698 }
699 }
700
701 // If the element is a Map with a 'value' key, unwrap it so
702 // myArray[0] returns the inner value directly and so that
703 // chained access like myArray[2].volume works (we operate on
704 // the inner value map)
705 if let Value::Map(ref m) = elem {
706 if let Some(inner) = m.get("value") {
707 current = Some(inner.clone());
708 } else {
709 current = Some(elem);
710 }
711 } else {
712 current = Some(elem);
713 }
714 } else {
715 return Err(anyhow::anyhow!(
716 "Index out of range: {} (len={})",
717 ui,
718 arr.len()
719 ));
720 }
721 }
722 Some(Value::Map(ref map)) => {
723 // use index token as key
724 let key = idx_tok_trim.trim_matches('"').trim_matches('\'');
725 current = map.get(key).cloned();
726 if current.is_none() {
727 return Ok(Value::Null);
728 }
729 }
730 _ => {
731 return Ok(Value::Null);
732 }
733 }
734 } else {
735 // unexpected char
736 break;
737 }
738 }
739
740 return Ok(current.unwrap_or(Value::Null));
741 }
742
743 // Otherwise, look in variables; if not found, treat bare identifier as a string token
744 return Ok(self
745 .variables
746 .get(name)
747 .cloned()
748 .unwrap_or(Value::String(name.clone())));
749 }
750 Value::Call { name, args } => {
751 // Evaluate call expression: resolve args then execute function/group/pattern
752 // Resolve each arg
753 let mut resolved_args: Vec<Value> = Vec::new();
754 for a in args.iter() {
755 let rv = self.resolve_value(a)?;
756 resolved_args.push(rv);
757 }
758
759 // Delegate to handler to execute call and return a Value
760 let res = super::handler::call_function(self, name, &resolved_args)?;
761 return Ok(res);
762 }
763 _ => return Ok(value.clone()),
764 }
765 }
766
767 /// Execute event handlers matching the event name
768 pub fn execute_event_handlers(&mut self, event_name: &str) -> Result<()> {
769 handler::execute_event_handlers(self, event_name)
770 }
771
772 /// Check if two values are equal
773 pub fn values_equal(&self, left: &Value, right: &Value) -> bool {
774 match (left, right) {
775 (Value::Number(a), Value::Number(b)) => (a - b).abs() < 0.0001,
776 (Value::String(a), Value::String(b)) => a == b,
777 (Value::Boolean(a), Value::Boolean(b)) => a == b,
778 (Value::Null, Value::Null) => true,
779 _ => false,
780 }
781 }
782
783 /// Compare two values
784 pub fn compare_values(
785 &self,
786 left: &Value,
787 right: &Value,
788 ordering: std::cmp::Ordering,
789 ) -> Result<bool> {
790 match (left, right) {
791 (Value::Number(a), Value::Number(b)) => {
792 Ok(a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) == ordering)
793 }
794 (Value::String(a), Value::String(b)) => Ok(a.cmp(b) == ordering),
795 _ => Err(anyhow::anyhow!("Cannot compare {:?} and {:?}", left, right)),
796 }
797 }
798
799 /// Handle property assignment: target.property = value
800 pub fn handle_assign(&mut self, target: &str, property: &str, value: &Value) -> Result<()> {
801 handler::handle_assign(self, target, property, value)
802 }
803
804 /// Extract synth definition from a map
805 pub fn extract_synth_def_from_map(
806 &self,
807 map: &HashMap<String, Value>,
808 ) -> Result<SynthDefinition> {
809 handler::extract_synth_def_from_map(self, map)
810 }
811
812 /// Handle MIDI file loading: @load "path.mid" as alias
813 pub fn handle_load(&mut self, source: &str, alias: &str) -> Result<()> {
814 handler::handle_load(self, source, alias)
815 }
816
817 /// Handle MIDI binding: bind source -> target { options }
818 pub fn handle_bind(&mut self, source: &str, target: &str, options: &Value) -> Result<()> {
819 handler::handle_bind(self, source, target, options)
820 }
821
822 /// Extract pattern string and options from pattern value
823 pub fn extract_pattern_data(
824 &self,
825 value: &Value,
826 ) -> (Option<String>, Option<HashMap<String, f32>>) {
827 handler::extract_pattern_data(self, value)
828 }
829
830 /// Execute a pattern with given target and pattern string
831 pub fn execute_pattern(
832 &mut self,
833 target: &str,
834 pattern: &str,
835 options: Option<HashMap<String, f32>>,
836 ) -> Result<()> {
837 handler::execute_pattern(self, target, pattern, options)
838 }
839
840 /// Resolve sample URI from bank.trigger notation (e.g., myBank.kick -> devalang://bank/devaloop.808/kick)
841 pub fn resolve_sample_uri(&self, target: &str) -> String {
842 handler::resolve_sample_uri(self, target)
843 }
844}
845
846#[cfg(test)]
847#[path = "test_arrays.rs"]
848mod tests_arrays;
849
850#[cfg(test)]
851#[path = "test_print.rs"]
852mod tests_print;
853
854#[cfg(test)]
855#[path = "test_functions.rs"]
856mod tests_functions;
857
858#[cfg(test)]
859#[path = "test_control_flow.rs"]
860mod tests_control_flow;