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}