1use crate::math::{exp2, log2, sin_turns};
34use crate::{Error, Result};
35
36pub const MAX_OUTPUT_CHANNELS: usize = 8;
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum OutputLayout {
41 Mono,
42 Stereo,
43 Quad,
44 Surround51,
45 Surround71,
46}
47impl OutputLayout {
48 pub const fn channels(self) -> usize {
49 match self {
50 Self::Mono => 1,
51 Self::Stereo => 2,
52 Self::Quad => 4,
53 Self::Surround51 => 6,
54 Self::Surround71 => 8,
55 }
56 }
57 pub fn from_channels(channels: usize) -> Result<Self> {
58 match channels {
59 1 => Ok(Self::Mono),
60 2 => Ok(Self::Stereo),
61 4 => Ok(Self::Quad),
62 6 => Ok(Self::Surround51),
63 8 => Ok(Self::Surround71),
64 _ => Err(Error::invalid(
65 "Output layout needs 1, 2, 4, 6 or 8 channels",
66 )),
67 }
68 }
69 pub const fn channel_mask(self) -> u32 {
71 match self {
72 Self::Mono => 4,
73 Self::Stereo => 3,
74 Self::Quad => 0x33,
75 Self::Surround51 => 0x3f,
76 Self::Surround71 => 0x63f,
77 }
78 }
79 pub fn downmix_stereo(self, input: &[f32], out: &mut [f32]) -> Result<usize> {
85 let ch = self.channels();
86 let frames = input.len() / ch;
87 if input.len() % ch != 0 || out.len() / 2 < frames {
88 return Err(Error::invalid(
89 "Downmix needs complete frames and sufficient stereo output",
90 ));
91 }
92 if self == Self::Stereo {
93 out[..input.len()].copy_from_slice(input);
94 return Ok(frames);
95 }
96 let matrix = self.stereo_matrix();
97 for (frame, target) in input.chunks_exact(ch).zip(out.chunks_exact_mut(2)) {
98 let (mut l, mut r) = (0.0, 0.0);
99 for c in 0..ch {
100 l += frame[c] as f64 * matrix[c][0];
101 r += frame[c] as f64 * matrix[c][1];
102 }
103 target[0] = l as f32;
104 target[1] = r as f32;
105 }
106 Ok(frames)
107 }
108 fn stereo_matrix(self) -> [[f64; 2]; 8] {
109 let mut m = [[0.0; 2]; 8];
110 let centre = std::f64::consts::FRAC_1_SQRT_2;
111 if self == Self::Mono {
112 m[0] = [centre, centre];
113 return m;
114 }
115 m[0] = [1.0, 0.0];
116 m[1] = [0.0, 1.0];
117 if self == Self::Quad {
118 m[2] = [1.0, 0.0];
119 m[3] = [0.0, 1.0];
120 } else {
121 m[2] = [centre, centre];
122 m[4] = [1.0, 0.0];
123 m[5] = [0.0, 1.0];
124 if self == Self::Surround71 {
125 m[6] = [1.0, 0.0];
126 m[7] = [0.0, 1.0];
127 }
128 }
129 m
130 }
131 fn pan(self, x: f64, z: f64) -> [f64; 8] {
132 let mut gains = [0.0; 8];
133 if self == Self::Mono {
134 gains[0] = 1.0;
135 return gains;
136 }
137 let norm = (x * x + z * z).sqrt();
138 let (x, z) = if norm < 1e-12 {
139 (0.0, 1.0)
140 } else {
141 (x / norm, z / norm)
142 };
143 if self == Self::Stereo {
144 let turns = (x + 1.0) / 8.0;
145 gains[0] = sin_turns(0.25 - turns);
146 gains[1] = sin_turns(turns);
147 return gains;
148 }
149 let speakers: &[(usize, f64, f64)] = match self {
152 Self::Quad => &[(0, -Q, Q), (1, Q, Q), (3, Q, -Q), (2, -Q, -Q)],
153 Self::Surround51 => &[
154 (0, -0.5, T),
155 (2, 0.0, 1.0),
156 (1, 0.5, T),
157 (5, S110, C110),
158 (4, -S110, C110),
159 ],
160 _ => &[
161 (0, -0.5, T),
162 (2, 0.0, 1.0),
163 (1, 0.5, T),
164 (7, 1.0, 0.0),
165 (5, 0.5, -T),
166 (4, -0.5, -T),
167 (6, -1.0, 0.0),
168 ],
169 };
170 for i in 0..speakers.len() {
171 let (a, ax, az) = speakers[i];
172 let (b, bx, bz) = speakers[(i + 1) % speakers.len()];
173 let det = ax * bz - bx * az;
174 let ga = (x * bz - bx * z) / det;
175 let gb = (ax * z - x * az) / det;
176 if ga >= -1e-12 && gb >= -1e-12 {
177 let (ga, gb) = (ga.max(0.0), gb.max(0.0));
178 let power = (ga * ga + gb * gb).sqrt();
179 gains[a] = ga / power;
180 gains[b] = gb / power;
181 break;
182 }
183 }
184 gains
185 }
186}
187const Q: f64 = std::f64::consts::FRAC_1_SQRT_2;
188const T: f64 = 0.866_025_403_784_438_6;
189const S110: f64 = 0.939_692_620_785_908_4;
190const C110: f64 = -0.342_020_143_325_668_7;
191
192#[derive(Clone, Copy, Debug, PartialEq)]
195pub struct Listener {
196 pub position: [f32; 3],
197 pub velocity: [f32; 3],
198 pub forward: [f32; 3],
199 pub up: [f32; 3],
200}
201impl Default for Listener {
202 fn default() -> Self {
203 Self {
204 position: [0.0; 3],
205 velocity: [0.0; 3],
206 forward: [0.0, 0.0, -1.0],
207 up: [0.0, 1.0, 0.0],
208 }
209 }
210}
211impl Listener {
212 pub fn validate(&self) -> Result<()> {
213 finite(self.position)?;
214 finite(self.velocity)?;
215 finite(self.forward)?;
216 finite(self.up)?;
217 let f = normalize(vector(self.forward));
218 let u = normalize(vector(self.up));
219 if f.is_none()
220 || u.is_none()
221 || length(cross(f.unwrap_or([0.0; 3]), u.unwrap_or([0.0; 3]))) < 1e-6
222 {
223 return Err(Error::invalid(
224 "Listener orientation axes must be nonzero and not parallel",
225 ));
226 }
227 Ok(())
228 }
229}
230
231#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
232pub enum DistanceModel {
233 Linear,
234 #[default]
235 Inverse,
236 Exponential,
237}
238#[derive(Clone, Copy, Debug, PartialEq)]
242pub struct Attenuation {
243 pub min_distance: f32,
245 pub max_distance: f32,
247 pub rolloff: f32,
249 pub model: DistanceModel,
250}
251impl Default for Attenuation {
252 fn default() -> Self {
253 Self {
254 min_distance: 1.0,
255 max_distance: 100.0,
256 rolloff: 1.0,
257 model: DistanceModel::Inverse,
258 }
259 }
260}
261impl Attenuation {
262 pub fn validate(&self) -> Result<()> {
263 if !(0.001..=1e9).contains(&self.min_distance)
264 || !self.max_distance.is_finite()
265 || self.max_distance <= self.min_distance
266 || self.max_distance > 1e12
267 || !(0.0..=16.0).contains(&self.rolloff)
268 {
269 return Err(Error::invalid(
270 "Invalid 3D attenuation distances or rolloff",
271 ));
272 }
273 Ok(())
274 }
275 pub fn gain(&self, distance: f64) -> Result<f64> {
276 self.validate()?;
277 if !distance.is_finite() || distance < 0.0 {
278 return Err(Error::invalid("Distance must be finite and nonnegative"));
279 }
280 Ok(self.evaluate(distance))
281 }
282 fn evaluate(&self, distance: f64) -> f64 {
283 let (min, max, r) = (
284 self.min_distance as f64,
285 self.max_distance as f64,
286 self.rolloff as f64,
287 );
288 let d = distance.clamp(min, max);
289 match self.model {
290 DistanceModel::Linear => (1.0 - r * (d - min) / (max - min)).clamp(0.0, 1.0),
291 DistanceModel::Inverse => min / (min + r * (d - min)),
292 DistanceModel::Exponential => exp2(-r * log2(d / min)),
293 }
294 }
295}
296#[derive(Clone, Copy, Debug, Default, PartialEq)]
299pub struct Emitter {
300 pub position: [f32; 3],
301 pub velocity: [f32; 3],
302 pub attenuation: Attenuation,
303}
304impl Emitter {
305 pub fn validate(&self) -> Result<()> {
306 finite(self.position)?;
307 finite(self.velocity)?;
308 self.attenuation.validate()
309 }
310}
311
312#[derive(Clone, Copy, Debug, PartialEq)]
313pub struct SpatialConfig {
314 pub speed_of_sound: f32,
316 pub doppler_factor: f32,
318}
319impl Default for SpatialConfig {
320 fn default() -> Self {
321 Self {
322 speed_of_sound: 343.3,
323 doppler_factor: 1.0,
324 }
325 }
326}
327#[derive(Clone, Copy, Debug, PartialEq)]
329pub struct Placement {
330 pub gains: [f64; 8],
331 pub distance_gain: f64,
332 pub doppler: f64,
333}
334impl SpatialConfig {
335 pub fn validate(&self) -> Result<()> {
336 if !(1.0..=1e6).contains(&self.speed_of_sound)
337 || !(0.0..=16.0).contains(&self.doppler_factor)
338 {
339 return Err(Error::invalid("Invalid sound speed or Doppler factor"));
340 }
341 Ok(())
342 }
343 pub fn place(
344 &self,
345 listener: &Listener,
346 emitter: &Emitter,
347 layout: OutputLayout,
348 ) -> Result<Placement> {
349 self.validate()?;
350 listener.validate()?;
351 emitter.validate()?;
352 Ok(self.place_validated(listener, emitter, layout))
353 }
354 pub(super) fn place_validated(
355 &self,
356 listener: &Listener,
357 emitter: &Emitter,
358 layout: OutputLayout,
359 ) -> Placement {
360 let relative =
361 std::array::from_fn(|i| emitter.position[i] as f64 - listener.position[i] as f64);
362 let distance = length(relative);
363 let forward = normalize(vector(listener.forward)).unwrap();
364 let right = normalize(cross(forward, vector(listener.up))).unwrap();
365 let gains = layout.pan(dot(relative, right), dot(relative, forward));
366 let doppler = if distance == 0.0 || self.doppler_factor == 0.0 {
367 1.0
368 } else {
369 let direction = relative.map(|v| v / distance);
370 let c = self.speed_of_sound as f64;
371 let factor = self.doppler_factor as f64;
372 let l = (factor * dot(vector(listener.velocity), direction)).clamp(-0.9 * c, 0.9 * c);
373 let s = (factor * dot(vector(emitter.velocity), direction)).clamp(-0.9 * c, 0.9 * c);
374 ((c + l) / (c + s)).clamp(0.25, 4.0)
375 };
376 Placement {
377 gains,
378 distance_gain: emitter.attenuation.evaluate(distance),
379 doppler,
380 }
381 }
382}
383fn finite(v: [f32; 3]) -> Result<()> {
384 if v.iter().all(|x| x.is_finite()) {
385 Ok(())
386 } else {
387 Err(Error::invalid("Spatial vectors must be finite"))
388 }
389}
390fn vector(v: [f32; 3]) -> [f64; 3] {
391 v.map(|x| x as f64)
392}
393fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
394 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
395}
396fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
397 [
398 a[1] * b[2] - a[2] * b[1],
399 a[2] * b[0] - a[0] * b[2],
400 a[0] * b[1] - a[1] * b[0],
401 ]
402}
403fn length(v: [f64; 3]) -> f64 {
404 dot(v, v).sqrt()
405}
406fn normalize(v: [f64; 3]) -> Option<[f64; 3]> {
407 let n = length(v);
408 (n > 0.0).then(|| v.map(|x| x / n))
409}