use core::str::FromStr;
use derive_more::{Display, IsVariant};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, IsVariant)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum ChannelLayoutKind {
Mono,
Stereo,
StereoDownmix,
Surround,
Quad,
Hexagonal,
Octagonal,
Hexadecagonal,
Cube,
Ch2_1,
Ch2_1Alt,
Ch2_2,
Ch3_1,
Ch3_1_2,
Ch4_0,
Ch4_1,
Ch5_0,
Ch5_0Back,
Ch5_1,
Ch5_1Back,
Ch5_1_2Back,
Ch5_1_4Back,
Ch6_0,
Ch6_0Front,
Ch6_1,
Ch6_1Back,
Ch6_1Front,
Ch7_0,
Ch7_0Front,
Ch7_1,
Ch7_1Wide,
Ch7_1WideBack,
Ch7_1TopBack,
Ch7_1_2,
Ch7_1_4Back,
Ch7_2_3,
Ch9_1_4Back,
Ch22_2,
Unknown,
}
impl Default for ChannelLayoutKind {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::Unknown
}
}
impl ChannelLayoutKind {
const ALL: &'static [Self] = &[
Self::Mono,
Self::Stereo,
Self::StereoDownmix,
Self::Surround,
Self::Quad,
Self::Hexagonal,
Self::Octagonal,
Self::Hexadecagonal,
Self::Cube,
Self::Ch2_1,
Self::Ch2_1Alt,
Self::Ch2_2,
Self::Ch3_1,
Self::Ch3_1_2,
Self::Ch4_0,
Self::Ch4_1,
Self::Ch5_0,
Self::Ch5_0Back,
Self::Ch5_1,
Self::Ch5_1Back,
Self::Ch5_1_2Back,
Self::Ch5_1_4Back,
Self::Ch6_0,
Self::Ch6_0Front,
Self::Ch6_1,
Self::Ch6_1Back,
Self::Ch6_1Front,
Self::Ch7_0,
Self::Ch7_0Front,
Self::Ch7_1,
Self::Ch7_1Wide,
Self::Ch7_1WideBack,
Self::Ch7_1TopBack,
Self::Ch7_1_2,
Self::Ch7_1_4Back,
Self::Ch7_2_3,
Self::Ch9_1_4Back,
Self::Ch22_2,
Self::Unknown,
];
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Mono => "mono",
Self::Stereo => "stereo",
Self::StereoDownmix => "stereo-downmix",
Self::Surround => "surround",
Self::Quad => "quad",
Self::Hexagonal => "hexagonal",
Self::Octagonal => "octagonal",
Self::Hexadecagonal => "hexadecagonal",
Self::Cube => "cube",
Self::Ch2_1 => "2.1",
Self::Ch2_1Alt => "2.1-alternative",
Self::Ch2_2 => "2.2",
Self::Ch3_1 => "3.1",
Self::Ch3_1_2 => "3.1.2",
Self::Ch4_0 => "4.0",
Self::Ch4_1 => "4.1",
Self::Ch5_0 => "5.0",
Self::Ch5_0Back => "5.0-back",
Self::Ch5_1 => "5.1",
Self::Ch5_1Back => "5.1-back",
Self::Ch5_1_2Back => "5.1.2-back",
Self::Ch5_1_4Back => "5.1.4-back",
Self::Ch6_0 => "6.0",
Self::Ch6_0Front => "6.0-front",
Self::Ch6_1 => "6.1",
Self::Ch6_1Back => "6.1-back",
Self::Ch6_1Front => "6.1-front",
Self::Ch7_0 => "7.0",
Self::Ch7_0Front => "7.0-front",
Self::Ch7_1 => "7.1",
Self::Ch7_1Wide => "7.1-wide",
Self::Ch7_1WideBack => "7.1-wide-back",
Self::Ch7_1TopBack => "7.1-top-back",
Self::Ch7_1_2 => "7.1.2",
Self::Ch7_1_4Back => "7.1.4-back",
Self::Ch7_2_3 => "7.2.3",
Self::Ch9_1_4Back => "9.1.4-back",
Self::Ch22_2 => "22.2",
Self::Unknown => "unknown",
}
}
#[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, Error)]
#[error("not a channel-layout-kind name")]
#[non_exhaustive]
pub struct ParseChannelLayoutKindError;
impl FromStr for ChannelLayoutKind {
type Err = ParseChannelLayoutKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL
.iter()
.find(|kind| kind.as_str().eq_ignore_ascii_case(s))
.copied()
.ok_or(ParseChannelLayoutKindError)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Display)]
#[display("{}", self.as_str())]
#[repr(u32)]
pub enum AudioChannelOrderKind {
#[default]
Unspecified = 0,
Native = 1,
Custom = 2,
Ambisonic = 3,
}
impl AudioChannelOrderKind {
const ALL: &'static [Self] = &[
Self::Unspecified,
Self::Native,
Self::Custom,
Self::Ambisonic,
];
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Unspecified => "unspecified",
Self::Native => "native",
Self::Custom => "custom",
Self::Ambisonic => "ambisonic",
}
}
#[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
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)]
#[error("not an audio-channel-order name")]
#[non_exhaustive]
pub struct ParseAudioChannelOrderKindError;
impl FromStr for AudioChannelOrderKind {
type Err = ParseAudioChannelOrderKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL
.iter()
.find(|order| order.as_str().eq_ignore_ascii_case(s))
.copied()
.ok_or(ParseAudioChannelOrderKindError)
}
}
#[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;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[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(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[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(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
mod serde_impls {
use core::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
use super::{AudioChannelOrderKind, ChannelLayoutKind};
macro_rules! serde_via_slug {
($ty:ty, $expecting:literal) => {
impl Serialize for $ty {
#[cfg_attr(not(tarpaulin), inline(always))]
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for $ty {
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
struct SlugVisitor;
impl Visitor<'_> for SlugVisitor {
type Value = $ty;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str($expecting)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<$ty>().map_err(E::custom)
}
}
de.deserialize_str(SlugVisitor)
}
}
};
}
serde_via_slug!(ChannelLayoutKind, "a channel-layout-kind slug");
serde_via_slug!(AudioChannelOrderKind, "an audio-channel-order slug");
}
#[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
mod arbitrary_impls {
use arbitrary::{Arbitrary, Result, Unstructured};
use super::{AudioChannelOrderKind, ChannelLayoutKind};
macro_rules! arbitrary_via_roster {
($ty:ty) => {
impl<'a> Arbitrary<'a> for $ty {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
Ok(*u.choose(<$ty>::ALL)?)
}
}
};
}
arbitrary_via_roster!(ChannelLayoutKind);
arbitrary_via_roster!(AudioChannelOrderKind);
#[cfg(any(feature = "alloc", feature = "std"))]
mod alloc_only {
use arbitrary::{Arbitrary, Result, Unstructured};
use smol_str::SmolStr;
use std::vec::Vec;
use super::super::{AudioChannelLayout, AudioChannelSpec};
impl<'a> Arbitrary<'a> for AudioChannelSpec {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
Ok(Self::new(u32::arbitrary(u)?, u32::arbitrary(u)?).with_label(SmolStr::arbitrary(u)?))
}
}
impl<'a> Arbitrary<'a> for AudioChannelLayout {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
Ok(
Self::new(u32::arbitrary(u)?)
.with_order(Arbitrary::arbitrary(u)?)
.with_known_kind(Arbitrary::arbitrary(u)?)
.with_native_mask(Option::<u64>::arbitrary(u)?)
.with_custom_channels(Vec::<AudioChannelSpec>::arbitrary(u)?)
.with_description(SmolStr::arbitrary(u)?),
)
}
}
}
}
#[cfg(feature = "quickcheck")]
#[cfg_attr(docsrs, doc(cfg(feature = "quickcheck")))]
mod quickcheck_impls {
use quickcheck::{Arbitrary, Gen};
use super::{AudioChannelOrderKind, ChannelLayoutKind};
macro_rules! quickcheck_via_roster {
($ty:ty) => {
impl Arbitrary for $ty {
fn arbitrary(g: &mut Gen) -> Self {
*g.choose(<$ty>::ALL).expect("the roster is never empty")
}
}
};
}
quickcheck_via_roster!(ChannelLayoutKind);
quickcheck_via_roster!(AudioChannelOrderKind);
#[cfg(any(feature = "alloc", feature = "std"))]
mod alloc_only {
use quickcheck::{Arbitrary, Gen};
use smol_str::SmolStr;
use std::{string::String, vec::Vec};
use super::super::{AudioChannelLayout, AudioChannelSpec};
impl Arbitrary for AudioChannelSpec {
fn arbitrary(g: &mut Gen) -> Self {
Self::new(u32::arbitrary(g), u32::arbitrary(g))
.with_label(SmolStr::new(String::arbitrary(g)))
}
}
impl Arbitrary for AudioChannelLayout {
fn arbitrary(g: &mut Gen) -> Self {
Self::new(u32::arbitrary(g))
.with_order(Arbitrary::arbitrary(g))
.with_known_kind(Arbitrary::arbitrary(g))
.with_native_mask(Option::<u64>::arbitrary(g))
.with_custom_channels(Vec::<AudioChannelSpec>::arbitrary(g))
.with_description(SmolStr::new(String::arbitrary(g)))
}
}
}
}
#[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() {
for &kind in ChannelLayoutKind::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");
for &kind in ChannelLayoutKind::ALL {
assert_eq!(
format!("{kind}"),
kind.as_str(),
"display drifted from as_str"
);
}
}
#[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 channel_layout_kind_slugs() {
let table = [
(ChannelLayoutKind::Mono, "mono"),
(ChannelLayoutKind::Stereo, "stereo"),
(ChannelLayoutKind::StereoDownmix, "stereo-downmix"),
(ChannelLayoutKind::Surround, "surround"),
(ChannelLayoutKind::Quad, "quad"),
(ChannelLayoutKind::Hexagonal, "hexagonal"),
(ChannelLayoutKind::Octagonal, "octagonal"),
(ChannelLayoutKind::Hexadecagonal, "hexadecagonal"),
(ChannelLayoutKind::Cube, "cube"),
(ChannelLayoutKind::Ch2_1, "2.1"),
(ChannelLayoutKind::Ch2_1Alt, "2.1-alternative"),
(ChannelLayoutKind::Ch2_2, "2.2"),
(ChannelLayoutKind::Ch3_1, "3.1"),
(ChannelLayoutKind::Ch3_1_2, "3.1.2"),
(ChannelLayoutKind::Ch4_0, "4.0"),
(ChannelLayoutKind::Ch4_1, "4.1"),
(ChannelLayoutKind::Ch5_0, "5.0"),
(ChannelLayoutKind::Ch5_0Back, "5.0-back"),
(ChannelLayoutKind::Ch5_1, "5.1"),
(ChannelLayoutKind::Ch5_1Back, "5.1-back"),
(ChannelLayoutKind::Ch5_1_2Back, "5.1.2-back"),
(ChannelLayoutKind::Ch5_1_4Back, "5.1.4-back"),
(ChannelLayoutKind::Ch6_0, "6.0"),
(ChannelLayoutKind::Ch6_0Front, "6.0-front"),
(ChannelLayoutKind::Ch6_1, "6.1"),
(ChannelLayoutKind::Ch6_1Back, "6.1-back"),
(ChannelLayoutKind::Ch6_1Front, "6.1-front"),
(ChannelLayoutKind::Ch7_0, "7.0"),
(ChannelLayoutKind::Ch7_0Front, "7.0-front"),
(ChannelLayoutKind::Ch7_1, "7.1"),
(ChannelLayoutKind::Ch7_1Wide, "7.1-wide"),
(ChannelLayoutKind::Ch7_1WideBack, "7.1-wide-back"),
(ChannelLayoutKind::Ch7_1TopBack, "7.1-top-back"),
(ChannelLayoutKind::Ch7_1_2, "7.1.2"),
(ChannelLayoutKind::Ch7_1_4Back, "7.1.4-back"),
(ChannelLayoutKind::Ch7_2_3, "7.2.3"),
(ChannelLayoutKind::Ch9_1_4Back, "9.1.4-back"),
(ChannelLayoutKind::Ch22_2, "22.2"),
(ChannelLayoutKind::Unknown, "unknown"),
];
assert_eq!(table.len(), ChannelLayoutKind::ALL.len());
for (kind, slug) in table {
assert_eq!(kind.as_str(), slug, "slug mismatch for {kind:?}");
}
}
#[test]
fn channel_layout_kind_round_trips_through_its_slug() {
for &kind in ChannelLayoutKind::ALL {
assert_eq!(
kind.as_str().parse::<ChannelLayoutKind>(),
Ok(kind),
"slug round-trip failed for {kind:?}"
);
}
}
#[test]
fn channel_layout_kind_folds_ascii_case() {
assert_eq!(
"MONO".parse::<ChannelLayoutKind>(),
Ok(ChannelLayoutKind::Mono)
);
assert_eq!(
"5.1-Back".parse::<ChannelLayoutKind>(),
Ok(ChannelLayoutKind::Ch5_1Back)
);
assert_eq!(
"Stereo-DOWNMIX".parse::<ChannelLayoutKind>(),
Ok(ChannelLayoutKind::StereoDownmix)
);
}
#[test]
fn channel_layout_kind_rejects_what_it_cannot_name() {
assert!("".parse::<ChannelLayoutKind>().is_err());
assert!("atmos".parse::<ChannelLayoutKind>().is_err());
assert!("5.1-back ".parse::<ChannelLayoutKind>().is_err());
assert!("5.1 back".parse::<ChannelLayoutKind>().is_err());
}
#[test]
fn channel_layout_kind_slugs_do_not_collide_under_ascii_folding() {
for (i, a) in ChannelLayoutKind::ALL.iter().enumerate() {
for b in &ChannelLayoutKind::ALL[i + 1..] {
assert!(
!a.as_str().eq_ignore_ascii_case(b.as_str()),
"{a:?} and {b:?} answer to the same name"
);
}
}
}
#[test]
fn channel_layout_kind_roster_is_complete() {
let mut live = 0;
for code in 0..=64u32 {
let kind = ChannelLayoutKind::from_u32(code);
if kind.to_u32() == code {
live += 1;
assert!(
ChannelLayoutKind::ALL.contains(&kind),
"{kind:?} (code {code}) is missing from the roster"
);
}
}
assert_eq!(
ChannelLayoutKind::ALL.len(),
live,
"the roster holds an entry no wire code decodes to"
);
}
#[test]
fn order_default_is_unspecified() {
assert_eq!(
AudioChannelOrderKind::default(),
AudioChannelOrderKind::Unspecified
);
}
#[test]
fn order_round_trip_u32() {
for &o in AudioChannelOrderKind::ALL {
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);
}
#[test]
fn order_slugs() {
let table = [
(AudioChannelOrderKind::Unspecified, "unspecified"),
(AudioChannelOrderKind::Native, "native"),
(AudioChannelOrderKind::Custom, "custom"),
(AudioChannelOrderKind::Ambisonic, "ambisonic"),
];
assert_eq!(table.len(), AudioChannelOrderKind::ALL.len());
for (order, slug) in table {
assert_eq!(order.as_str(), slug, "slug mismatch for {order:?}");
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[test]
fn order_display_is_the_slug() {
assert_eq!(format!("{}", AudioChannelOrderKind::Ambisonic), "ambisonic");
for &order in AudioChannelOrderKind::ALL {
assert_eq!(
format!("{order}"),
order.as_str(),
"display drifted from as_str"
);
}
}
#[test]
fn order_round_trips_through_its_slug() {
for &order in AudioChannelOrderKind::ALL {
assert_eq!(
order.as_str().parse::<AudioChannelOrderKind>(),
Ok(order),
"slug round-trip failed for {order:?}"
);
}
}
#[test]
fn order_folds_ascii_case() {
assert_eq!(
"Native".parse::<AudioChannelOrderKind>(),
Ok(AudioChannelOrderKind::Native)
);
assert_eq!(
"AMBISONIC".parse::<AudioChannelOrderKind>(),
Ok(AudioChannelOrderKind::Ambisonic)
);
}
#[test]
fn order_rejects_what_it_cannot_name() {
assert!("".parse::<AudioChannelOrderKind>().is_err());
assert!("interleaved".parse::<AudioChannelOrderKind>().is_err());
assert_eq!(
AudioChannelOrderKind::from_u32(42),
AudioChannelOrderKind::Unspecified
);
}
#[test]
fn order_slugs_do_not_collide_under_ascii_folding() {
for (i, a) in AudioChannelOrderKind::ALL.iter().enumerate() {
for b in &AudioChannelOrderKind::ALL[i + 1..] {
assert!(
!a.as_str().eq_ignore_ascii_case(b.as_str()),
"{a:?} and {b:?} answer to the same name"
);
}
}
}
#[test]
fn order_roster_is_complete() {
let mut live = 0;
for code in 0..=16u32 {
let order = AudioChannelOrderKind::from_u32(code);
if order.as_u32() == code {
live += 1;
assert!(
AudioChannelOrderKind::ALL.contains(&order),
"{order:?} (code {code}) is missing from the roster"
);
}
}
assert_eq!(
AudioChannelOrderKind::ALL.len(),
live,
"the roster holds an entry no wire code decodes to"
);
}
#[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));
}
}
#[cfg(feature = "serde")]
mod serde_tests {
use serde::{
Deserialize, Serialize,
de::{
IntoDeserializer,
value::{Error as ValueError, StrDeserializer, U32Deserializer},
},
ser::{Impossible, Serializer},
};
use super::*;
struct SlugOnly<'a> {
expected: &'a str,
}
#[derive(Debug)]
struct Unreachable;
impl core::fmt::Display for Unreachable {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("the slug serializer cannot fail")
}
}
impl core::error::Error for Unreachable {}
impl serde::ser::Error for Unreachable {
fn custom<T: core::fmt::Display>(_: T) -> Self {
Self
}
}
macro_rules! not_a_slug {
($($method:ident($($arg:ty),*);)*) => {
$(
fn $method(self, $(_: $arg),*) -> Result<Self::Ok, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
)*
};
}
impl Serializer for SlugOnly<'_> {
type Ok = ();
type Error = Unreachable;
type SerializeSeq = Impossible<(), Unreachable>;
type SerializeTuple = Impossible<(), Unreachable>;
type SerializeTupleStruct = Impossible<(), Unreachable>;
type SerializeTupleVariant = Impossible<(), Unreachable>;
type SerializeMap = Impossible<(), Unreachable>;
type SerializeStruct = Impossible<(), Unreachable>;
type SerializeStructVariant = Impossible<(), Unreachable>;
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
assert_eq!(v, self.expected, "wrong slug on the wire");
Ok(())
}
not_a_slug! {
serialize_bool(bool);
serialize_i8(i8);
serialize_i16(i16);
serialize_i32(i32);
serialize_i64(i64);
serialize_u8(u8);
serialize_u16(u16);
serialize_u32(u32);
serialize_u64(u64);
serialize_f32(f32);
serialize_f64(f64);
serialize_char(char);
serialize_bytes(&[u8]);
serialize_none();
serialize_unit();
serialize_unit_struct(&'static str);
serialize_unit_variant(&'static str, u32, &'static str);
}
fn serialize_some<T>(self, _: &T) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_newtype_struct<T>(self, _: &'static str, _: &T) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_newtype_variant<T>(
self,
_: &'static str,
_: u32,
_: &'static str,
_: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_tuple_struct(
self,
_: &'static str,
_: usize,
) -> Result<Self::SerializeTupleStruct, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_tuple_variant(
self,
_: &'static str,
_: u32,
_: &'static str,
_: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_map(self, _: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_struct(
self,
_: &'static str,
_: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
fn serialize_struct_variant(
self,
_: &'static str,
_: u32,
_: &'static str,
_: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
panic!("a channel vocabulary must reach the wire as its slug")
}
}
fn assert_wire_slug<T: Serialize>(value: &T, expected: &str) {
value
.serialize(SlugOnly { expected })
.expect("the slug serializer cannot fail");
}
#[test]
fn every_variant_serializes_as_its_slug() {
for &kind in ChannelLayoutKind::ALL {
assert_wire_slug(&kind, kind.as_str());
}
for &order in AudioChannelOrderKind::ALL {
assert_wire_slug(&order, order.as_str());
}
}
#[test]
fn every_variant_deserializes_from_its_slug() {
for &kind in ChannelLayoutKind::ALL {
let de: StrDeserializer<'_, ValueError> = kind.as_str().into_deserializer();
assert_eq!(ChannelLayoutKind::deserialize(de).unwrap(), kind);
}
for &order in AudioChannelOrderKind::ALL {
let de: StrDeserializer<'_, ValueError> = order.as_str().into_deserializer();
assert_eq!(AudioChannelOrderKind::deserialize(de).unwrap(), order);
}
}
#[test]
fn deserialization_folds_case_like_the_door() {
let de: StrDeserializer<'_, ValueError> = "5.1-BACK".into_deserializer();
assert_eq!(
ChannelLayoutKind::deserialize(de).unwrap(),
ChannelLayoutKind::Ch5_1Back
);
let de: StrDeserializer<'_, ValueError> = "Ambisonic".into_deserializer();
assert_eq!(
AudioChannelOrderKind::deserialize(de).unwrap(),
AudioChannelOrderKind::Ambisonic
);
}
#[test]
fn an_unknown_slug_is_an_error_not_an_invented_value() {
let de: StrDeserializer<'_, ValueError> = "atmos".into_deserializer();
assert!(ChannelLayoutKind::deserialize(de).is_err());
let de: StrDeserializer<'_, ValueError> = "interleaved".into_deserializer();
assert!(AudioChannelOrderKind::deserialize(de).is_err());
}
#[test]
fn a_number_is_not_a_name() {
let de: U32Deserializer<ValueError> = 19u32.into_deserializer();
assert!(ChannelLayoutKind::deserialize(de).is_err());
let de: U32Deserializer<ValueError> = 1u32.into_deserializer();
assert!(AudioChannelOrderKind::deserialize(de).is_err());
}
#[cfg(any(feature = "alloc", feature = "std"))]
mod records {
use super::*;
#[test]
fn a_spec_is_a_map_of_its_accessor_names() {
let spec = AudioChannelSpec::new(2, 5).with_label("FL");
let json = serde_json::to_string(&spec).expect("a spec always serializes");
assert_eq!(json, r#"{"index":2,"raw_id":5,"label":"FL"}"#);
assert_eq!(
serde_json::from_str::<AudioChannelSpec>(&json).expect("its own output parses"),
spec
);
}
#[test]
fn a_layout_carries_its_vocabularies_as_slugs() {
let layout = AudioChannelLayout::new(6)
.with_order(AudioChannelOrderKind::Native)
.with_known_kind(ChannelLayoutKind::Ch5_1)
.with_native_mask(Some(0x3F))
.with_description("5.1(side)");
let json = serde_json::to_string(&layout).expect("a layout always serializes");
assert_eq!(
json,
r#"{"order":"native","channels":6,"known_kind":"5.1","native_mask":63,"custom_channels":[],"description":"5.1(side)"}"#
);
assert_eq!(
serde_json::from_str::<AudioChannelLayout>(&json).expect("its own output parses"),
layout
);
}
#[test]
fn a_custom_layout_round_trips_its_channel_list() {
let layout = AudioChannelLayout::new(2)
.with_order(AudioChannelOrderKind::Custom)
.with_custom_channels(vec![
AudioChannelSpec::new(0, 1).with_label("FL"),
AudioChannelSpec::new(1, 2).with_label("FR"),
]);
let json = serde_json::to_string(&layout).expect("a layout always serializes");
assert_eq!(
serde_json::from_str::<AudioChannelLayout>(&json).expect("its own output parses"),
layout
);
}
#[test]
fn a_record_inherits_the_vocabulary_doors_refusal() {
assert!(
serde_json::from_str::<AudioChannelLayout>(
r#"{"order":"interleaved","channels":2,"known_kind":"unknown","native_mask":null,"custom_channels":[],"description":""}"#
)
.is_err()
);
}
}
}
#[cfg(feature = "arbitrary")]
mod arbitrary_tests {
use arbitrary::{Arbitrary, Unstructured};
use super::*;
#[test]
fn every_variant_is_reachable() {
let mut layout_seen = [false; 64];
let mut order_seen = [false; 64];
for byte in 0..=u8::MAX {
let data = [byte];
let mut u = Unstructured::new(&data);
let kind = ChannelLayoutKind::arbitrary(&mut u).unwrap();
assert!(ChannelLayoutKind::ALL.contains(&kind));
layout_seen[kind.to_u32() as usize] = true;
let mut u = Unstructured::new(&data);
let order = AudioChannelOrderKind::arbitrary(&mut u).unwrap();
assert!(AudioChannelOrderKind::ALL.contains(&order));
order_seen[order.as_u32() as usize] = true;
}
assert_eq!(
layout_seen.iter().filter(|&&seen| seen).count(),
ChannelLayoutKind::ALL.len(),
"a layout kind the generator never produces"
);
assert_eq!(
order_seen.iter().filter(|&&seen| seen).count(),
AudioChannelOrderKind::ALL.len(),
"an order kind the generator never produces"
);
}
#[cfg(any(feature = "alloc", feature = "std"))]
mod records {
use super::*;
fn seeded_bytes(seed: u32) -> [u8; 48] {
let mut out = [0u8; 48];
let mut state = seed.wrapping_mul(2_654_435_761).wrapping_add(1);
for byte in &mut out {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
*byte = (state >> 24) as u8;
}
out
}
#[test]
fn a_spec_varies_across_every_field() {
let mut indices = 0u32;
let mut raw_ids = 0u32;
let mut labelled = false;
for seed in 0..4096 {
let data = seeded_bytes(seed);
let mut u = Unstructured::new(&data);
let spec = AudioChannelSpec::arbitrary(&mut u).expect("the spec generator is total");
indices |= spec.index();
raw_ids |= spec.raw_id();
labelled |= !spec.label().is_empty();
}
assert_ne!(indices, 0, "index never left zero");
assert_ne!(raw_ids, 0, "raw_id never left zero");
assert!(labelled, "label was never populated");
}
#[test]
fn a_layout_reaches_its_incoherent_combinations() {
let mut custom_without_channels = false;
let mut native_without_mask = false;
for seed in 0..4096 {
let data = seeded_bytes(seed);
let mut u = Unstructured::new(&data);
let layout = AudioChannelLayout::arbitrary(&mut u).expect("the generator is total");
custom_without_channels |=
layout.order() == AudioChannelOrderKind::Custom && layout.custom_channels().is_empty();
native_without_mask |=
layout.order() == AudioChannelOrderKind::Native && layout.native_mask().is_none();
if custom_without_channels && native_without_mask {
break;
}
}
assert!(
custom_without_channels,
"a Custom order with no channel list is unreachable"
);
assert!(
native_without_mask,
"a Native order with no mask is unreachable"
);
}
}
}
#[cfg(feature = "quickcheck")]
mod quickcheck_tests {
use quickcheck::{Arbitrary, Gen};
use super::*;
#[test]
fn every_variant_is_reachable() {
let mut g = Gen::new(16);
let mut layout_seen = [false; 64];
let mut order_seen = [false; 64];
for _ in 0..2000 {
let kind = ChannelLayoutKind::arbitrary(&mut g);
assert!(ChannelLayoutKind::ALL.contains(&kind));
layout_seen[kind.to_u32() as usize] = true;
let order = AudioChannelOrderKind::arbitrary(&mut g);
assert!(AudioChannelOrderKind::ALL.contains(&order));
order_seen[order.as_u32() as usize] = true;
}
assert_eq!(
layout_seen.iter().filter(|&&seen| seen).count(),
ChannelLayoutKind::ALL.len(),
"a layout kind the generator never produces"
);
assert_eq!(
order_seen.iter().filter(|&&seen| seen).count(),
AudioChannelOrderKind::ALL.len(),
"an order kind the generator never produces"
);
}
#[cfg(any(feature = "alloc", feature = "std"))]
mod records {
use super::*;
#[test]
fn the_records_vary_across_every_field() {
let mut g = Gen::new(8);
let mut indices = 0u32;
let mut raw_ids = 0u32;
let mut spec_labelled = false;
let mut channels = 0u32;
let mut masked = false;
let mut populated = false;
let mut described = false;
for _ in 0..500 {
let spec = AudioChannelSpec::arbitrary(&mut g);
indices |= spec.index();
raw_ids |= spec.raw_id();
spec_labelled |= !spec.label().is_empty();
let layout = AudioChannelLayout::arbitrary(&mut g);
channels |= layout.channels();
masked |= layout.native_mask().is_some();
populated |= !layout.custom_channels().is_empty();
described |= !layout.description().is_empty();
}
assert_ne!(indices, 0, "spec index never left zero");
assert_ne!(raw_ids, 0, "spec raw_id never left zero");
assert!(spec_labelled, "spec label was never populated");
assert_ne!(channels, 0, "layout channels never left zero");
assert!(masked, "layout native_mask was never Some");
assert!(populated, "layout custom_channels was never non-empty");
assert!(described, "layout description was never populated");
}
}
}
#[cfg(all(
feature = "serde",
feature = "quickcheck",
any(feature = "alloc", feature = "std")
))]
mod matrix_agreement_tests {
use quickcheck::{Arbitrary, Gen};
use super::*;
#[test]
fn every_generated_value_survives_the_wire() {
let mut g = Gen::new(8);
for _ in 0..200 {
let kind = ChannelLayoutKind::arbitrary(&mut g);
let json = serde_json::to_string(&kind).expect("a kind always serializes");
assert_eq!(
serde_json::from_str::<ChannelLayoutKind>(&json).expect("its own output parses"),
kind
);
let order = AudioChannelOrderKind::arbitrary(&mut g);
let json = serde_json::to_string(&order).expect("an order always serializes");
assert_eq!(
serde_json::from_str::<AudioChannelOrderKind>(&json).expect("its own output parses"),
order
);
let spec = AudioChannelSpec::arbitrary(&mut g);
let json = serde_json::to_string(&spec).expect("a spec always serializes");
assert_eq!(
serde_json::from_str::<AudioChannelSpec>(&json).expect("its own output parses"),
spec
);
let layout = AudioChannelLayout::arbitrary(&mut g);
let json = serde_json::to_string(&layout).expect("a layout always serializes");
assert_eq!(
serde_json::from_str::<AudioChannelLayout>(&json).expect("its own output parses"),
layout
);
}
}
}
}