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// Provide a lightweight stub for BankRegistry when CLI feature is disabled (WASM/plugin builds)
13#[cfg(not(feature = "cli"))]
14#[allow(dead_code)]
15#[derive(Clone)]
16pub struct BankRegistry;
17
18#[cfg(not(feature = "cli"))]
19impl BankRegistry {
20    pub fn new() -> Self {
21        BankRegistry
22    }
23    pub fn list_banks(&self) -> Vec<(String, StubBank)> {
24        Vec::new()
25    }
26    pub fn resolve_trigger(&self, _var: &str, _prop: &str) -> Option<std::path::PathBuf> {
27        None
28    }
29    pub fn register_bank(
30        &self,
31        _alias: String,
32        _name: &str,
33        _cwd: &std::path::Path,
34        _cwd2: &std::path::Path,
35    ) -> Result<(), anyhow::Error> {
36        Ok(())
37    }
38}
39
40#[cfg(not(feature = "cli"))]
41#[allow(dead_code)]
42#[derive(Clone)]
43pub struct StubBank;
44
45#[cfg(not(feature = "cli"))]
46impl StubBank {
47    pub fn list_triggers(&self) -> Vec<String> {
48        Vec::new()
49    }
50}
51
52/// Context for note-mode automations: contains templates and their temporal bounds
53#[derive(Clone, Debug)]
54pub struct NoteAutomationContext {
55    pub templates: Vec<crate::engine::audio::automation::AutomationParamTemplate>,
56    pub start_time: f32,
57    pub end_time: f32,
58}
59
60impl NoteAutomationContext {
61    /// Calculate the total duration of this automation block
62    pub fn duration(&self) -> f32 {
63        (self.end_time - self.start_time).max(0.001) // Avoid division by zero
64    }
65
66    /// Calculate global progress (0.0 to 1.0) for a given time point within this block
67    pub fn progress_at_time(&self, time: f32) -> f32 {
68        ((time - self.start_time) / self.duration()).clamp(0.0, 1.0)
69    }
70}
71
72/// Audio interpreter driver - main execution loop
73use anyhow::Result;
74use std::collections::HashMap;
75
76pub mod collector;
77pub mod extractor;
78pub mod handler;
79pub mod renderer;
80
81pub struct AudioInterpreter {
82    pub sample_rate: u32,
83    pub bpm: f32,
84    pub function_registry: FunctionRegistry,
85    pub events: AudioEventList,
86    pub variables: HashMap<String, Value>,
87    pub groups: HashMap<String, Vec<Statement>>,
88    pub banks: BankRegistry,
89    /// Registered global automations
90    pub automation_registry: crate::engine::audio::automation::AutomationRegistry,
91    /// Per-target note-mode automation contexts (including templates and timing info)
92    pub note_automation_templates: std::collections::HashMap<String, NoteAutomationContext>,
93    pub cursor_time: f32,
94    pub special_vars: SpecialVarContext,
95    pub event_registry: EventRegistry,
96    #[cfg(feature = "cli")]
97    pub midi_manager: Option<std::sync::Arc<std::sync::Mutex<MidiManager>>>,
98    /// Track current statement location for better error reporting
99    current_statement_location: Option<(usize, usize)>, // (line, column)
100    /// Internal guard to avoid re-entrant beat emission during handler execution
101    pub suppress_beat_emit: bool,
102}
103
104impl AudioInterpreter {
105    pub fn new(sample_rate: u32) -> Self {
106        Self {
107            sample_rate,
108            bpm: 120.0,
109            function_registry: FunctionRegistry::new(),
110            events: AudioEventList::new(),
111            variables: HashMap::new(),
112            groups: HashMap::new(),
113            banks: BankRegistry::new(),
114            automation_registry: crate::engine::audio::automation::AutomationRegistry::new(),
115            note_automation_templates: std::collections::HashMap::new(),
116            cursor_time: 0.0,
117            special_vars: SpecialVarContext::new(120.0, sample_rate),
118            event_registry: EventRegistry::new(),
119            #[cfg(feature = "cli")]
120            midi_manager: None,
121            current_statement_location: None,
122            suppress_beat_emit: false,
123        }
124    }
125
126    /// Handle a trigger statement (e.g., .kit.kick or kit.kick)
127    fn handle_trigger(&mut self, entity: &str) -> Result<()> {
128        // Delegate detailed trigger handling to the handler module
129        handler::handle_trigger(self, entity)
130    }
131
132    /// Helper to print banks and triggers for debugging
133    fn debug_list_banks(&self) {
134        println!("🔍 Available triggers in BankRegistry:");
135        for (bank_name, bank) in self.banks.list_banks() {
136            println!("   Bank: {}", bank_name);
137            for trigger in bank.list_triggers() {
138                println!("      Trigger: {}", trigger);
139            }
140        }
141    }
142
143    pub fn interpret(&mut self, statements: &[Statement]) -> Result<Vec<f32>> {
144        // Initialize special vars context
145        let total_duration = self.calculate_total_duration(statements)?;
146        self.special_vars.total_duration = total_duration;
147        self.special_vars.update_bpm(self.bpm);
148
149        // Phase 1: Collect events
150        self.collect_events(statements)?;
151
152        // Phase 2: Render audio
153
154        // Phase 2: Render audio
155        self.render_audio()
156    }
157
158    /// Get reference to collected audio events (for MIDI export)
159    pub fn events(&self) -> &AudioEventList {
160        &self.events
161    }
162
163    /// Get current statement location for error reporting
164    pub fn current_statement_location(&self) -> Option<(usize, usize)> {
165        self.current_statement_location
166    }
167
168    /// Calculate approximate total duration by scanning statements
169    pub fn calculate_total_duration(&self, _statements: &[Statement]) -> Result<f32> {
170        // For now, return a default duration (will be updated during collect_events)
171        Ok(60.0) // Default 60 seconds
172    }
173
174    pub fn collect_events(&mut self, statements: &[Statement]) -> Result<()> {
175        // Delegate to the collector child module
176        collector::collect_events(self, statements)
177    }
178    pub fn handle_let(&mut self, name: &str, value: &Value) -> Result<()> {
179        handler::handle_let(self, name, value)
180    }
181
182    pub fn extract_audio_event(
183        &mut self,
184        target: &str,
185        context: &crate::engine::functions::FunctionContext,
186    ) -> Result<()> {
187        // Delegate to extractor child module
188        extractor::extract_audio_event(self, target, context)
189    }
190
191    pub fn render_audio(&self) -> Result<Vec<f32>> {
192        // Delegate to renderer child module
193        renderer::render_audio(self)
194    }
195
196    pub fn set_bpm(&mut self, bpm: f32) {
197        self.bpm = bpm.max(1.0).min(999.0);
198    }
199
200    pub fn samples_per_beat(&self) -> usize {
201        ((60.0 / self.bpm) * self.sample_rate as f32) as usize
202    }
203
204    /// Get duration of one beat in seconds
205    pub fn beat_duration(&self) -> f32 {
206        60.0 / self.bpm
207    }
208
209    /// Execute print statement with variable interpolation
210    /// Supports {variable_name} syntax
211    pub fn execute_print(&self, value: &Value) -> Result<()> {
212        handler::execute_print(self, value)
213    }
214
215    /// Interpolate variables in a string
216    /// Replaces {variable_name} with the variable's value
217    pub fn interpolate_string(&self, template: &str) -> String {
218        let mut result = template.to_string();
219
220        // Find all {variable} patterns
221        let re = regex::Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap();
222
223        for cap in re.captures_iter(template) {
224            let full_match = &cap[0]; // {variable_name}
225            let var_name = &cap[1]; // variable_name
226
227            if let Some(value) = self.variables.get(var_name) {
228                let replacement = self.value_to_string(value);
229                result = result.replace(full_match, &replacement);
230            } else {
231                // Variable not found, leave placeholder or show error
232                result = result.replace(full_match, &format!("<undefined:{}>", var_name));
233            }
234        }
235
236        result
237    }
238
239    /// Convert a Value to a displayable string
240    fn value_to_string(&self, value: &Value) -> String {
241        match value {
242            Value::String(s) => {
243                // Remove surrounding quotes if present
244                s.trim_matches('"').trim_matches('\'').to_string()
245            }
246            Value::Number(n) => {
247                // Format nicely: remove trailing zeros
248                if n.fract() == 0.0 {
249                    format!("{:.0}", n)
250                } else {
251                    format!("{}", n)
252                }
253            }
254            Value::Boolean(b) => b.to_string(),
255            Value::Array(arr) => {
256                let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
257                format!("[{}]", items.join(", "))
258            }
259            Value::Identifier(id) => id.clone(),
260            _ => format!("{:?}", value),
261        }
262    }
263
264    /// Execute if statement with condition evaluation
265    pub fn execute_if(
266        &mut self,
267        condition: &Value,
268        body: &[Statement],
269        else_body: &Option<Vec<Statement>>,
270    ) -> Result<()> {
271        handler::execute_if(self, condition, body, else_body)
272    }
273
274    /// Evaluate a condition to a boolean
275    /// Supports: ==, !=, <, >, <=, >=
276    pub fn evaluate_condition(&self, condition: &Value) -> Result<bool> {
277        // Condition is stored as a Map with operator and operands
278        if let Value::Map(map) = condition {
279            let operator = map
280                .get("operator")
281                .and_then(|v| {
282                    if let Value::String(s) = v {
283                        Some(s.as_str())
284                    } else {
285                        None
286                    }
287                })
288                .unwrap_or("==");
289
290            let left = map
291                .get("left")
292                .ok_or_else(|| anyhow::anyhow!("Missing left operand"))?;
293            let right = map
294                .get("right")
295                .ok_or_else(|| anyhow::anyhow!("Missing right operand"))?;
296
297            // Resolve values (replace identifiers with their values)
298            let left_val = self.resolve_value(left);
299            let right_val = self.resolve_value(right);
300
301            // Compare based on operator
302            match operator {
303                "==" => Ok(self.values_equal(&left_val, &right_val)),
304                "!=" => Ok(!self.values_equal(&left_val, &right_val)),
305                "<" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less),
306                ">" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater),
307                "<=" => {
308                    let less =
309                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less)?;
310                    let equal = self.values_equal(&left_val, &right_val);
311                    Ok(less || equal)
312                }
313                ">=" => {
314                    let greater =
315                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater)?;
316                    let equal = self.values_equal(&left_val, &right_val);
317                    Ok(greater || equal)
318                }
319                _ => Err(anyhow::anyhow!("Unknown operator: {}", operator)),
320            }
321        } else {
322            // Direct boolean value
323            match condition {
324                Value::Boolean(b) => Ok(*b),
325                Value::Number(n) => Ok(*n != 0.0),
326                Value::String(s) => Ok(!s.is_empty()),
327                _ => Ok(false),
328            }
329        }
330    }
331
332    /// Resolve a value (replace identifiers with their values from variables)
333    pub fn resolve_value(&self, value: &Value) -> Value {
334        match value {
335            Value::Identifier(name) => {
336                // Check if it's a special variable
337                if is_special_var(name) {
338                    if let Some(special_val) = resolve_special_var(name, &self.special_vars) {
339                        return special_val;
340                    }
341                }
342
343                // Otherwise, look in variables
344                self.variables.get(name).cloned().unwrap_or(Value::Null)
345            }
346            _ => value.clone(),
347        }
348    }
349
350    /// Execute event handlers matching the event name
351    pub fn execute_event_handlers(&mut self, event_name: &str) -> Result<()> {
352        handler::execute_event_handlers(self, event_name)
353    }
354
355    /// Check if two values are equal
356    pub fn values_equal(&self, left: &Value, right: &Value) -> bool {
357        match (left, right) {
358            (Value::Number(a), Value::Number(b)) => (a - b).abs() < 0.0001,
359            (Value::String(a), Value::String(b)) => a == b,
360            (Value::Boolean(a), Value::Boolean(b)) => a == b,
361            (Value::Null, Value::Null) => true,
362            _ => false,
363        }
364    }
365
366    /// Compare two values
367    pub fn compare_values(
368        &self,
369        left: &Value,
370        right: &Value,
371        ordering: std::cmp::Ordering,
372    ) -> Result<bool> {
373        match (left, right) {
374            (Value::Number(a), Value::Number(b)) => {
375                Ok(a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) == ordering)
376            }
377            (Value::String(a), Value::String(b)) => Ok(a.cmp(b) == ordering),
378            _ => Err(anyhow::anyhow!("Cannot compare {:?} and {:?}", left, right)),
379        }
380    }
381
382    /// Handle property assignment: target.property = value
383    pub fn handle_assign(&mut self, target: &str, property: &str, value: &Value) -> Result<()> {
384        handler::handle_assign(self, target, property, value)
385    }
386
387    /// Extract synth definition from a map
388    pub fn extract_synth_def_from_map(
389        &self,
390        map: &HashMap<String, Value>,
391    ) -> Result<SynthDefinition> {
392        handler::extract_synth_def_from_map(self, map)
393    }
394
395    /// Handle MIDI file loading: @load "path.mid" as alias
396    pub fn handle_load(&mut self, source: &str, alias: &str) -> Result<()> {
397        handler::handle_load(self, source, alias)
398    }
399
400    /// Handle MIDI binding: bind source -> target { options }
401    pub fn handle_bind(&mut self, source: &str, target: &str, options: &Value) -> Result<()> {
402        handler::handle_bind(self, source, target, options)
403    }
404
405    /// Extract pattern string and options from pattern value
406    pub fn extract_pattern_data(
407        &self,
408        value: &Value,
409    ) -> (Option<String>, Option<HashMap<String, f32>>) {
410        handler::extract_pattern_data(self, value)
411    }
412
413    /// Execute a pattern with given target and pattern string
414    pub fn execute_pattern(
415        &mut self,
416        target: &str,
417        pattern: &str,
418        options: Option<HashMap<String, f32>>,
419    ) -> Result<()> {
420        handler::execute_pattern(self, target, pattern, options)
421    }
422
423    /// Resolve sample URI from bank.trigger notation (e.g., myBank.kick -> devalang://bank/devaloop.808/kick)
424    pub fn resolve_sample_uri(&self, target: &str) -> String {
425        handler::resolve_sample_uri(self, target)
426    }
427}