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;