use crate::Error;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Layout {
Mono,
#[default]
Stereo,
Discrete(u32),
TwoPointOne,
ThreePointZero,
Quad,
FourPointZero,
FivePointZero,
FivePointOne,
SixPointOne,
SevenPointOne,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Speaker {
FrontLeft,
FrontRight,
FrontCenter,
Lfe,
BackLeft,
BackRight,
BackCenter,
SideLeft,
SideRight,
}
impl Layout {
pub fn from_channels(channels: u32) -> Result<Self, Error> {
match channels {
0 => Err(Error::Unsupported(
"audio layout must contain at least one channel".into(),
)),
1 => Ok(Self::Mono),
2 => Ok(Self::Stereo),
3 => Ok(Self::TwoPointOne),
4 => Ok(Self::Quad),
5 => Ok(Self::FivePointZero),
6 => Ok(Self::FivePointOne),
7 => Ok(Self::SixPointOne),
8 => Ok(Self::SevenPointOne),
channels => Ok(Self::Discrete(channels)),
}
}
pub fn channels(self) -> u32 {
match self {
Self::Discrete(channels) => channels,
layout => layout.speakers().map_or(0, <[_]>::len) as u32,
}
}
pub(crate) fn speakers(self) -> Option<&'static [Speaker]> {
use Speaker::*;
Some(match self {
Self::Mono => &[FrontCenter],
Self::Stereo => &[FrontLeft, FrontRight],
Self::Discrete(_) => return None,
Self::TwoPointOne => &[FrontLeft, FrontRight, Lfe],
Self::ThreePointZero => &[FrontLeft, FrontRight, FrontCenter],
Self::Quad => &[FrontLeft, FrontRight, BackLeft, BackRight],
Self::FourPointZero => &[FrontLeft, FrontRight, FrontCenter, BackCenter],
Self::FivePointZero => &[FrontLeft, FrontRight, FrontCenter, SideLeft, SideRight],
Self::FivePointOne => &[FrontLeft, FrontRight, FrontCenter, Lfe, SideLeft, SideRight],
Self::SixPointOne => &[FrontLeft, FrontRight, FrontCenter, Lfe, BackCenter, SideLeft, SideRight],
Self::SevenPointOne => &[
FrontLeft,
FrontRight,
FrontCenter,
Lfe,
BackLeft,
BackRight,
SideLeft,
SideRight,
],
})
}
pub(crate) fn validate(self) -> Result<(), Error> {
if self.channels() == 0 {
return Err(Error::Unsupported(
"audio layout must contain at least one channel".into(),
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_map_to_the_wave_defaults() {
let expected = [
Layout::Mono,
Layout::Stereo,
Layout::TwoPointOne,
Layout::Quad,
Layout::FivePointZero,
Layout::FivePointOne,
Layout::SixPointOne,
Layout::SevenPointOne,
];
for (count, layout) in (1..).zip(expected) {
assert_eq!(Layout::from_channels(count).unwrap(), layout);
assert_eq!(layout.channels(), count);
}
assert_eq!(Layout::from_channels(9).unwrap(), Layout::Discrete(9));
assert!(Layout::from_channels(0).is_err());
}
#[test]
fn speakers_follow_the_canonical_order() {
let layouts = [
Layout::Mono,
Layout::Stereo,
Layout::TwoPointOne,
Layout::ThreePointZero,
Layout::Quad,
Layout::FourPointZero,
Layout::FivePointZero,
Layout::FivePointOne,
Layout::SixPointOne,
Layout::SevenPointOne,
];
for layout in layouts {
let speakers = layout.speakers().unwrap();
assert!(
speakers.windows(2).all(|pair| (pair[0] as u8) < (pair[1] as u8)),
"{layout:?} is out of order"
);
}
}
}