Skip to main content

dsd_reader/
lib.rs

1//! A library for reading DSD audio data. Allows for reading from standard in ("stdin"),
2//! DSD container files (e.g. DSF or DFF), and raw DSD files, which are assumed to contain
3//! no metadata. For reading stdin or raw DSD files, the library relies on certain input
4//! parameters to interpret the format of the DSD data.
5//!
6//! Provides an iterator over the frames of the DSD data, which is basically a vector
7//! of channels in planar format, with a `block_size` slice for each channel in least
8//! significant bit first order. Channels are ordered by number (ch1,ch2,...).
9//! This planar format was chosen due to the prevalence of DSF files and the efficiency
10//! with which it can be iterated over and processed in certain scenarios,
11//! however it should be trivial for the implementor to convert to interleaved format if needed.
12//!
13//! ## Examples
14//!
15//! Opening and reading a DFF file:
16//!
17//!```no_run
18//! use std::path::PathBuf;
19//! use dsd_reader::DsdReader;
20//!
21//! let in_path = PathBuf::from("my/music.dff");
22//! // Constructor for use with container files. DSF works the same
23//! let dsd_reader = DsdReader::from_container(in_path.clone()).unwrap();
24//! let channels_num = dsd_reader.channels_num();
25//! let dsd_iter = dsd_reader.dsd_iter().unwrap();
26//!
27//! for (read_size, chan_bufs) in dsd_iter {
28//!     eprintln!("read_size: usize is {} bytes.", read_size);
29//!     for chan in 0..channels_num {
30//!         my_process_channel(chan, &chan_bufs[chan]);
31//!     }
32//! }
33//!
34//! fn my_process_channel(chan: usize, chan_bytes: &[u8]) {
35//!     eprintln!("Processing channel {} with {} bytes. Not guaranteed to have filled buffers.", chan + 1, chan_bytes.len());
36//!     // do stuff
37//! }
38//! ```
39//!
40//! Reading from stdin:
41//!```no_run
42//! use dsd_reader::{DsdReader, Endianness, FmtType, DsdRate};
43//!
44//! let dsd_reader = DsdReader::new(
45//!     None, // in_path: None triggers stdin reading
46//!     FmtType::Interleaved,
47//!     Endianness::MsbFirst,
48//!     DsdRate::DSD64,
49//!     4096, // A safe choice of block size for all DSD inputs
50//!     2 // Stereo
51//! ).unwrap();
52//! let channels_num = dsd_reader.channels_num();
53//! let dsd_iter = dsd_reader.dsd_iter().unwrap();
54//!
55//! for (read_size, chan_bufs) in dsd_iter {
56//!     eprintln!("read_size: usize is {} bytes.", read_size);
57//!     for chan in 0..channels_num {
58//!         my_process_channel(chan, &chan_bufs[chan]);
59//!     }
60//! }
61//!
62//! fn my_process_channel(chan: usize, chan_bytes: &[u8]) {
63//!     eprintln!("Processing channel {} with {} bytes. Not guaranteed to have filled buffers.", chan + 1, chan_bytes.len());
64//!     // do stuff
65//! }
66//! ```
67//!
68//! Reading from raw dsd file (no metadata contained within):
69//!```no_run
70//! use dsd_reader::{DsdReader, Endianness, FmtType, DsdRate};
71//! use std::path::PathBuf;
72//!
73//! let in_path = PathBuf::from("my/raw_audio.dsd");
74//!
75//! let dsd_reader = DsdReader::new(
76//!     Some(in_path.clone()),
77//!     FmtType::Planar,
78//!     Endianness::LsbFirst,
79//!     DsdRate::DSD128,
80//!     4096, // A safe choice of block size for all DSD inputs
81//!     1 // Mono
82//! ).unwrap();
83//! let channels_num = dsd_reader.channels_num();
84//! let dsd_iter = dsd_reader.dsd_iter().unwrap();
85//!
86//! for (read_size, chan_bufs) in dsd_iter {
87//!     eprintln!(
88//!         "read_size: usize is {} bytes. Not guaranteed to have filled buffers.",
89//!         read_size
90//!     );
91//!     my_process_channel(0, &chan_bufs[0]);
92//! }
93//!
94//! fn my_process_channel(chan: usize, chan_bytes: &[u8]) {
95//!     eprintln!("Processing channel {} with {} bytes.", chan + 1, chan_bytes.len());
96//!     // do stuff
97//! }
98//! ```
99
100pub mod dsd_file;
101use crate::dsd_file::{
102    DFF_BLOCK_SIZE, DSD_64_RATE, DSF_BLOCK_SIZE, DsdFile, DsdFileFormat,
103    FormatExtensions,
104};
105use log::{debug, error, info, warn};
106use std::convert::{TryFrom, TryInto};
107use std::error::Error;
108use std::ffi::OsString;
109use std::fs::File;
110use std::io::{self, BufReader, ErrorKind, Read, Seek, SeekFrom};
111use std::path::{Path, PathBuf};
112
113const RETRIES: usize = 1; // Max retries for progress send
114
115/// DSD bit endianness
116#[derive(Copy, Clone, PartialEq, Debug)]
117pub enum Endianness {
118    LsbFirst,
119    MsbFirst,
120}
121
122/// DSD channel format
123#[derive(Copy, Clone, PartialEq, Debug)]
124pub enum FmtType {
125    /// Block per channel
126    Planar,
127    /// Byte per channel
128    Interleaved,
129}
130
131/// DSD rate multiplier
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
133pub enum DsdRate {
134    #[default]
135    DSD64 = 1,
136    DSD128 = 2,
137    DSD256 = 4,
138}
139
140impl TryFrom<u32> for DsdRate {
141    type Error = &'static str;
142    fn try_from(v: u32) -> Result<Self, Self::Error> {
143        match v {
144            1 => Ok(DsdRate::DSD64),
145            2 => Ok(DsdRate::DSD128),
146            4 => Ok(DsdRate::DSD256),
147            _ => Err("Invalid DSD rate multiplier (expected 1,2,4)"),
148        }
149    }
150}
151
152struct InputPathAttrs {
153    std_in: bool,
154    file_format: Option<DsdFileFormat>,
155    parent_path: Option<PathBuf>,
156    file_name: OsString,
157}
158
159/// DSD input context for reading DSD audio data from various sources.
160pub struct DsdReader {
161    dsd_rate: DsdRate,
162    channels_num: usize,
163    std_in: bool,
164    tag: Option<id3::Tag>,
165    file_name: OsString,
166    audio_length: u64,
167    block_size: u32,
168    parent_path: Option<PathBuf>,
169    in_path: Option<PathBuf>,
170    lsbit_first: bool,
171    interleaved: bool,
172    audio_pos: u64,
173    file: Option<File>,
174    file_format: Option<DsdFileFormat>,
175}
176impl Default for DsdReader {
177    fn default() -> Self {
178        DsdReader {
179            dsd_rate: DsdRate::default(),
180            channels_num: 2,
181            std_in: true,
182            tag: None,
183            file_name: OsString::from("stdin"),
184            audio_length: 0,
185            block_size: DSF_BLOCK_SIZE,
186            parent_path: None,
187            in_path: None,
188            lsbit_first: false,
189            interleaved: true,
190            audio_pos: 0,
191            file: None,
192            file_format: None,
193        }
194    }
195}
196
197impl DsdReader {
198    pub fn dsd_rate(&self) -> i32 {
199        self.dsd_rate as i32
200    }
201    pub fn channels_num(&self) -> usize {
202        self.channels_num
203    }
204    pub fn std_in(&self) -> bool {
205        self.std_in
206    }
207    pub fn tag(&self) -> &Option<id3::Tag> {
208        &self.tag
209    }
210    pub fn file_name(&self) -> &OsString {
211        &self.file_name
212    }
213    pub fn audio_length(&self) -> u64 {
214        self.audio_length
215    }
216    pub fn block_size(&self) -> u32 {
217        self.block_size
218    }
219    pub fn parent_path(&self) -> &Option<PathBuf> {
220        &self.parent_path
221    }
222    pub fn in_path(&self) -> &Option<PathBuf> {
223        &self.in_path
224    }
225
226    /// Construct `DsdReader` from stdin or a file path. Note that for container files,
227    /// like DSF or DFF, you may prefer to use `from_container(in_path)` instead. If `in_path` is
228    /// `None`, stdin is assumed. If `in_path` is a file containing raw DSD data, the other
229    /// parameters are used to interpret the data. If `in_path` is a container file, the
230    /// container metadata will override the other parameters.
231    /// # Arguments
232    /// * `in_path` - Optional path to input file. If `None`, stdin is used.
233    /// * `format` - DSD channel format (planar or interleaved)
234    /// * `endianness` - DSD bit endianness (LSB-first or MSB-first)
235    /// * `dsd_rate` - DSD rate multiplier
236    /// * `block_size` - Block size of each read in bytes per channel
237    /// * `channels` - Number of audio channels
238    pub fn new(
239        in_path: Option<PathBuf>,
240        format: FmtType,
241        endianness: Endianness,
242        dsd_rate: DsdRate,
243        block_size: u32,
244        channels: usize,
245    ) -> Result<Self, Box<dyn Error>> {
246        let lsbit_first = match endianness {
247            Endianness::LsbFirst => true,
248            Endianness::MsbFirst => false,
249        };
250
251        let interleaved = match format {
252            FmtType::Planar => false,
253            FmtType::Interleaved => true,
254        };
255
256        let path_attrs = Self::get_path_attrs(&in_path)?;
257
258        // Only enforce CLI dsd_rate for stdin or raw inputs
259        if path_attrs.std_in && ![1, 2, 4].contains(&(dsd_rate as u32)) {
260            return Err("Unsupported DSD input rate.".into());
261        }
262
263        let mut ctx = Self {
264            lsbit_first,
265            interleaved,
266            std_in: path_attrs.std_in,
267            dsd_rate,
268            in_path,
269            parent_path: path_attrs.parent_path,
270            channels_num: channels,
271            block_size: block_size,
272            audio_length: 0,
273            audio_pos: 0,
274            file: None,
275            tag: None,
276            file_name: path_attrs.file_name,
277            file_format: path_attrs.file_format,
278        };
279
280        debug!("Set block_size={}", ctx.block_size);
281
282        ctx.init()?;
283
284        Ok(ctx)
285    }
286
287    /// Construct DsdReader from container file input (e.g. DSF, DFF)
288    pub fn from_container(
289        in_path: PathBuf,
290    ) -> Result<Self, Box<dyn Error>> {
291        if in_path.is_dir() {
292            return Err("Input path cannot be a directory".into());
293        }
294        let file_format = DsdFileFormat::from(&in_path);
295        let file_name = in_path
296            .file_name()
297            .unwrap_or_else(|| "stdin".as_ref())
298            .to_os_string();
299        if !file_format.is_container() {
300            let name = file_name.to_string_lossy();
301            return Err(format!(
302                "{name} is not a recognized container format ({:?})",
303                file_format
304            )
305            .into());
306        }
307        let parent_path =
308            Some(in_path.parent().unwrap_or(Path::new("")).to_path_buf());
309
310        let mut ctx = Self::default();
311        ctx.in_path = Some(in_path);
312        ctx.std_in = false;
313        ctx.parent_path = parent_path;
314        ctx.file_name = file_name;
315        ctx.file_format = Some(file_format);
316
317        debug!("Set block_size={}", ctx.block_size);
318
319        ctx.update_from_file(file_format)?;
320
321        Ok(ctx)
322    }
323
324    /// Construct and return instance of DSD sample iterator for reading DSD data frames.
325    pub fn dsd_iter(&self) -> Result<DsdIter, Box<dyn Error>> {
326        DsdIter::new(self)
327    }
328
329    fn get_path_attrs(
330        path: &Option<PathBuf>,
331    ) -> Result<InputPathAttrs, Box<dyn Error>> {
332        if let Some(p) = path {
333            if p.is_dir() {
334                return Err("Input path cannot be a directory".into());
335            }
336            let file_format = Some(DsdFileFormat::from(p));
337            let parent_path =
338                Some(p.parent().unwrap_or(Path::new("")).to_path_buf());
339            let file_name = p
340                .file_name()
341                .unwrap_or_else(|| "stdin".as_ref())
342                .to_os_string();
343
344            Ok(InputPathAttrs {
345                std_in: false,
346                file_format,
347                parent_path,
348                file_name,
349            })
350        } else {
351            Ok(InputPathAttrs {
352                std_in: true,
353                file_format: None,
354                parent_path: None,
355                file_name: OsString::from("stdin"),
356            })
357        }
358    }
359
360    fn init(&mut self) -> Result<(), Box<dyn Error>> {
361        if self.std_in {
362            self.debug_stdin();
363        } else if let Some(format) = self.file_format {
364            self.update_from_file(format)?;
365        } else {
366            return Err("No valid input specified".into());
367        }
368
369        Ok(())
370    }
371
372    fn debug_stdin(&mut self) {
373        debug!("Reading from stdin");
374        debug!(
375            "Using CLI parameters: {} channels, LSB first: {}, Interleaved: {}",
376            self.channels_num,
377            if self.lsbit_first == true {
378                "true"
379            } else {
380                "false"
381            },
382            self.interleaved
383        );
384    }
385
386    fn update_from_file(
387        &mut self,
388        dsd_file_format: DsdFileFormat,
389    ) -> Result<(), Box<dyn std::error::Error>> {
390        let Some(path) = &self.in_path else {
391            return Err("No readable input file".into());
392        };
393
394        info!("Opening input file: {}", self.file_name.to_string_lossy());
395        debug!(
396            "Parent path: {}",
397            self.parent_path.as_ref().unwrap().display()
398        );
399        match DsdFile::new(path, dsd_file_format) {
400            Ok(my_dsd) => {
401                // Pull raw fields
402                let file_len = my_dsd.file().metadata()?.len();
403                debug!("File size: {} bytes", file_len);
404
405                self.file = Some(my_dsd.file().try_clone()?);
406                self.tag = my_dsd.tag().cloned();
407
408                self.audio_pos = my_dsd.audio_pos();
409                // Clamp audio_length to what the file can actually contain
410                let max_len: u64 = (file_len - self.audio_pos).max(0);
411                self.audio_length = if my_dsd.audio_length() > 0
412                    && my_dsd.audio_length() <= max_len
413                {
414                    my_dsd.audio_length()
415                } else {
416                    max_len
417                };
418
419                // Channels from container (fallback to CLI on nonsense)
420                if let Some(chans_num) = my_dsd.channel_count() {
421                    self.channels_num = chans_num;
422                }
423
424                // Bit order from container
425                if let Some(lsb) = my_dsd.is_lsb() {
426                    self.lsbit_first = lsb;
427                }
428
429                // Interleaving from container (DSF = block-interleaved → treat as planar per frame)
430                match my_dsd.container_format() {
431                    DsdFileFormat::Dsdiff => self.interleaved = true,
432                    DsdFileFormat::Dsf => self.interleaved = false,
433                    DsdFileFormat::Raw => {}
434                }
435
436                // Block size from container.
437                // For dff, which always has a block size per channel of 1,
438                // we accept the user-supplied or default block size, which really
439                // just governs how many bytes we read at a time.
440                // For DSF, we treat the block size as representing the
441                // block size per channel and override any user-supplied
442                // or default values for block size.
443                if let Some(block_size) = my_dsd.block_size()
444                    && block_size > DFF_BLOCK_SIZE
445                {
446                    self.block_size = block_size;
447                    debug!("Set block_size={}", self.block_size,);
448                }
449
450                // DSD rate from container sample_rate if valid (2.8224MHz → 1, 5.6448MHz → 2)
451                if let Some(sample_rate) = my_dsd.sample_rate() {
452                    if sample_rate % DSD_64_RATE == 0 {
453                        self.dsd_rate =
454                            (sample_rate / DSD_64_RATE).try_into()?;
455                    } else {
456                        // Fallback: keep CLI value (avoid triggering “Invalid DSD rate”)
457                        info!(
458                            "Container sample_rate {} not standard; keeping CLI dsd_rate={}",
459                            sample_rate, self.dsd_rate as i32
460                        );
461                    }
462                }
463
464                debug!("Audio length in bytes: {}", self.audio_length);
465                debug!(
466                    "Container: sr={}Hz channels={} interleaved={}",
467                    self.dsd_rate as u32 * DSD_64_RATE,
468                    self.channels_num,
469                    self.interleaved,
470                );
471            }
472            Err(e) if dsd_file_format != DsdFileFormat::Raw => {
473                info!("Container open failed with error: {}", e);
474                info!("Treating input as raw DSD (no container)");
475                self.update_from_file(DsdFileFormat::Raw)?;
476            }
477            Err(e) => {
478                return Err(e);
479            }
480        }
481        Ok(())
482    }
483}
484
485/// DSD data iterator that yields frames of DSD data. Provided by [DsdReader].
486pub struct DsdIter {
487    std_in: bool,
488    bytes_remaining: u64,
489    channels_num: usize,
490    channel_buffers: Vec<Box<[u8]>>,
491    block_size: u32,
492    reader: Box<dyn Read + Send>,
493    frame_size: u32,
494    interleaved: bool,
495    lsbit_first: bool,
496    dsd_data: Vec<u8>,
497    file: Option<File>,
498    audio_pos: u64,
499    retries: usize,
500}
501impl DsdIter {
502    pub fn new(dsd_input: &DsdReader) -> Result<Self, Box<dyn Error>> {
503        let mut ctx = DsdIter {
504            std_in: dsd_input.std_in,
505            bytes_remaining: if dsd_input.std_in {
506                dsd_input.block_size as u64 * dsd_input.channels_num as u64
507            } else {
508                dsd_input.audio_length
509            },
510            channels_num: dsd_input.channels_num,
511            channel_buffers: Vec::new(),
512            block_size: 0,
513            reader: Box::new(io::empty()),
514            frame_size: 0,
515            interleaved: dsd_input.interleaved,
516            lsbit_first: dsd_input.lsbit_first,
517            dsd_data: vec![
518                0;
519                dsd_input.block_size as usize
520                    * dsd_input.channels_num
521            ],
522            file: if let Some(file) = &dsd_input.file {
523                Some(file.try_clone()?)
524            } else {
525                None
526            },
527            audio_pos: dsd_input.audio_pos,
528            retries: 0,
529        };
530        ctx.set_block_size(dsd_input.block_size);
531        ctx.set_reader()?;
532        Ok(ctx)
533    }
534
535    /// Read one frame of DSD data into the channel buffers and return read size.
536    #[inline(always)]
537    pub fn load_frame(&mut self) -> Result<usize, Box<dyn Error>> {
538        //stdin always reads frame_size
539        let partial_frame = if self.bytes_remaining
540            < self.frame_size as u64
541            && !self.std_in
542        {
543            true
544        } else {
545            false
546        };
547
548        if self.interleaved {
549            if partial_frame {
550                self.set_block_size(
551                    (self.bytes_remaining / self.channels_num as u64)
552                        as u32,
553                );
554            }
555            self.reader.read_exact(
556                &mut self.dsd_data[..self.frame_size as usize],
557            )?;
558            // Copy interleaved data into channel buffers
559            for chan in 0..self.channels_num {
560                let chan_bytes = self
561                    .get_chan_bytes_interl(chan, self.frame_size as usize);
562                self.channel_buffers[chan].copy_from_slice(&chan_bytes);
563            }
564            Ok(self.frame_size as usize)
565        } else {
566            // Planar DSF: Each channel block is fixed size (block_size) and the last
567            // frame may contain zero-padded tail inside each channel block.
568            let mut total_valid = 0usize;
569            let remaining = if partial_frame {
570                self.reset_buffers();
571                self.bytes_remaining
572            } else {
573                self.frame_size as u64
574            };
575            let valid_for_chan =
576                (remaining / self.channels_num as u64) as usize;
577            let padding = self.block_size as usize - valid_for_chan;
578
579            for chan in 0..self.channels_num {
580                let chan_buf = &mut self.channel_buffers[chan];
581
582                self.reader.read_exact(&mut chan_buf[..valid_for_chan])?;
583                total_valid += valid_for_chan;
584
585                if padding > 0 {
586                    // If block is padded, discard padding from file so next channel starts aligned.
587                    let byte_reader = self.reader.as_mut();
588                    for _ in 0..padding {
589                        byte_reader.bytes().next();
590                    }
591                }
592            }
593            Ok(total_valid)
594        }
595    }
596
597    /// Take frame of interleaved DSD bytes from internal buffer and return one
598    /// channel with the endianness we need.
599    #[inline(always)]
600    fn get_chan_bytes_interl(
601        &self,
602        chan: usize,
603        read_size: usize,
604    ) -> Vec<u8> {
605        let chan_size = read_size / self.channels_num;
606        let mut chan_bytes: Vec<u8> = Vec::with_capacity(chan_size);
607
608        for i in 0..chan_size {
609            let byte_index = chan + i * self.channels_num;
610            if byte_index >= self.dsd_data.len() {
611                break;
612            }
613            let b = self.dsd_data[byte_index];
614            chan_bytes.push(if self.lsbit_first {
615                b
616            } else {
617                b.reverse_bits()
618            });
619        }
620        chan_bytes
621    }
622
623    fn set_block_size(&mut self, block_size: u32) {
624        self.block_size = block_size;
625        self.frame_size = self.block_size * self.channels_num as u32;
626
627        self.channel_buffers = (0..self.channels_num)
628            .map(|_| {
629                vec![0x69u8; self.block_size as usize].into_boxed_slice()
630            })
631            .collect();
632
633        debug!("Set block_size={}", self.block_size,);
634    }
635
636    fn reset_buffers(&mut self) {
637        for chan in 0..self.channels_num {
638            let chan_buf = &mut self.channel_buffers[chan];
639            for byte in chan_buf.iter_mut() {
640                *byte = 0x69;
641            }
642        }
643    }
644
645    fn set_reader(&mut self) -> Result<(), Box<dyn Error>> {
646        if self.std_in {
647            // Use Stdin (not StdinLock) so the reader is 'static + Send for threaded use
648            self.reader = Box::new(BufReader::with_capacity(
649                self.frame_size as usize * 8,
650                io::stdin(),
651            ));
652            return Ok(());
653        }
654        // Obtain an owned File by cloning the handle from InputContext, then seek if needed.
655        let mut file = self
656            .file
657            .as_ref()
658            .ok_or_else(|| {
659                io::Error::new(
660                    io::ErrorKind::Other,
661                    "Missing input file handle",
662                )
663            })?
664            .try_clone()?;
665
666        if self.audio_pos > 0 {
667            file.seek(SeekFrom::Start(self.audio_pos as u64))?;
668            debug!(
669                "Seeked to audio start position: {}",
670                file.stream_position()?
671            );
672        }
673        self.reader = Box::new(BufReader::with_capacity(
674            self.frame_size as usize * 8,
675            file,
676        ));
677        Ok(())
678    }
679
680    /// Returns true if we have reached the end of file for non-stdin inputs
681    #[inline(always)]
682    pub fn is_eof(&self) -> bool {
683        !self.std_in && self.bytes_remaining <= 0
684    }
685}
686
687impl Iterator for DsdIter {
688    /// Item is (frame_read_size, Vec<channel_data_bytes>), where each channel_data_bytes is a boxed slice of bytes for a channel. The Vec of channels is in order (channel 1, channel 2, ...).
689    type Item = (usize, Vec<Box<[u8]>>);
690
691    fn next(&mut self) -> Option<Self::Item> {
692        if self.is_eof() {
693            return None;
694        }
695        match self.load_frame() {
696            Ok(read_size) => {
697                self.retries = 0;
698                if !self.std_in {
699                    self.bytes_remaining -= read_size as u64;
700                }
701                return Some((read_size, self.channel_buffers.clone()));
702            }
703            Err(e) => {
704                if let Some(io_err) = e.downcast_ref::<io::Error>() {
705                    match io_err.kind() {
706                        ErrorKind::Interrupted => {
707                            if self.retries < RETRIES {
708                                warn!("I/O interrupted, retrying read.");
709                                self.retries += 1;
710                                return self.next();
711                            } else {
712                                error!(
713                                    "Max retries reached for interrupted I/O."
714                                );
715                                return None;
716                            }
717                        }
718                        _ => {
719                            return None;
720                        }
721                    }
722                }
723                error!("Error reading DSD frame: {}", e);
724                return None;
725            }
726        }
727    }
728}