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    /// Optional deadline for schedule "during" clause (cutoff time for parent loop)
178    pub schedule_deadline: Option<f32>,
179}
180
181impl AudioInterpreter {
182    pub fn new(sample_rate: u32) -> Self {
183        Self {
184            sample_rate,
185            bpm: 120.0,
186            function_registry: FunctionRegistry::new(),
187            events: AudioEventList::new(),
188            variables: HashMap::new(),
189            groups: HashMap::new(),
190            banks: BankRegistry::new(),
191            automation_registry: crate::engine::audio::automation::AutomationRegistry::new(),
192            note_automation_templates: std::collections::HashMap::new(),
193            cursor_time: 0.0,
194            special_vars: SpecialVarContext::new(120.0, sample_rate),
195            event_registry: EventRegistry::new(),
196            #[cfg(feature = "cli")]
197            midi_manager: None,
198            current_statement_location: None,
199            suppress_beat_emit: false,
200            suppress_print: false,
201            break_flag: false,
202            background_event_tx: None,
203            background_event_rx: None,
204            background_workers: Vec::new(),
205            realtime_print_tx: None,
206            function_call_depth: 0,
207            returning_flag: false,
208            return_value: None,
209            routing: RoutingSetup::default(),
210            audio_graph: crate::engine::audio::interpreter::AudioGraph::new(),
211            schedule_deadline: None,
212        }
213    }
214
215    /// Handle a trigger statement (e.g., .kit.kick or kit.kick)
216    fn handle_trigger(&mut self, entity: &str) -> Result<()> {
217        // Delegate detailed trigger handling to the handler module
218        handler::handle_trigger(self, entity, None)
219    }
220
221    /// Helper to print banks and triggers for debugging
222    fn debug_list_banks(&self) {
223        // helper intentionally silent in production builds
224    }
225
226    pub fn interpret(&mut self, statements: &[Statement]) -> Result<Vec<f32>> {
227        // Initialize special vars context
228        let total_duration = self.calculate_total_duration(statements)?;
229        self.special_vars.total_duration = total_duration;
230        self.special_vars.update_bpm(self.bpm);
231
232        // Phase 1: Collect events
233        self.collect_events(statements)?;
234
235        // If background 'pass' workers were spawned, drain their produced batches
236        // until we've received events covering the estimated total duration. This
237        // ensures offline/non-live renders include asynchronous loop-pass events
238        // (which are produced by background workers) before rendering audio.
239        if self.background_event_rx.is_some() {
240            use std::sync::mpsc::RecvTimeoutError;
241            use std::time::{Duration, Instant};
242
243            let rx = self.background_event_rx.as_mut().unwrap();
244            let start = Instant::now();
245            // Hard cap to avoid waiting forever: allow up to total_duration + 5s
246            let hard_limit = Duration::from_secs_f32(total_duration + 5.0);
247            loop {
248                match rx.recv_timeout(Duration::from_millis(200)) {
249                    Ok(events) => {
250                        self.events.merge(events);
251                    }
252                    Err(RecvTimeoutError::Timeout) => {
253                        // If we've already got events extending to the target duration, stop draining
254                        if self.events.total_duration() >= (total_duration - 0.001) {
255                            break;
256                        }
257                        if start.elapsed() > hard_limit {
258                            // give up after hard limit
259                            break;
260                        }
261                        // else continue waiting
262                    }
263                    Err(RecvTimeoutError::Disconnected) => {
264                        // Sender(s) dropped; nothing more will arrive
265                        break;
266                    }
267                }
268            }
269        }
270
271        // Phase 2: Render audio
272
273        // Phase 2: Render audio
274        self.render_audio()
275    }
276
277    /// Get reference to collected audio events (for MIDI export)
278    pub fn events(&self) -> &AudioEventList {
279        &self.events
280    }
281
282    /// Get current statement location for error reporting
283    pub fn current_statement_location(&self) -> Option<(usize, usize)> {
284        self.current_statement_location
285    }
286
287    /// Calculate approximate total duration by scanning statements
288    pub fn calculate_total_duration(&self, _statements: &[Statement]) -> Result<f32> {
289        // For now, return a default duration (will be updated during collect_events)
290        Ok(60.0) // Default 60 seconds
291    }
292
293    pub fn collect_events(&mut self, statements: &[Statement]) -> Result<()> {
294        // Delegate to the collector child module
295        collector::collect_events(self, statements)
296    }
297    pub fn handle_let(&mut self, name: &str, value: &Value) -> Result<()> {
298        handler::handle_let(self, name, value)
299    }
300
301    pub fn extract_audio_event(
302        &mut self,
303        target: &str,
304        context: &crate::engine::functions::FunctionContext,
305    ) -> Result<()> {
306        // Delegate to extractor child module
307        extractor::extract_audio_event(self, target, context)
308    }
309
310    pub fn render_audio(&self) -> Result<Vec<f32>> {
311        // Delegate to renderer child module
312        renderer::render_audio(self)
313    }
314
315    pub fn set_bpm(&mut self, bpm: f32) {
316        self.bpm = bpm.max(1.0).min(999.0);
317        // Keep special vars in sync so $beat/$bar calculations use the updated BPM
318        self.special_vars.update_bpm(self.bpm);
319    }
320
321    pub fn samples_per_beat(&self) -> usize {
322        ((60.0 / self.bpm) * self.sample_rate as f32) as usize
323    }
324
325    /// Get duration of one beat in seconds
326    pub fn beat_duration(&self) -> f32 {
327        60.0 / self.bpm
328    }
329
330    /// Execute print statement with variable interpolation
331    /// Supports {variable_name} syntax
332    pub fn execute_print(&mut self, value: &Value) -> Result<()> {
333        handler::execute_print(self, value)
334    }
335
336    /// Interpolate variables in a string
337    /// Replaces {variable_name} with the variable's value
338    pub fn interpolate_string(&self, template: &str) -> String {
339        let mut result = template.to_string();
340
341        // Find all {variable} patterns
342        let re = regex::Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap();
343
344        for cap in re.captures_iter(template) {
345            let full_match = &cap[0]; // {variable_name}
346            let var_name = &cap[1]; // variable_name
347
348            if let Some(value) = self.variables.get(var_name) {
349                let replacement = self.value_to_string(value);
350                result = result.replace(full_match, &replacement);
351            } else {
352                // Variable not found, leave placeholder or show error
353                result = result.replace(full_match, &format!("<undefined:{}>", var_name));
354            }
355        }
356
357        result
358    }
359
360    /// Convert a Value to a displayable string
361    fn value_to_string(&self, value: &Value) -> String {
362        match value {
363            Value::String(s) => {
364                // Remove surrounding quotes if present
365                s.trim_matches('"').trim_matches('\'').to_string()
366            }
367            Value::Number(n) => {
368                // Format nicely: remove trailing zeros
369                if n.fract() == 0.0 {
370                    format!("{:.0}", n)
371                } else {
372                    format!("{}", n)
373                }
374            }
375            Value::Boolean(b) => b.to_string(),
376            Value::Array(arr) => {
377                let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
378                format!("[{}]", items.join(", "))
379            }
380            Value::Identifier(id) => id.clone(),
381            _ => format!("{:?}", value),
382        }
383    }
384
385    /// Execute if statement with condition evaluation
386    pub fn execute_if(
387        &mut self,
388        condition: &Value,
389        body: &[Statement],
390        else_body: &Option<Vec<Statement>>,
391    ) -> Result<()> {
392        handler::execute_if(self, condition, body, else_body)
393    }
394
395    /// Evaluate a condition to a boolean
396    /// Supports: ==, !=, <, >, <=, >=
397    pub fn evaluate_condition(&mut self, condition: &Value) -> Result<bool> {
398        // Condition is stored as a Map with operator and operands
399        if let Value::Map(map) = condition {
400            let operator = map
401                .get("operator")
402                .and_then(|v| {
403                    if let Value::String(s) = v {
404                        Some(s.as_str())
405                    } else {
406                        None
407                    }
408                })
409                .unwrap_or("==");
410
411            // Handle logical operators first (highest priority)
412            match operator {
413                "&&" => {
414                    let left = map
415                        .get("left")
416                        .ok_or_else(|| anyhow::anyhow!("Missing left operand for &&"))?;
417                    let right = map
418                        .get("right")
419                        .ok_or_else(|| anyhow::anyhow!("Missing right operand for &&"))?;
420
421                    let left_result = self.evaluate_condition(left)?;
422                    let right_result = self.evaluate_condition(right)?;
423                    return Ok(left_result && right_result);
424                }
425                "||" => {
426                    let left = map
427                        .get("left")
428                        .ok_or_else(|| anyhow::anyhow!("Missing left operand for ||"))?;
429                    let right = map
430                        .get("right")
431                        .ok_or_else(|| anyhow::anyhow!("Missing right operand for ||"))?;
432
433                    let left_result = self.evaluate_condition(left)?;
434                    let right_result = self.evaluate_condition(right)?;
435                    return Ok(left_result || right_result);
436                }
437                "!" => {
438                    let operand = map
439                        .get("operand")
440                        .ok_or_else(|| anyhow::anyhow!("Missing operand for !"))?;
441
442                    let operand_result = self.evaluate_condition(operand)?;
443                    return Ok(!operand_result);
444                }
445                _ => {}
446            }
447
448            // Handle comparison operators
449            let left = map
450                .get("left")
451                .ok_or_else(|| anyhow::anyhow!("Missing left operand"))?;
452            let right = map
453                .get("right")
454                .ok_or_else(|| anyhow::anyhow!("Missing right operand"))?;
455
456            // Resolve values (replace identifiers with their values)
457            let left_val = self.resolve_value(left)?;
458            let right_val = self.resolve_value(right)?;
459
460            // Compare based on operator
461            match operator {
462                "==" => Ok(self.values_equal(&left_val, &right_val)),
463                "!=" => Ok(!self.values_equal(&left_val, &right_val)),
464                "<" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less),
465                ">" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater),
466                "<=" => {
467                    let less =
468                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less)?;
469                    let equal = self.values_equal(&left_val, &right_val);
470                    Ok(less || equal)
471                }
472                ">=" => {
473                    let greater =
474                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater)?;
475                    let equal = self.values_equal(&left_val, &right_val);
476                    Ok(greater || equal)
477                }
478                _ => Err(anyhow::anyhow!("Unknown operator: {}", operator)),
479            }
480        } else {
481            // Direct boolean value or identifier: resolve identifiers first
482            let resolved = self.resolve_value(condition)?;
483            match resolved {
484                Value::Boolean(b) => Ok(b),
485                Value::Number(n) => Ok(n != 0.0),
486                Value::String(s) => Ok(!s.is_empty()),
487                Value::Identifier(id) => {
488                    // Resolve further if needed (shouldn't usually happen)
489                    let further = self.resolve_value(&Value::Identifier(id))?;
490                    match further {
491                        Value::Boolean(b2) => Ok(b2),
492                        Value::Number(n2) => Ok(n2 != 0.0),
493                        Value::String(s2) => Ok(!s2.is_empty()),
494                        _ => Ok(false),
495                    }
496                }
497                _ => Ok(false),
498            }
499        }
500    }
501
502    /// Resolve a value (replace identifiers with their values from variables)
503    pub fn resolve_value(&mut self, value: &Value) -> Result<Value> {
504        match value {
505            Value::Identifier(name) => {
506                // Check if it's a special variable
507                if is_special_var(name) {
508                    if let Some(special_val) = resolve_special_var(name, &self.special_vars) {
509                        return Ok(special_val);
510                    }
511                }
512
513                // Support combined property/index access like `obj.prop` and `arr[0]` or mixed `arr[0].prop`
514                if name.contains('.') || name.contains('[') {
515                    // parse segments: start with initial identifier, then .prop or [index]
516                    let mut chars = name.chars().peekable();
517                    // read initial identifier
518                    let mut ident = String::new();
519                    while let Some(&c) = chars.peek() {
520                        if c == '.' || c == '[' {
521                            break;
522                        }
523                        ident.push(c);
524                        chars.next();
525                    }
526
527                    let mut current: Option<Value> = if ident.is_empty() {
528                        None
529                    } else if is_special_var(&ident) {
530                        resolve_special_var(&ident, &self.special_vars)
531                    } else {
532                        self.variables.get(&ident).cloned()
533                    };
534
535                    if current.is_none() {
536                        return Ok(Value::Null);
537                    }
538
539                    // iterate segments
540                    while let Some(&c) = chars.peek() {
541                        if c == '.' {
542                            // consume '.'
543                            chars.next();
544                            // read property name
545                            let mut prop = String::new();
546                            while let Some(&nc) = chars.peek() {
547                                if nc == '.' || nc == '[' {
548                                    break;
549                                }
550                                prop.push(nc);
551                                chars.next();
552                            }
553                            // descend into map
554                            match current {
555                                Some(Value::Map(ref map)) => {
556                                    current = map.get(&prop).cloned();
557                                }
558                                _ => {
559                                    return Ok(Value::Null);
560                                }
561                            }
562                            if current.is_none() {
563                                return Ok(Value::Null);
564                            }
565                        } else if c == '[' {
566                            // consume '['
567                            chars.next();
568                            // read until ']'
569                            let mut idx_tok = String::new();
570                            while let Some(&nc) = chars.peek() {
571                                if nc == ']' {
572                                    break;
573                                }
574                                idx_tok.push(nc);
575                                chars.next();
576                            }
577                            // consume ']'
578                            if let Some(&nc) = chars.peek() {
579                                if nc == ']' {
580                                    chars.next();
581                                } else {
582                                    return Ok(Value::Null); // malformed
583                                }
584                            } else {
585                                return Ok(Value::Null); // malformed
586                            }
587
588                            let idx_tok_trim = idx_tok.trim();
589
590                            // resolve index token: it can be a number or identifier
591                            let index_value = if let Ok(n) = idx_tok_trim.parse::<usize>() {
592                                Value::Number(n as f32)
593                            } else {
594                                Value::Identifier(idx_tok_trim.to_string())
595                            };
596
597                            // apply index to current
598                            match current {
599                                Some(Value::Array(ref arr)) => {
600                                    // resolve index value to number. Support simple expressions like
601                                    // `i + 1` and post-increment `i++` (treated as i+1 without mutation).
602                                    // Evaluate index token. Support mutation for `i++` (post-increment):
603                                    // When encountering `ident++`, mutate the variable in-place and use the old value.
604                                    let resolved_idx = if idx_tok_trim.ends_with("++") {
605                                        // post-increment: mutate var and return old value
606                                        let varname = idx_tok_trim[..idx_tok_trim.len() - 2].trim();
607                                        let cur = match self.resolve_value(&Value::Identifier(
608                                            varname.to_string(),
609                                        ))? {
610                                            Value::Number(n2) => n2 as isize,
611                                            _ => return Ok(Value::Null),
612                                        };
613                                        self.variables.insert(
614                                            varname.to_string(),
615                                            Value::Number((cur + 1) as f32),
616                                        );
617                                        cur
618                                    } else if idx_tok_trim.ends_with("--") {
619                                        // post-decrement: mutate var and return old value
620                                        let varname = idx_tok_trim[..idx_tok_trim.len() - 2].trim();
621                                        let cur = match self.resolve_value(&Value::Identifier(
622                                            varname.to_string(),
623                                        ))? {
624                                            Value::Number(n2) => n2 as isize,
625                                            _ => return Ok(Value::Null),
626                                        };
627                                        self.variables.insert(
628                                            varname.to_string(),
629                                            Value::Number((cur - 1) as f32),
630                                        );
631                                        cur
632                                    } else if idx_tok_trim.contains('+') {
633                                        let parts: Vec<&str> =
634                                            idx_tok_trim.splitn(2, '+').collect();
635                                        let left = parts[0].trim();
636                                        let right = parts[1].trim();
637                                        let left_val = if let Ok(n) = left.parse::<isize>() {
638                                            n
639                                        } else {
640                                            match self.resolve_value(&Value::Identifier(
641                                                left.to_string(),
642                                            ))? {
643                                                Value::Number(n2) => n2 as isize,
644                                                _ => return Ok(Value::Null),
645                                            }
646                                        };
647                                        let right_val = if let Ok(n) = right.parse::<isize>() {
648                                            n
649                                        } else {
650                                            match self.resolve_value(&Value::Identifier(
651                                                right.to_string(),
652                                            ))? {
653                                                Value::Number(n2) => n2 as isize,
654                                                _ => return Ok(Value::Null),
655                                            }
656                                        };
657                                        left_val + right_val
658                                    } else if idx_tok_trim.contains('*') {
659                                        let parts: Vec<&str> =
660                                            idx_tok_trim.splitn(2, '*').collect();
661                                        let left = parts[0].trim();
662                                        let right = parts[1].trim();
663                                        let left_val = if let Ok(n) = left.parse::<isize>() {
664                                            n
665                                        } else {
666                                            match self.resolve_value(&Value::Identifier(
667                                                left.to_string(),
668                                            ))? {
669                                                Value::Number(n2) => n2 as isize,
670                                                _ => return Ok(Value::Null),
671                                            }
672                                        };
673                                        let right_val = if let Ok(n) = right.parse::<isize>() {
674                                            n
675                                        } else {
676                                            match self.resolve_value(&Value::Identifier(
677                                                right.to_string(),
678                                            ))? {
679                                                Value::Number(n2) => n2 as isize,
680                                                _ => return Ok(Value::Null),
681                                            }
682                                        };
683                                        left_val * right_val
684                                    } else if idx_tok_trim.contains('/') {
685                                        let parts: Vec<&str> =
686                                            idx_tok_trim.splitn(2, '/').collect();
687                                        let left = parts[0].trim();
688                                        let right = parts[1].trim();
689                                        let left_val = if let Ok(n) = left.parse::<isize>() {
690                                            n
691                                        } else {
692                                            match self.resolve_value(&Value::Identifier(
693                                                left.to_string(),
694                                            ))? {
695                                                Value::Number(n2) => n2 as isize,
696                                                _ => return Ok(Value::Null),
697                                            }
698                                        };
699                                        let right_val = if let Ok(n) = right.parse::<isize>() {
700                                            n
701                                        } else {
702                                            match self.resolve_value(&Value::Identifier(
703                                                right.to_string(),
704                                            ))? {
705                                                Value::Number(n2) => n2 as isize,
706                                                _ => return Ok(Value::Null),
707                                            }
708                                        };
709                                        if right_val == 0 {
710                                            return Err(anyhow::anyhow!(
711                                                "Division by zero in index expression"
712                                            ));
713                                        }
714                                        left_val / right_val
715                                    } else if idx_tok_trim.contains('%') {
716                                        let parts: Vec<&str> =
717                                            idx_tok_trim.splitn(2, '%').collect();
718                                        let left = parts[0].trim();
719                                        let right = parts[1].trim();
720                                        let left_val = if let Ok(n) = left.parse::<isize>() {
721                                            n
722                                        } else {
723                                            match self.resolve_value(&Value::Identifier(
724                                                left.to_string(),
725                                            ))? {
726                                                Value::Number(n2) => n2 as isize,
727                                                _ => return Ok(Value::Null),
728                                            }
729                                        };
730                                        let right_val = if let Ok(n) = right.parse::<isize>() {
731                                            n
732                                        } else {
733                                            match self.resolve_value(&Value::Identifier(
734                                                right.to_string(),
735                                            ))? {
736                                                Value::Number(n2) => n2 as isize,
737                                                _ => return Ok(Value::Null),
738                                            }
739                                        };
740                                        if right_val == 0 {
741                                            return Err(anyhow::anyhow!(
742                                                "Modulo by zero in index expression"
743                                            ));
744                                        }
745                                        left_val % right_val
746                                    } else if idx_tok_trim.contains('-') {
747                                        let parts: Vec<&str> =
748                                            idx_tok_trim.splitn(2, '-').collect();
749                                        let left = parts[0].trim();
750                                        let right = parts[1].trim();
751                                        let left_val = if let Ok(n) = left.parse::<isize>() {
752                                            n
753                                        } else {
754                                            match self.resolve_value(&Value::Identifier(
755                                                left.to_string(),
756                                            ))? {
757                                                Value::Number(n2) => n2 as isize,
758                                                _ => return Ok(Value::Null),
759                                            }
760                                        };
761                                        let right_val = if let Ok(n) = right.parse::<isize>() {
762                                            n
763                                        } else {
764                                            match self.resolve_value(&Value::Identifier(
765                                                right.to_string(),
766                                            ))? {
767                                                Value::Number(n2) => n2 as isize,
768                                                _ => return Ok(Value::Null),
769                                            }
770                                        };
771                                        left_val - right_val
772                                    } else {
773                                        match index_value {
774                                            Value::Number(n) => n as isize,
775                                            Value::Identifier(ref id) => match self
776                                                .resolve_value(&Value::Identifier(id.clone()))?
777                                            {
778                                                Value::Number(n2) => n2 as isize,
779                                                _ => return Ok(Value::Null),
780                                            },
781                                            _ => return Ok(Value::Null),
782                                        }
783                                    };
784
785                                    if resolved_idx < 0 {
786                                        return Ok(Value::Null);
787                                    }
788                                    let ui = resolved_idx as usize;
789                                    if ui < arr.len() {
790                                        let mut elem = arr[ui].clone();
791                                        // If the element is a bare Identifier (like C4) and there is no
792                                        // variable with that name, treat it as a String token for convenience
793                                        if let Value::Identifier(ref id) = elem {
794                                            if !is_special_var(id)
795                                                && self.variables.get(id).is_none()
796                                            {
797                                                elem = Value::String(id.clone());
798                                            }
799                                        }
800
801                                        // If the element is a Map with a 'value' key, unwrap it so
802                                        // myArray[0] returns the inner value directly and so that
803                                        // chained access like myArray[2].volume works (we operate on
804                                        // the inner value map)
805                                        if let Value::Map(ref m) = elem {
806                                            if let Some(inner) = m.get("value") {
807                                                current = Some(inner.clone());
808                                            } else {
809                                                current = Some(elem);
810                                            }
811                                        } else {
812                                            current = Some(elem);
813                                        }
814                                    } else {
815                                        return Err(anyhow::anyhow!(
816                                            "Index out of range: {} (len={})",
817                                            ui,
818                                            arr.len()
819                                        ));
820                                    }
821                                }
822                                Some(Value::Map(ref map)) => {
823                                    // use index token as key
824                                    let key = idx_tok_trim.trim_matches('"').trim_matches('\'');
825                                    current = map.get(key).cloned();
826                                    if current.is_none() {
827                                        return Ok(Value::Null);
828                                    }
829                                }
830                                _ => {
831                                    return Ok(Value::Null);
832                                }
833                            }
834                        } else {
835                            // unexpected char
836                            break;
837                        }
838                    }
839
840                    return Ok(current.unwrap_or(Value::Null));
841                }
842
843                // Otherwise, look in variables; if not found, treat bare identifier as a string token
844                return Ok(self
845                    .variables
846                    .get(name)
847                    .cloned()
848                    .unwrap_or(Value::String(name.clone())));
849            }
850            Value::Call { name, args } => {
851                // Evaluate call expression: resolve args then execute function/group/pattern
852                // Resolve each arg
853                let mut resolved_args: Vec<Value> = Vec::new();
854                for a in args.iter() {
855                    let rv = self.resolve_value(a)?;
856                    resolved_args.push(rv);
857                }
858
859                // Delegate to handler to execute call and return a Value
860                let res = super::handler::call_function(self, name, &resolved_args)?;
861                return Ok(res);
862            }
863            _ => return Ok(value.clone()),
864        }
865    }
866
867    /// Execute event handlers matching the event name
868    pub fn execute_event_handlers(&mut self, event_name: &str) -> Result<()> {
869        handler::execute_event_handlers(self, event_name)
870    }
871
872    /// Check if two values are equal
873    pub fn values_equal(&self, left: &Value, right: &Value) -> bool {
874        match (left, right) {
875            (Value::Number(a), Value::Number(b)) => (a - b).abs() < 0.0001,
876            (Value::String(a), Value::String(b)) => a == b,
877            (Value::Boolean(a), Value::Boolean(b)) => a == b,
878            (Value::Null, Value::Null) => true,
879            _ => false,
880        }
881    }
882
883    /// Compare two values
884    pub fn compare_values(
885        &self,
886        left: &Value,
887        right: &Value,
888        ordering: std::cmp::Ordering,
889    ) -> Result<bool> {
890        match (left, right) {
891            (Value::Number(a), Value::Number(b)) => {
892                Ok(a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) == ordering)
893            }
894            (Value::String(a), Value::String(b)) => Ok(a.cmp(b) == ordering),
895            _ => Err(anyhow::anyhow!("Cannot compare {:?} and {:?}", left, right)),
896        }
897    }
898
899    /// Handle property assignment: target.property = value
900    pub fn handle_assign(&mut self, target: &str, property: &str, value: &Value) -> Result<()> {
901        handler::handle_assign(self, target, property, value)
902    }
903
904    /// Extract synth definition from a map
905    pub fn extract_synth_def_from_map(
906        &self,
907        map: &HashMap<String, Value>,
908    ) -> Result<SynthDefinition> {
909        handler::extract_synth_def_from_map(self, map)
910    }
911
912    /// Handle MIDI file loading: @load "path.mid" as alias
913    pub fn handle_load(&mut self, source: &str, alias: &str) -> Result<()> {
914        handler::handle_load(self, source, alias)
915    }
916
917    /// Handle MIDI binding: bind source -> target { options }
918    pub fn handle_bind(&mut self, source: &str, target: &str, options: &Value) -> Result<()> {
919        handler::handle_bind(self, source, target, options)
920    }
921
922    /// Extract pattern string and options from pattern value
923    pub fn extract_pattern_data(
924        &self,
925        value: &Value,
926    ) -> (Option<String>, Option<HashMap<String, f32>>) {
927        handler::extract_pattern_data(self, value)
928    }
929
930    /// Execute a pattern with given target and pattern string
931    pub fn execute_pattern(
932        &mut self,
933        target: &str,
934        pattern: &str,
935        options: Option<HashMap<String, f32>>,
936    ) -> Result<()> {
937        handler::execute_pattern(self, target, pattern, options)
938    }
939
940    /// Execute a pattern with source tracking for routing
941    pub fn execute_pattern_with_source(
942        &mut self,
943        target: &str,
944        pattern: &str,
945        options: Option<HashMap<String, f32>>,
946        source: Option<&str>,
947    ) -> Result<()> {
948        handler::execute_pattern_with_source(self, target, pattern, options, source)
949    }
950
951    /// Resolve sample URI from bank.trigger notation (e.g., myBank.kick -> devalang://bank/devalang.808/kick)
952    pub fn resolve_sample_uri(&self, target: &str) -> String {
953        handler::resolve_sample_uri(self, target)
954    }
955
956    /// Convert a DurationValue to seconds based on current BPM
957    pub fn duration_to_seconds(
958        &self,
959        duration: &crate::language::syntax::ast::DurationValue,
960    ) -> Result<f32> {
961        use crate::language::syntax::ast::DurationValue;
962
963        match duration {
964            DurationValue::Milliseconds(ms) => Ok(ms / 1000.0),
965            DurationValue::Beats(b) => Ok(b * (60.0 / self.bpm)),
966            DurationValue::Beat(s) => {
967                // parse a fraction like "3/4" into beats
968                let mut sp = s.split('/');
969                if let (Some(a), Some(b)) = (sp.next(), sp.next()) {
970                    if let (Ok(an), Ok(bn)) = (a.trim().parse::<f32>(), b.trim().parse::<f32>()) {
971                        if bn.abs() > f32::EPSILON {
972                            return Ok((an / bn) * (60.0 / self.bpm));
973                        }
974                    }
975                }
976                anyhow::bail!("Invalid beat fraction: {}", s)
977            }
978            DurationValue::Number(n) => Ok(n / 1000.0),
979            DurationValue::Identifier(id) => {
980                // Handle temporal identifiers like "__temporal__4_beat"
981                // These are always stored in beats (pre-normalized)
982                if id.starts_with("__temporal__") {
983                    let rest = &id[12..]; // Skip "__temporal__"
984                    if let Some(last_underscore) = rest.rfind('_') {
985                        if let Ok(beat_count) = rest[..last_underscore].parse::<f32>() {
986                            // The identifier already contains beats, so just convert to seconds
987                            return Ok(beat_count * (60.0 / self.bpm));
988                        }
989                    }
990                }
991                anyhow::bail!("Cannot resolve identifier duration: {}", id)
992            }
993            DurationValue::Auto => {
994                // Auto duration: use beat interval (1 beat)
995                Ok(60.0 / self.bpm)
996            }
997        }
998    }
999}
1000
1001#[cfg(test)]
1002#[path = "test_arrays.rs"]
1003mod tests_arrays;
1004
1005#[cfg(test)]
1006#[path = "test_print.rs"]
1007mod tests_print;
1008
1009#[cfg(test)]
1010#[path = "test_functions.rs"]
1011mod tests_functions;
1012
1013#[cfg(test)]
1014#[path = "test_control_flow.rs"]
1015mod tests_control_flow;