use core::str::FromStr;
use derive_more::{Display, IsVariant, TryUnwrap, Unwrap};
use smol_str::SmolStr;
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_helpers::strings::sample_format")
)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, IsVariant)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum SampleFormat {
U8,
S16,
S32,
Flt,
Dbl,
U8p,
S16p,
S32p,
Fltp,
Dblp,
S64,
S64p,
Unknown(u32),
Other(SmolStr),
}
impl Default for SampleFormat {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::Unknown(u32::MAX)
}
}
impl SampleFormat {
pub fn as_str(&self) -> &str {
match self {
Self::U8 => "u8",
Self::S16 => "s16",
Self::S32 => "s32",
Self::Flt => "flt",
Self::Dbl => "dbl",
Self::U8p => "u8p",
Self::S16p => "s16p",
Self::S32p => "s32p",
Self::Fltp => "fltp",
Self::Dblp => "dblp",
Self::S64 => "s64",
Self::S64p => "s64p",
Self::Unknown(_) => "unknown",
Self::Other(s) => s.as_str(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn to_u32(&self) -> u32 {
match self {
Self::U8 => 0,
Self::S16 => 1,
Self::S32 => 2,
Self::Flt => 3,
Self::Dbl => 4,
Self::U8p => 5,
Self::S16p => 6,
Self::S32p => 7,
Self::Fltp => 8,
Self::Dblp => 9,
Self::S64 => 10,
Self::S64p => 11,
Self::Unknown(v) => *v,
Self::Other(_) => u32::MAX,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn from_u32(v: u32) -> Self {
match v {
0 => Self::U8,
1 => Self::S16,
2 => Self::S32,
3 => Self::Flt,
4 => Self::Dbl,
5 => Self::U8p,
6 => Self::S16p,
7 => Self::S32p,
8 => Self::Fltp,
9 => Self::Dblp,
10 => Self::S64,
11 => Self::S64p,
_ => Self::Unknown(v),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_planar(&self) -> bool {
matches!(
self,
Self::U8p | Self::S16p | Self::S32p | Self::Fltp | Self::Dblp | Self::S64p
)
}
}
impl FromStr for SampleFormat {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"u8" => Self::U8,
"s16" => Self::S16,
"s32" => Self::S32,
"flt" => Self::Flt,
"dbl" => Self::Dbl,
"u8p" => Self::U8p,
"s16p" => Self::S16p,
"s32p" => Self::S32p,
"fltp" => Self::Fltp,
"dblp" => Self::Dblp,
"s64" => Self::S64,
"s64p" => Self::S64p,
other => Self::Other(SmolStr::new(other)),
})
}
}
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_helpers::strings::audio_container_format")
)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, IsVariant, Unwrap, TryUnwrap)]
#[display("{}", self.as_str())]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
#[non_exhaustive]
pub enum ContainerFormat {
#[is_variant(ignore)]
Mp3,
Aac,
Flac,
Ogg,
Opus,
Wav,
Aiff,
Alac,
Wma,
Ape,
Wv,
Mka,
#[is_variant(ignore)]
M4a,
Caf,
Other(SmolStr),
}
impl Default for ContainerFormat {
#[inline]
fn default() -> Self {
Self::Other(SmolStr::new_inline(""))
}
}
impl ContainerFormat {
#[inline(always)]
pub const fn is_mp3(&self) -> bool {
matches!(self, Self::Mp3)
}
#[inline(always)]
pub const fn is_m4a(&self) -> bool {
matches!(self, Self::M4a)
}
pub fn as_str(&self) -> &str {
match self {
Self::Mp3 => "mp3",
Self::Aac => "aac",
Self::Flac => "flac",
Self::Ogg => "ogg",
Self::Opus => "opus",
Self::Wav => "wav",
Self::Aiff => "aiff",
Self::Alac => "alac",
Self::Wma => "wma",
Self::Ape => "ape",
Self::Wv => "wv",
Self::Mka => "mka",
Self::M4a => "m4a",
Self::Caf => "caf",
Self::Other(s) => s.as_str(),
}
}
#[inline(always)]
pub const fn as_extension(&self) -> &'static str {
match self {
Self::Mp3 => "mp3",
Self::Aac => "aac",
Self::Flac => "flac",
Self::Ogg => "ogg",
Self::Opus => "opus",
Self::Wav => "wav",
Self::Aiff => "aiff",
Self::Alac => "m4a",
Self::Wma => "wma",
Self::Ape => "ape",
Self::Wv => "wv",
Self::Mka => "mka",
Self::M4a => "m4a",
Self::Caf => "caf",
Self::Other(_) => "",
}
}
}
impl FromStr for ContainerFormat {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"mp3" => Self::Mp3,
"aac" => Self::Aac,
"flac" => Self::Flac,
"ogg" => Self::Ogg,
"opus" => Self::Opus,
"wav" => Self::Wav,
"aiff" => Self::Aiff,
"alac" => Self::Alac,
"wma" => Self::Wma,
"ape" => Self::Ape,
"wv" => Self::Wv,
"mka" => Self::Mka,
"m4a" => Self::M4a,
"caf" => Self::Caf,
other => Self::Other(SmolStr::new(other)),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use ::std::string::ToString;
#[test]
fn audio_format_u32_round_trips_named_variants() {
for v in [
SampleFormat::U8,
SampleFormat::S16,
SampleFormat::S32,
SampleFormat::Flt,
SampleFormat::Dbl,
SampleFormat::U8p,
SampleFormat::S16p,
SampleFormat::S32p,
SampleFormat::Fltp,
SampleFormat::Dblp,
SampleFormat::S64,
SampleFormat::S64p,
] {
let back = SampleFormat::from_u32(v.to_u32());
assert_eq!(back, v, "round-trip mismatch for `{}`", v.as_str());
}
}
#[test]
fn audio_format_unknown_u32_round_trips() {
let v = SampleFormat::Unknown(12_345);
assert_eq!(SampleFormat::from_u32(v.to_u32()), v);
}
#[test]
fn audio_format_from_str_named() {
for slug in [
"u8", "s16", "s32", "flt", "dbl", "u8p", "s16p", "s32p", "fltp", "dblp", "s64", "s64p",
] {
let v: SampleFormat = slug.parse().unwrap();
assert!(!v.is_other(), "`{slug}` should be a named variant");
assert_eq!(v.as_str(), slug);
}
}
#[test]
fn audio_format_unknown_slug_lands_in_other() {
let v: SampleFormat = "weird_sample_fmt".parse().unwrap();
assert!(v.is_other());
assert_eq!(v.as_str(), "weird_sample_fmt");
}
#[test]
fn audio_format_is_planar_predicate() {
assert!(SampleFormat::U8p.is_planar());
assert!(SampleFormat::S16p.is_planar());
assert!(SampleFormat::Fltp.is_planar());
assert!(!SampleFormat::U8.is_planar());
assert!(!SampleFormat::Flt.is_planar());
}
#[test]
fn audio_format_display_matches_as_str() {
assert_eq!(SampleFormat::Flt.to_string(), "flt");
assert_eq!(SampleFormat::Fltp.to_string(), "fltp");
}
#[test]
fn audio_container_round_trips_named_variants() {
for slug in [
"mp3", "aac", "flac", "ogg", "opus", "wav", "aiff", "alac", "wma", "ape", "wv", "mka", "m4a",
"caf",
] {
let v: ContainerFormat = slug.parse().unwrap();
assert!(!v.is_other(), "`{slug}` should be a named variant");
assert_eq!(v.as_str(), slug);
}
}
#[test]
fn audio_container_unknown_lands_in_other() {
let v: ContainerFormat = "weird_audio_container".parse().unwrap();
assert!(v.is_other());
assert_eq!(v.as_str(), "weird_audio_container");
}
#[test]
fn audio_container_display_matches_as_str() {
assert_eq!(ContainerFormat::Mp3.to_string(), "mp3");
assert_eq!(ContainerFormat::Flac.to_string(), "flac");
assert_eq!(
ContainerFormat::Other(SmolStr::new("snd")).to_string(),
"snd"
);
}
#[test]
fn audio_container_unwrap_other_borrowed_view() {
let v = ContainerFormat::Other(SmolStr::new("custom_audio"));
assert_eq!(v.unwrap_other_ref().as_str(), "custom_audio");
assert!(v.try_unwrap_other_ref().is_ok());
let named = ContainerFormat::Flac;
assert!(named.try_unwrap_other_ref().is_err());
}
#[test]
fn audio_container_as_extension_matches_disk_form() {
for (variant, ext) in [
(ContainerFormat::Mp3, "mp3"),
(ContainerFormat::Aac, "aac"),
(ContainerFormat::Flac, "flac"),
(ContainerFormat::Ogg, "ogg"),
(ContainerFormat::Opus, "opus"),
(ContainerFormat::Wav, "wav"),
(ContainerFormat::Aiff, "aiff"),
(ContainerFormat::Wma, "wma"),
(ContainerFormat::Ape, "ape"),
(ContainerFormat::Wv, "wv"),
(ContainerFormat::Mka, "mka"),
(ContainerFormat::M4a, "m4a"),
(ContainerFormat::Caf, "caf"),
] {
assert_eq!(variant.as_extension(), ext);
}
assert_eq!(ContainerFormat::Alac.as_str(), "alac");
assert_eq!(ContainerFormat::Alac.as_extension(), "m4a");
assert_eq!(
ContainerFormat::Other(SmolStr::new("weird")).as_extension(),
""
);
}
}