devalang_wasm/engine/audio/interpreter/
driver.rs

1use crate::engine::audio::effects::processors::{
2    DelayProcessor, DriveProcessor, EffectProcessor, ReverbProcessor,
3};
4use crate::engine::audio::events::{
5    AudioEventList, SynthDefinition, extract_filters, extract_number, extract_string,
6};
7use crate::engine::audio::generator::{
8    SynthParams, generate_chord_with_options, generate_note_with_options,
9};
10use crate::engine::events::{EventHandler, EventRegistry};
11use crate::engine::functions::FunctionRegistry;
12use crate::engine::functions::note::parse_note_to_midi;
13use crate::engine::special_vars::{SpecialVarContext, is_special_var, resolve_special_var};
14use crate::language::syntax::ast::{Statement, StatementKind, Value};
15/// Audio interpreter driver - main execution loop
16use anyhow::Result;
17use rayon::prelude::*;
18use std::collections::HashMap;
19
20pub struct AudioInterpreter {
21    pub sample_rate: u32,
22    pub bpm: f32,
23    function_registry: FunctionRegistry,
24    events: AudioEventList,
25    pub(crate) variables: HashMap<String, Value>,
26    groups: HashMap<String, Vec<Statement>>,
27    cursor_time: f32,
28    pub special_vars: SpecialVarContext,
29    pub event_registry: EventRegistry,
30    /// Track current statement location for better error reporting
31    current_statement_location: Option<(usize, usize)>, // (line, column)
32}
33
34impl AudioInterpreter {
35    pub fn new(sample_rate: u32) -> Self {
36        Self {
37            sample_rate,
38            bpm: 120.0,
39            function_registry: FunctionRegistry::new(),
40            events: AudioEventList::new(),
41            variables: HashMap::new(),
42            groups: HashMap::new(),
43            cursor_time: 0.0,
44            special_vars: SpecialVarContext::new(120.0, sample_rate),
45            event_registry: EventRegistry::new(),
46            current_statement_location: None,
47        }
48    }
49
50    pub fn interpret(&mut self, statements: &[Statement]) -> Result<Vec<f32>> {
51        // Initialize special vars context
52        let total_duration = self.calculate_total_duration(statements)?;
53        self.special_vars.total_duration = total_duration;
54        self.special_vars.update_bpm(self.bpm);
55
56        // Phase 1: Collect events
57        self.collect_events(statements)?;
58
59        // Phase 2: Render audio
60        self.render_audio()
61    }
62
63    /// Get reference to collected audio events (for MIDI export)
64    pub fn events(&self) -> &AudioEventList {
65        &self.events
66    }
67
68    /// Get current statement location for error reporting
69    pub fn current_statement_location(&self) -> Option<(usize, usize)> {
70        self.current_statement_location
71    }
72
73    /// Calculate approximate total duration by scanning statements
74    fn calculate_total_duration(&self, _statements: &[Statement]) -> Result<f32> {
75        // For now, return a default duration (will be updated during collect_events)
76        Ok(60.0) // Default 60 seconds
77    }
78
79    pub(crate) fn collect_events(&mut self, statements: &[Statement]) -> Result<()> {
80        // Partition statements: separate spawns from others for parallel execution
81        let (spawns, others): (Vec<_>, Vec<_>) = statements
82            .iter()
83            .partition(|stmt| matches!(stmt.kind, StatementKind::Spawn { .. }));
84
85        // Execute sequential statements first
86        for stmt in &others {
87            // Track current statement location for error reporting
88            self.current_statement_location = Some((stmt.line, stmt.column));
89
90            // Update special variables context with current time
91            self.special_vars.update_time(self.cursor_time);
92
93            match &stmt.kind {
94                StatementKind::Let { name, value } => {
95                    if let Some(val) = value {
96                        self.handle_let(name, val)?;
97                    }
98                }
99
100                StatementKind::ArrowCall {
101                    target,
102                    method,
103                    args,
104                } => {
105                    // Extract chain if present from stmt.value
106                    let chain = if let Value::Map(map) = &stmt.value {
107                        map.get("chain").and_then(|v| {
108                            if let Value::Array(arr) = v {
109                                Some(arr.as_slice())
110                            } else {
111                                None
112                            }
113                        })
114                    } else {
115                        None
116                    };
117
118                    // Execute arrow call with error capture for WASM
119                    let context = super::statements::arrow_call::execute_arrow_call(
120                        &self.function_registry,
121                        target,
122                        method,
123                        args,
124                        chain,
125                        self.cursor_time,
126                        self.bpm,
127                    )
128                    .map_err(|e| {
129                        // Capture error with statement location for WASM debugging
130                        #[cfg(target_arch = "wasm32")]
131                        {
132                            use crate::web::registry::debug;
133                            if debug::is_debug_errors_enabled() {
134                                debug::push_parse_error_from_parts(
135                                    format!("{}", e),
136                                    stmt.line,
137                                    stmt.column,
138                                    "RuntimeError".to_string(),
139                                );
140                            }
141                        }
142                        e
143                    })?;
144
145                    // Extract event from context and add to events list
146                    self.extract_audio_event(target, &context)?;
147
148                    // Update cursor time
149                    self.cursor_time += context.duration;
150                }
151
152                StatementKind::Tempo => {
153                    if let Value::Number(bpm_value) = &stmt.value {
154                        self.set_bpm(*bpm_value);
155                    }
156                }
157
158                StatementKind::Sleep => {
159                    // Extract duration from stmt.value
160                    if let Value::Number(duration) = &stmt.value {
161                        self.cursor_time += duration / 1000.0; // Convert ms to seconds
162                    }
163                }
164
165                StatementKind::Group { name, body } => {
166                    // Store group definition for later execution
167                    self.groups.insert(name.clone(), body.clone());
168                }
169
170                StatementKind::Call { name, args: _ } => {
171                    // Check if this is an inline pattern assignment
172                    if let Value::Map(map) = &stmt.value {
173                        if let Some(Value::Boolean(true)) = map.get("inline_pattern") {
174                            // Execute inline pattern
175                            if let (Some(Value::String(target)), Some(Value::String(pattern))) = 
176                                (map.get("target"), map.get("pattern")) 
177                            {
178                                println!("🎵 Call inline pattern: {} = \"{}\"", target, pattern);
179                                let target = target.clone();
180                                let pattern = pattern.clone();
181                                self.execute_pattern(&target, &pattern, None)?;
182                                continue;
183                            }
184                        }
185                    }
186
187                    // Check if this is a pattern call (not a group)
188                    // Clone the pattern data first to avoid borrow issues
189                    let pattern_data: Option<(String, String, String, Option<HashMap<String, f32>>)> = 
190                        if let Some(pattern_value) = self.variables.get(name) {
191                            if let Value::Statement(stmt_box) = pattern_value {
192                                if let StatementKind::Pattern { target, .. } = &stmt_box.kind {
193                                    if let Some(tgt) = target {
194                                        let (pattern_str, options) = self.extract_pattern_data(&stmt_box.value);
195                                        if let Some(pat) = pattern_str {
196                                            Some((name.clone(), tgt.clone(), pat, options))
197                                        } else {
198                                            None
199                                        }
200                                    } else {
201                                        None
202                                    }
203                                } else {
204                                    None
205                                }
206                            } else {
207                                None
208                            }
209                        } else {
210                            None
211                        };
212
213                    if let Some((pname, target, pattern, options)) = pattern_data {
214                        println!("🎵 Call pattern: {} with {}", pname, target);
215                        self.execute_pattern(&target, &pattern, options)?;
216                        continue;
217                    }
218
219                    // Execute group by name
220                    if let Some(body) = self.groups.get(name).cloned() {
221                        println!("📞 Call group: {}", name);
222                        self.collect_events(&body)?;
223                    } else {
224                        println!("⚠️  Warning: Group or pattern '{}' not found", name);
225                    }
226                }
227
228                StatementKind::Spawn { .. } => {
229                    // Spawns are handled separately in parallel execution below
230                    // This branch should not be reached due to partitioning
231                    unreachable!("Spawn statements should be handled in parallel section");
232                }
233
234                StatementKind::Loop { count, body } => {
235                    // Execute loop N times
236                    self.execute_loop(count, body)?;
237                }
238
239                StatementKind::For {
240                    variable,
241                    iterable,
242                    body,
243                } => {
244                    // Execute for each item in array/range
245                    self.execute_for(variable, iterable, body)?;
246                }
247
248                StatementKind::Print => {
249                    // Execute print with variable interpolation
250                    self.execute_print(&stmt.value)?;
251                }
252
253                StatementKind::If {
254                    condition,
255                    body,
256                    else_body,
257                } => {
258                    // Execute condition and run appropriate branch
259                    self.execute_if(condition, body, else_body)?;
260                }
261
262                StatementKind::On { event, args, body } => {
263                    // Register event handler
264                    let once = args
265                        .as_ref()
266                        .and_then(|a| a.first())
267                        .and_then(|v| {
268                            if let Value::String(s) = v {
269                                Some(s == "once")
270                            } else {
271                                None
272                            }
273                        })
274                        .unwrap_or(false);
275
276                    let handler = EventHandler {
277                        event_name: event.clone(),
278                        body: body.clone(),
279                        once,
280                    };
281
282                    self.event_registry.register_handler(handler);
283                    println!("📡 Event handler registered: {} (once={})", event, once);
284                }
285
286                StatementKind::Emit { event, payload } => {
287                    // Emit event with payload
288                    let data = if let Some(Value::Map(map)) = payload {
289                        map.clone()
290                    } else {
291                        HashMap::new()
292                    };
293
294                    self.event_registry
295                        .emit(event.clone(), data, self.cursor_time);
296
297                    // Execute matching handlers
298                    self.execute_event_handlers(event)?;
299
300                    println!("📤 Event emitted: {}", event);
301                }
302
303                StatementKind::Assign { target, property } => {
304                    // Handle property assignment: target.property = value
305                    self.handle_assign(target, property, &stmt.value)?;
306                }
307
308                StatementKind::Load { source, alias } => {
309                    // Load MIDI file and store in variables
310                    self.handle_load(source, alias)?;
311                }
312
313                StatementKind::Pattern { name, target } => {
314                    // Store pattern definition in variables
315                    // Pattern will be executed when called
316                    let pattern_stmt = Statement {
317                        kind: StatementKind::Pattern {
318                            name: name.clone(),
319                            target: target.clone(),
320                        },
321                        value: stmt.value.clone(),
322                        indent: stmt.indent,
323                        line: stmt.line,
324                        column: stmt.column,
325                    };
326
327                    self.variables
328                        .insert(name.clone(), Value::Statement(Box::new(pattern_stmt)));
329
330                    println!("📝 Pattern defined: {}", name);
331                }
332
333                StatementKind::Bank { name, alias } => {
334                    // Handle bank alias: bank devaloop.808 as kit
335                    // In WASM mode, banks are pre-registered, so we just create an alias
336                    // Look for the bank by name in existing variables
337
338                    let target_alias = alias.clone().unwrap_or_else(|| {
339                        // Default alias: use last part of name (e.g., "devaloop.808" -> "808")
340                        name.split('.').last().unwrap_or(name).to_string()
341                    });
342
343                    // Try to find the bank in variables (it might be registered with a different alias)
344                    let mut bank_found = false;
345
346                    // First check if there's already a variable with the target name
347                    if let Some(existing_value) = self.variables.get(name) {
348                        // Clone the bank data to the new alias
349                        self.variables
350                            .insert(target_alias.clone(), existing_value.clone());
351                        bank_found = true;
352
353                        #[cfg(target_arch = "wasm32")]
354                        web_sys::console::log_1(
355                            &format!("🏦 Bank alias created: {} -> {}", name, target_alias).into(),
356                        );
357                        #[cfg(not(target_arch = "wasm32"))]
358                        println!("🏦 Bank alias created: {} -> {}", name, target_alias);
359                    } else {
360                        // Search for bank by full_name in registered banks (WASM mode)
361                        #[cfg(target_arch = "wasm32")]
362                        {
363                            use crate::web::registry::banks::REGISTERED_BANKS;
364
365                            REGISTERED_BANKS.with(|banks| {
366                                for bank in banks.borrow().iter() {
367                                    if bank.full_name == *name {
368                                        // Found the bank! Get its current alias and clone it
369                                        if let Some(Value::Map(bank_map)) =
370                                            self.variables.get(&bank.alias)
371                                        {
372                                            self.variables.insert(
373                                                target_alias.clone(),
374                                                Value::Map(bank_map.clone()),
375                                            );
376                                            bank_found = true;
377
378                                            web_sys::console::log_1(
379                                                &format!(
380                                                    "🏦 Bank alias created: {} ({}) -> {}",
381                                                    name, bank.alias, target_alias
382                                                )
383                                                .into(),
384                                            );
385                                        }
386                                    }
387                                }
388                            });
389                        }
390                        
391                        // Native mode: check if bank exists in sample registry
392                        #[cfg(not(target_arch = "wasm32"))]
393                        {
394                            use std::collections::HashMap;
395                            
396                            // Create a simple bank map with the bank name
397                            let mut bank_map = HashMap::new();
398                            bank_map.insert("_name".to_string(), Value::String(name.clone()));
399                            bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
400                            
401                            // Store the bank alias
402                            self.variables.insert(target_alias.clone(), Value::Map(bank_map));
403                            bank_found = true;
404                            
405                            println!("🏦 Bank alias created: {} -> {}", name, target_alias);
406                        }
407                    }
408
409                    if !bank_found {
410                        #[cfg(target_arch = "wasm32")]
411                        web_sys::console::warn_1(&format!("⚠️ Bank not found: {}", name).into());
412                        #[cfg(not(target_arch = "wasm32"))]
413                        println!("⚠️ Bank not found: {}", name);
414                    }
415                }
416
417                StatementKind::Bind { source, target } => {
418                    // Bind MIDI data to synth
419                    self.handle_bind(source, target, &stmt.value)?;
420                }
421
422                StatementKind::Trigger {
423                    entity,
424                    duration: _,
425                    effects: _,
426                } => {
427                    // Play a trigger/sample directly: .kit.kick
428                    // Resolve the entity (remove leading dot if present)
429                    let resolved_entity = if entity.starts_with('.') {
430                        &entity[1..]
431                    } else {
432                        entity
433                    };
434
435                    // Check if it's a nested variable (e.g., "kit.kick")
436                    if resolved_entity.contains('.') {
437                        let parts: Vec<&str> = resolved_entity.split('.').collect();
438                        if parts.len() == 2 {
439                            let (var_name, property) = (parts[0], parts[1]);
440
441                            // Try to resolve from variables
442                            if let Some(Value::Map(map)) = self.variables.get(var_name) {
443                                if let Some(Value::String(sample_uri)) = map.get(property) {
444                                    let uri = sample_uri.trim_matches('"').trim_matches('\'');
445
446                                    #[cfg(target_arch = "wasm32")]
447                                    web_sys::console::log_1(
448                                        &format!(
449                                            "🎵 Trigger: {}.{} -> {} at {:.3}s",
450                                            var_name, property, uri, self.cursor_time
451                                        )
452                                        .into(),
453                                    );
454                                    #[cfg(not(target_arch = "wasm32"))]
455                                    println!(
456                                        "🎵 Trigger: {}.{} -> {} at {:.3}s",
457                                        var_name, property, uri, self.cursor_time
458                                    );
459
460                                    // Create and add sample event
461                                    self.events.add_sample_event(
462                                        uri,
463                                        self.cursor_time,
464                                        1.0, // Default velocity
465                                    );
466
467                                    // Advance cursor_time by 1 beat for sequential playback (respects BPM)
468                                    let beat_duration = self.beat_duration();
469                                    self.cursor_time += beat_duration;
470
471                                    #[cfg(target_arch = "wasm32")]
472                                    web_sys::console::log_1(&format!("   Next trigger at {:.3}s (advanced by 1 beat = {:.3}s)", 
473                                        self.cursor_time, beat_duration).into());
474                                }
475                            }
476                        }
477                    } else {
478                        // Check if it's a direct sample/trigger variable
479                        if let Some(Value::String(sample_uri)) = self.variables.get(resolved_entity)
480                        {
481                            let uri = sample_uri.trim_matches('"').trim_matches('\'');
482
483                            #[cfg(target_arch = "wasm32")]
484                            web_sys::console::log_1(
485                                &format!(
486                                    "🎵 Trigger: {} -> {} at {:.3}s",
487                                    resolved_entity, uri, self.cursor_time
488                                )
489                                .into(),
490                            );
491                            #[cfg(not(target_arch = "wasm32"))]
492                            println!(
493                                "🎵 Trigger: {} -> {} at {:.3}s",
494                                resolved_entity, uri, self.cursor_time
495                            );
496
497                            // Create and add sample event
498                            self.events.add_sample_event(
499                                uri,
500                                self.cursor_time,
501                                1.0, // Default velocity
502                            );
503
504                            // Advance cursor_time by 1 beat for sequential playback (respects BPM)
505                            let beat_duration = self.beat_duration();
506                            self.cursor_time += beat_duration;
507
508                            #[cfg(target_arch = "wasm32")]
509                            web_sys::console::log_1(
510                                &format!(
511                                    "   Next trigger at {:.3}s (advanced by 1 beat = {:.3}s)",
512                                    self.cursor_time, beat_duration
513                                )
514                                .into(),
515                            );
516                        }
517                    }
518                }
519
520                _ => {
521                    // Other statements not yet implemented
522                }
523            }
524        }
525
526        // Execute spawns in parallel using rayon
527        if !spawns.is_empty() {
528            println!("🚀 Executing {} spawn(s) in parallel", spawns.len());
529
530            // Capture current state for parallel execution
531            let current_time = self.cursor_time;
532            let current_bpm = self.bpm;
533            let groups_snapshot = self.groups.clone();
534            let variables_snapshot = self.variables.clone();
535            let special_vars_snapshot = self.special_vars.clone();
536
537            // Execute all spawns in parallel and collect results
538            let spawn_results: Vec<Result<AudioEventList>> = spawns
539                .par_iter()
540                .map(|stmt| {
541                    if let StatementKind::Spawn { name, args: _ } = &stmt.kind {
542                        // Resolve variable name (remove leading dot if present)
543                        let resolved_name = if name.starts_with('.') {
544                            &name[1..]
545                        } else {
546                            name
547                        };
548                        // Check if it's a nested variable (e.g., "kit.kick")
549                        if resolved_name.contains('.') {
550                            let parts: Vec<&str> = resolved_name.split('.').collect();
551                            if parts.len() == 2 {
552                                let (var_name, property) = (parts[0], parts[1]);
553                                // Try to resolve from variables
554                                if let Some(Value::Map(map)) = variables_snapshot.get(var_name) {
555                                    if let Some(Value::String(sample_uri)) = map.get(property) {
556                                        println!("🎵 Spawn nested sample: {}.{} -> {}", var_name, property, sample_uri);
557                                        let mut event_list = AudioEventList::new();
558                                        event_list.add_sample_event(
559                                            sample_uri.trim_matches('"').trim_matches('\''),
560                                            current_time,
561                                            1.0, // Default velocity
562                                        );
563                                        return Ok(event_list);
564                                    }
565                                }
566                            }
567                        }
568                        // Check if it's a direct sample/trigger variable
569                        if let Some(sample_value) = variables_snapshot.get(resolved_name) {
570                            if let Value::String(sample_uri) = sample_value {
571                                println!("🎵 Spawn sample: {} -> {}", resolved_name, sample_uri);
572                                // Create a sample playback event
573                                let mut event_list = AudioEventList::new();
574                                event_list.add_sample_event(
575                                    sample_uri.trim_matches('"').trim_matches('\''),
576                                    current_time,
577                                    1.0, // Default velocity
578                                );
579                                return Ok(event_list);
580                            }
581                        }
582                        // Create a new interpreter instance for each spawn (group)
583                        let mut local_interpreter = AudioInterpreter {
584                            sample_rate: self.sample_rate,
585                            bpm: current_bpm,
586                            function_registry: FunctionRegistry::new(), // Create new instance instead of clone
587                            events: AudioEventList::new(),
588                            variables: variables_snapshot.clone(),
589                            groups: groups_snapshot.clone(),
590                            cursor_time: current_time,
591                            special_vars: special_vars_snapshot.clone(),
592                            event_registry: EventRegistry::new(),
593                            current_statement_location: None, // Spawn doesn't need statement tracking
594                        };
595                        // Execute the spawned group
596                        if let Some(body) = groups_snapshot.get(resolved_name) {
597                            println!("🎬 Spawn group: {} (parallel)", resolved_name);
598                            local_interpreter.collect_events(body)?;
599                            Ok(local_interpreter.events)
600                        } else {
601                            println!("⚠️  Warning: Spawn target '{}' not found (neither sample nor group)", resolved_name);
602                            Ok(AudioEventList::new())
603                        }
604                    } else {
605                        Ok(AudioEventList::new())
606                    }
607                })
608                .collect();
609
610            // Merge all spawn results into main event list
611            for result in spawn_results {
612                match result {
613                    Ok(spawn_events) => {
614                        self.events.merge(spawn_events);
615                    }
616                    Err(e) => {
617                        println!("⚠️  Error in spawn execution: {}", e);
618                        // Continue with other spawns even if one fails
619                    }
620                }
621            }
622
623            println!("✅ Parallel spawn execution completed");
624        }
625
626        Ok(())
627    }
628
629    fn handle_let(&mut self, name: &str, value: &Value) -> Result<()> {
630        // Check if this is a synth definition (has waveform parameter)
631        if let Value::Map(map) = value {
632            if map.contains_key("waveform") {
633                // Extract synth parameters
634                let waveform = extract_string(map, "waveform", "sine");
635                let attack = extract_number(map, "attack", 0.01);
636                let decay = extract_number(map, "decay", 0.1);
637                let sustain = extract_number(map, "sustain", 0.7);
638                let release = extract_number(map, "release", 0.2);
639
640                // Extract synth type (pluck, arp, pad, etc.)
641                let synth_type = if let Some(Value::String(t)) = map.get("type") {
642                    // Remove quotes if present (parser includes them)
643                    let clean = t.trim_matches('"').trim_matches('\'');
644                    if clean.is_empty() || clean == "synth" {
645                        None
646                    } else {
647                        Some(clean.to_string())
648                    }
649                } else {
650                    None
651                };
652
653                // Extract filters array
654                let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
655                    extract_filters(filters_arr)
656                } else {
657                    Vec::new()
658                };
659
660                // Extract options (gate, click_amount, rate, etc.)
661                let mut options = std::collections::HashMap::new();
662                for (key, val) in map.iter() {
663                    if ![
664                        "waveform", "attack", "decay", "sustain", "release", "type", "filters",
665                    ]
666                    .contains(&key.as_str())
667                    {
668                        if let Value::Number(n) = val {
669                            options.insert(key.clone(), *n);
670                        }
671                    }
672                }
673
674                let type_info = synth_type
675                    .as_ref()
676                    .map(|t| format!(" [{}]", t))
677                    .unwrap_or_default();
678                let opts_info = if !options.is_empty() {
679                    let opts: Vec<String> = options
680                        .iter()
681                        .map(|(k, v)| format!("{}={:.2}", k, v))
682                        .collect();
683                    format!(" (options: {})", opts.join(", "))
684                } else {
685                    String::new()
686                };
687
688                let synth_def = SynthDefinition {
689                    waveform,
690                    attack,
691                    decay,
692                    sustain,
693                    release,
694                    synth_type,
695                    filters,
696                    options,
697                };
698
699                self.events.add_synth(name.to_string(), synth_def);
700
701                println!("🎹 Synth registered: {}{}{}", name, type_info, opts_info);
702            }
703        }
704
705        // Store variable
706        self.variables.insert(name.to_string(), value.clone());
707        Ok(())
708    }
709
710    fn extract_audio_event(
711        &mut self,
712        target: &str,
713        context: &crate::engine::functions::FunctionContext,
714    ) -> Result<()> {
715        // Check if there's a note event
716        if let Some(Value::String(note_name)) = context.get("note") {
717            let midi = parse_note_to_midi(note_name)?;
718            let duration = if let Some(Value::Number(d)) = context.get("duration") {
719                d / 1000.0 // Convert ms to seconds
720            } else {
721                0.5
722            };
723            let velocity = if let Some(Value::Number(v)) = context.get("velocity") {
724                v / 100.0 // Normalize 0-100 to 0.0-1.0
725            } else {
726                0.8
727            };
728
729            // Extract audio options
730            let pan = if let Some(Value::Number(p)) = context.get("pan") {
731                *p
732            } else {
733                0.0
734            };
735
736            let detune = if let Some(Value::Number(d)) = context.get("detune") {
737                *d
738            } else {
739                0.0
740            };
741
742            let gain = if let Some(Value::Number(g)) = context.get("gain") {
743                *g
744            } else {
745                1.0
746            };
747
748            let attack = if let Some(Value::Number(a)) = context.get("attack") {
749                Some(*a)
750            } else {
751                None
752            };
753
754            let release = if let Some(Value::Number(r)) = context.get("release") {
755                Some(*r)
756            } else {
757                None
758            };
759
760            // Extract effects parameters
761            let delay_time = if let Some(Value::Number(t)) = context.get("delay_time") {
762                Some(*t)
763            } else {
764                None
765            };
766
767            let delay_feedback = if let Some(Value::Number(f)) = context.get("delay_feedback") {
768                Some(*f)
769            } else {
770                None
771            };
772
773            let delay_mix = if let Some(Value::Number(m)) = context.get("delay_mix") {
774                Some(*m)
775            } else {
776                None
777            };
778
779            let reverb_amount = if let Some(Value::Number(a)) = context.get("reverb_amount") {
780                Some(*a)
781            } else {
782                None
783            };
784
785            let drive_amount = if let Some(Value::Number(a)) = context.get("drive_amount") {
786                Some(*a)
787            } else {
788                None
789            };
790
791            let drive_color = if let Some(Value::Number(c)) = context.get("drive_color") {
792                Some(*c)
793            } else {
794                None
795            };
796
797            self.events.add_note_event(
798                target,
799                midi,
800                context.start_time,
801                duration,
802                velocity,
803                pan,
804                detune,
805                gain,
806                attack,
807                release,
808                delay_time,
809                delay_feedback,
810                delay_mix,
811                reverb_amount,
812                drive_amount,
813                drive_color,
814            );
815
816            // Generate playhead events (note on/off)
817            #[cfg(target_arch = "wasm32")]
818            {
819                use crate::web::registry::playhead::{PlayheadEvent, push_event};
820
821                // Note on event
822                push_event(PlayheadEvent {
823                    event_type: "note_on".to_string(),
824                    midi: vec![midi],
825                    time: context.start_time,
826                    velocity,
827                    synth_id: target.to_string(),
828                });
829
830                // Note off event
831                push_event(PlayheadEvent {
832                    event_type: "note_off".to_string(),
833                    midi: vec![midi],
834                    time: context.start_time + duration,
835                    velocity,
836                    synth_id: target.to_string(),
837                });
838            }
839        }
840
841        // Check if there's a chord event
842        if let Some(Value::Array(notes)) = context.get("notes") {
843            let mut midis = Vec::new();
844            for note_val in notes {
845                if let Value::String(note_name) = note_val {
846                    midis.push(parse_note_to_midi(note_name)?);
847                }
848            }
849
850            let duration = if let Some(Value::Number(d)) = context.get("duration") {
851                d / 1000.0
852            } else {
853                0.5
854            };
855            let velocity = if let Some(Value::Number(v)) = context.get("velocity") {
856                v / 100.0
857            } else {
858                0.8
859            };
860
861            // Extract audio options
862            let pan = if let Some(Value::Number(p)) = context.get("pan") {
863                *p
864            } else {
865                0.0
866            };
867
868            let detune = if let Some(Value::Number(d)) = context.get("detune") {
869                *d
870            } else {
871                0.0
872            };
873
874            let spread = if let Some(Value::Number(s)) = context.get("spread") {
875                *s
876            } else {
877                0.0
878            };
879
880            let gain = if let Some(Value::Number(g)) = context.get("gain") {
881                *g
882            } else {
883                1.0
884            };
885
886            let attack = if let Some(Value::Number(a)) = context.get("attack") {
887                Some(*a)
888            } else {
889                None
890            };
891
892            let release = if let Some(Value::Number(r)) = context.get("release") {
893                Some(*r)
894            } else {
895                None
896            };
897
898            // Extract effects parameters
899            let delay_time = if let Some(Value::Number(t)) = context.get("delay_time") {
900                Some(*t)
901            } else {
902                None
903            };
904
905            let delay_feedback = if let Some(Value::Number(f)) = context.get("delay_feedback") {
906                Some(*f)
907            } else {
908                None
909            };
910
911            let delay_mix = if let Some(Value::Number(m)) = context.get("delay_mix") {
912                Some(*m)
913            } else {
914                None
915            };
916
917            let reverb_amount = if let Some(Value::Number(a)) = context.get("reverb_amount") {
918                Some(*a)
919            } else {
920                None
921            };
922
923            let drive_amount = if let Some(Value::Number(a)) = context.get("drive_amount") {
924                Some(*a)
925            } else {
926                None
927            };
928
929            let drive_color = if let Some(Value::Number(c)) = context.get("drive_color") {
930                Some(*c)
931            } else {
932                None
933            };
934
935            self.events.add_chord_event(
936                target,
937                midis.clone(),
938                context.start_time,
939                duration,
940                velocity,
941                pan,
942                detune,
943                spread,
944                gain,
945                attack,
946                release,
947                delay_time,
948                delay_feedback,
949                delay_mix,
950                reverb_amount,
951                drive_amount,
952                drive_color,
953            );
954
955            // Generate playhead events (chord on/off)
956            #[cfg(target_arch = "wasm32")]
957            {
958                use crate::web::registry::playhead::{PlayheadEvent, push_event};
959
960                // Chord on event
961                push_event(PlayheadEvent {
962                    event_type: "chord_on".to_string(),
963                    midi: midis.clone(),
964                    time: context.start_time,
965                    velocity,
966                    synth_id: target.to_string(),
967                });
968
969                // Chord off event
970                push_event(PlayheadEvent {
971                    event_type: "chord_off".to_string(),
972                    midi: midis,
973                    time: context.start_time + duration,
974                    velocity,
975                    synth_id: target.to_string(),
976                });
977            }
978        }
979
980        Ok(())
981    }
982
983    fn render_audio(&self) -> Result<Vec<f32>> {
984        let total_duration = self.events.total_duration();
985        if total_duration <= 0.0 {
986            return Ok(Vec::new());
987        }
988
989        let total_samples = (total_duration * self.sample_rate as f32).ceil() as usize;
990        let mut buffer = vec![0.0f32; total_samples * 2]; // stereo
991
992        // Count events by type
993        let mut _note_count = 0;
994        let mut _chord_count = 0;
995        let mut _sample_count = 0;
996        for event in &self.events.events {
997            match event {
998                crate::engine::audio::events::AudioEvent::Note { .. } => _note_count += 1,
999                crate::engine::audio::events::AudioEvent::Chord { .. } => _chord_count += 1,
1000                crate::engine::audio::events::AudioEvent::Sample { .. } => _sample_count += 1,
1001            }
1002        }
1003
1004        // Render each event
1005        for event in &self.events.events {
1006            match event {
1007                crate::engine::audio::events::AudioEvent::Note {
1008                    midi,
1009                    start_time,
1010                    duration,
1011                    velocity,
1012                    synth_id: _,
1013                    synth_def,
1014                    pan,
1015                    detune,
1016                    gain,
1017                    attack,
1018                    release,
1019                    delay_time,
1020                    delay_feedback,
1021                    delay_mix,
1022                    reverb_amount,
1023                    drive_amount,
1024                    drive_color,
1025                } => {
1026                    // Use captured synth definition from event creation time
1027                    let mut params = SynthParams {
1028                        waveform: synth_def.waveform.clone(),
1029                        attack: synth_def.attack,
1030                        decay: synth_def.decay,
1031                        sustain: synth_def.sustain,
1032                        release: synth_def.release,
1033                        synth_type: synth_def.synth_type.clone(),
1034                        filters: synth_def.filters.clone(),
1035                        options: synth_def.options.clone(),
1036                    };
1037
1038                    // Override attack/release if specified
1039                    if let Some(a) = attack {
1040                        params.attack = a / 1000.0; // Convert ms to seconds
1041                    }
1042                    if let Some(r) = release {
1043                        params.release = r / 1000.0; // Convert ms to seconds
1044                    }
1045
1046                    let mut samples = generate_note_with_options(
1047                        *midi,
1048                        duration * 1000.0,
1049                        velocity * gain, // Apply gain to velocity
1050                        &params,
1051                        self.sample_rate,
1052                        *pan,
1053                        *detune,
1054                    )?;
1055
1056                    // Apply effects in order: Drive -> Reverb -> Delay
1057
1058                    // Apply drive (saturation)
1059                    if let Some(amount) = drive_amount {
1060                        let color = drive_color.unwrap_or(0.5);
1061                        let mix = 0.7; // Drive mix
1062                        let mut processor = DriveProcessor::new(*amount, color, mix);
1063                        processor.process(&mut samples, self.sample_rate);
1064                    }
1065
1066                    // Apply reverb
1067                    if let Some(amount) = reverb_amount {
1068                        let room_size = *amount; // Use amount as room size
1069                        let damping = 0.5; // Default damping
1070                        let mix = *amount * 0.5; // Scale mix with amount
1071                        let mut processor = ReverbProcessor::new(room_size, damping, mix);
1072                        processor.process(&mut samples, self.sample_rate);
1073                    }
1074
1075                    // Apply delay
1076                    if let Some(time) = delay_time {
1077                        let feedback = delay_feedback.unwrap_or(0.3);
1078                        let mix = delay_mix.unwrap_or(0.5);
1079                        let mut processor = DelayProcessor::new(*time, feedback, mix);
1080                        processor.process(&mut samples, self.sample_rate);
1081                    }
1082
1083                    // Mix into buffer
1084                    let start_sample = (*start_time * self.sample_rate as f32) as usize * 2;
1085                    for (i, &sample) in samples.iter().enumerate() {
1086                        let buf_idx = start_sample + i;
1087                        if buf_idx < buffer.len() {
1088                            buffer[buf_idx] += sample;
1089                        }
1090                    }
1091                }
1092
1093                crate::engine::audio::events::AudioEvent::Chord {
1094                    midis,
1095                    start_time,
1096                    duration,
1097                    velocity,
1098                    synth_id: _,
1099                    synth_def,
1100                    pan,
1101                    detune,
1102                    spread,
1103                    gain,
1104                    attack,
1105                    release,
1106                    delay_time,
1107                    delay_feedback,
1108                    delay_mix,
1109                    reverb_amount,
1110                    drive_amount,
1111                    drive_color,
1112                } => {
1113                    // Use captured synth definition from event creation time
1114                    let mut params = SynthParams {
1115                        waveform: synth_def.waveform.clone(),
1116                        attack: synth_def.attack,
1117                        decay: synth_def.decay,
1118                        sustain: synth_def.sustain,
1119                        release: synth_def.release,
1120                        synth_type: synth_def.synth_type.clone(),
1121                        filters: synth_def.filters.clone(),
1122                        options: synth_def.options.clone(),
1123                    };
1124
1125                    // Override attack/release if specified
1126                    if let Some(a) = attack {
1127                        params.attack = a / 1000.0; // Convert ms to seconds
1128                    }
1129                    if let Some(r) = release {
1130                        params.release = r / 1000.0; // Convert ms to seconds
1131                    }
1132
1133                    let mut samples = generate_chord_with_options(
1134                        midis,
1135                        duration * 1000.0,
1136                        velocity * gain, // Apply gain to velocity
1137                        &params,
1138                        self.sample_rate,
1139                        *pan,
1140                        *detune,
1141                        *spread,
1142                    )?;
1143
1144                    // Apply effects in order: Drive -> Reverb -> Delay
1145
1146                    // Apply drive (saturation)
1147                    if let Some(amount) = drive_amount {
1148                        let color = drive_color.unwrap_or(0.5);
1149                        let mix = 0.7; // Drive mix
1150                        let mut processor = DriveProcessor::new(*amount, color, mix);
1151                        processor.process(&mut samples, self.sample_rate);
1152                    }
1153
1154                    // Apply reverb
1155                    if let Some(amount) = reverb_amount {
1156                        let room_size = *amount; // Use amount as room size
1157                        let damping = 0.5; // Default damping
1158                        let mix = *amount * 0.5; // Scale mix with amount
1159                        let mut processor = ReverbProcessor::new(room_size, damping, mix);
1160                        processor.process(&mut samples, self.sample_rate);
1161                    }
1162
1163                    // Apply delay
1164                    if let Some(time) = delay_time {
1165                        let feedback = delay_feedback.unwrap_or(0.3);
1166                        let mix = delay_mix.unwrap_or(0.5);
1167                        let mut processor = DelayProcessor::new(*time, feedback, mix);
1168                        processor.process(&mut samples, self.sample_rate);
1169                    }
1170
1171                    // Mix into buffer
1172                    let start_sample = (*start_time * self.sample_rate as f32) as usize * 2;
1173                    for (i, &sample) in samples.iter().enumerate() {
1174                        let buf_idx = start_sample + i;
1175                        if buf_idx < buffer.len() {
1176                            buffer[buf_idx] += sample;
1177                        }
1178                    }
1179                }
1180
1181                crate::engine::audio::events::AudioEvent::Sample {
1182                    uri,
1183                    start_time,
1184                    velocity,
1185                } => {
1186                    // Get sample PCM data from registry (WASM feature)
1187                    #[cfg(target_arch = "wasm32")]
1188                    {
1189                        use crate::web::registry::samples::get_sample;
1190
1191                        if let Some(pcm_data) = get_sample(uri) {
1192                            // Convert i16 PCM to f32 and apply velocity
1193                            let start_sample_idx = (*start_time * self.sample_rate as f32) as usize;
1194
1195                            web_sys::console::log_1(
1196                                &format!(
1197                                    "🔊 Rendering sample: {} at {:.3}s, {} frames, velocity {:.2}",
1198                                    uri,
1199                                    start_time,
1200                                    pcm_data.len(),
1201                                    velocity
1202                                )
1203                                .into(),
1204                            );
1205                            web_sys::console::log_1(
1206                                &format!(
1207                                    "   Start buffer index: {} (stereo pos: {})",
1208                                    start_sample_idx,
1209                                    start_sample_idx * 2
1210                                )
1211                                .into(),
1212                            );
1213
1214                            // Assume mono samples - duplicate to stereo
1215                            for (i, &pcm_value) in pcm_data.iter().enumerate() {
1216                                // Convert i16 to f32 (-1.0 to 1.0) and apply velocity
1217                                let sample = (pcm_value as f32 / 32768.0) * velocity;
1218
1219                                // Calculate buffer positions for stereo (multiply by 2 because stereo)
1220                                let stereo_pos = (start_sample_idx + i) * 2;
1221                                let buf_idx_l = stereo_pos;
1222                                let buf_idx_r = stereo_pos + 1;
1223
1224                                // Mix into buffer (duplicate mono to both channels)
1225                                if buf_idx_l < buffer.len() {
1226                                    buffer[buf_idx_l] += sample;
1227                                }
1228                                if buf_idx_r < buffer.len() {
1229                                    buffer[buf_idx_r] += sample;
1230                                }
1231                            }
1232                        } else {
1233                            println!("⚠️  Sample not found in registry: {}", uri);
1234                        }
1235                    }
1236
1237                    // For native builds, try to load sample from registry
1238                    #[cfg(not(target_arch = "wasm32"))]
1239                    {
1240                        use crate::engine::audio::samples;
1241                        
1242                        if let Some(sample_data) = samples::get_sample(uri) {
1243                            // Sample found in registry, render it
1244                            let start_sample_idx = (*start_time * self.sample_rate as f32) as usize;
1245                            let velocity_scale = velocity / 100.0;
1246                            
1247                            // Resample if needed
1248                            let resample_ratio = self.sample_rate as f32 / sample_data.sample_rate as f32;
1249                            
1250                            for (i, &sample) in sample_data.samples.iter().enumerate() {
1251                                // Calculate output position considering resampling
1252                                let output_idx = start_sample_idx + (i as f32 * resample_ratio) as usize;
1253                                let stereo_pos = output_idx * 2;
1254                                let buf_idx_l = stereo_pos;
1255                                let buf_idx_r = stereo_pos + 1;
1256                                
1257                                let scaled_sample = sample * velocity_scale;
1258                                
1259                                // Mix into stereo buffer
1260                                if buf_idx_l < buffer.len() {
1261                                    buffer[buf_idx_l] += scaled_sample;
1262                                }
1263                                if buf_idx_r < buffer.len() {
1264                                    buffer[buf_idx_r] += scaled_sample;
1265                                }
1266                            }
1267                        } else {
1268                            // Sample not found in registry - log error
1269                            eprintln!("❌ Error: Bank sample not found: {}", uri);
1270                            eprintln!("   Make sure the bank is loaded and the sample exists.");
1271                            // Note: No audio will be rendered for this sample
1272                        }
1273                    }
1274                }
1275            }
1276        }
1277
1278        // Normalize to prevent clipping
1279        let max_amplitude = buffer.iter().map(|&s| s.abs()).fold(0.0f32, f32::max);
1280        if max_amplitude > 1.0 {
1281            for sample in buffer.iter_mut() {
1282                *sample /= max_amplitude;
1283            }
1284        }
1285
1286        Ok(buffer)
1287    }
1288
1289    pub fn set_bpm(&mut self, bpm: f32) {
1290        self.bpm = bpm.max(1.0).min(999.0);
1291    }
1292
1293    pub fn samples_per_beat(&self) -> usize {
1294        ((60.0 / self.bpm) * self.sample_rate as f32) as usize
1295    }
1296
1297    /// Get duration of one beat in seconds
1298    pub fn beat_duration(&self) -> f32 {
1299        60.0 / self.bpm
1300    }
1301
1302    /// Execute print statement with variable interpolation
1303    /// Supports {variable_name} syntax
1304    fn execute_print(&self, value: &Value) -> Result<()> {
1305        let message = match value {
1306            Value::String(s) => {
1307                // Check if string contains variable interpolation
1308                if s.contains('{') && s.contains('}') {
1309                    self.interpolate_string(s)
1310                } else {
1311                    s.clone()
1312                }
1313            }
1314            Value::Number(n) => n.to_string(),
1315            Value::Boolean(b) => b.to_string(),
1316            Value::Array(arr) => format!("{:?}", arr),
1317            Value::Map(map) => format!("{:?}", map),
1318            _ => format!("{:?}", value),
1319        };
1320
1321        println!("💬 {}", message);
1322        Ok(())
1323    }
1324
1325    /// Interpolate variables in a string
1326    /// Replaces {variable_name} with the variable's value
1327    fn interpolate_string(&self, template: &str) -> String {
1328        let mut result = template.to_string();
1329
1330        // Find all {variable} patterns
1331        let re = regex::Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap();
1332
1333        for cap in re.captures_iter(template) {
1334            let full_match = &cap[0]; // {variable_name}
1335            let var_name = &cap[1]; // variable_name
1336
1337            if let Some(value) = self.variables.get(var_name) {
1338                let replacement = self.value_to_string(value);
1339                result = result.replace(full_match, &replacement);
1340            } else {
1341                // Variable not found, leave placeholder or show error
1342                result = result.replace(full_match, &format!("<undefined:{}>", var_name));
1343            }
1344        }
1345
1346        result
1347    }
1348
1349    /// Convert a Value to a displayable string
1350    fn value_to_string(&self, value: &Value) -> String {
1351        match value {
1352            Value::String(s) => {
1353                // Remove surrounding quotes if present
1354                s.trim_matches('"').trim_matches('\'').to_string()
1355            }
1356            Value::Number(n) => {
1357                // Format nicely: remove trailing zeros
1358                if n.fract() == 0.0 {
1359                    format!("{:.0}", n)
1360                } else {
1361                    format!("{}", n)
1362                }
1363            }
1364            Value::Boolean(b) => b.to_string(),
1365            Value::Array(arr) => {
1366                let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
1367                format!("[{}]", items.join(", "))
1368            }
1369            Value::Identifier(id) => id.clone(),
1370            _ => format!("{:?}", value),
1371        }
1372    }
1373
1374    /// Execute if statement with condition evaluation
1375    fn execute_if(
1376        &mut self,
1377        condition: &Value,
1378        body: &[Statement],
1379        else_body: &Option<Vec<Statement>>,
1380    ) -> Result<()> {
1381        let condition_result = self.evaluate_condition(condition)?;
1382
1383        if condition_result {
1384            // Condition is true, execute if body
1385            self.collect_events(body)?;
1386        } else if let Some(else_stmts) = else_body {
1387            // Condition is false, execute else body
1388            self.collect_events(else_stmts)?;
1389        }
1390
1391        Ok(())
1392    }
1393
1394    /// Evaluate a condition to a boolean
1395    /// Supports: ==, !=, <, >, <=, >=
1396    fn evaluate_condition(&self, condition: &Value) -> Result<bool> {
1397        // Condition is stored as a Map with operator and operands
1398        if let Value::Map(map) = condition {
1399            let operator = map
1400                .get("operator")
1401                .and_then(|v| {
1402                    if let Value::String(s) = v {
1403                        Some(s.as_str())
1404                    } else {
1405                        None
1406                    }
1407                })
1408                .unwrap_or("==");
1409
1410            let left = map
1411                .get("left")
1412                .ok_or_else(|| anyhow::anyhow!("Missing left operand"))?;
1413            let right = map
1414                .get("right")
1415                .ok_or_else(|| anyhow::anyhow!("Missing right operand"))?;
1416
1417            // Resolve values (replace identifiers with their values)
1418            let left_val = self.resolve_value(left);
1419            let right_val = self.resolve_value(right);
1420
1421            // Compare based on operator
1422            match operator {
1423                "==" => Ok(self.values_equal(&left_val, &right_val)),
1424                "!=" => Ok(!self.values_equal(&left_val, &right_val)),
1425                "<" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less),
1426                ">" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater),
1427                "<=" => {
1428                    let less =
1429                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less)?;
1430                    let equal = self.values_equal(&left_val, &right_val);
1431                    Ok(less || equal)
1432                }
1433                ">=" => {
1434                    let greater =
1435                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater)?;
1436                    let equal = self.values_equal(&left_val, &right_val);
1437                    Ok(greater || equal)
1438                }
1439                _ => Err(anyhow::anyhow!("Unknown operator: {}", operator)),
1440            }
1441        } else {
1442            // Direct boolean value
1443            match condition {
1444                Value::Boolean(b) => Ok(*b),
1445                Value::Number(n) => Ok(*n != 0.0),
1446                Value::String(s) => Ok(!s.is_empty()),
1447                _ => Ok(false),
1448            }
1449        }
1450    }
1451
1452    /// Resolve a value (replace identifiers with their values from variables)
1453    fn resolve_value(&self, value: &Value) -> Value {
1454        match value {
1455            Value::Identifier(name) => {
1456                // Check if it's a special variable
1457                if is_special_var(name) {
1458                    if let Some(special_val) = resolve_special_var(name, &self.special_vars) {
1459                        return special_val;
1460                    }
1461                }
1462
1463                // Otherwise, look in variables
1464                self.variables.get(name).cloned().unwrap_or(Value::Null)
1465            }
1466            _ => value.clone(),
1467        }
1468    }
1469
1470    /// Execute event handlers matching the event name
1471    fn execute_event_handlers(&mut self, event_name: &str) -> Result<()> {
1472        let handlers = self.event_registry.get_handlers_matching(event_name);
1473
1474        for (index, handler) in handlers.iter().enumerate() {
1475            // Check if this is a "once" handler that has already been executed
1476            if handler.once && !self.event_registry.should_execute_once(event_name, index) {
1477                continue;
1478            }
1479
1480            // Execute the handler body
1481            let body_clone = handler.body.clone();
1482            self.collect_events(&body_clone)?;
1483        }
1484
1485        Ok(())
1486    }
1487
1488    /// Check if two values are equal
1489    fn values_equal(&self, left: &Value, right: &Value) -> bool {
1490        match (left, right) {
1491            (Value::Number(a), Value::Number(b)) => (a - b).abs() < 0.0001,
1492            (Value::String(a), Value::String(b)) => a == b,
1493            (Value::Boolean(a), Value::Boolean(b)) => a == b,
1494            (Value::Null, Value::Null) => true,
1495            _ => false,
1496        }
1497    }
1498
1499    /// Compare two values
1500    fn compare_values(
1501        &self,
1502        left: &Value,
1503        right: &Value,
1504        ordering: std::cmp::Ordering,
1505    ) -> Result<bool> {
1506        match (left, right) {
1507            (Value::Number(a), Value::Number(b)) => {
1508                Ok(a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) == ordering)
1509            }
1510            (Value::String(a), Value::String(b)) => Ok(a.cmp(b) == ordering),
1511            _ => Err(anyhow::anyhow!("Cannot compare {:?} and {:?}", left, right)),
1512        }
1513    }
1514
1515    /// Handle property assignment: target.property = value
1516    fn handle_assign(&mut self, target: &str, property: &str, value: &Value) -> Result<()> {
1517        // Get the variable (which should be a Map for synth definitions)
1518        if let Some(var) = self.variables.get_mut(target) {
1519            if let Value::Map(map) = var {
1520                // Update the property in the map
1521                map.insert(property.to_string(), value.clone());
1522
1523                // If this is a synth and the property affects the definition, update it
1524                if self.events.synths.contains_key(target) {
1525                    // Clone the map to avoid borrow issues
1526                    let map_clone = map.clone();
1527                    // Update synth definition
1528                    let updated_def = self.extract_synth_def_from_map(&map_clone)?;
1529                    self.events.synths.insert(target.to_string(), updated_def);
1530                    println!("🔧 Updated {}.{} = {:?}", target, property, value);
1531                }
1532            } else {
1533                return Err(anyhow::anyhow!(
1534                    "Cannot assign property '{}' to non-map variable '{}'",
1535                    property,
1536                    target
1537                ));
1538            }
1539        } else {
1540            return Err(anyhow::anyhow!("Variable '{}' not found", target));
1541        }
1542
1543        Ok(())
1544    }
1545
1546    /// Extract synth definition from a map
1547    fn extract_synth_def_from_map(&self, map: &HashMap<String, Value>) -> Result<SynthDefinition> {
1548        use crate::engine::audio::events::extract_filters;
1549
1550        let waveform = extract_string(map, "waveform", "sine");
1551        let attack = extract_number(map, "attack", 0.01);
1552        let decay = extract_number(map, "decay", 0.1);
1553        let sustain = extract_number(map, "sustain", 0.7);
1554        let release = extract_number(map, "release", 0.2);
1555
1556        let synth_type = if let Some(Value::String(t)) = map.get("type") {
1557            let clean = t.trim_matches('"').trim_matches('\'');
1558            if clean.is_empty() || clean == "synth" {
1559                None
1560            } else {
1561                Some(clean.to_string())
1562            }
1563        } else {
1564            None
1565        };
1566
1567        let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
1568            extract_filters(filters_arr)
1569        } else {
1570            Vec::new()
1571        };
1572
1573        let mut options = HashMap::new();
1574        for (key, val) in map.iter() {
1575            if ![
1576                "waveform", "attack", "decay", "sustain", "release", "type", "filters",
1577            ]
1578            .contains(&key.as_str())
1579            {
1580                if let Value::Number(n) = val {
1581                    options.insert(key.clone(), *n);
1582                }
1583            }
1584        }
1585
1586        Ok(SynthDefinition {
1587            waveform,
1588            attack,
1589            decay,
1590            sustain,
1591            release,
1592            synth_type,
1593            filters,
1594            options,
1595        })
1596    }
1597
1598    /// Handle MIDI file loading: @load "path.mid" as alias
1599    fn handle_load(&mut self, source: &str, alias: &str) -> Result<()> {
1600        use crate::engine::audio::midi::load_midi_file;
1601        use std::path::Path;
1602
1603        // Load the MIDI file
1604        let path = Path::new(source);
1605        let midi_data = load_midi_file(path)?;
1606
1607        // Store in variables
1608        self.variables.insert(alias.to_string(), midi_data);
1609
1610        println!("🎵 Loaded MIDI file: {} as {}", source, alias);
1611
1612        Ok(())
1613    }
1614
1615    /// Handle MIDI binding: bind source -> target { options }
1616    fn handle_bind(&mut self, source: &str, target: &str, options: &Value) -> Result<()> {
1617        // Get MIDI data from variables
1618        let midi_data = self
1619            .variables
1620            .get(source)
1621            .ok_or_else(|| anyhow::anyhow!("MIDI source '{}' not found", source))?
1622            .clone();
1623
1624        // Extract notes from MIDI data
1625        if let Value::Map(midi_map) = &midi_data {
1626            let notes = midi_map
1627                .get("notes")
1628                .ok_or_else(|| anyhow::anyhow!("MIDI data has no notes"))?;
1629
1630            if let Value::Array(notes_array) = notes {
1631                // Get synth definition (for validation)
1632                let _synth_def = self
1633                    .events
1634                    .synths
1635                    .get(target)
1636                    .ok_or_else(|| anyhow::anyhow!("Synth '{}' not found", target))?
1637                    .clone();
1638
1639                // Extract options (velocity, bpm, etc.)
1640                let default_velocity = 100;
1641                let mut velocity = default_velocity;
1642
1643                if let Value::Map(opts) = options {
1644                    if let Some(Value::Number(v)) = opts.get("velocity") {
1645                        velocity = *v as u8;
1646                    }
1647                }
1648
1649                // Generate events for each note
1650                for note_val in notes_array {
1651                    if let Value::Map(note_map) = note_val {
1652                        let time = extract_number(note_map, "time", 0.0);
1653                        let note = extract_number(note_map, "note", 60.0) as u8;
1654                        let note_velocity =
1655                            extract_number(note_map, "velocity", velocity as f32) as u8;
1656
1657                        // Create audio event
1658                        use crate::engine::audio::events::AudioEvent;
1659                        // Capture synth definition snapshot
1660                        let synth_def = self.events.get_synth(target).cloned().unwrap_or_default();
1661                        
1662                        let event = AudioEvent::Note {
1663                            midi: note,
1664                            start_time: time / 1000.0, // Convert from ticks to seconds (simplified)
1665                            duration: 0.5,             // Default duration, could be in options
1666                            velocity: note_velocity as f32,
1667                            synth_id: target.to_string(),
1668                            synth_def,
1669                            pan: 0.0,
1670                            detune: 0.0,
1671                            gain: 1.0,
1672                            attack: None,
1673                            release: None,
1674                            delay_time: None,
1675                            delay_feedback: None,
1676                            delay_mix: None,
1677                            reverb_amount: None,
1678                            drive_amount: None,
1679                            drive_color: None,
1680                        };
1681
1682                        self.events.events.push(event);
1683                    }
1684                }
1685
1686                println!(
1687                    "🔗 Bound {} notes from {} to {}",
1688                    notes_array.len(),
1689                    source,
1690                    target
1691                );
1692            }
1693        }
1694
1695        Ok(())
1696    }
1697
1698    /// Extract pattern string and options from pattern value
1699    fn extract_pattern_data(&self, value: &Value) -> (Option<String>, Option<HashMap<String, f32>>) {
1700        match value {
1701            Value::String(pattern) => (Some(pattern.clone()), None),
1702            Value::Map(map) => {
1703                let pattern = map.get("pattern").and_then(|v| {
1704                    if let Value::String(s) = v {
1705                        Some(s.clone())
1706                    } else {
1707                        None
1708                    }
1709                });
1710
1711                // Extract numeric options (swing, humanize, velocity, tempo)
1712                let mut options = HashMap::new();
1713                for (key, val) in map.iter() {
1714                    if key != "pattern" {
1715                        if let Value::Number(num) = val {
1716                            options.insert(key.clone(), *num);
1717                        }
1718                    }
1719                }
1720
1721                let opts = if options.is_empty() {
1722                    None
1723                } else {
1724                    Some(options)
1725                };
1726
1727                (pattern, opts)
1728            }
1729            _ => (None, None),
1730        }
1731    }
1732
1733    /// Execute a pattern with given target and pattern string
1734    fn execute_pattern(
1735        &mut self,
1736        target: &str,
1737        pattern: &str,
1738        options: Option<HashMap<String, f32>>,
1739    ) -> Result<()> {
1740        use crate::engine::audio::events::AudioEvent;
1741
1742        // Extract options or use defaults
1743        let swing = options.as_ref().and_then(|o| o.get("swing").copied()).unwrap_or(0.0);
1744        let humanize = options.as_ref().and_then(|o| o.get("humanize").copied()).unwrap_or(0.0);
1745        let velocity_mult = options.as_ref().and_then(|o| o.get("velocity").copied()).unwrap_or(1.0);
1746        let tempo_override = options.as_ref().and_then(|o| o.get("tempo").copied());
1747
1748        // Use tempo override or default BPM
1749        let effective_bpm = tempo_override.unwrap_or(self.bpm);
1750
1751        // Resolve target URI (e.g., myBank.kick -> devalang://bank/devaloop.808/kick)
1752        let resolved_uri = self.resolve_sample_uri(target);
1753
1754        // Remove whitespace from pattern
1755        let pattern_chars: Vec<char> = pattern.chars().filter(|c| !c.is_whitespace()).collect();
1756        let step_count = pattern_chars.len() as f32;
1757
1758        if step_count == 0.0 {
1759            return Ok(());
1760        }
1761
1762        // Calculate step duration based on BPM
1763        // Assume 4 beats per bar (4/4 time signature)
1764        let bar_duration = (60.0 / effective_bpm) * 4.0;
1765        let step_duration = bar_duration / step_count;
1766
1767        // Generate trigger events for each step
1768        for (i, &ch) in pattern_chars.iter().enumerate() {
1769            if ch == 'x' || ch == 'X' {
1770                // Calculate time with swing
1771                let mut time = self.cursor_time + (i as f32 * step_duration);
1772
1773                // Apply swing to every other step
1774                if swing > 0.0 && i % 2 == 1 {
1775                    time += step_duration * swing;
1776                }
1777
1778                // Apply humanization (random timing offset)
1779                if humanize > 0.0 {
1780                    use rand::Rng;
1781                    let mut rng = rand::thread_rng();
1782                    let offset = rng.gen_range(-humanize..humanize);
1783                    time += offset;
1784                }
1785
1786                // Create sample event for trigger
1787                let event = AudioEvent::Sample {
1788                    uri: resolved_uri.clone(),
1789                    start_time: time,
1790                    velocity: 100.0 * velocity_mult,
1791                };
1792
1793                self.events.events.push(event);
1794            }
1795        }
1796
1797        // Update cursor time to end of pattern
1798        self.cursor_time += bar_duration;
1799
1800        Ok(())
1801    }
1802
1803    /// Resolve sample URI from bank.trigger notation (e.g., myBank.kick -> devalang://bank/devaloop.808/kick)
1804    fn resolve_sample_uri(&self, target: &str) -> String {
1805        // Split target by '.' to get bank_alias and trigger_name
1806        if let Some(dot_pos) = target.find('.') {
1807            let bank_alias = &target[..dot_pos];
1808            let trigger_name = &target[dot_pos + 1..];
1809
1810            // Look up bank_alias in variables to get actual bank name
1811            if let Some(Value::Map(bank_map)) = self.variables.get(bank_alias) {
1812                if let Some(Value::String(bank_name)) = bank_map.get("_name") {
1813                    // Construct the devalang URI
1814                    return format!("devalang://bank/{}/{}", bank_name, trigger_name);
1815                }
1816            }
1817        }
1818
1819        // Fallback: return original target if resolution fails
1820        target.to_string()
1821    }
1822}
1823