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