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}
76
77impl Default for SynthDefinition {
78    fn default() -> Self {
79        Self {
80            waveform: "sine".to_string(),
81            attack: 0.01,
82            decay: 0.1,
83            sustain: 0.7,
84            release: 0.2,
85            synth_type: None,
86            filters: Vec::new(),
87            options: HashMap::new(),
88        }
89    }
90}
91
92impl AudioEventList {
93    pub fn new() -> Self {
94        Self {
95            events: Vec::new(),
96            synths: HashMap::new(),
97        }
98    }
99
100    pub fn add_synth(&mut self, name: String, definition: SynthDefinition) {
101        self.synths.insert(name, definition);
102    }
103
104    pub fn add_note_event(
105        &mut self,
106        synth_id: &str,
107        midi: u8,
108        start_time: f32,
109        duration: f32,
110        velocity: f32,
111        pan: f32,
112        detune: f32,
113        gain: f32,
114        attack: Option<f32>,
115        release: Option<f32>,
116        delay_time: Option<f32>,
117        delay_feedback: Option<f32>,
118        delay_mix: Option<f32>,
119        reverb_amount: Option<f32>,
120        drive_amount: Option<f32>,
121        drive_color: Option<f32>,
122    ) {
123        // Capture synth definition snapshot at event creation time
124        let synth_def = self.get_synth(synth_id).cloned().unwrap_or_default();
125        
126        self.events.push(AudioEvent::Note {
127            midi,
128            start_time,
129            duration,
130            velocity,
131            synth_id: synth_id.to_string(),
132            synth_def,
133            pan,
134            detune,
135            gain,
136            attack,
137            release,
138            delay_time,
139            delay_feedback,
140            delay_mix,
141            reverb_amount,
142            drive_amount,
143            drive_color,
144        });
145    }
146
147    pub fn add_chord_event(
148        &mut self,
149        synth_id: &str,
150        midis: Vec<u8>,
151        start_time: f32,
152        duration: f32,
153        velocity: f32,
154        pan: f32,
155        detune: f32,
156        spread: f32,
157        gain: f32,
158        attack: Option<f32>,
159        release: Option<f32>,
160        delay_time: Option<f32>,
161        delay_feedback: Option<f32>,
162        delay_mix: Option<f32>,
163        reverb_amount: Option<f32>,
164        drive_amount: Option<f32>,
165        drive_color: Option<f32>,
166    ) {
167        // Capture synth definition snapshot at event creation time
168        let synth_def = self.get_synth(synth_id).cloned().unwrap_or_default();
169        
170        self.events.push(AudioEvent::Chord {
171            midis,
172            start_time,
173            duration,
174            velocity,
175            synth_id: synth_id.to_string(),
176            synth_def,
177            pan,
178            detune,
179            spread,
180            gain,
181            attack,
182            release,
183            delay_time,
184            delay_feedback,
185            delay_mix,
186            reverb_amount,
187            drive_amount,
188            drive_color,
189        });
190    }
191
192    pub fn add_sample_event(&mut self, uri: &str, start_time: f32, velocity: f32) {
193        self.events.push(AudioEvent::Sample {
194            uri: uri.to_string(),
195            start_time,
196            velocity,
197        });
198    }
199
200    pub fn get_synth(&self, name: &str) -> Option<&SynthDefinition> {
201        self.synths.get(name)
202    }
203
204    pub fn total_duration(&self) -> f32 {
205        self.events
206            .iter()
207            .map(|event| match event {
208                AudioEvent::Note {
209                    start_time,
210                    duration,
211                    ..
212                } => start_time + duration,
213                AudioEvent::Chord {
214                    start_time,
215                    duration,
216                    ..
217                } => start_time + duration,
218                AudioEvent::Sample {
219                    start_time, uri, ..
220                } => {
221                    // Get actual sample duration from registry
222                    #[cfg(target_arch = "wasm32")]
223                    {
224                        use crate::web::registry::samples::get_sample;
225                        if let Some(pcm) = get_sample(uri) {
226                            // Assume 44.1kHz sample rate
227                            let duration = pcm.len() as f32 / 44100.0;
228                            start_time + duration
229                        } else {
230                            // Fallback: estimate 2 seconds
231                            start_time + 2.0
232                        }
233                    }
234                    #[cfg(not(target_arch = "wasm32"))]
235                    {
236                        // Fallback for native: estimate 2 seconds
237                        let _ = uri; // Silence unused warning on non-WASM targets
238                        start_time + 2.0
239                    }
240                }
241            })
242            .fold(0.0, f32::max)
243    }
244
245    /// Merge another AudioEventList into this one
246    /// This is used for parallel spawn execution
247    pub fn merge(&mut self, other: AudioEventList) {
248        // Merge events
249        self.events.extend(other.events);
250
251        // Merge synth definitions (prefer existing definitions on conflict)
252        for (name, def) in other.synths {
253            if !self.synths.contains_key(&name) {
254                self.synths.insert(name, def);
255            }
256        }
257    }
258}
259
260/// Helper to extract values from Value::Map
261pub fn extract_number(map: &HashMap<String, Value>, key: &str, default: f32) -> f32 {
262    map.get(key)
263        .and_then(|v| {
264            if let Value::Number(n) = v {
265                Some(*n)
266            } else {
267                None
268            }
269        })
270        .unwrap_or(default)
271}
272
273pub fn extract_string(map: &HashMap<String, Value>, key: &str, default: &str) -> String {
274    map.get(key)
275        .and_then(|v| {
276            if let Value::String(s) = v {
277                Some(s.clone())
278            } else {
279                None
280            }
281        })
282        .unwrap_or_else(|| default.to_string())
283}
284
285pub fn extract_filters(filters_arr: &[Value]) -> Vec<FilterDef> {
286    filters_arr
287        .iter()
288        .filter_map(|v| {
289            if let Value::Map(filter_map) = v {
290                let filter_type = extract_string(filter_map, "type", "lowpass");
291                let cutoff = filter_map
292                    .get("cutoff")
293                    .and_then(|v| match v {
294                        Value::Number(n) => Some(*n),
295                        _ => None,
296                    })
297                    .unwrap_or(1000.0);
298                let resonance = filter_map
299                    .get("resonance")
300                    .and_then(|v| match v {
301                        Value::Number(n) => Some(*n),
302                        _ => None,
303                    })
304                    .unwrap_or(1.0);
305
306                Some(FilterDef {
307                    filter_type,
308                    cutoff,
309                    resonance,
310                })
311            } else {
312                None
313            }
314        })
315        .collect()
316}