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;