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                    // Execute group by name
172                    if let Some(body) = self.groups.get(name).cloned() {
173                        println!("📞 Call: {}", name);
174                        self.collect_events(&body)?;
175                    } else {
176                        println!("⚠️  Warning: Group '{}' not found", name);
177                    }
178                }
179
180                StatementKind::Spawn { .. } => {
181                    // Spawns are handled separately in parallel execution below
182                    // This branch should not be reached due to partitioning
183                    unreachable!("Spawn statements should be handled in parallel section");
184                }
185
186                StatementKind::Loop { count, body } => {
187                    // Execute loop N times
188                    self.execute_loop(count, body)?;
189                }
190
191                StatementKind::For {
192                    variable,
193                    iterable,
194                    body,
195                } => {
196                    // Execute for each item in array/range
197                    self.execute_for(variable, iterable, body)?;
198                }
199
200                StatementKind::Print => {
201                    // Execute print with variable interpolation
202                    self.execute_print(&stmt.value)?;
203                }
204
205                StatementKind::If {
206                    condition,
207                    body,
208                    else_body,
209                } => {
210                    // Execute condition and run appropriate branch
211                    self.execute_if(condition, body, else_body)?;
212                }
213
214                StatementKind::On { event, args, body } => {
215                    // Register event handler
216                    let once = args
217                        .as_ref()
218                        .and_then(|a| a.first())
219                        .and_then(|v| {
220                            if let Value::String(s) = v {
221                                Some(s == "once")
222                            } else {
223                                None
224                            }
225                        })
226                        .unwrap_or(false);
227
228                    let handler = EventHandler {
229                        event_name: event.clone(),
230                        body: body.clone(),
231                        once,
232                    };
233
234                    self.event_registry.register_handler(handler);
235                    println!("📡 Event handler registered: {} (once={})", event, once);
236                }
237
238                StatementKind::Emit { event, payload } => {
239                    // Emit event with payload
240                    let data = if let Some(Value::Map(map)) = payload {
241                        map.clone()
242                    } else {
243                        HashMap::new()
244                    };
245
246                    self.event_registry
247                        .emit(event.clone(), data, self.cursor_time);
248
249                    // Execute matching handlers
250                    self.execute_event_handlers(event)?;
251
252                    println!("📤 Event emitted: {}", event);
253                }
254
255                StatementKind::Assign { target, property } => {
256                    // Handle property assignment: target.property = value
257                    self.handle_assign(target, property, &stmt.value)?;
258                }
259
260                StatementKind::Load { source, alias } => {
261                    // Load MIDI file and store in variables
262                    self.handle_load(source, alias)?;
263                }
264
265                StatementKind::Bank { name, alias } => {
266                    // Handle bank alias: bank devaloop.808 as kit
267                    // In WASM mode, banks are pre-registered, so we just create an alias
268                    // Look for the bank by name in existing variables
269
270                    let target_alias = alias.clone().unwrap_or_else(|| {
271                        // Default alias: use last part of name (e.g., "devaloop.808" -> "808")
272                        name.split('.').last().unwrap_or(name).to_string()
273                    });
274
275                    // Try to find the bank in variables (it might be registered with a different alias)
276                    let mut bank_found = false;
277
278                    // First check if there's already a variable with the target name
279                    if let Some(existing_value) = self.variables.get(name) {
280                        // Clone the bank data to the new alias
281                        self.variables
282                            .insert(target_alias.clone(), existing_value.clone());
283                        bank_found = true;
284
285                        #[cfg(target_arch = "wasm32")]
286                        web_sys::console::log_1(
287                            &format!("🏦 Bank alias created: {} -> {}", name, target_alias).into(),
288                        );
289                        #[cfg(not(target_arch = "wasm32"))]
290                        println!("🏦 Bank alias created: {} -> {}", name, target_alias);
291                    } else {
292                        // Search for bank by full_name in registered banks (WASM mode)
293                        #[cfg(target_arch = "wasm32")]
294                        {
295                            use crate::web::registry::banks::REGISTERED_BANKS;
296
297                            REGISTERED_BANKS.with(|banks| {
298                                for bank in banks.borrow().iter() {
299                                    if bank.full_name == *name {
300                                        // Found the bank! Get its current alias and clone it
301                                        if let Some(Value::Map(bank_map)) =
302                                            self.variables.get(&bank.alias)
303                                        {
304                                            self.variables.insert(
305                                                target_alias.clone(),
306                                                Value::Map(bank_map.clone()),
307                                            );
308                                            bank_found = true;
309
310                                            web_sys::console::log_1(
311                                                &format!(
312                                                    "🏦 Bank alias created: {} ({}) -> {}",
313                                                    name, bank.alias, target_alias
314                                                )
315                                                .into(),
316                                            );
317                                        }
318                                    }
319                                }
320                            });
321                        }
322                    }
323
324                    if !bank_found {
325                        #[cfg(target_arch = "wasm32")]
326                        web_sys::console::warn_1(&format!("⚠️ Bank not found: {}", name).into());
327                        #[cfg(not(target_arch = "wasm32"))]
328                        println!("⚠️ Bank not found: {}", name);
329                    }
330                }
331
332                StatementKind::Bind { source, target } => {
333                    // Bind MIDI data to synth
334                    self.handle_bind(source, target, &stmt.value)?;
335                }
336
337                StatementKind::Trigger {
338                    entity,
339                    duration: _,
340                    effects: _,
341                } => {
342                    // Play a trigger/sample directly: .kit.kick
343                    // Resolve the entity (remove leading dot if present)
344                    let resolved_entity = if entity.starts_with('.') {
345                        &entity[1..]
346                    } else {
347                        entity
348                    };
349
350                    // Check if it's a nested variable (e.g., "kit.kick")
351                    if resolved_entity.contains('.') {
352                        let parts: Vec<&str> = resolved_entity.split('.').collect();
353                        if parts.len() == 2 {
354                            let (var_name, property) = (parts[0], parts[1]);
355
356                            // Try to resolve from variables
357                            if let Some(Value::Map(map)) = self.variables.get(var_name) {
358                                if let Some(Value::String(sample_uri)) = map.get(property) {
359                                    let uri = sample_uri.trim_matches('"').trim_matches('\'');
360
361                                    #[cfg(target_arch = "wasm32")]
362                                    web_sys::console::log_1(
363                                        &format!(
364                                            "🎵 Trigger: {}.{} -> {} at {:.3}s",
365                                            var_name, property, uri, self.cursor_time
366                                        )
367                                        .into(),
368                                    );
369                                    #[cfg(not(target_arch = "wasm32"))]
370                                    println!(
371                                        "🎵 Trigger: {}.{} -> {} at {:.3}s",
372                                        var_name, property, uri, self.cursor_time
373                                    );
374
375                                    // Create and add sample event
376                                    self.events.add_sample_event(
377                                        uri,
378                                        self.cursor_time,
379                                        1.0, // Default velocity
380                                    );
381
382                                    // Advance cursor_time by 1 beat for sequential playback (respects BPM)
383                                    let beat_duration = self.beat_duration();
384                                    self.cursor_time += beat_duration;
385
386                                    #[cfg(target_arch = "wasm32")]
387                                    web_sys::console::log_1(&format!("   Next trigger at {:.3}s (advanced by 1 beat = {:.3}s)", 
388                                        self.cursor_time, beat_duration).into());
389                                }
390                            }
391                        }
392                    } else {
393                        // Check if it's a direct sample/trigger variable
394                        if let Some(Value::String(sample_uri)) = self.variables.get(resolved_entity)
395                        {
396                            let uri = sample_uri.trim_matches('"').trim_matches('\'');
397
398                            #[cfg(target_arch = "wasm32")]
399                            web_sys::console::log_1(
400                                &format!(
401                                    "🎵 Trigger: {} -> {} at {:.3}s",
402                                    resolved_entity, uri, self.cursor_time
403                                )
404                                .into(),
405                            );
406                            #[cfg(not(target_arch = "wasm32"))]
407                            println!(
408                                "🎵 Trigger: {} -> {} at {:.3}s",
409                                resolved_entity, uri, self.cursor_time
410                            );
411
412                            // Create and add sample event
413                            self.events.add_sample_event(
414                                uri,
415                                self.cursor_time,
416                                1.0, // Default velocity
417                            );
418
419                            // Advance cursor_time by 1 beat for sequential playback (respects BPM)
420                            let beat_duration = self.beat_duration();
421                            self.cursor_time += beat_duration;
422
423                            #[cfg(target_arch = "wasm32")]
424                            web_sys::console::log_1(
425                                &format!(
426                                    "   Next trigger at {:.3}s (advanced by 1 beat = {:.3}s)",
427                                    self.cursor_time, beat_duration
428                                )
429                                .into(),
430                            );
431                        }
432                    }
433                }
434
435                _ => {
436                    // Other statements not yet implemented
437                }
438            }
439        }
440
441        // Execute spawns in parallel using rayon
442        if !spawns.is_empty() {
443            println!("🚀 Executing {} spawn(s) in parallel", spawns.len());
444
445            // Capture current state for parallel execution
446            let current_time = self.cursor_time;
447            let current_bpm = self.bpm;
448            let groups_snapshot = self.groups.clone();
449            let variables_snapshot = self.variables.clone();
450            let special_vars_snapshot = self.special_vars.clone();
451
452            // Execute all spawns in parallel and collect results
453            let spawn_results: Vec<Result<AudioEventList>> = spawns
454                .par_iter()
455                .map(|stmt| {
456                    if let StatementKind::Spawn { name, args: _ } = &stmt.kind {
457                        // Resolve variable name (remove leading dot if present)
458                        let resolved_name = if name.starts_with('.') {
459                            &name[1..]
460                        } else {
461                            name
462                        };
463                        // Check if it's a nested variable (e.g., "kit.kick")
464                        if resolved_name.contains('.') {
465                            let parts: Vec<&str> = resolved_name.split('.').collect();
466                            if parts.len() == 2 {
467                                let (var_name, property) = (parts[0], parts[1]);
468                                // Try to resolve from variables
469                                if let Some(Value::Map(map)) = variables_snapshot.get(var_name) {
470                                    if let Some(Value::String(sample_uri)) = map.get(property) {
471                                        println!("🎵 Spawn nested sample: {}.{} -> {}", var_name, property, sample_uri);
472                                        let mut event_list = AudioEventList::new();
473                                        event_list.add_sample_event(
474                                            sample_uri.trim_matches('"').trim_matches('\''),
475                                            current_time,
476                                            1.0, // Default velocity
477                                        );
478                                        return Ok(event_list);
479                                    }
480                                }
481                            }
482                        }
483                        // Check if it's a direct sample/trigger variable
484                        if let Some(sample_value) = variables_snapshot.get(resolved_name) {
485                            if let Value::String(sample_uri) = sample_value {
486                                println!("🎵 Spawn sample: {} -> {}", resolved_name, sample_uri);
487                                // Create a sample playback event
488                                let mut event_list = AudioEventList::new();
489                                event_list.add_sample_event(
490                                    sample_uri.trim_matches('"').trim_matches('\''),
491                                    current_time,
492                                    1.0, // Default velocity
493                                );
494                                return Ok(event_list);
495                            }
496                        }
497                        // Create a new interpreter instance for each spawn (group)
498                        let mut local_interpreter = AudioInterpreter {
499                            sample_rate: self.sample_rate,
500                            bpm: current_bpm,
501                            function_registry: FunctionRegistry::new(), // Create new instance instead of clone
502                            events: AudioEventList::new(),
503                            variables: variables_snapshot.clone(),
504                            groups: groups_snapshot.clone(),
505                            cursor_time: current_time,
506                            special_vars: special_vars_snapshot.clone(),
507                            event_registry: EventRegistry::new(),
508                            current_statement_location: None, // Spawn doesn't need statement tracking
509                        };
510                        // Execute the spawned group
511                        if let Some(body) = groups_snapshot.get(resolved_name) {
512                            println!("🎬 Spawn group: {} (parallel)", resolved_name);
513                            local_interpreter.collect_events(body)?;
514                            Ok(local_interpreter.events)
515                        } else {
516                            println!("⚠️  Warning: Spawn target '{}' not found (neither sample nor group)", resolved_name);
517                            Ok(AudioEventList::new())
518                        }
519                    } else {
520                        Ok(AudioEventList::new())
521                    }
522                })
523                .collect();
524
525            // Merge all spawn results into main event list
526            for result in spawn_results {
527                match result {
528                    Ok(spawn_events) => {
529                        self.events.merge(spawn_events);
530                    }
531                    Err(e) => {
532                        println!("⚠️  Error in spawn execution: {}", e);
533                        // Continue with other spawns even if one fails
534                    }
535                }
536            }
537
538            println!("✅ Parallel spawn execution completed");
539        }
540
541        Ok(())
542    }
543
544    fn handle_let(&mut self, name: &str, value: &Value) -> Result<()> {
545        // Check if this is a synth definition (has waveform parameter)
546        if let Value::Map(map) = value {
547            if map.contains_key("waveform") {
548                // Extract synth parameters
549                let waveform = extract_string(map, "waveform", "sine");
550                let attack = extract_number(map, "attack", 0.01);
551                let decay = extract_number(map, "decay", 0.1);
552                let sustain = extract_number(map, "sustain", 0.7);
553                let release = extract_number(map, "release", 0.2);
554
555                // Extract synth type (pluck, arp, pad, etc.)
556                let synth_type = if let Some(Value::String(t)) = map.get("type") {
557                    // Remove quotes if present (parser includes them)
558                    let clean = t.trim_matches('"').trim_matches('\'');
559                    if clean.is_empty() || clean == "synth" {
560                        None
561                    } else {
562                        Some(clean.to_string())
563                    }
564                } else {
565                    None
566                };
567
568                // Extract filters array
569                let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
570                    extract_filters(filters_arr)
571                } else {
572                    Vec::new()
573                };
574
575                // Extract options (gate, click_amount, rate, etc.)
576                let mut options = std::collections::HashMap::new();
577                for (key, val) in map.iter() {
578                    if ![
579                        "waveform", "attack", "decay", "sustain", "release", "type", "filters",
580                    ]
581                    .contains(&key.as_str())
582                    {
583                        if let Value::Number(n) = val {
584                            options.insert(key.clone(), *n);
585                        }
586                    }
587                }
588
589                let type_info = synth_type
590                    .as_ref()
591                    .map(|t| format!(" [{}]", t))
592                    .unwrap_or_default();
593                let opts_info = if !options.is_empty() {
594                    let opts: Vec<String> = options
595                        .iter()
596                        .map(|(k, v)| format!("{}={:.2}", k, v))
597                        .collect();
598                    format!(" (options: {})", opts.join(", "))
599                } else {
600                    String::new()
601                };
602
603                let synth_def = SynthDefinition {
604                    waveform,
605                    attack,
606                    decay,
607                    sustain,
608                    release,
609                    synth_type,
610                    filters,
611                    options,
612                };
613
614                self.events.add_synth(name.to_string(), synth_def);
615
616                println!("🎹 Synth registered: {}{}{}", name, type_info, opts_info);
617            }
618        }
619
620        // Store variable
621        self.variables.insert(name.to_string(), value.clone());
622        Ok(())
623    }
624
625    fn extract_audio_event(
626        &mut self,
627        target: &str,
628        context: &crate::engine::functions::FunctionContext,
629    ) -> Result<()> {
630        // Check if there's a note event
631        if let Some(Value::String(note_name)) = context.get("note") {
632            let midi = parse_note_to_midi(note_name)?;
633            let duration = if let Some(Value::Number(d)) = context.get("duration") {
634                d / 1000.0 // Convert ms to seconds
635            } else {
636                0.5
637            };
638            let velocity = if let Some(Value::Number(v)) = context.get("velocity") {
639                v / 100.0 // Normalize 0-100 to 0.0-1.0
640            } else {
641                0.8
642            };
643
644            // Extract audio options
645            let pan = if let Some(Value::Number(p)) = context.get("pan") {
646                *p
647            } else {
648                0.0
649            };
650
651            let detune = if let Some(Value::Number(d)) = context.get("detune") {
652                *d
653            } else {
654                0.0
655            };
656
657            let gain = if let Some(Value::Number(g)) = context.get("gain") {
658                *g
659            } else {
660                1.0
661            };
662
663            let attack = if let Some(Value::Number(a)) = context.get("attack") {
664                Some(*a)
665            } else {
666                None
667            };
668
669            let release = if let Some(Value::Number(r)) = context.get("release") {
670                Some(*r)
671            } else {
672                None
673            };
674
675            // Extract effects parameters
676            let delay_time = if let Some(Value::Number(t)) = context.get("delay_time") {
677                Some(*t)
678            } else {
679                None
680            };
681
682            let delay_feedback = if let Some(Value::Number(f)) = context.get("delay_feedback") {
683                Some(*f)
684            } else {
685                None
686            };
687
688            let delay_mix = if let Some(Value::Number(m)) = context.get("delay_mix") {
689                Some(*m)
690            } else {
691                None
692            };
693
694            let reverb_amount = if let Some(Value::Number(a)) = context.get("reverb_amount") {
695                Some(*a)
696            } else {
697                None
698            };
699
700            let drive_amount = if let Some(Value::Number(a)) = context.get("drive_amount") {
701                Some(*a)
702            } else {
703                None
704            };
705
706            let drive_color = if let Some(Value::Number(c)) = context.get("drive_color") {
707                Some(*c)
708            } else {
709                None
710            };
711
712            self.events.add_note_event(
713                target,
714                midi,
715                context.start_time,
716                duration,
717                velocity,
718                pan,
719                detune,
720                gain,
721                attack,
722                release,
723                delay_time,
724                delay_feedback,
725                delay_mix,
726                reverb_amount,
727                drive_amount,
728                drive_color,
729            );
730
731            // Generate playhead events (note on/off)
732            #[cfg(target_arch = "wasm32")]
733            {
734                use crate::web::registry::playhead::{PlayheadEvent, push_event};
735
736                // Note on event
737                push_event(PlayheadEvent {
738                    event_type: "note_on".to_string(),
739                    midi: vec![midi],
740                    time: context.start_time,
741                    velocity,
742                    synth_id: target.to_string(),
743                });
744
745                // Note off event
746                push_event(PlayheadEvent {
747                    event_type: "note_off".to_string(),
748                    midi: vec![midi],
749                    time: context.start_time + duration,
750                    velocity,
751                    synth_id: target.to_string(),
752                });
753            }
754        }
755
756        // Check if there's a chord event
757        if let Some(Value::Array(notes)) = context.get("notes") {
758            let mut midis = Vec::new();
759            for note_val in notes {
760                if let Value::String(note_name) = note_val {
761                    midis.push(parse_note_to_midi(note_name)?);
762                }
763            }
764
765            let duration = if let Some(Value::Number(d)) = context.get("duration") {
766                d / 1000.0
767            } else {
768                0.5
769            };
770            let velocity = if let Some(Value::Number(v)) = context.get("velocity") {
771                v / 100.0
772            } else {
773                0.8
774            };
775
776            // Extract audio options
777            let pan = if let Some(Value::Number(p)) = context.get("pan") {
778                *p
779            } else {
780                0.0
781            };
782
783            let detune = if let Some(Value::Number(d)) = context.get("detune") {
784                *d
785            } else {
786                0.0
787            };
788
789            let spread = if let Some(Value::Number(s)) = context.get("spread") {
790                *s
791            } else {
792                0.0
793            };
794
795            let gain = if let Some(Value::Number(g)) = context.get("gain") {
796                *g
797            } else {
798                1.0
799            };
800
801            let attack = if let Some(Value::Number(a)) = context.get("attack") {
802                Some(*a)
803            } else {
804                None
805            };
806
807            let release = if let Some(Value::Number(r)) = context.get("release") {
808                Some(*r)
809            } else {
810                None
811            };
812
813            // Extract effects parameters
814            let delay_time = if let Some(Value::Number(t)) = context.get("delay_time") {
815                Some(*t)
816            } else {
817                None
818            };
819
820            let delay_feedback = if let Some(Value::Number(f)) = context.get("delay_feedback") {
821                Some(*f)
822            } else {
823                None
824            };
825
826            let delay_mix = if let Some(Value::Number(m)) = context.get("delay_mix") {
827                Some(*m)
828            } else {
829                None
830            };
831
832            let reverb_amount = if let Some(Value::Number(a)) = context.get("reverb_amount") {
833                Some(*a)
834            } else {
835                None
836            };
837
838            let drive_amount = if let Some(Value::Number(a)) = context.get("drive_amount") {
839                Some(*a)
840            } else {
841                None
842            };
843
844            let drive_color = if let Some(Value::Number(c)) = context.get("drive_color") {
845                Some(*c)
846            } else {
847                None
848            };
849
850            self.events.add_chord_event(
851                target,
852                midis.clone(),
853                context.start_time,
854                duration,
855                velocity,
856                pan,
857                detune,
858                spread,
859                gain,
860                attack,
861                release,
862                delay_time,
863                delay_feedback,
864                delay_mix,
865                reverb_amount,
866                drive_amount,
867                drive_color,
868            );
869
870            // Generate playhead events (chord on/off)
871            #[cfg(target_arch = "wasm32")]
872            {
873                use crate::web::registry::playhead::{PlayheadEvent, push_event};
874
875                // Chord on event
876                push_event(PlayheadEvent {
877                    event_type: "chord_on".to_string(),
878                    midi: midis.clone(),
879                    time: context.start_time,
880                    velocity,
881                    synth_id: target.to_string(),
882                });
883
884                // Chord off event
885                push_event(PlayheadEvent {
886                    event_type: "chord_off".to_string(),
887                    midi: midis,
888                    time: context.start_time + duration,
889                    velocity,
890                    synth_id: target.to_string(),
891                });
892            }
893        }
894
895        Ok(())
896    }
897
898    fn render_audio(&self) -> Result<Vec<f32>> {
899        let total_duration = self.events.total_duration();
900        if total_duration <= 0.0 {
901            return Ok(Vec::new());
902        }
903
904        let total_samples = (total_duration * self.sample_rate as f32).ceil() as usize;
905        let mut buffer = vec![0.0f32; total_samples * 2]; // stereo
906
907        // Count events by type
908        let mut _note_count = 0;
909        let mut _chord_count = 0;
910        let mut _sample_count = 0;
911        for event in &self.events.events {
912            match event {
913                crate::engine::audio::events::AudioEvent::Note { .. } => _note_count += 1,
914                crate::engine::audio::events::AudioEvent::Chord { .. } => _chord_count += 1,
915                crate::engine::audio::events::AudioEvent::Sample { .. } => _sample_count += 1,
916            }
917        }
918
919        // Render each event
920        for event in &self.events.events {
921            match event {
922                crate::engine::audio::events::AudioEvent::Note {
923                    midi,
924                    start_time,
925                    duration,
926                    velocity,
927                    synth_id,
928                    pan,
929                    detune,
930                    gain,
931                    attack,
932                    release,
933                    delay_time,
934                    delay_feedback,
935                    delay_mix,
936                    reverb_amount,
937                    drive_amount,
938                    drive_color,
939                } => {
940                    let synth_def = self.events.get_synth(synth_id).cloned().unwrap_or_default();
941
942                    let mut params = SynthParams {
943                        waveform: synth_def.waveform,
944                        attack: synth_def.attack,
945                        decay: synth_def.decay,
946                        sustain: synth_def.sustain,
947                        release: synth_def.release,
948                        synth_type: synth_def.synth_type.clone(),
949                        filters: synth_def.filters.clone(),
950                        options: synth_def.options.clone(),
951                    };
952
953                    // Override attack/release if specified
954                    if let Some(a) = attack {
955                        params.attack = a / 1000.0; // Convert ms to seconds
956                    }
957                    if let Some(r) = release {
958                        params.release = r / 1000.0; // Convert ms to seconds
959                    }
960
961                    let mut samples = generate_note_with_options(
962                        *midi,
963                        duration * 1000.0,
964                        velocity * gain, // Apply gain to velocity
965                        &params,
966                        self.sample_rate,
967                        *pan,
968                        *detune,
969                    )?;
970
971                    // Apply effects in order: Drive -> Reverb -> Delay
972
973                    // Apply drive (saturation)
974                    if let Some(amount) = drive_amount {
975                        let color = drive_color.unwrap_or(0.5);
976                        let mix = 0.7; // Drive mix
977                        let mut processor = DriveProcessor::new(*amount, color, mix);
978                        processor.process(&mut samples, self.sample_rate);
979                    }
980
981                    // Apply reverb
982                    if let Some(amount) = reverb_amount {
983                        let room_size = *amount; // Use amount as room size
984                        let damping = 0.5; // Default damping
985                        let mix = *amount * 0.5; // Scale mix with amount
986                        let mut processor = ReverbProcessor::new(room_size, damping, mix);
987                        processor.process(&mut samples, self.sample_rate);
988                    }
989
990                    // Apply delay
991                    if let Some(time) = delay_time {
992                        let feedback = delay_feedback.unwrap_or(0.3);
993                        let mix = delay_mix.unwrap_or(0.5);
994                        let mut processor = DelayProcessor::new(*time, feedback, mix);
995                        processor.process(&mut samples, self.sample_rate);
996                    }
997
998                    // Mix into buffer
999                    let start_sample = (*start_time * self.sample_rate as f32) as usize * 2;
1000                    for (i, &sample) in samples.iter().enumerate() {
1001                        let buf_idx = start_sample + i;
1002                        if buf_idx < buffer.len() {
1003                            buffer[buf_idx] += sample;
1004                        }
1005                    }
1006                }
1007
1008                crate::engine::audio::events::AudioEvent::Chord {
1009                    midis,
1010                    start_time,
1011                    duration,
1012                    velocity,
1013                    synth_id,
1014                    pan,
1015                    detune,
1016                    spread,
1017                    gain,
1018                    attack,
1019                    release,
1020                    delay_time,
1021                    delay_feedback,
1022                    delay_mix,
1023                    reverb_amount,
1024                    drive_amount,
1025                    drive_color,
1026                } => {
1027                    let synth_def = self.events.get_synth(synth_id).cloned().unwrap_or_default();
1028
1029                    let mut params = SynthParams {
1030                        waveform: synth_def.waveform,
1031                        attack: synth_def.attack,
1032                        decay: synth_def.decay,
1033                        sustain: synth_def.sustain,
1034                        release: synth_def.release,
1035                        synth_type: synth_def.synth_type.clone(),
1036                        filters: synth_def.filters.clone(),
1037                        options: synth_def.options.clone(),
1038                    };
1039
1040                    // Override attack/release if specified
1041                    if let Some(a) = attack {
1042                        params.attack = a / 1000.0; // Convert ms to seconds
1043                    }
1044                    if let Some(r) = release {
1045                        params.release = r / 1000.0; // Convert ms to seconds
1046                    }
1047
1048                    let mut samples = generate_chord_with_options(
1049                        midis,
1050                        duration * 1000.0,
1051                        velocity * gain, // Apply gain to velocity
1052                        &params,
1053                        self.sample_rate,
1054                        *pan,
1055                        *detune,
1056                        *spread,
1057                    )?;
1058
1059                    // Apply effects in order: Drive -> Reverb -> Delay
1060
1061                    // Apply drive (saturation)
1062                    if let Some(amount) = drive_amount {
1063                        let color = drive_color.unwrap_or(0.5);
1064                        let mix = 0.7; // Drive mix
1065                        let mut processor = DriveProcessor::new(*amount, color, mix);
1066                        processor.process(&mut samples, self.sample_rate);
1067                    }
1068
1069                    // Apply reverb
1070                    if let Some(amount) = reverb_amount {
1071                        let room_size = *amount; // Use amount as room size
1072                        let damping = 0.5; // Default damping
1073                        let mix = *amount * 0.5; // Scale mix with amount
1074                        let mut processor = ReverbProcessor::new(room_size, damping, mix);
1075                        processor.process(&mut samples, self.sample_rate);
1076                    }
1077
1078                    // Apply delay
1079                    if let Some(time) = delay_time {
1080                        let feedback = delay_feedback.unwrap_or(0.3);
1081                        let mix = delay_mix.unwrap_or(0.5);
1082                        let mut processor = DelayProcessor::new(*time, feedback, mix);
1083                        processor.process(&mut samples, self.sample_rate);
1084                    }
1085
1086                    // Mix into buffer
1087                    let start_sample = (*start_time * self.sample_rate as f32) as usize * 2;
1088                    for (i, &sample) in samples.iter().enumerate() {
1089                        let buf_idx = start_sample + i;
1090                        if buf_idx < buffer.len() {
1091                            buffer[buf_idx] += sample;
1092                        }
1093                    }
1094                }
1095
1096                crate::engine::audio::events::AudioEvent::Sample {
1097                    uri,
1098                    start_time,
1099                    velocity,
1100                } => {
1101                    // Get sample PCM data from registry (WASM feature only)
1102                    #[cfg(target_arch = "wasm32")]
1103                    {
1104                        use crate::web::registry::samples::get_sample;
1105
1106                        if let Some(pcm_data) = get_sample(uri) {
1107                            // Convert i16 PCM to f32 and apply velocity
1108                            let start_sample_idx = (*start_time * self.sample_rate as f32) as usize;
1109
1110                            web_sys::console::log_1(
1111                                &format!(
1112                                    "🔊 Rendering sample: {} at {:.3}s, {} frames, velocity {:.2}",
1113                                    uri,
1114                                    start_time,
1115                                    pcm_data.len(),
1116                                    velocity
1117                                )
1118                                .into(),
1119                            );
1120                            web_sys::console::log_1(
1121                                &format!(
1122                                    "   Start buffer index: {} (stereo pos: {})",
1123                                    start_sample_idx,
1124                                    start_sample_idx * 2
1125                                )
1126                                .into(),
1127                            );
1128
1129                            // Assume mono samples - duplicate to stereo
1130                            for (i, &pcm_value) in pcm_data.iter().enumerate() {
1131                                // Convert i16 to f32 (-1.0 to 1.0) and apply velocity
1132                                let sample = (pcm_value as f32 / 32768.0) * velocity;
1133
1134                                // Calculate buffer positions for stereo (multiply by 2 because stereo)
1135                                let stereo_pos = (start_sample_idx + i) * 2;
1136                                let buf_idx_l = stereo_pos;
1137                                let buf_idx_r = stereo_pos + 1;
1138
1139                                // Mix into buffer (duplicate mono to both channels)
1140                                if buf_idx_l < buffer.len() {
1141                                    buffer[buf_idx_l] += sample;
1142                                }
1143                                if buf_idx_r < buffer.len() {
1144                                    buffer[buf_idx_r] += sample;
1145                                }
1146                            }
1147                        } else {
1148                            println!("⚠️  Sample not found in registry: {}", uri);
1149                        }
1150                    }
1151
1152                    // For non-WASM builds, samples are not supported
1153                    #[cfg(not(target_arch = "wasm32"))]
1154                    {
1155                        let _ = (start_time, velocity); // Silence unused warnings on non-WASM targets
1156                        println!("⚠️  Sample playback not supported in native build: {}", uri);
1157                    }
1158                }
1159            }
1160        }
1161
1162        // Normalize to prevent clipping
1163        let max_amplitude = buffer.iter().map(|&s| s.abs()).fold(0.0f32, f32::max);
1164        if max_amplitude > 1.0 {
1165            for sample in buffer.iter_mut() {
1166                *sample /= max_amplitude;
1167            }
1168        }
1169
1170        Ok(buffer)
1171    }
1172
1173    pub fn set_bpm(&mut self, bpm: f32) {
1174        self.bpm = bpm.max(1.0).min(999.0);
1175    }
1176
1177    pub fn samples_per_beat(&self) -> usize {
1178        ((60.0 / self.bpm) * self.sample_rate as f32) as usize
1179    }
1180
1181    /// Get duration of one beat in seconds
1182    pub fn beat_duration(&self) -> f32 {
1183        60.0 / self.bpm
1184    }
1185
1186    /// Execute print statement with variable interpolation
1187    /// Supports {variable_name} syntax
1188    fn execute_print(&self, value: &Value) -> Result<()> {
1189        let message = match value {
1190            Value::String(s) => {
1191                // Check if string contains variable interpolation
1192                if s.contains('{') && s.contains('}') {
1193                    self.interpolate_string(s)
1194                } else {
1195                    s.clone()
1196                }
1197            }
1198            Value::Number(n) => n.to_string(),
1199            Value::Boolean(b) => b.to_string(),
1200            Value::Array(arr) => format!("{:?}", arr),
1201            Value::Map(map) => format!("{:?}", map),
1202            _ => format!("{:?}", value),
1203        };
1204
1205        println!("💬 {}", message);
1206        Ok(())
1207    }
1208
1209    /// Interpolate variables in a string
1210    /// Replaces {variable_name} with the variable's value
1211    fn interpolate_string(&self, template: &str) -> String {
1212        let mut result = template.to_string();
1213
1214        // Find all {variable} patterns
1215        let re = regex::Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap();
1216
1217        for cap in re.captures_iter(template) {
1218            let full_match = &cap[0]; // {variable_name}
1219            let var_name = &cap[1]; // variable_name
1220
1221            if let Some(value) = self.variables.get(var_name) {
1222                let replacement = self.value_to_string(value);
1223                result = result.replace(full_match, &replacement);
1224            } else {
1225                // Variable not found, leave placeholder or show error
1226                result = result.replace(full_match, &format!("<undefined:{}>", var_name));
1227            }
1228        }
1229
1230        result
1231    }
1232
1233    /// Convert a Value to a displayable string
1234    fn value_to_string(&self, value: &Value) -> String {
1235        match value {
1236            Value::String(s) => {
1237                // Remove surrounding quotes if present
1238                s.trim_matches('"').trim_matches('\'').to_string()
1239            }
1240            Value::Number(n) => {
1241                // Format nicely: remove trailing zeros
1242                if n.fract() == 0.0 {
1243                    format!("{:.0}", n)
1244                } else {
1245                    format!("{}", n)
1246                }
1247            }
1248            Value::Boolean(b) => b.to_string(),
1249            Value::Array(arr) => {
1250                let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
1251                format!("[{}]", items.join(", "))
1252            }
1253            Value::Identifier(id) => id.clone(),
1254            _ => format!("{:?}", value),
1255        }
1256    }
1257
1258    /// Execute if statement with condition evaluation
1259    fn execute_if(
1260        &mut self,
1261        condition: &Value,
1262        body: &[Statement],
1263        else_body: &Option<Vec<Statement>>,
1264    ) -> Result<()> {
1265        let condition_result = self.evaluate_condition(condition)?;
1266
1267        if condition_result {
1268            // Condition is true, execute if body
1269            self.collect_events(body)?;
1270        } else if let Some(else_stmts) = else_body {
1271            // Condition is false, execute else body
1272            self.collect_events(else_stmts)?;
1273        }
1274
1275        Ok(())
1276    }
1277
1278    /// Evaluate a condition to a boolean
1279    /// Supports: ==, !=, <, >, <=, >=
1280    fn evaluate_condition(&self, condition: &Value) -> Result<bool> {
1281        // Condition is stored as a Map with operator and operands
1282        if let Value::Map(map) = condition {
1283            let operator = map
1284                .get("operator")
1285                .and_then(|v| {
1286                    if let Value::String(s) = v {
1287                        Some(s.as_str())
1288                    } else {
1289                        None
1290                    }
1291                })
1292                .unwrap_or("==");
1293
1294            let left = map
1295                .get("left")
1296                .ok_or_else(|| anyhow::anyhow!("Missing left operand"))?;
1297            let right = map
1298                .get("right")
1299                .ok_or_else(|| anyhow::anyhow!("Missing right operand"))?;
1300
1301            // Resolve values (replace identifiers with their values)
1302            let left_val = self.resolve_value(left);
1303            let right_val = self.resolve_value(right);
1304
1305            // Compare based on operator
1306            match operator {
1307                "==" => Ok(self.values_equal(&left_val, &right_val)),
1308                "!=" => Ok(!self.values_equal(&left_val, &right_val)),
1309                "<" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less),
1310                ">" => self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater),
1311                "<=" => {
1312                    let less =
1313                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Less)?;
1314                    let equal = self.values_equal(&left_val, &right_val);
1315                    Ok(less || equal)
1316                }
1317                ">=" => {
1318                    let greater =
1319                        self.compare_values(&left_val, &right_val, std::cmp::Ordering::Greater)?;
1320                    let equal = self.values_equal(&left_val, &right_val);
1321                    Ok(greater || equal)
1322                }
1323                _ => Err(anyhow::anyhow!("Unknown operator: {}", operator)),
1324            }
1325        } else {
1326            // Direct boolean value
1327            match condition {
1328                Value::Boolean(b) => Ok(*b),
1329                Value::Number(n) => Ok(*n != 0.0),
1330                Value::String(s) => Ok(!s.is_empty()),
1331                _ => Ok(false),
1332            }
1333        }
1334    }
1335
1336    /// Resolve a value (replace identifiers with their values from variables)
1337    fn resolve_value(&self, value: &Value) -> Value {
1338        match value {
1339            Value::Identifier(name) => {
1340                // Check if it's a special variable
1341                if is_special_var(name) {
1342                    if let Some(special_val) = resolve_special_var(name, &self.special_vars) {
1343                        return special_val;
1344                    }
1345                }
1346
1347                // Otherwise, look in variables
1348                self.variables.get(name).cloned().unwrap_or(Value::Null)
1349            }
1350            _ => value.clone(),
1351        }
1352    }
1353
1354    /// Execute event handlers matching the event name
1355    fn execute_event_handlers(&mut self, event_name: &str) -> Result<()> {
1356        let handlers = self.event_registry.get_handlers_matching(event_name);
1357
1358        for (index, handler) in handlers.iter().enumerate() {
1359            // Check if this is a "once" handler that has already been executed
1360            if handler.once && !self.event_registry.should_execute_once(event_name, index) {
1361                continue;
1362            }
1363
1364            // Execute the handler body
1365            let body_clone = handler.body.clone();
1366            self.collect_events(&body_clone)?;
1367        }
1368
1369        Ok(())
1370    }
1371
1372    /// Check if two values are equal
1373    fn values_equal(&self, left: &Value, right: &Value) -> bool {
1374        match (left, right) {
1375            (Value::Number(a), Value::Number(b)) => (a - b).abs() < 0.0001,
1376            (Value::String(a), Value::String(b)) => a == b,
1377            (Value::Boolean(a), Value::Boolean(b)) => a == b,
1378            (Value::Null, Value::Null) => true,
1379            _ => false,
1380        }
1381    }
1382
1383    /// Compare two values
1384    fn compare_values(
1385        &self,
1386        left: &Value,
1387        right: &Value,
1388        ordering: std::cmp::Ordering,
1389    ) -> Result<bool> {
1390        match (left, right) {
1391            (Value::Number(a), Value::Number(b)) => {
1392                Ok(a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) == ordering)
1393            }
1394            (Value::String(a), Value::String(b)) => Ok(a.cmp(b) == ordering),
1395            _ => Err(anyhow::anyhow!("Cannot compare {:?} and {:?}", left, right)),
1396        }
1397    }
1398
1399    /// Handle property assignment: target.property = value
1400    fn handle_assign(&mut self, target: &str, property: &str, value: &Value) -> Result<()> {
1401        // Get the variable (which should be a Map for synth definitions)
1402        if let Some(var) = self.variables.get_mut(target) {
1403            if let Value::Map(map) = var {
1404                // Update the property in the map
1405                map.insert(property.to_string(), value.clone());
1406
1407                // If this is a synth and the property affects the definition, update it
1408                if self.events.synths.contains_key(target) {
1409                    // Clone the map to avoid borrow issues
1410                    let map_clone = map.clone();
1411                    // Update synth definition
1412                    let updated_def = self.extract_synth_def_from_map(&map_clone)?;
1413                    self.events.synths.insert(target.to_string(), updated_def);
1414                    println!("🔧 Updated {}.{} = {:?}", target, property, value);
1415                }
1416            } else {
1417                return Err(anyhow::anyhow!(
1418                    "Cannot assign property '{}' to non-map variable '{}'",
1419                    property,
1420                    target
1421                ));
1422            }
1423        } else {
1424            return Err(anyhow::anyhow!("Variable '{}' not found", target));
1425        }
1426
1427        Ok(())
1428    }
1429
1430    /// Extract synth definition from a map
1431    fn extract_synth_def_from_map(&self, map: &HashMap<String, Value>) -> Result<SynthDefinition> {
1432        use crate::engine::audio::events::extract_filters;
1433
1434        let waveform = extract_string(map, "waveform", "sine");
1435        let attack = extract_number(map, "attack", 0.01);
1436        let decay = extract_number(map, "decay", 0.1);
1437        let sustain = extract_number(map, "sustain", 0.7);
1438        let release = extract_number(map, "release", 0.2);
1439
1440        let synth_type = if let Some(Value::String(t)) = map.get("type") {
1441            let clean = t.trim_matches('"').trim_matches('\'');
1442            if clean.is_empty() || clean == "synth" {
1443                None
1444            } else {
1445                Some(clean.to_string())
1446            }
1447        } else {
1448            None
1449        };
1450
1451        let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
1452            extract_filters(filters_arr)
1453        } else {
1454            Vec::new()
1455        };
1456
1457        let mut options = HashMap::new();
1458        for (key, val) in map.iter() {
1459            if ![
1460                "waveform", "attack", "decay", "sustain", "release", "type", "filters",
1461            ]
1462            .contains(&key.as_str())
1463            {
1464                if let Value::Number(n) = val {
1465                    options.insert(key.clone(), *n);
1466                }
1467            }
1468        }
1469
1470        Ok(SynthDefinition {
1471            waveform,
1472            attack,
1473            decay,
1474            sustain,
1475            release,
1476            synth_type,
1477            filters,
1478            options,
1479        })
1480    }
1481
1482    /// Handle MIDI file loading: @load "path.mid" as alias
1483    fn handle_load(&mut self, source: &str, alias: &str) -> Result<()> {
1484        use crate::engine::audio::midi::load_midi_file;
1485        use std::path::Path;
1486
1487        // Load the MIDI file
1488        let path = Path::new(source);
1489        let midi_data = load_midi_file(path)?;
1490
1491        // Store in variables
1492        self.variables.insert(alias.to_string(), midi_data);
1493
1494        println!("🎵 Loaded MIDI file: {} as {}", source, alias);
1495
1496        Ok(())
1497    }
1498
1499    /// Handle MIDI binding: bind source -> target { options }
1500    fn handle_bind(&mut self, source: &str, target: &str, options: &Value) -> Result<()> {
1501        // Get MIDI data from variables
1502        let midi_data = self
1503            .variables
1504            .get(source)
1505            .ok_or_else(|| anyhow::anyhow!("MIDI source '{}' not found", source))?
1506            .clone();
1507
1508        // Extract notes from MIDI data
1509        if let Value::Map(midi_map) = &midi_data {
1510            let notes = midi_map
1511                .get("notes")
1512                .ok_or_else(|| anyhow::anyhow!("MIDI data has no notes"))?;
1513
1514            if let Value::Array(notes_array) = notes {
1515                // Get synth definition (for validation)
1516                let _synth_def = self
1517                    .events
1518                    .synths
1519                    .get(target)
1520                    .ok_or_else(|| anyhow::anyhow!("Synth '{}' not found", target))?
1521                    .clone();
1522
1523                // Extract options (velocity, bpm, etc.)
1524                let default_velocity = 100;
1525                let mut velocity = default_velocity;
1526
1527                if let Value::Map(opts) = options {
1528                    if let Some(Value::Number(v)) = opts.get("velocity") {
1529                        velocity = *v as u8;
1530                    }
1531                }
1532
1533                // Generate events for each note
1534                for note_val in notes_array {
1535                    if let Value::Map(note_map) = note_val {
1536                        let time = extract_number(note_map, "time", 0.0);
1537                        let note = extract_number(note_map, "note", 60.0) as u8;
1538                        let note_velocity =
1539                            extract_number(note_map, "velocity", velocity as f32) as u8;
1540
1541                        // Create audio event
1542                        use crate::engine::audio::events::AudioEvent;
1543                        let event = AudioEvent::Note {
1544                            midi: note,
1545                            start_time: time / 1000.0, // Convert from ticks to seconds (simplified)
1546                            duration: 0.5,             // Default duration, could be in options
1547                            velocity: note_velocity as f32,
1548                            synth_id: target.to_string(),
1549                            pan: 0.0,
1550                            detune: 0.0,
1551                            gain: 1.0,
1552                            attack: None,
1553                            release: None,
1554                            delay_time: None,
1555                            delay_feedback: None,
1556                            delay_mix: None,
1557                            reverb_amount: None,
1558                            drive_amount: None,
1559                            drive_color: None,
1560                        };
1561
1562                        self.events.events.push(event);
1563                    }
1564                }
1565
1566                println!(
1567                    "🔗 Bound {} notes from {} to {}",
1568                    notes_array.len(),
1569                    source,
1570                    target
1571                );
1572            }
1573        }
1574
1575        Ok(())
1576    }
1577}