1use std::sync::Arc;
4pub const MAX_CLIP_FRAMES: usize = 8 * 1024 * 1024;
6const MAX_WAV_BYTES: usize = 64 * 1024 * 1024;
7#[derive(Clone, Debug)]
8pub struct Clip {
9 pub sample_rate: u32,
10 pub samples: Arc<[[f32; 2]]>,
11}
12impl Clip {
13 pub fn wav(bytes: &[u8]) -> Option<Self> {
15 if bytes.len() < 12
16 || bytes.len() > MAX_WAV_BYTES
17 || &bytes[..4] != b"RIFF"
18 || &bytes[8..12] != b"WAVE"
19 {
20 return None;
21 }
22 let declared = u32::from_le_bytes(bytes[4..8].try_into().ok()?);
23 let end = usize::try_from(declared).ok()?.checked_add(8)?;
24 if end != bytes.len() {
25 return None;
26 }
27 let mut at = 12_usize;
28 let mut format = None;
29 let mut pcm = None;
30 while at < end {
31 let header_end = at.checked_add(8)?;
32 let header = bytes.get(at..header_end)?;
33 let len = u32::from_le_bytes(header[4..8].try_into().ok()?) as usize;
34 at = header_end;
35 let chunk_end = at.checked_add(len)?;
36 let chunk = bytes.get(at..chunk_end)?;
37 match &header[..4] {
38 b"fmt " if format.is_none() && len >= 16 => {
39 let u16at = |i| u16::from_le_bytes(chunk[i..i + 2].try_into().unwrap());
40 let u32at = |i| u32::from_le_bytes(chunk[i..i + 4].try_into().unwrap());
41 let channels = u16at(2);
42 let rate = u32at(4);
43 let stride = u16at(12);
44 if u16at(0) != 1
45 || !(1..=2).contains(&channels)
46 || !(8000..=96000).contains(&rate)
47 || u16at(14) != 16
48 || stride != channels * 2
49 || u32at(8) != rate * u32::from(stride)
50 {
51 return None;
52 }
53 format = Some((usize::from(channels), rate));
54 }
55 b"data" if pcm.is_none() => pcm = Some(chunk),
56 b"fmt " | b"data" => return None,
57 _ => {}
58 }
59 at = chunk_end.checked_add(len % 2)?;
60 if at > end {
61 return None;
62 }
63 }
64 let (channels, sample_rate) = format?;
65 let pcm = pcm?;
66 let frames = pcm.len() / (channels * 2);
67 if !pcm.len().is_multiple_of(channels * 2) || frames > frame_limit(sample_rate) {
68 return None;
69 }
70 let samples: Vec<_> = pcm
71 .chunks_exact(channels * 2)
72 .map(|frame| {
73 let left = f32::from(i16::from_le_bytes([frame[0], frame[1]])) / 32768.0;
74 let right = if channels == 1 {
75 left
76 } else {
77 f32::from(i16::from_le_bytes([frame[2], frame[3]])) / 32768.0
78 };
79 [left, right]
80 })
81 .collect();
82 Some(Self {
83 sample_rate,
84 samples: samples.into(),
85 })
86 }
87 pub fn tone(sample_rate: u32, hz: f32, seconds: f32) -> Option<Self> {
88 if !(8000..=96000).contains(&sample_rate)
89 || !hz.is_finite()
90 || hz <= 0.0
91 || hz > sample_rate as f32 * 0.5
92 || !seconds.is_finite()
93 || !(0.0..=10.0).contains(&seconds)
94 {
95 return None;
96 }
97 let n = (sample_rate as f32 * seconds) as usize;
98 let samples: Vec<_> = (0..n)
99 .map(|i| {
100 let envelope = 1.0 - i as f32 / n.max(1) as f32;
101 let phase =
103 i as f64 * f64::from(hz) * std::f64::consts::TAU / f64::from(sample_rate);
104 let v = phase.sin() as f32 * envelope * 0.3;
105 [v, v]
106 })
107 .collect();
108 Some(Self {
109 sample_rate,
110 samples: samples.into(),
111 })
112 }
113}
114fn frame_limit(rate: u32) -> usize {
115 (rate as usize * 600).min(MAX_CLIP_FRAMES)
116}
117struct Voice {
118 clip: Clip,
119 cursor: f64,
120 position: [f32; 3],
121 gain: f32,
122 looping: bool,
123}
124pub struct Mixer {
125 sample_rate: u32,
126 voices: Vec<Voice>,
127 limit: usize,
128 pub listener: [f32; 3],
129 pub right: [f32; 3],
130}
131impl Mixer {
132 pub fn new(sample_rate: u32, limit: usize) -> Option<Self> {
133 if !(8000..=96000).contains(&sample_rate) || !(1..=128).contains(&limit) {
134 return None;
135 }
136 Some(Self {
137 sample_rate,
138 voices: Vec::with_capacity(limit),
139 limit,
140 listener: [0.0; 3],
141 right: [1.0, 0.0, 0.0],
142 })
143 }
144 pub fn play(&mut self, clip: Clip, position: [f32; 3], gain: f32, looping: bool) -> bool {
147 if self.voices.len() >= self.limit
148 || clip.samples.is_empty()
149 || !(8000..=96000).contains(&clip.sample_rate)
150 || clip.samples.len() > frame_limit(clip.sample_rate)
151 || !position.iter().all(|v| v.is_finite())
152 || !gain.is_finite()
153 || !(0.0..=4.0).contains(&gain)
154 || clip
155 .samples
156 .iter()
157 .flatten()
158 .any(|v| !v.is_finite() || v.abs() > 1.0)
159 {
160 return false;
161 }
162 self.voices.push(Voice {
163 clip,
164 cursor: 0.0,
165 position,
166 gain,
167 looping,
168 });
169 true
170 }
171 pub fn active_voices(&self) -> usize {
172 self.voices.len()
173 }
174 pub fn render(&mut self, out: &mut [[f32; 2]]) {
177 out.fill([0.0; 2]);
178 let right = self.right.map(f64::from);
179 let right_len = right.iter().map(|v| v * v).sum::<f64>().sqrt();
180 let listener_valid = self.listener.iter().all(|v| v.is_finite());
181 for voice in &mut self.voices {
182 let delta = std::array::from_fn::<_, 3, _>(|i| {
183 f64::from(voice.position[i]) - f64::from(self.listener[i])
184 });
185 let distance_squared = delta.iter().map(|v| v * v).sum::<f64>();
186 let distance = distance_squared.sqrt();
187 let pan = if distance > 1.0e-5 && right_len > 1.0e-5 && right_len.is_finite() {
188 (delta.iter().zip(right).map(|(a, b)| a * b).sum::<f64>() / (distance * right_len))
189 .clamp(-1.0, 1.0)
190 } else {
191 0.0
192 };
193 let gain = if listener_valid {
194 f64::from(voice.gain) / (1.0 + distance_squared * 0.05)
195 } else {
196 0.0
197 };
198 let gains = [
199 (((1.0 - pan) * 0.5).sqrt() * gain) as f32,
200 (((1.0 + pan) * 0.5).sqrt() * gain) as f32,
201 ];
202 let step = f64::from(voice.clip.sample_rate) / f64::from(self.sample_rate);
203 for sample in out.iter_mut() {
204 let len = voice.clip.samples.len();
205 if voice.cursor >= len as f64 {
206 if voice.looping {
207 voice.cursor %= len as f64;
208 } else {
209 break;
210 }
211 }
212 let index = voice.cursor as usize;
213 let next = if index + 1 < len {
214 index + 1
215 } else if voice.looping {
216 0
217 } else {
218 index
219 };
220 let fraction = (voice.cursor - index as f64) as f32;
221 for channel in 0..2 {
222 let a = voice.clip.samples[index][channel];
223 let b = voice.clip.samples[next][channel];
224 sample[channel] += (a + (b - a) * fraction) * gains[channel];
225 }
226 voice.cursor += step;
227 }
228 }
229 self.voices
230 .retain(|v| v.looping || v.cursor < v.clip.samples.len() as f64);
231 for sample in out {
232 for v in sample {
233 *v = if v.is_finite() {
234 v.clamp(-1.0, 1.0)
235 } else {
236 0.0
237 };
238 }
239 }
240 }
241}
242pub fn encode_wav(sample_rate: u32, samples: &[[f32; 2]]) -> Option<Vec<u8>> {
243 if !(8000..=96000).contains(&sample_rate) || samples.len() > frame_limit(sample_rate) {
244 return None;
245 }
246 let len = u32::try_from(samples.len().checked_mul(4)?).ok()?;
247 let mut out = Vec::with_capacity(len as usize + 44);
248 out.extend(b"RIFF");
249 out.extend((36 + len).to_le_bytes());
250 out.extend(b"WAVEfmt ");
251 out.extend(16_u32.to_le_bytes());
252 out.extend(1_u16.to_le_bytes());
253 out.extend(2_u16.to_le_bytes());
254 out.extend(sample_rate.to_le_bytes());
255 out.extend((sample_rate * 4).to_le_bytes());
256 out.extend(4_u16.to_le_bytes());
257 out.extend(16_u16.to_le_bytes());
258 out.extend(b"data");
259 out.extend(len.to_le_bytes());
260 for frame in samples {
261 for sample in frame {
262 let v = if sample.is_finite() {
263 sample.clamp(-1.0, 1.0)
264 } else {
265 0.0
266 };
267 let scale = if v < 0.0 { 32768.0 } else { 32767.0 };
268 out.extend(((v * scale).round() as i16).to_le_bytes());
269 }
270 }
271 Some(out)
272}
273#[cfg(test)]
274mod tests {
275 use super::*;
276 #[test]
277 fn spatial_pan_voice_limits_and_resampling() {
278 let clip = Clip {
279 sample_rate: 8000,
280 samples: vec![[0.5; 2]; 8].into(),
281 };
282 let mut m = Mixer::new(16000, 1).unwrap();
283 let capacity = m.voices.capacity();
284 assert!(m.play(clip.clone(), [2.0, 0.0, 0.0], 1.0, false));
285 assert!(!m.play(clip, [0.0; 3], 1.0, true));
286 let mut out = [[0.0; 2]; 32];
287 m.render(&mut out);
288 assert!(out[0][1] > 0.3);
289 assert_eq!(out[0][0], 0.0);
290 assert_eq!(out[16], [0.0; 2]);
291 assert_eq!(m.active_voices(), 0);
292 assert_eq!(m.voices.capacity(), capacity);
293 }
294 #[test]
295 fn wav_roundtrip_and_truncation() {
296 let samples = [[0.25, -0.5]; 100];
297 let bytes = encode_wav(48000, &samples).unwrap();
298 let clip = Clip::wav(&bytes).unwrap();
299 assert_eq!(clip.sample_rate, 48000);
300 assert!((clip.samples[0][0] - 0.25).abs() < 0.0001);
301 for i in 0..bytes.len() {
302 assert!(Clip::wav(&bytes[..i]).is_none());
303 }
304 }
305 #[test]
306 fn invalid_clip_and_listener_do_not_poison_other_voices() {
307 let mut m = Mixer::new(8000, 3).unwrap();
308 assert!(!m.play(
309 Clip {
310 sample_rate: 8000,
311 samples: vec![[f32::NAN; 2]].into()
312 },
313 [0.0; 3],
314 1.0,
315 false
316 ));
317 let clip = Clip {
318 sample_rate: 8000,
319 samples: vec![[0.5; 2]; 4].into(),
320 };
321 assert!(m.play(clip.clone(), [0.0; 3], 1.0, false));
322 m.listener = [f32::NAN; 3];
323 let mut out = [[1.0; 2]; 8];
324 m.render(&mut out);
325 assert_eq!(out, [[0.0; 2]; 8]);
326 assert_eq!(m.active_voices(), 0);
327 m.listener = [-f32::MAX; 3];
328 m.right = [f32::MAX; 3];
329 assert!(m.play(clip, [f32::MAX; 3], 1.0, false));
330 m.render(&mut out);
331 assert!(out.iter().flatten().all(|v| v.is_finite()));
332 }
333 #[test]
334 fn looping_and_block_partition_preserve_sample_time() {
335 let clip = Clip {
336 sample_rate: 8000,
337 samples: vec![[0.2; 2], [0.6; 2]].into(),
338 };
339 let mut a = Mixer::new(12000, 1).unwrap();
340 let mut b = Mixer::new(12000, 1).unwrap();
341 a.play(clip.clone(), [0.0; 3], 1.0, true);
342 b.play(clip, [0.0; 3], 1.0, true);
343 let mut whole = [[0.0; 2]; 30];
344 a.render(&mut whole);
345 let mut pieces = [[0.0; 2]; 30];
346 for part in pieces.chunks_mut(3) {
347 b.render(part);
348 }
349 assert_eq!(whole, pieces);
350 }
351 #[test]
352 fn mono_pcm_is_duplicated_and_chunks_can_arrive_out_of_order() {
353 let mut bytes = encode_wav(8000, &[[1.0, -1.0]]).unwrap();
354 bytes[22..24].copy_from_slice(&1_u16.to_le_bytes());
355 bytes[28..32].copy_from_slice(&16000_u32.to_le_bytes());
356 bytes[32..34].copy_from_slice(&2_u16.to_le_bytes());
357 let mut reordered = bytes[..12].to_vec();
358 reordered.extend_from_slice(&bytes[36..]);
359 reordered.extend_from_slice(&bytes[12..36]);
360 let clip = Clip::wav(&reordered).unwrap();
361 assert_eq!(clip.samples.len(), 2);
362 assert_eq!(clip.samples[0][0], clip.samples[0][1]);
363 assert_eq!(clip.samples[1], [-1.0; 2]);
364 }
365 #[test]
366 fn wav_rejects_huge_lengths_duplicate_chunks_and_missing_padding() {
367 let bytes = encode_wav(8000, &[[1.0, -1.0]]).unwrap();
368 let decoded = Clip::wav(&bytes).unwrap();
369 assert_eq!(decoded.samples[0][1], -1.0);
370 let mut huge = bytes.clone();
371 huge[4..8].copy_from_slice(&u32::MAX.to_le_bytes());
372 assert!(Clip::wav(&huge).is_none());
373 let mut duplicate = bytes.clone();
374 duplicate.extend_from_slice(&bytes[36..]);
375 let declared = (duplicate.len() - 8) as u32;
376 duplicate[4..8].copy_from_slice(&declared.to_le_bytes());
377 assert!(Clip::wav(&duplicate).is_none());
378 let mut odd = bytes;
379 odd.extend(b"JUNK");
380 odd.extend(1_u32.to_le_bytes());
381 odd.push(0);
382 let declared = (odd.len() - 8) as u32;
383 odd[4..8].copy_from_slice(&declared.to_le_bytes());
384 assert!(Clip::wav(&odd).is_none());
385 odd.push(0);
386 let declared = (odd.len() - 8) as u32;
387 odd[4..8].copy_from_slice(&declared.to_le_bytes());
388 assert!(Clip::wav(&odd).is_some());
389 }
390}