devalang_wasm/engine/audio/mixer/
mod.rs1use 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 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 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 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(¤t)
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}