1use std::fmt;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16pub struct ChannelMask(u32);
17
18impl ChannelMask {
19 pub const FRONT_LEFT: u32 = 1 << 0;
20 pub const FRONT_RIGHT: u32 = 1 << 1;
21 pub const FRONT_CENTER: u32 = 1 << 2;
22 pub const LFE1: u32 = 1 << 3;
23 pub const REAR_LEFT: u32 = 1 << 4;
24 pub const REAR_RIGHT: u32 = 1 << 5;
25 pub const FRONT_LEFT_CENTER: u32 = 1 << 6;
26 pub const FRONT_RIGHT_CENTER: u32 = 1 << 7;
27 pub const REAR_CENTER: u32 = 1 << 8;
28 pub const SIDE_LEFT: u32 = 1 << 9;
29 pub const SIDE_RIGHT: u32 = 1 << 10;
30 pub const TOP_CENTER: u32 = 1 << 11;
31 pub const TOP_FRONT_LEFT: u32 = 1 << 12;
32 pub const TOP_FRONT_CENTER: u32 = 1 << 13;
33 pub const TOP_FRONT_RIGHT: u32 = 1 << 14;
34 pub const TOP_REAR_LEFT: u32 = 1 << 15;
35 pub const TOP_REAR_CENTER: u32 = 1 << 16;
36 pub const TOP_REAR_RIGHT: u32 = 1 << 17;
37
38 const fn new(bits: u32) -> Self {
39 Self(bits)
40 }
41
42 pub const fn from_bits(bits: u32) -> Option<Self> {
45 if bits < (1 << 18) {
46 Some(Self::new(bits))
47 } else {
48 None
49 }
50 }
51
52 pub const fn bits(self) -> u32 {
53 self.0
54 }
55
56 pub const fn channels(self) -> usize {
57 self.0.count_ones() as usize
58 }
59
60 pub fn positions(self) -> Vec<ChannelPosition> {
63 (0..18)
64 .filter(|index| self.0 & (1 << index) != 0)
65 .filter_map(ChannelPosition::from_index)
66 .collect()
67 }
68
69 pub fn position(self, channel: usize) -> Option<ChannelPosition> {
70 self.positions().get(channel).copied()
71 }
72
73 pub fn pan(self) -> Vec<PanInfo> {
74 self.positions()
75 .into_iter()
76 .map(ChannelPosition::pan)
77 .collect()
78 }
79}
80
81impl fmt::Display for ChannelMask {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 let labels = self
84 .positions()
85 .into_iter()
86 .map(|position| position.label())
87 .collect::<Vec<_>>()
88 .join(",");
89 write!(f, "0x{:05x} [{}]", self.bits(), labels)
90 }
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95pub enum ChannelPosition {
96 FrontLeft,
97 FrontRight,
98 FrontCenter,
99 Lfe1,
100 RearLeft,
101 RearRight,
102 FrontLeftCenter,
103 FrontRightCenter,
104 RearCenter,
105 SideLeft,
106 SideRight,
107 TopCenter,
108 TopFrontLeft,
109 TopFrontCenter,
110 TopFrontRight,
111 TopRearLeft,
112 TopRearCenter,
113 TopRearRight,
114}
115
116impl ChannelPosition {
117 pub const fn from_index(index: u32) -> Option<Self> {
118 Some(match index {
119 0 => Self::FrontLeft,
120 1 => Self::FrontRight,
121 2 => Self::FrontCenter,
122 3 => Self::Lfe1,
123 4 => Self::RearLeft,
124 5 => Self::RearRight,
125 6 => Self::FrontLeftCenter,
126 7 => Self::FrontRightCenter,
127 8 => Self::RearCenter,
128 9 => Self::SideLeft,
129 10 => Self::SideRight,
130 11 => Self::TopCenter,
131 12 => Self::TopFrontLeft,
132 13 => Self::TopFrontCenter,
133 14 => Self::TopFrontRight,
134 15 => Self::TopRearLeft,
135 16 => Self::TopRearCenter,
136 17 => Self::TopRearRight,
137 _ => return None,
138 })
139 }
140
141 pub const fn label(self) -> &'static str {
142 match self {
143 Self::FrontLeft => "FL",
144 Self::FrontRight => "FR",
145 Self::FrontCenter => "FC",
146 Self::Lfe1 => "LFE",
147 Self::RearLeft => "RL",
148 Self::RearRight => "RR",
149 Self::FrontLeftCenter => "FLC",
150 Self::FrontRightCenter => "FRC",
151 Self::RearCenter => "RC",
152 Self::SideLeft => "SL",
153 Self::SideRight => "SR",
154 Self::TopCenter => "TC",
155 Self::TopFrontLeft => "TFL",
156 Self::TopFrontCenter => "TFC",
157 Self::TopFrontRight => "TFR",
158 Self::TopRearLeft => "TRL",
159 Self::TopRearCenter => "TRC",
160 Self::TopRearRight => "TRR",
161 }
162 }
163
164 pub const fn pan(self) -> PanInfo {
167 match self {
168 Self::FrontLeft => PanInfo::new(-30.0, 0.0),
169 Self::FrontRight => PanInfo::new(30.0, 0.0),
170 Self::FrontCenter => PanInfo::new(0.0, 0.0),
171 Self::Lfe1 => PanInfo::new(0.0, -10.0),
172 Self::RearLeft => PanInfo::new(-110.0, 0.0),
173 Self::RearRight => PanInfo::new(110.0, 0.0),
174 Self::FrontLeftCenter => PanInfo::new(-15.0, 0.0),
175 Self::FrontRightCenter => PanInfo::new(15.0, 0.0),
176 Self::RearCenter => PanInfo::new(180.0, 0.0),
177 Self::SideLeft => PanInfo::new(-90.0, 0.0),
178 Self::SideRight => PanInfo::new(90.0, 0.0),
179 Self::TopCenter => PanInfo::new(0.0, 90.0),
180 Self::TopFrontLeft => PanInfo::new(-30.0, 45.0),
181 Self::TopFrontCenter => PanInfo::new(0.0, 45.0),
182 Self::TopFrontRight => PanInfo::new(30.0, 45.0),
183 Self::TopRearLeft => PanInfo::new(-110.0, 45.0),
184 Self::TopRearCenter => PanInfo::new(180.0, 45.0),
185 Self::TopRearRight => PanInfo::new(110.0, 45.0),
186 }
187 }
188}
189
190#[derive(Clone, Copy, Debug, PartialEq)]
192pub struct PanInfo {
193 pub azimuth_degrees: f32,
194 pub elevation_degrees: f32,
195 pub gain: f32,
196}
197
198impl PanInfo {
199 const fn new(azimuth_degrees: f32, elevation_degrees: f32) -> Self {
200 Self {
201 azimuth_degrees,
202 elevation_degrees,
203 gain: 1.0,
204 }
205 }
206}
207
208#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
215pub enum ChannelLayout {
216 #[default]
218 Mono,
219 Stereo,
221 TwoPointOne,
223 Quad,
225 FivePointZero,
227 FivePointOne,
229 SixPointOne,
232 SevenPointOne,
235 Unknown(usize),
237}
238
239impl ChannelLayout {
240 pub const fn from_channel_count(channels: usize) -> Self {
242 match channels {
243 1 => Self::Mono,
244 2 => Self::Stereo,
245 3 => Self::TwoPointOne,
246 4 => Self::Quad,
247 5 => Self::FivePointZero,
248 6 => Self::FivePointOne,
249 7 => Self::SixPointOne,
250 8 => Self::SevenPointOne,
251 other => Self::Unknown(other),
252 }
253 }
254
255 pub const fn channels(self) -> usize {
257 match self {
258 Self::Mono => 1,
259 Self::Stereo => 2,
260 Self::TwoPointOne => 3,
261 Self::Quad => 4,
262 Self::FivePointZero => 5,
263 Self::FivePointOne => 6,
264 Self::SixPointOne => 7,
265 Self::SevenPointOne => 8,
266 Self::Unknown(channels) => channels,
267 }
268 }
269
270 pub const fn mask(self) -> Option<ChannelMask> {
272 match self {
273 Self::Mono => Some(ChannelMask::new(ChannelMask::FRONT_CENTER)),
274 Self::Stereo => Some(ChannelMask::new(
275 ChannelMask::FRONT_LEFT | ChannelMask::FRONT_RIGHT,
276 )),
277 Self::TwoPointOne => Some(ChannelMask::new(
278 ChannelMask::FRONT_LEFT | ChannelMask::FRONT_RIGHT | ChannelMask::LFE1,
279 )),
280 Self::Quad => Some(ChannelMask::new(
281 ChannelMask::FRONT_LEFT
282 | ChannelMask::FRONT_RIGHT
283 | ChannelMask::REAR_LEFT
284 | ChannelMask::REAR_RIGHT,
285 )),
286 Self::FivePointZero => Some(ChannelMask::new(
287 ChannelMask::FRONT_LEFT
288 | ChannelMask::FRONT_RIGHT
289 | ChannelMask::FRONT_CENTER
290 | ChannelMask::REAR_LEFT
291 | ChannelMask::REAR_RIGHT,
292 )),
293 Self::FivePointOne => Some(ChannelMask::new(
294 ChannelMask::FRONT_LEFT
295 | ChannelMask::FRONT_RIGHT
296 | ChannelMask::FRONT_CENTER
297 | ChannelMask::LFE1
298 | ChannelMask::REAR_LEFT
299 | ChannelMask::REAR_RIGHT,
300 )),
301 Self::SixPointOne => Some(ChannelMask::new(
302 ChannelMask::FRONT_LEFT
303 | ChannelMask::FRONT_RIGHT
304 | ChannelMask::FRONT_CENTER
305 | ChannelMask::LFE1
306 | ChannelMask::REAR_CENTER
307 | ChannelMask::SIDE_LEFT
308 | ChannelMask::SIDE_RIGHT,
309 )),
310 Self::SevenPointOne => Some(ChannelMask::new(
311 ChannelMask::FRONT_LEFT
312 | ChannelMask::FRONT_RIGHT
313 | ChannelMask::FRONT_CENTER
314 | ChannelMask::LFE1
315 | ChannelMask::REAR_LEFT
316 | ChannelMask::REAR_RIGHT
317 | ChannelMask::SIDE_LEFT
318 | ChannelMask::SIDE_RIGHT,
319 )),
320 Self::Unknown(_) => None,
321 }
322 }
323
324 pub fn from_channel_mask(mask: ChannelMask) -> Self {
326 let bits = mask.bits();
327 for layout in [
328 Self::Mono,
329 Self::Stereo,
330 Self::TwoPointOne,
331 Self::Quad,
332 Self::FivePointZero,
333 Self::FivePointOne,
334 Self::SixPointOne,
335 Self::SevenPointOne,
336 ] {
337 if layout.mask().is_some_and(|known| known.bits() == bits) {
338 return layout;
339 }
340 }
341 Self::Unknown(mask.channels())
342 }
343
344 pub const fn name(self) -> &'static str {
346 match self {
347 Self::Mono => "mono",
348 Self::Stereo => "stereo",
349 Self::TwoPointOne => "2.1",
350 Self::Quad => "quad",
351 Self::FivePointZero => "5.0",
352 Self::FivePointOne => "5.1",
353 Self::SixPointOne => "6.1",
354 Self::SevenPointOne => "7.1",
355 Self::Unknown(_) => "unknown",
356 }
357 }
358
359 pub const fn is_surround(self) -> bool {
361 matches!(
362 self,
363 Self::TwoPointOne
364 | Self::Quad
365 | Self::FivePointZero
366 | Self::FivePointOne
367 | Self::SixPointOne
368 | Self::SevenPointOne
369 )
370 }
371}
372
373impl fmt::Display for ChannelLayout {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 match self {
376 Self::Unknown(channels) => write!(f, "unknown ({channels}ch)"),
377 known => f.write_str(known.name()),
378 }
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn maps_standard_channel_counts() {
388 assert_eq!(ChannelLayout::from_channel_count(1), ChannelLayout::Mono);
389 assert_eq!(ChannelLayout::from_channel_count(2), ChannelLayout::Stereo);
390 assert_eq!(
391 ChannelLayout::from_channel_count(6),
392 ChannelLayout::FivePointOne
393 );
394 assert_eq!(
395 ChannelLayout::from_channel_count(8),
396 ChannelLayout::SevenPointOne
397 );
398 }
399
400 #[test]
401 fn unknown_layout_keeps_its_channel_count() {
402 let layout = ChannelLayout::from_channel_count(12);
403 assert_eq!(layout, ChannelLayout::Unknown(12));
404 assert_eq!(layout.channels(), 12);
405 assert_eq!(layout.to_string(), "unknown (12ch)");
406 }
407
408 #[test]
409 fn masks_and_pan_positions_follow_canonical_channel_order() {
410 let mask = ChannelLayout::FivePointOne.mask().unwrap();
411 assert_eq!(mask.bits(), 0x3f);
412 assert_eq!(
413 mask.positions()[..3],
414 [
415 ChannelPosition::FrontLeft,
416 ChannelPosition::FrontRight,
417 ChannelPosition::FrontCenter,
418 ]
419 );
420 assert_eq!(
421 ChannelLayout::from_channel_mask(mask),
422 ChannelLayout::FivePointOne
423 );
424 assert_eq!(mask.pan()[0].azimuth_degrees, -30.0);
425 assert_eq!(mask.pan()[5].azimuth_degrees, 110.0);
426 }
427
428 #[test]
429 fn side_surround_mask_is_not_mistaken_for_rear_surround() {
430 let bits = ChannelMask::FRONT_LEFT
431 | ChannelMask::FRONT_RIGHT
432 | ChannelMask::FRONT_CENTER
433 | ChannelMask::LFE1
434 | ChannelMask::SIDE_LEFT
435 | ChannelMask::SIDE_RIGHT;
436 let mask = ChannelMask::from_bits(bits).unwrap();
437 assert_eq!(
438 ChannelLayout::from_channel_mask(mask),
439 ChannelLayout::Unknown(6)
440 );
441 assert_eq!(mask.positions()[4], ChannelPosition::SideLeft);
442 }
443}