Skip to main content

dsd_source/
lib.rs

1//! A trait abstraction over DSD audio sources, plus the small set of shared
2//! DSD model types (bit endianness, channel layout, rate multiplier) used to
3//! describe them.
4//!
5//! Container/metadata crates (e.g. `dsf-meta`, `dff-meta`, or a WavPack
6//! equivalent) implement [`DsdSource`] to describe the *native* shape of the
7//! DSD audio they expose ([`DsdSourceInfo`]) and to hand back a byte stream
8//! of that native-shape audio data.
9//!
10//! Readers (e.g. `dsd-reader`) depend on this crate instead of on any
11//! specific container format, and are responsible for reshaping the native
12//! byte stream into whatever output shape a caller asks for (planar vs.
13//! interleaved, LSB- vs. MSB-first, output block size). This crate does not
14//! perform that reshaping itself; it only describes the source.
15
16use std::convert::TryFrom;
17use std::io::Read;
18
19/// Error type returned by [`DsdSource`] methods.
20///
21/// A boxed, `Send + Sync` trait object is used so that implementers can
22/// wrap their own error types (`std::io::Error`, or a crate-specific error
23/// enum) without this crate needing to know about them.
24pub type DsdSourceError = Box<dyn std::error::Error + Send + Sync + 'static>;
25
26/// The DSD64 native sample rate, in Hz. All other [`DsdRate`] variants are
27/// integer multiples of this base rate.
28pub const DSD_64_RATE: u32 = 2_822_400;
29
30/// DSD bit endianness.
31#[derive(Copy, Clone, PartialEq, Debug)]
32pub enum Endianness {
33    LsbFirst,
34    MsbFirst,
35}
36
37/// DSD channel layout.
38#[derive(Copy, Clone, PartialEq, Debug)]
39pub enum FmtType {
40    /// Block per channel.
41    Planar,
42    /// Byte per channel.
43    Interleaved,
44}
45
46/// DSD rate multiplier, relative to the DSD64 base rate ([`DSD_64_RATE`]).
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub enum DsdRate {
49    #[default]
50    DSD64 = 1,
51    DSD128 = 2,
52    DSD256 = 4,
53    DSD512 = 8,
54}
55
56impl TryFrom<u32> for DsdRate {
57    type Error = &'static str;
58    fn try_from(v: u32) -> Result<Self, Self::Error> {
59        match v {
60            1 => Ok(DsdRate::DSD64),
61            2 => Ok(DsdRate::DSD128),
62            4 => Ok(DsdRate::DSD256),
63            8 => Ok(DsdRate::DSD512),
64            _ => Err("Invalid DSD rate multiplier (expected 1,2,4,8)"),
65        }
66    }
67}
68
69/// Describes the native shape of a [`DsdSource`]'s audio data.
70#[derive(Clone)]
71pub struct DsdSourceInfo {
72    pub channels: usize,
73    pub endianness: Endianness,
74    pub layout: FmtType,
75    /// Native (or suggested) block size in bytes per channel used when
76    /// reading frames of audio data.
77    pub block_size: u32,
78    /// Native sample rate of the audio data, in Hz.
79    pub sample_rate: u32,
80    /// Total length of the audio data, in bytes, across all channels.
81    pub audio_length: u64,
82    /// Byte offset within the container where [`DsdSource::reader`] begins
83    /// yielding data. Informational only (`reader()` already accounts for
84    /// it); useful for callers that want to sanity-check container-reported
85    /// lengths against the underlying file size.
86    pub data_offset: u64,
87    /// ID3 tag associated with this source, if any.
88    pub tag: Option<id3::Tag>,
89}
90
91impl std::fmt::Debug for DsdSourceInfo {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        f.debug_struct("DsdSourceInfo")
94            .field("channels", &self.channels)
95            .field("endianness", &self.endianness)
96            .field("layout", &self.layout)
97            .field("block_size", &self.block_size)
98            .field("sample_rate", &self.sample_rate)
99            .field("audio_length", &self.audio_length)
100            .field("data_offset", &self.data_offset)
101            .field("tag", &self.tag.is_some())
102            .finish()
103    }
104}
105
106/// A source of DSD audio data with a known native shape.
107///
108/// Implementers describe how their audio is laid out (see [`DsdSourceInfo`])
109/// and provide a [`reader`](DsdSource::reader) that yields that data as raw
110/// bytes, starting from the first byte of audio data. Implementers are not
111/// responsible for converting between planar/interleaved layouts or bit
112/// endianness; that is the responsibility of the reader consuming this trait.
113pub trait DsdSource: Send {
114    /// Describes the native shape of this source's audio data. Fallible
115    /// since some containers only discover certain properties (e.g. channel
116    /// count, sample rate) while parsing, and may not have them available
117    /// even after a successful open.
118    fn info(&self) -> Result<DsdSourceInfo, DsdSourceError>;
119
120    /// Returns a fresh, independent byte stream of this source's native-shape
121    /// audio data, positioned at the first byte of audio data.
122    fn reader(&self) -> Result<Box<dyn Read + Send>, DsdSourceError>;
123}
124
125/// File extensions (lowercase, no leading dot) recognized as this source's
126/// container format. Kept separate from [`DsdSource`] since associated
127/// consts aren't dyn-compatible: implementers declare this alongside
128/// `DsdSource`, and callers reference it on the concrete type (e.g.
129/// `DsfFile::EXTENSIONS`) to build a format dispatch table without a reader
130/// needing to hardcode each format's extensions itself.
131pub trait DsdSourceExtensions {
132    const EXTENSIONS: &'static [&'static str];
133}