devalang_wasm/engine/audio/
events.rs

1use crate::engine::audio::generator::FilterDef;
2/// Audio events system - stores note/chord events to be rendered
3use crate::language::syntax::ast::Value;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
7pub enum AudioEvent {
8    Note {
9        midi: u8,
10        start_time: f32,
11        duration: f32,
12        velocity: f32,
13        synth_id: String,
14        synth_def: SynthDefinition, // Snapshot of synth at event creation time
15        // Audio options
16        pan: f32,             // -1.0 to 1.0
17        detune: f32,          // cents
18        gain: f32,            // multiplier
19        attack: Option<f32>,  // ms override
20        release: Option<f32>, // ms override
21        // Effects
22        delay_time: Option<f32>,     // ms
23        delay_feedback: Option<f32>, // 0.0-1.0
24        delay_mix: Option<f32>,      // 0.0-1.0
25        reverb_amount: Option<f32>,  // 0.0-1.0
26        drive_amount: Option<f32>,   // 0.0-1.0
27        drive_color: Option<f32>,    // 0.0-1.0
28    },
29    Chord {
30        midis: Vec<u8>,
31        start_time: f32,
32        duration: f32,
33        velocity: f32,
34        synth_id: String,
35        synth_def: SynthDefinition, // Snapshot of synth at event creation time
36        // Audio options
37        pan: f32,             // -1.0 to 1.0
38        detune: f32,          // cents
39        spread: f32,          // 0.0 to 1.0
40        gain: f32,            // multiplier
41        attack: Option<f32>,  // ms override
42        release: Option<f32>, // ms override
43        // Effects
44        delay_time: Option<f32>,     // ms
45        delay_feedback: Option<f32>, // 0.0-1.0
46        delay_mix: Option<f32>,      // 0.0-1.0
47        reverb_amount: Option<f32>,  // 0.0-1.0
48        drive_amount: Option<f32>,   // 0.0-1.0
49        drive_color: Option<f32>,    // 0.0-1.0
50    },
51    Sample {
52        uri: String,
53        start_time: f32,
54        velocity: f32,
55    },
56}
57
58/// Audio events collector
59#[derive(Debug, Default)]
60pub struct AudioEventList {
61    pub events: Vec<AudioEvent>,
62    pub synths: HashMap<String, SynthDefinition>,
63}
64
65#[derive(Debug, Clone)]
66pub struct SynthDefinition {
67    pub waveform: String,
68    pub attack: f32,
69    pub decay: f32,
70    pub sustain: f32,
71    pub release: f32,
72    pub synth_type: Option<String>,
73    pub filters: Vec<FilterDef>,
74    pub options: HashMap<String, f32>, // Configurable synth type options
75    // Plugin support
76    pub plugin_author: Option<String>,
77    pub plugin_name: Option<String>,
78    pub plugin_export: Option<String>,
79}
80
81impl Default for SynthDefinition {
82    fn default() -> Self {
83        Self {
84            waveform: "sine".to_string(),
85            attack: 0.01,
86            decay: 0.1,
87            sustain: 0.7,
88            release: 0.2,
89            synth_type: None,
90            filters: Vec::new(),
91            options: HashMap::new(),
92            plugin_author: None,
93            plugin_name: None,
94            plugin_export: None,
95        }
96    }
97}
98
99impl AudioEventList {
100    pub fn new() -> Self {
101        Self {
102            events: Vec::new(),
103            synths: HashMap::new(),
104        }
105    }
106
107    pub fn add_synth(&mut self, name: String, definition: SynthDefinition) {
108        self.synths.insert(name, definition);
109    }
110
111    pub fn add_note_event(
112        &mut self,
113        synth_id: &str,
114        midi: u8,
115        start_time: f32,
116        duration: f32,
117        velocity: f32,
118        pan: f32,
119        detune: f32,
120        gain: f32,
121        attack: Option<f32>,
122        release: Option<f32>,
123        delay_time: Option<f32>,
124        delay_feedback: Option<f32>,
125        delay_mix: Option<f32>,
126        reverb_amount: Option<f32>,
127        drive_amount: Option<f32>,
128        drive_color: Option<f32>,
129    ) {
130        // Capture synth definition snapshot at event creation time
131        let synth_def = self.get_synth(synth_id).cloned().unwrap_or_else(|| {
132            println!("⚠️  Warning: Synth '{}' not found when creating note event. Available synths: {:?}", 
133                     synth_id, self.synths.keys().collect::<Vec<_>>());
134            SynthDefinition::default()
135        });
136
137        self.events.push(AudioEvent::Note {
138            midi,
139            start_time,
140            duration,
141            velocity,
142            synth_id: synth_id.to_string(),
143            synth_def,
144            pan,
145            detune,
146            gain,
147            attack,
148            release,
149            delay_time,
150            delay_feedback,
151            delay_mix,
152            reverb_amount,
153            drive_amount,
154            drive_color,
155        });
156    }
157
158    pub fn add_chord_event(
159        &mut self,
160        synth_id: &str,
161        midis: Vec<u8>,
162        start_time: f32,
163        duration: f32,
164        velocity: f32,
165        pan: f32,
166        detune: f32,
167        spread: f32,
168        gain: f32,
169        attack: Option<f32>,
170        release: Option<f32>,
171        delay_time: Option<f32>,
172        delay_feedback: Option<f32>,
173        delay_mix: Option<f32>,
174        reverb_amount: Option<f32>,
175        drive_amount: Option<f32>,
176        drive_color: Option<f32>,
177    ) {
178        // Capture synth definition snapshot at event creation time
179        let synth_def = self.get_synth(synth_id).cloned().unwrap_or_default();
180
181        self.events.push(AudioEvent::Chord {
182            midis,
183            start_time,
184            duration,
185            velocity,
186            synth_id: synth_id.to_string(),
187            synth_def,
188            pan,
189            detune,
190            spread,
191            gain,
192            attack,
193            release,
194            delay_time,
195            delay_feedback,
196            delay_mix,
197            reverb_amount,
198            drive_amount,
199            drive_color,
200        });
201    }
202
203    pub fn add_sample_event(&mut self, uri: &str, start_time: f32, velocity: f32) {
204        self.events.push(AudioEvent::Sample {
205            uri: uri.to_string(),
206            start_time,
207            velocity,
208        });
209    }
210
211    pub fn get_synth(&self, name: &str) -> Option<&SynthDefinition> {
212        self.synths.get(name)
213    }
214
215    pub fn total_duration(&self) -> f32 {
216        self.events
217            .iter()
218            .map(|event| match event {
219                AudioEvent::Note {
220                    start_time,
221                    duration,
222                    ..
223                } => start_time + duration,
224                AudioEvent::Chord {
225                    start_time,
226                    duration,
227                    ..
228                } => start_time + duration,
229                AudioEvent::Sample {
230                    start_time, uri, ..
231                } => {
232                    // Get actual sample duration from registry
233                    #[cfg(target_arch = "wasm32")]
234                    {
235                        use crate::web::registry::samples::get_sample;
236                        if let Some(pcm) = get_sample(uri) {
237                            // Assume 44.1kHz sample rate
238                            let duration = pcm.len() as f32 / 44100.0;
239                            start_time + duration
240                        } else {
241                            // Fallback: estimate 2 seconds
242                            start_time + 2.0
243                        }
244                    }
245                    #[cfg(not(target_arch = "wasm32"))]
246                    {
247                        // Fallback for native: estimate 2 seconds
248                        let _ = uri; // Silence unused warning on non-WASM targets
249                        start_time + 2.0
250                    }
251                }
252            })
253            .fold(0.0, f32::max)
254    }
255
256    /// Merge another AudioEventList into this one
257    /// This is used for parallel spawn execution
258    pub fn merge(&mut self, other: AudioEventList) {
259        // Merge synth definitions FIRST (prefer existing definitions on conflict)
260        for (name, def) in other.synths {
261            if !self.synths.contains_key(&name) {
262                self.synths.insert(name, def);
263            }
264        }
265
266        // Merge events and update their synth_def snapshots if needed
267        for mut event in other.events {
268            // Update synth_def snapshot for Note and Chord events
269            match &mut event {
270                AudioEvent::Note {
271                    synth_id,
272                    synth_def,
273                    ..
274                } => {
275                    // If this event's synth now exists in merged synths, update the snapshot
276                    if let Some(updated_def) = self.synths.get(synth_id) {
277                        *synth_def = updated_def.clone();
278                    }
279                }
280                AudioEvent::Chord {
281                    synth_id,
282                    synth_def,
283                    ..
284                } => {
285                    // If this event's synth now exists in merged synths, update the snapshot
286                    if let Some(updated_def) = self.synths.get(synth_id) {
287                        *synth_def = updated_def.clone();
288                    }
289                }
290                _ => {}
291            }
292            self.events.push(event);
293        }
294    }
295}
296
297/// Helper to extract values from Value::Map
298pub fn extract_number(map: &HashMap<String, Value>, key: &str, default: f32) -> f32 {
299    map.get(key)
300        .and_then(|v| {
301            if let Value::Number(n) = v {
302                Some(*n)
303            } else {
304                None
305            }
306        })
307        .unwrap_or(default)
308}
309
310pub fn extract_string(map: &HashMap<String, Value>, key: &str, default: &str) -> String {
311    map.get(key)
312        .and_then(|v| {
313            if let Value::String(s) = v {
314                Some(s.clone())
315            } else {
316                None
317            }
318        })
319        .unwrap_or_else(|| default.to_string())
320}
321
322pub fn extract_filters(filters_arr: &[Value]) -> Vec<FilterDef> {
323    filters_arr
324        .iter()
325        .filter_map(|v| {
326            if let Value::Map(filter_map) = v {
327                let filter_type = extract_string(filter_map, "type", "lowpass");
328                let cutoff = filter_map
329                    .get("cutoff")
330                    .and_then(|v| match v {
331                        Value::Number(n) => Some(*n),
332                        _ => None,
333                    })
334                    .unwrap_or(1000.0);
335                let resonance = filter_map
336                    .get("resonance")
337                    .and_then(|v| match v {
338                        Value::Number(n) => Some(*n),
339                        _ => None,
340                    })
341                    .unwrap_or(1.0);
342
343                Some(FilterDef {
344                    filter_type,
345                    cutoff,
346                    resonance,
347                })
348            } else {
349                None
350            }
351        })
352        .collect()
353}