devalang_wasm/engine/audio/mixer/
mod.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4pub const MASTER_INSERT: &str = "master";
5
6#[derive(Debug, Clone)]
7pub struct SampleBuffer {
8    data: Arc<Vec<f32>>,
9    frames: usize,
10    channels: usize,
11    sample_rate: u32,
12}
13
14impl SampleBuffer {
15    pub fn new(data: Arc<Vec<f32>>, channels: usize, sample_rate: u32) -> Self {
16        let frames = if channels == 0 {
17            0
18        } else {
19            data.len() / channels.max(1)
20        };
21        Self {
22            data,
23            frames,
24            channels: channels.max(1),
25            sample_rate,
26        }
27    }
28
29    pub fn frames(&self) -> usize {
30        self.frames
31    }
32
33    pub fn channels(&self) -> usize {
34        self.channels
35    }
36
37    pub fn sample_rate(&self) -> u32 {
38        self.sample_rate
39    }
40
41    pub fn sample_channel(&self, frame: usize, channel: usize, target_channels: usize) -> f32 {
42        if self.frames == 0 {
43            return 0.0;
44        }
45        let sample_channels = self.channels.max(1);
46        let source_channel = match (sample_channels, target_channels) {
47            (1, _) => 0,
48            (2, 1) => {
49                let left = self.data[frame * sample_channels];
50                let right = self.data[frame * sample_channels + 1];
51                return (left + right) * 0.5;
52            }
53            _ => channel % sample_channels,
54        };
55        self.data[frame * sample_channels + source_channel]
56    }
57
58    /// Create a new SampleBuffer with modified data (for effects processing)
59    pub fn with_modified_data(&self, new_data: Vec<f32>, new_channels: Option<usize>) -> Self {
60        let channels = new_channels.unwrap_or(self.channels);
61        Self::new(Arc::new(new_data), channels, self.sample_rate)
62    }
63
64    /// Get a mutable copy of the internal data for processing
65    pub fn data_clone(&self) -> Vec<f32> {
66        self.data.as_ref().clone()
67    }
68}
69
70#[derive(Debug, Clone)]
71struct AudioInsert {
72    name: String,
73    parent: Option<String>,
74    buffer: Vec<f32>,
75}
76
77impl AudioInsert {
78    fn new(name: impl Into<String>) -> Self {
79        Self {
80            name: name.into(),
81            parent: None,
82            buffer: Vec::new(),
83        }
84    }
85
86    fn set_parent(&mut self, parent: Option<String>) {
87        if self.name == MASTER_INSERT {
88            self.parent = None;
89        } else {
90            self.parent = parent;
91        }
92    }
93
94    fn ensure_frames(&mut self, frames: usize, channels: usize) {
95        let required = frames.saturating_mul(channels);
96        if self.buffer.len() < required {
97            self.buffer.resize(required, 0.0);
98        }
99    }
100}
101
102#[derive(Debug)]
103pub struct AudioMixer {
104    sample_rate: u32,
105    channels: usize,
106    inserts: HashMap<String, AudioInsert>,
107}
108
109impl AudioMixer {
110    pub fn new(sample_rate: u32, channels: usize) -> Self {
111        let mut inserts = HashMap::new();
112        inserts.insert(MASTER_INSERT.to_string(), AudioInsert::new(MASTER_INSERT));
113        Self {
114            sample_rate,
115            channels: channels.max(1),
116            inserts,
117        }
118    }
119
120    pub fn register_insert(&mut self, name: impl Into<String>, parent: Option<&str>) -> String {
121        let key = name.into();
122        if key != MASTER_INSERT {
123            if let Some(parent_name) = parent {
124                if !self.inserts.contains_key(parent_name) {
125                    self.register_insert(parent_name.to_string(), None);
126                }
127            }
128        }
129        use std::collections::hash_map::Entry;
130        match self.inserts.entry(key.clone()) {
131            Entry::Occupied(mut entry) => {
132                if key != MASTER_INSERT {
133                    if let Some(parent_name) = parent {
134                        if entry.get().parent.as_deref() != Some(parent_name) {
135                            entry.get_mut().set_parent(Some(parent_name.to_string()));
136                        }
137                    }
138                }
139            }
140            Entry::Vacant(slot) => {
141                let mut insert = AudioInsert::new(&key);
142                if key != MASTER_INSERT {
143                    insert.set_parent(parent.map(|p| p.to_string()));
144                }
145                slot.insert(insert);
146            }
147        }
148        key
149    }
150
151    pub fn ensure_master_frames(&mut self, frames: usize) {
152        if let Some(master) = self.inserts.get_mut(MASTER_INSERT) {
153            master.ensure_frames(frames, self.channels);
154        }
155    }
156
157    pub fn mix_sample(
158        &mut self,
159        insert: &str,
160        start_frame: usize,
161        duration: f32,
162        sample: &SampleBuffer,
163    ) {
164        if sample.frames() == 0 {
165            return;
166        }
167        let route = self.route_chain(insert);
168        for route_insert in route {
169            if !self.inserts.contains_key(&route_insert) {
170                // Unknown insert; skip mixing to avoid panics.
171                continue;
172            }
173            if let Some(target) = self.inserts.get_mut(&route_insert) {
174                Self::mix_into_insert(
175                    target,
176                    self.channels,
177                    start_frame,
178                    duration,
179                    self.sample_rate,
180                    sample,
181                );
182            }
183        }
184    }
185
186    pub fn into_master_buffer(mut self, total_frames: usize) -> Vec<f32> {
187        let samples = total_frames.saturating_mul(self.channels);
188        self.ensure_master_frames(total_frames);
189        let mut master = self
190            .inserts
191            .remove(MASTER_INSERT)
192            .unwrap_or_else(|| AudioInsert::new(MASTER_INSERT));
193        if samples == 0 {
194            master.buffer.clear();
195            return master.buffer;
196        }
197        if master.buffer.len() < samples {
198            master.buffer.resize(samples, 0.0);
199        } else if master.buffer.len() > samples {
200            master.buffer.truncate(samples);
201        }
202        master.buffer
203    }
204
205    pub fn sanitize_label(label: &str) -> String {
206        label
207            .chars()
208            .map(|c| {
209                if c.is_ascii_alphanumeric() {
210                    c.to_ascii_lowercase()
211                } else {
212                    '_'
213                }
214            })
215            .collect()
216    }
217
218    fn route_chain(&mut self, insert: &str) -> Vec<String> {
219        if !self.inserts.contains_key(insert) {
220            self.register_insert(insert.to_string(), Some(MASTER_INSERT));
221        }
222        let mut chain = Vec::new();
223        let mut current = insert.to_string();
224        let mut visited = HashSet::new();
225        loop {
226            if !visited.insert(current.clone()) {
227                break;
228            }
229            chain.push(current.clone());
230            if current == MASTER_INSERT {
231                break;
232            }
233            let parent = self
234                .inserts
235                .get(&current)
236                .and_then(|insert| insert.parent.as_deref())
237                .unwrap_or(MASTER_INSERT);
238            current = parent.to_string();
239        }
240        if !chain.iter().any(|name| name == MASTER_INSERT) {
241            chain.push(MASTER_INSERT.to_string());
242        }
243        chain
244    }
245
246    fn mix_into_insert(
247        insert: &mut AudioInsert,
248        channel_count: usize,
249        start_frame: usize,
250        duration: f32,
251        output_rate: u32,
252        sample: &SampleBuffer,
253    ) {
254        let channel_count = channel_count.max(1);
255        let mut max_play_frames = (duration * output_rate as f32).ceil() as usize;
256        if max_play_frames == 0 {
257            let scaled =
258                (sample.frames() as f32 * output_rate as f32) / sample.sample_rate() as f32;
259            max_play_frames = scaled.ceil() as usize;
260        }
261        if max_play_frames == 0 {
262            return;
263        }
264        let required_frames = start_frame.saturating_add(max_play_frames);
265        insert.ensure_frames(required_frames, channel_count);
266        let ratio = if output_rate == 0 {
267            1.0
268        } else {
269            sample.sample_rate() as f32 / output_rate as f32
270        };
271        for frame_idx in 0..max_play_frames {
272            let buffer_frame = start_frame + frame_idx;
273            let sample_pos = frame_idx as f32 * ratio;
274            if sample_pos >= sample.frames() as f32 {
275                break;
276            }
277            let base = sample_pos.floor() as usize;
278            let next = if base + 1 >= sample.frames() {
279                sample.frames().saturating_sub(1)
280            } else {
281                base + 1
282            };
283            let frac = sample_pos - base as f32;
284            for ch in 0..channel_count {
285                let v0 = sample.sample_channel(base, ch, channel_count);
286                let v1 = sample.sample_channel(next, ch, channel_count);
287                let interpolated = v0 + (v1 - v0) * frac;
288                let buffer_index = buffer_frame * channel_count + ch;
289                if let Some(slot) = insert.buffer.get_mut(buffer_index) {
290                    *slot += interpolated;
291                }
292            }
293        }
294    }
295}