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