1use crate::Error;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum Layout {
12 Mono,
14 #[default]
16 Stereo,
17 Discrete(u32),
21 TwoPointOne,
23 ThreePointZero,
25 Quad,
27 FourPointZero,
29 FivePointZero,
31 FivePointOne,
33 SixPointOne,
35 SevenPointOne,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub(crate) enum Speaker {
42 FrontLeft,
43 FrontRight,
44 FrontCenter,
45 Lfe,
46 BackLeft,
47 BackRight,
48 BackCenter,
49 SideLeft,
50 SideRight,
51}
52
53impl Layout {
54 pub fn from_channels(channels: u32) -> Result<Self, Error> {
60 match channels {
61 0 => Err(Error::Unsupported(
62 "audio layout must contain at least one channel".into(),
63 )),
64 1 => Ok(Self::Mono),
65 2 => Ok(Self::Stereo),
66 3 => Ok(Self::TwoPointOne),
67 4 => Ok(Self::Quad),
68 5 => Ok(Self::FivePointZero),
69 6 => Ok(Self::FivePointOne),
70 7 => Ok(Self::SixPointOne),
71 8 => Ok(Self::SevenPointOne),
72 channels => Ok(Self::Discrete(channels)),
73 }
74 }
75
76 pub fn channels(self) -> u32 {
78 match self {
79 Self::Discrete(channels) => channels,
80 layout => layout.speakers().map_or(0, <[_]>::len) as u32,
81 }
82 }
83
84 pub(crate) fn speakers(self) -> Option<&'static [Speaker]> {
87 use Speaker::*;
88
89 Some(match self {
90 Self::Mono => &[FrontCenter],
91 Self::Stereo => &[FrontLeft, FrontRight],
92 Self::Discrete(_) => return None,
93 Self::TwoPointOne => &[FrontLeft, FrontRight, Lfe],
94 Self::ThreePointZero => &[FrontLeft, FrontRight, FrontCenter],
95 Self::Quad => &[FrontLeft, FrontRight, BackLeft, BackRight],
96 Self::FourPointZero => &[FrontLeft, FrontRight, FrontCenter, BackCenter],
97 Self::FivePointZero => &[FrontLeft, FrontRight, FrontCenter, SideLeft, SideRight],
98 Self::FivePointOne => &[FrontLeft, FrontRight, FrontCenter, Lfe, SideLeft, SideRight],
99 Self::SixPointOne => &[FrontLeft, FrontRight, FrontCenter, Lfe, BackCenter, SideLeft, SideRight],
100 Self::SevenPointOne => &[
101 FrontLeft,
102 FrontRight,
103 FrontCenter,
104 Lfe,
105 BackLeft,
106 BackRight,
107 SideLeft,
108 SideRight,
109 ],
110 })
111 }
112
113 pub(crate) fn validate(self) -> Result<(), Error> {
114 if self.channels() == 0 {
115 return Err(Error::Unsupported(
116 "audio layout must contain at least one channel".into(),
117 ));
118 }
119 Ok(())
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn counts_map_to_the_wave_defaults() {
129 let expected = [
130 Layout::Mono,
131 Layout::Stereo,
132 Layout::TwoPointOne,
133 Layout::Quad,
134 Layout::FivePointZero,
135 Layout::FivePointOne,
136 Layout::SixPointOne,
137 Layout::SevenPointOne,
138 ];
139 for (count, layout) in (1..).zip(expected) {
140 assert_eq!(Layout::from_channels(count).unwrap(), layout);
141 assert_eq!(layout.channels(), count);
142 }
143
144 assert_eq!(Layout::from_channels(9).unwrap(), Layout::Discrete(9));
145 assert!(Layout::from_channels(0).is_err());
146 }
147
148 #[test]
151 fn speakers_follow_the_canonical_order() {
152 let layouts = [
153 Layout::Mono,
154 Layout::Stereo,
155 Layout::TwoPointOne,
156 Layout::ThreePointZero,
157 Layout::Quad,
158 Layout::FourPointZero,
159 Layout::FivePointZero,
160 Layout::FivePointOne,
161 Layout::SixPointOne,
162 Layout::SevenPointOne,
163 ];
164 for layout in layouts {
165 let speakers = layout.speakers().unwrap();
166 assert!(
167 speakers.windows(2).all(|pair| (pair[0] as u8) < (pair[1] as u8)),
168 "{layout:?} is out of order"
169 );
170 }
171 }
172}