oaat-core 0.2.0

Core types, wire format, and protocol messages for OAAT (Open Advanced Audio Transport)
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AudioFormat {
    PcmS16le,
    PcmS24le,
    PcmS24le4,
    PcmS32le,
    PcmF32le,
    DsdU8,
    DsdU16le,
    DsdU32le,
    Flac,
    Opus,
}

impl AudioFormat {
    pub fn wire_id(self) -> u8 {
        match self {
            Self::PcmS16le => 0x01,
            Self::PcmS24le => 0x02,
            Self::PcmS24le4 => 0x03,
            Self::PcmS32le => 0x04,
            Self::PcmF32le => 0x05,
            Self::DsdU8 => 0x10,
            Self::DsdU16le => 0x11,
            Self::DsdU32le => 0x12,
            Self::Flac => 0x20,
            Self::Opus => 0x21,
        }
    }

    pub fn from_wire_id(id: u8) -> Option<Self> {
        match id {
            0x01 => Some(Self::PcmS16le),
            0x02 => Some(Self::PcmS24le),
            0x03 => Some(Self::PcmS24le4),
            0x04 => Some(Self::PcmS32le),
            0x05 => Some(Self::PcmF32le),
            0x10 => Some(Self::DsdU8),
            0x11 => Some(Self::DsdU16le),
            0x12 => Some(Self::DsdU32le),
            0x20 => Some(Self::Flac),
            0x21 => Some(Self::Opus),
            _ => None,
        }
    }

    pub fn is_pcm(self) -> bool {
        matches!(
            self,
            Self::PcmS16le | Self::PcmS24le | Self::PcmS24le4 | Self::PcmS32le | Self::PcmF32le
        )
    }

    pub fn is_dsd(self) -> bool {
        matches!(self, Self::DsdU8 | Self::DsdU16le | Self::DsdU32le)
    }

    pub fn is_compressed(self) -> bool {
        matches!(self, Self::Flac | Self::Opus)
    }

    pub fn bytes_per_sample(self) -> Option<usize> {
        match self {
            Self::PcmS16le => Some(2),
            Self::PcmS24le => Some(3),
            Self::PcmS24le4 | Self::PcmS32le | Self::PcmF32le => Some(4),
            _ => None,
        }
    }
}

impl std::fmt::Display for AudioFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::PcmS16le => write!(f, "PCM_S16LE"),
            Self::PcmS24le => write!(f, "PCM_S24LE"),
            Self::PcmS24le4 => write!(f, "PCM_S24LE4"),
            Self::PcmS32le => write!(f, "PCM_S32LE"),
            Self::PcmF32le => write!(f, "PCM_F32LE"),
            Self::DsdU8 => write!(f, "DSD_U8"),
            Self::DsdU16le => write!(f, "DSD_U16LE"),
            Self::DsdU32le => write!(f, "DSD_U32LE"),
            Self::Flac => write!(f, "FLAC"),
            Self::Opus => write!(f, "OPUS"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChannelLayout {
    Mono,
    Stereo,
    #[serde(rename = "2.1")]
    TwoPointOne,
    Quad,
    #[serde(rename = "5.1")]
    FivePointOne,
    #[serde(rename = "7.1")]
    SevenPointOne,
}

impl ChannelLayout {
    pub fn channel_count(self) -> u8 {
        match self {
            Self::Mono => 1,
            Self::Stereo => 2,
            Self::TwoPointOne => 3,
            Self::Quad => 4,
            Self::FivePointOne => 6,
            Self::SevenPointOne => 8,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DsdRate {
    Dsd64 = 64,
    Dsd128 = 128,
    Dsd256 = 256,
    Dsd512 = 512,
}

impl DsdRate {
    pub fn bitstream_rate_hz(self) -> u64 {
        match self {
            Self::Dsd64 => 2_822_400,
            Self::Dsd128 => 5_644_800,
            Self::Dsd256 => 11_289_600,
            Self::Dsd512 => 22_579_200,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SampleRateFamily {
    F44100,
    F48000,
}

impl SampleRateFamily {
    pub fn of(rate: u32) -> Option<Self> {
        const F441: [u32; 5] = [44100, 88200, 176400, 352800, 705600];
        const F480: [u32; 5] = [48000, 96000, 192000, 384000, 768000];
        if F441.contains(&rate) {
            Some(Self::F44100)
        } else if F480.contains(&rate) {
            Some(Self::F48000)
        } else {
            None
        }
    }

    pub fn rates(self) -> &'static [u32] {
        match self {
            Self::F44100 => &[44100, 88200, 176400, 352800, 705600],
            Self::F48000 => &[48000, 96000, 192000, 384000, 768000],
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn wire_id_roundtrip() {
        for fmt in [
            AudioFormat::PcmS16le,
            AudioFormat::PcmS24le,
            AudioFormat::PcmS24le4,
            AudioFormat::PcmS32le,
            AudioFormat::PcmF32le,
            AudioFormat::DsdU8,
            AudioFormat::DsdU16le,
            AudioFormat::DsdU32le,
            AudioFormat::Flac,
            AudioFormat::Opus,
        ] {
            assert_eq!(AudioFormat::from_wire_id(fmt.wire_id()), Some(fmt));
        }
    }

    #[test]
    fn sample_rate_family() {
        assert_eq!(SampleRateFamily::of(44100), Some(SampleRateFamily::F44100));
        assert_eq!(SampleRateFamily::of(192000), Some(SampleRateFamily::F48000));
        assert_eq!(SampleRateFamily::of(50000), None);
    }
}