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