use derive_more::{Display, IsVariant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, IsVariant)]
#[non_exhaustive]
pub enum ChannelLayoutKind {
#[display("mono")]
Mono,
#[display("stereo")]
Stereo,
#[display("stereo downmix")]
StereoDownmix,
#[display("surround")]
Surround,
#[display("quad")]
Quad,
#[display("hexagonal")]
Hexagonal,
#[display("octagonal")]
Octagonal,
#[display("hexadecagonal")]
Hexadecagonal,
#[display("cube")]
Cube,
#[display("2.1")]
Ch2_1,
#[display("2.1 alternative")]
Ch2_1Alt,
#[display("2.2")]
Ch2_2,
#[display("3.1")]
Ch3_1,
#[display("3.1.2")]
Ch3_1_2,
#[display("4.0")]
Ch4_0,
#[display("4.1")]
Ch4_1,
#[display("5.0")]
Ch5_0,
#[display("5.0 back")]
Ch5_0Back,
#[display("5.1")]
Ch5_1,
#[display("5.1 back")]
Ch5_1Back,
#[display("5.1.2 back")]
Ch5_1_2Back,
#[display("5.1.4 back")]
Ch5_1_4Back,
#[display("6.0")]
Ch6_0,
#[display("6.0 front")]
Ch6_0Front,
#[display("6.1")]
Ch6_1,
#[display("6.1 back")]
Ch6_1Back,
#[display("6.1 front")]
Ch6_1Front,
#[display("7.0")]
Ch7_0,
#[display("7.0 front")]
Ch7_0Front,
#[display("7.1")]
Ch7_1,
#[display("7.1 wide")]
Ch7_1Wide,
#[display("7.1 wide back")]
Ch7_1WideBack,
#[display("7.1 top back")]
Ch7_1TopBack,
#[display("7.1.2")]
Ch7_1_2,
#[display("7.1.4 back")]
Ch7_1_4Back,
#[display("7.2.3")]
Ch7_2_3,
#[display("9.1.4 back")]
Ch9_1_4Back,
#[display("22.2")]
Ch22_2,
#[display("unknown")]
Unknown,
}
impl Default for ChannelLayoutKind {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::Unknown
}
}
impl ChannelLayoutKind {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn from_u32(value: u32) -> Self {
match value {
1 => Self::Mono,
2 => Self::Stereo,
3 => Self::StereoDownmix,
4 => Self::Surround,
5 => Self::Quad,
6 => Self::Hexagonal,
7 => Self::Octagonal,
8 => Self::Hexadecagonal,
9 => Self::Cube,
10 => Self::Ch2_1,
11 => Self::Ch2_1Alt,
12 => Self::Ch2_2,
13 => Self::Ch3_1,
14 => Self::Ch3_1_2,
15 => Self::Ch4_0,
16 => Self::Ch4_1,
17 => Self::Ch5_0,
18 => Self::Ch5_0Back,
19 => Self::Ch5_1,
20 => Self::Ch5_1Back,
21 => Self::Ch5_1_2Back,
22 => Self::Ch5_1_4Back,
23 => Self::Ch6_0,
24 => Self::Ch6_0Front,
25 => Self::Ch6_1,
26 => Self::Ch6_1Back,
27 => Self::Ch6_1Front,
28 => Self::Ch7_0,
29 => Self::Ch7_0Front,
30 => Self::Ch7_1,
31 => Self::Ch7_1Wide,
32 => Self::Ch7_1WideBack,
33 => Self::Ch7_1TopBack,
34 => Self::Ch7_1_2,
35 => Self::Ch7_1_4Back,
36 => Self::Ch7_2_3,
37 => Self::Ch9_1_4Back,
38 => Self::Ch22_2,
_ => Self::Unknown,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn to_u32(self) -> u32 {
match self {
Self::Unknown => 0,
Self::Mono => 1,
Self::Stereo => 2,
Self::StereoDownmix => 3,
Self::Surround => 4,
Self::Quad => 5,
Self::Hexagonal => 6,
Self::Octagonal => 7,
Self::Hexadecagonal => 8,
Self::Cube => 9,
Self::Ch2_1 => 10,
Self::Ch2_1Alt => 11,
Self::Ch2_2 => 12,
Self::Ch3_1 => 13,
Self::Ch3_1_2 => 14,
Self::Ch4_0 => 15,
Self::Ch4_1 => 16,
Self::Ch5_0 => 17,
Self::Ch5_0Back => 18,
Self::Ch5_1 => 19,
Self::Ch5_1Back => 20,
Self::Ch5_1_2Back => 21,
Self::Ch5_1_4Back => 22,
Self::Ch6_0 => 23,
Self::Ch6_0Front => 24,
Self::Ch6_1 => 25,
Self::Ch6_1Back => 26,
Self::Ch6_1Front => 27,
Self::Ch7_0 => 28,
Self::Ch7_0Front => 29,
Self::Ch7_1 => 30,
Self::Ch7_1Wide => 31,
Self::Ch7_1WideBack => 32,
Self::Ch7_1TopBack => 33,
Self::Ch7_1_2 => 34,
Self::Ch7_1_4Back => 35,
Self::Ch7_2_3 => 36,
Self::Ch9_1_4Back => 37,
Self::Ch22_2 => 38,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u32)]
pub enum AudioChannelOrderKind {
#[default]
Unspecified = 0,
Native = 1,
Custom = 2,
Ambisonic = 3,
}
impl AudioChannelOrderKind {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn from_u32(value: u32) -> Self {
match value {
1 => Self::Native,
2 => Self::Custom,
3 => Self::Ambisonic,
_ => Self::Unspecified,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_u32(self) -> u32 {
self as u32
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
pub use alloc_only::{AudioChannelLayout, AudioChannelSpec};
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
mod alloc_only {
use super::{AudioChannelOrderKind, ChannelLayoutKind};
use smol_str::SmolStr;
use std::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AudioChannelSpec {
index: u32,
raw_id: u32,
label: SmolStr,
}
impl AudioChannelSpec {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(index: u32, raw_id: u32) -> Self {
Self {
index,
raw_id,
label: SmolStr::new_inline(""),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn index(&self) -> u32 {
self.index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn raw_id(&self) -> u32 {
self.raw_id
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn label(&self) -> &str {
self.label.as_str()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_index(mut self, value: u32) -> Self {
self.set_index(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_index(&mut self, value: u32) -> &mut Self {
self.index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_raw_id(mut self, value: u32) -> Self {
self.set_raw_id(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_raw_id(&mut self, value: u32) -> &mut Self {
self.raw_id = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_label(mut self, value: impl Into<SmolStr>) -> Self {
self.set_label(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_label(&mut self, value: impl Into<SmolStr>) -> &mut Self {
self.label = value.into();
self
}
}
#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AudioChannelLayout {
order: AudioChannelOrderKind,
channels: u32,
known_kind: ChannelLayoutKind,
native_mask: Option<u64>,
custom_channels: Vec<AudioChannelSpec>,
description: SmolStr,
}
impl AudioChannelLayout {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(channels: u32) -> Self {
Self {
channels,
order: AudioChannelOrderKind::Unspecified,
known_kind: ChannelLayoutKind::Unknown,
native_mask: None,
custom_channels: Vec::new(),
description: SmolStr::new_inline(""),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn order(&self) -> AudioChannelOrderKind {
self.order
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channels(&self) -> u32 {
self.channels
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn known_kind(&self) -> ChannelLayoutKind {
self.known_kind
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn native_mask(&self) -> Option<u64> {
self.native_mask
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn custom_channels(&self) -> &[AudioChannelSpec] {
self.custom_channels.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn description(&self) -> &str {
self.description.as_str()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn is_empty(&self) -> bool {
self.channels == 0
&& self.order == AudioChannelOrderKind::Unspecified
&& self.known_kind == ChannelLayoutKind::Unknown
&& self.native_mask.is_none()
&& self.custom_channels.is_empty()
&& self.description.is_empty()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_order(mut self, value: AudioChannelOrderKind) -> Self {
self.set_order(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_order(&mut self, value: AudioChannelOrderKind) -> &mut Self {
self.order = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_channels(mut self, value: u32) -> Self {
self.set_channels(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_channels(&mut self, value: u32) -> &mut Self {
self.channels = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_known_kind(mut self, value: ChannelLayoutKind) -> Self {
self.set_known_kind(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_known_kind(&mut self, value: ChannelLayoutKind) -> &mut Self {
self.known_kind = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_native_mask(mut self, value: Option<u64>) -> Self {
self.set_native_mask(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_native_mask(&mut self, value: Option<u64>) -> &mut Self {
self.native_mask = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_custom_channels(mut self, value: Vec<AudioChannelSpec>) -> Self {
self.set_custom_channels(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_custom_channels(&mut self, value: Vec<AudioChannelSpec>) -> &mut Self {
self.custom_channels = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_description(mut self, value: impl Into<SmolStr>) -> Self {
self.set_description(value);
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_description(&mut self, value: impl Into<SmolStr>) -> &mut Self {
self.description = value.into();
self
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn channel_layout_kind_default_is_unknown() {
assert!(matches!(
ChannelLayoutKind::default(),
ChannelLayoutKind::Unknown
));
}
#[test]
fn channel_layout_kind_round_trip_u32() {
let all = [
ChannelLayoutKind::Unknown,
ChannelLayoutKind::Mono,
ChannelLayoutKind::Stereo,
ChannelLayoutKind::StereoDownmix,
ChannelLayoutKind::Surround,
ChannelLayoutKind::Quad,
ChannelLayoutKind::Hexagonal,
ChannelLayoutKind::Octagonal,
ChannelLayoutKind::Hexadecagonal,
ChannelLayoutKind::Cube,
ChannelLayoutKind::Ch2_1,
ChannelLayoutKind::Ch2_1Alt,
ChannelLayoutKind::Ch2_2,
ChannelLayoutKind::Ch3_1,
ChannelLayoutKind::Ch3_1_2,
ChannelLayoutKind::Ch4_0,
ChannelLayoutKind::Ch4_1,
ChannelLayoutKind::Ch5_0,
ChannelLayoutKind::Ch5_0Back,
ChannelLayoutKind::Ch5_1,
ChannelLayoutKind::Ch5_1Back,
ChannelLayoutKind::Ch5_1_2Back,
ChannelLayoutKind::Ch5_1_4Back,
ChannelLayoutKind::Ch6_0,
ChannelLayoutKind::Ch6_0Front,
ChannelLayoutKind::Ch6_1,
ChannelLayoutKind::Ch6_1Back,
ChannelLayoutKind::Ch6_1Front,
ChannelLayoutKind::Ch7_0,
ChannelLayoutKind::Ch7_0Front,
ChannelLayoutKind::Ch7_1,
ChannelLayoutKind::Ch7_1Wide,
ChannelLayoutKind::Ch7_1WideBack,
ChannelLayoutKind::Ch7_1TopBack,
ChannelLayoutKind::Ch7_1_2,
ChannelLayoutKind::Ch7_1_4Back,
ChannelLayoutKind::Ch7_2_3,
ChannelLayoutKind::Ch9_1_4Back,
ChannelLayoutKind::Ch22_2,
];
for kind in all {
let n = kind.to_u32();
assert_eq!(
ChannelLayoutKind::from_u32(n),
kind,
"round-trip failed for {kind:?}"
);
}
}
#[test]
fn channel_layout_kind_unknown_for_garbage() {
assert_eq!(
ChannelLayoutKind::from_u32(99_999),
ChannelLayoutKind::Unknown
);
assert_eq!(ChannelLayoutKind::from_u32(0), ChannelLayoutKind::Unknown);
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[test]
fn channel_layout_kind_display() {
assert_eq!(format!("{}", ChannelLayoutKind::Mono), "mono");
assert_eq!(format!("{}", ChannelLayoutKind::Ch5_1), "5.1");
assert_eq!(
format!("{}", ChannelLayoutKind::Ch7_1WideBack),
"7.1 wide back"
);
assert_eq!(format!("{}", ChannelLayoutKind::Unknown), "unknown");
}
#[test]
fn channel_layout_kind_is_variant() {
assert!(ChannelLayoutKind::Mono.is_mono());
assert!(!ChannelLayoutKind::Stereo.is_mono());
assert!(ChannelLayoutKind::Ch5_1.is_ch_5_1());
assert!(ChannelLayoutKind::Unknown.is_unknown());
}
#[test]
fn order_default_is_unspecified() {
assert_eq!(
AudioChannelOrderKind::default(),
AudioChannelOrderKind::Unspecified
);
}
#[test]
fn order_round_trip_u32() {
for o in [
AudioChannelOrderKind::Unspecified,
AudioChannelOrderKind::Native,
AudioChannelOrderKind::Custom,
AudioChannelOrderKind::Ambisonic,
] {
assert_eq!(AudioChannelOrderKind::from_u32(o.as_u32()), o);
}
}
#[test]
fn order_unspecified_for_garbage() {
assert_eq!(
AudioChannelOrderKind::from_u32(42),
AudioChannelOrderKind::Unspecified
);
assert_eq!(
AudioChannelOrderKind::from_u32(0),
AudioChannelOrderKind::Unspecified
);
}
#[test]
fn order_repr_matches_as_u32() {
assert_eq!(AudioChannelOrderKind::Unspecified as u32, 0);
assert_eq!(AudioChannelOrderKind::Native as u32, 1);
assert_eq!(AudioChannelOrderKind::Custom as u32, 2);
assert_eq!(AudioChannelOrderKind::Ambisonic as u32, 3);
assert_eq!(AudioChannelOrderKind::Native.as_u32(), 1);
}
#[cfg(any(feature = "std", feature = "alloc"))]
mod alloc_tests {
use super::*;
#[test]
fn spec_construct_and_access() {
let s = AudioChannelSpec::new(2, 4);
assert_eq!(s.index(), 2);
assert_eq!(s.raw_id(), 4);
assert_eq!(s.label(), "");
}
#[test]
fn spec_builders_chain() {
let s = AudioChannelSpec::default()
.with_index(1)
.with_raw_id(3)
.with_label("FL");
assert_eq!(s.index(), 1);
assert_eq!(s.raw_id(), 3);
assert_eq!(s.label(), "FL");
}
#[test]
fn spec_setters_chain() {
let mut s = AudioChannelSpec::default();
s.set_index(7).set_raw_id(11).set_label("BC");
assert_eq!(s.index(), 7);
assert_eq!(s.raw_id(), 11);
assert_eq!(s.label(), "BC");
}
#[test]
fn layout_default_is_empty() {
let l = AudioChannelLayout::default();
assert!(l.is_empty());
assert_eq!(l.channels(), 0);
assert_eq!(l.order(), AudioChannelOrderKind::Unspecified);
assert_eq!(l.known_kind(), ChannelLayoutKind::Unknown);
assert!(l.native_mask().is_none());
assert!(l.custom_channels().is_empty());
assert_eq!(l.description(), "");
}
#[test]
fn layout_new_with_channels_only() {
let l = AudioChannelLayout::new(6);
assert!(!l.is_empty()); assert_eq!(l.channels(), 6);
}
#[test]
fn layout_builders_chain() {
let l = AudioChannelLayout::new(6)
.with_order(AudioChannelOrderKind::Native)
.with_known_kind(ChannelLayoutKind::Ch5_1)
.with_native_mask(Some(0x3F))
.with_description("5.1 side");
assert_eq!(l.channels(), 6);
assert_eq!(l.order(), AudioChannelOrderKind::Native);
assert_eq!(l.known_kind(), ChannelLayoutKind::Ch5_1);
assert_eq!(l.native_mask(), Some(0x3F));
assert_eq!(l.description(), "5.1 side");
}
#[test]
fn layout_custom_channels_round_trip() {
let custom = vec![
AudioChannelSpec::new(0, 1).with_label("FL"),
AudioChannelSpec::new(1, 2).with_label("FR"),
];
let l = AudioChannelLayout::new(2)
.with_order(AudioChannelOrderKind::Custom)
.with_custom_channels(custom);
assert_eq!(l.custom_channels().len(), 2);
assert_eq!(l.custom_channels()[0].label(), "FL");
assert_eq!(l.custom_channels()[1].label(), "FR");
}
#[test]
fn layout_setters_chain() {
let mut l = AudioChannelLayout::default();
l.set_channels(8)
.set_order(AudioChannelOrderKind::Native)
.set_known_kind(ChannelLayoutKind::Ch7_1)
.set_native_mask(Some(0x63F));
assert_eq!(l.channels(), 8);
assert!(matches!(l.known_kind(), ChannelLayoutKind::Ch7_1));
}
}
}