1use std::f32::consts::FRAC_PI_4;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
10use std::sync::mpsc::Receiver;
11
12use glam::Vec3;
13
14use super::data::SoundSlot;
15use crate::math;
16
17const MAX_VOICES: usize = 512;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum Group {
23 Sound,
25 Music,
27 Ui,
29}
30
31impl Group {
32 fn index(self) -> usize {
33 match self {
34 Group::Sound => 0,
35 Group::Music => 1,
36 Group::Ui => 2,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy)]
43pub(crate) struct Spatial {
44 pub position: Vec3,
45 pub min_distance: f32,
46 pub max_distance: f32,
47 pub rolloff: f32,
48}
49
50#[derive(Debug, Default)]
52pub(crate) struct VoiceShared {
53 pub finished: AtomicBool,
54 pub position: AtomicU64,
56}
57
58pub(crate) enum Change {
59 Volume { target: f32, fade: f32 },
60 Stop { fade: f32 },
61 Pitch(f32),
62 Pan(f32),
63 Position(Vec3),
64 Paused(bool),
65 Looping(bool),
66 Distance { min: f32, max: f32 },
67 Seek(f32),
68}
69
70pub(crate) enum Command {
71 Play(Box<Voice>),
72 Change(u64, Change),
73 StopAll { group: Option<Group>, fade: f32 },
74 Listener { position: Vec3, yaw: f32, pitch: f32 },
75 GroupVolume(Group, f32),
76 MasterVolume(f32),
77}
78
79pub(crate) struct Voice {
80 pub id: u64,
81 pub slot: Arc<SoundSlot>,
82 pub shared: Arc<VoiceShared>,
83 pub group: Group,
84 pub looping: bool,
85 pub paused: bool,
86 pub pitch: f32,
87 pub pan: f32,
88 pub spatial: Option<Spatial>,
89 pub volume: f32,
91 pub target_volume: f32,
92 pub fade_step: f32,
93 pub stop_after_fade: bool,
94
95 pos: f64,
96 gains: [f32; 2],
97 lowpass: f32,
98 lp_state: [f32; 2],
99 fresh: bool,
100}
101
102impl Voice {
103 pub(crate) fn new(id: u64, slot: Arc<SoundSlot>, shared: Arc<VoiceShared>, group: Group) -> Self {
104 Self {
105 id,
106 slot,
107 shared,
108 group,
109 looping: false,
110 paused: false,
111 pitch: 1.0,
112 pan: 0.0,
113 spatial: None,
114 volume: 1.0,
115 target_volume: 1.0,
116 fade_step: 0.0,
117 stop_after_fade: false,
118 pos: 0.0,
119 gains: [1.0, 1.0],
120 lowpass: 1.0,
121 lp_state: [0.0; 2],
122 fresh: true,
123 }
124 }
125
126 pub(crate) fn fade_in(&mut self, seconds: f32, rate: f32) {
128 self.target_volume = self.volume;
129 self.volume = 0.0;
130 self.fade_step = fade_step(self.target_volume, seconds, rate);
131 }
132
133 fn finish(&mut self) {
134 self.shared.finished.store(true, Ordering::Release);
135 }
136
137 fn is_finished(&self) -> bool {
138 self.shared.finished.load(Ordering::Acquire)
139 }
140
141 fn apply(&mut self, change: Change, rate: f32) {
142 match change {
143 Change::Volume { target, fade } => {
144 self.target_volume = target.max(0.0);
145 self.stop_after_fade = false;
146 self.fade_step = fade_step((self.target_volume - self.volume).abs(), fade, rate);
147 }
148 Change::Stop { fade } => {
149 if fade <= 0.0 {
150 self.finish();
151 } else {
152 self.target_volume = 0.0;
153 self.stop_after_fade = true;
154 self.fade_step = fade_step(self.volume, fade, rate);
155 }
156 }
157 Change::Pitch(p) => self.pitch = p.clamp(0.01, 16.0),
158 Change::Pan(p) => self.pan = p.clamp(-1.0, 1.0),
159 Change::Position(p) => {
160 if let Some(s) = &mut self.spatial {
161 s.position = p;
162 }
163 }
164 Change::Paused(p) => self.paused = p,
165 Change::Looping(l) => self.looping = l,
166 Change::Distance { min, max } => {
167 if let Some(s) = &mut self.spatial {
168 s.min_distance = min.max(0.01);
169 s.max_distance = max.max(s.min_distance + 0.01);
170 }
171 }
172 Change::Seek(seconds) => {
173 if let Some(data) = self.slot.get() {
174 self.pos = (seconds.max(0.0) as f64 * data.sample_rate as f64)
175 .min(data.frames().saturating_sub(1) as f64);
176 }
177 }
178 }
179 }
180
181 fn targets(&self, listener: &Listener, rate: f32, source_channels: usize) -> ([f32; 2], f32) {
183 let Some(s) = &self.spatial else {
184 let p = self.pan;
186 let gains = if source_channels == 1 && p == 0.0 {
187 [1.0, 1.0]
188 } else {
189 [(1.0 - p).min(1.0), (1.0 + p).min(1.0)]
190 };
191 return (gains, 1.0);
192 };
193
194 let rel = s.position - listener.position;
196 let local_x = rel.dot(math::right(listener.yaw));
197 let local_y = rel.dot(math::forward_3d(listener.yaw, listener.pitch));
198 let local_z = rel.dot(math::up_3d(listener.yaw, listener.pitch));
199 let distance = rel.length();
200
201 if distance >= s.max_distance {
202 return ([0.0, 0.0], 1.0);
203 }
204 let near = s.min_distance / distance.max(s.min_distance);
205 let t = ((distance - s.min_distance) / (s.max_distance - s.min_distance)).clamp(0.0, 1.0);
206 let mut gain = near.powf(s.rolloff) * (1.0 - t * t);
207
208 let (pan, behind, below) = if distance > 1e-4 {
209 ((local_x / distance) * 0.9, (-local_y / distance).max(0.0), (-local_z / distance).max(0.0))
210 } else {
211 (0.0, 0.0, 0.0)
212 };
213 gain *= 1.0 - 0.3 * behind - 0.15 * below * (1.0 - behind);
218 let muffle = behind.max(0.6 * below);
219 let cutoff = math::lerp(20_000.0, 2_500.0, muffle);
220 let lowpass = 1.0 - (-std::f32::consts::TAU * cutoff / rate).exp();
221
222 let angle = (pan + 1.0) * FRAC_PI_4;
223 ([angle.cos() * gain, angle.sin() * gain], lowpass)
224 }
225
226 fn render(&mut self, out: &mut [f32], listener: &Listener, group_gain: f32, rate: f32) -> bool {
228 if self.is_finished() {
229 return false;
230 }
231 let slot = self.slot.clone();
232 let Some(data) = slot.get() else {
233 if slot.has_failed() {
234 self.finish();
235 return false;
236 }
237 return true; };
239 if self.paused {
240 return true;
241 }
242
243 let frames = out.len() / 2;
244 let total = data.frames();
245 if total == 0 {
246 self.finish();
247 return false;
248 }
249 let ch = data.channels;
250 let (target_gains, lowpass) = self.targets(listener, rate, ch);
251 if self.fresh {
252 self.gains = target_gains;
253 self.lowpass = lowpass;
254 self.fresh = false;
255 }
256 let start_gains = self.gains;
257 let gain_step = [
258 (target_gains[0] - start_gains[0]) / frames as f32,
259 (target_gains[1] - start_gains[1]) / frames as f32,
260 ];
261 let lp_start = self.lowpass;
262 let lp_step = (lowpass - lp_start) / frames as f32;
263 let mono = self.spatial.is_some() || ch == 1;
264 let step = data.sample_rate as f64 / rate as f64 * self.pitch as f64;
265 let s = &data.samples;
266
267 let read = |frame: usize, c: usize| s[frame * ch + c.min(ch - 1)];
268
269 for i in 0..frames {
270 if self.volume != self.target_volume {
272 if self.volume < self.target_volume {
273 self.volume = (self.volume + self.fade_step).min(self.target_volume);
274 } else {
275 self.volume = (self.volume - self.fade_step).max(self.target_volume);
276 }
277 } else if self.stop_after_fade {
278 self.finish();
279 break;
280 }
281
282 let idx = self.pos as usize;
283 let frac = (self.pos - idx as f64) as f32;
284 let next = if idx + 1 < total {
285 Some(idx + 1)
286 } else if self.looping {
287 Some(0)
288 } else {
289 None
290 };
291 let sample = |c: usize| {
292 let a = read(idx, c);
293 let b = next.map_or(0.0, |n| read(n, c));
294 a + (b - a) * frac
295 };
296 let (mut l, mut r) = if mono {
297 let m = if ch == 1 {
298 sample(0)
299 } else {
300 (0..ch).map(sample).sum::<f32>() / ch as f32
301 };
302 (m, m)
303 } else {
304 (sample(0), sample(1))
305 };
306
307 let fi = i as f32;
308 let lp = lp_start + lp_step * fi;
309 if lp < 0.999 {
310 self.lp_state[0] += lp * (l - self.lp_state[0]);
311 self.lp_state[1] += lp * (r - self.lp_state[1]);
312 l = self.lp_state[0];
313 r = self.lp_state[1];
314 } else {
315 self.lp_state = [l, r];
316 }
317
318 let v = self.volume * group_gain;
319 out[i * 2] += l * (start_gains[0] + gain_step[0] * fi) * v;
320 out[i * 2 + 1] += r * (start_gains[1] + gain_step[1] * fi) * v;
321
322 self.pos += step;
323 if self.pos >= total as f64 {
324 if self.looping {
325 self.pos %= total as f64;
326 } else {
327 self.finish();
328 break;
329 }
330 }
331 }
332 self.gains = target_gains;
333 self.lowpass = lowpass;
334 let seconds = self.pos / data.sample_rate as f64;
335 self.shared.position.store(seconds.to_bits(), Ordering::Relaxed);
336 !self.is_finished()
337 }
338}
339
340fn fade_step(distance: f32, seconds: f32, rate: f32) -> f32 {
341 if seconds <= 0.0 { f32::INFINITY } else { distance / (seconds * rate) }
342}
343
344#[derive(Debug, Clone, Copy)]
345pub(crate) struct Listener {
346 pub position: Vec3,
347 pub yaw: f32,
348 pub pitch: f32,
349}
350
351pub(crate) struct Mixer {
352 rx: Receiver<Command>,
353 voices: Vec<Voice>,
354 listener: Listener,
355 groups: [f32; 3],
356 master: f32,
357 rate: f32,
358}
359
360impl Mixer {
361 pub(crate) fn new(rx: Receiver<Command>, rate: u32) -> Self {
362 Self {
363 rx,
364 voices: Vec::with_capacity(64),
365 listener: Listener { position: Vec3::ZERO, yaw: 0.0, pitch: 0.0 },
366 groups: [1.0; 3],
367 master: 1.0,
368 rate: rate as f32,
369 }
370 }
371
372 fn handle_commands(&mut self) {
373 while let Ok(cmd) = self.rx.try_recv() {
374 match cmd {
375 Command::Play(mut voice) => {
376 if self.voices.len() >= MAX_VOICES {
377 let oldest = self.voices.remove(0);
378 oldest.shared.finished.store(true, Ordering::Release);
379 }
380 if voice.fade_step == 0.0 {
381 voice.fade_step = f32::INFINITY;
382 }
383 self.voices.push(*voice);
384 }
385 Command::Change(id, change) => {
386 let rate = self.rate;
387 if let Some(v) = self.voices.iter_mut().find(|v| v.id == id) {
388 v.apply(change, rate);
389 }
390 }
391 Command::StopAll { group, fade } => {
392 let rate = self.rate;
393 for v in self.voices.iter_mut().filter(|v| group.is_none_or(|g| v.group == g)) {
394 v.apply(Change::Stop { fade }, rate);
395 }
396 }
397 Command::Listener { position, yaw, pitch } => self.listener = Listener { position, yaw, pitch },
398 Command::GroupVolume(group, volume) => self.groups[group.index()] = volume.max(0.0),
399 Command::MasterVolume(volume) => self.master = volume.max(0.0),
400 }
401 }
402 }
403
404 pub(crate) fn render(&mut self, out: &mut [f32]) {
406 self.handle_commands();
407 out.fill(0.0);
408 let (listener, rate, master, groups) = (self.listener, self.rate, self.master, self.groups);
409 self.voices.retain_mut(|v| v.render(out, &listener, master * groups[v.group.index()], rate));
410 for s in out.iter_mut() {
411 *s = s.clamp(-1.0, 1.0);
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::super::data::SoundData;
419 use super::*;
420 use std::sync::mpsc::channel;
421
422 fn voice(id: u64, data: SoundData) -> (Box<Voice>, Arc<VoiceShared>) {
423 let shared = Arc::new(VoiceShared::default());
424 let v = Voice::new(id, Arc::new(SoundSlot::ready(data)), shared.clone(), Group::Sound);
425 (Box::new(v), shared)
426 }
427
428 #[test]
429 fn plays_and_finishes() {
430 let (tx, rx) = channel();
431 let mut mixer = Mixer::new(rx, 44_100);
432 let (v, shared) = voice(1, SoundData::from_samples(vec![0.5; 100], 1, 44_100));
433 tx.send(Command::Play(v)).unwrap();
434 let mut out = vec![0.0; 128];
435 mixer.render(&mut out);
436 assert!((out[0] - 0.5).abs() < 1e-4 && (out[1] - 0.5).abs() < 1e-4);
437 assert!(!shared.finished.load(Ordering::Acquire));
438 mixer.render(&mut out);
439 assert!(shared.finished.load(Ordering::Acquire));
440 assert!(mixer.voices.is_empty());
441 }
442
443 #[test]
444 fn spatial_left_is_louder_in_left_ear() {
445 let (tx, rx) = channel();
446 let mut mixer = Mixer::new(rx, 44_100);
447 let (mut v, _) = voice(1, SoundData::from_samples(vec![0.5; 1000], 1, 44_100));
448 v.spatial = Some(Spatial { position: Vec3::new(-3.0, 0.0, 0.0), min_distance: 1.0, max_distance: 30.0, rolloff: 1.0 });
449 tx.send(Command::Play(v)).unwrap();
450 let mut out = vec![0.0; 64];
451 mixer.render(&mut out);
452 assert!(out[0] > out[1] * 3.0);
453 }
454
455 #[test]
456 fn height_and_pitch_change_the_sound() {
457 let (_, rx) = channel();
458 let mixer = Mixer::new(rx, 44_100);
459 let at = |position: Vec3, pitch: f32| {
460 let (mut v, _) = voice(1, SoundData::from_samples(vec![0.5; 10], 1, 44_100));
461 v.spatial = Some(Spatial { position, min_distance: 1.0, max_distance: 30.0, rolloff: 1.0 });
462 let listener = Listener { position: Vec3::ZERO, yaw: 0.0, pitch };
463 let ([l, r], lowpass) = v.targets(&listener, mixer.rate, 1);
464 (l + r, lowpass)
465 };
466 let (above, above_lp) = at(Vec3::new(0.0, 0.0, 3.0), 0.0);
467 let (below, below_lp) = at(Vec3::new(0.0, 0.0, -3.0), 0.0);
468 let (behind, behind_lp) = at(Vec3::new(0.0, -3.0, 0.0), 0.0);
469 assert!(above > below && below > behind);
470 assert!(above_lp > below_lp && below_lp > behind_lp);
471 let (_, looking_down_lp) = at(Vec3::new(0.0, 0.0, 3.0), -90.0);
473 assert!((looking_down_lp - behind_lp).abs() < 1e-4);
474 }
475}