Skip to main content

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