aede_core/playback/decoder/
integer.rs

1//! Progressive source-depth integers for the future transparent playback path.
2
3use std::fs::File;
4use std::io::{Read, Seek, SeekFrom};
5use std::path::Path;
6
7use flaccompagnon_core::AnalysisError;
8
9use crate::playback::format::IntegerPcmFormat;
10
11use super::flac::IntegerFlacStream;
12use super::integer_wav::IntegerWavStream;
13use super::{Error, FlacMd5Status, SeekResult};
14
15/// Decode native FLAC or PCM WAV without converting the source to floating PCM.
16///
17/// The initial profile accepts integer 16/24-bit mono/stereo sources. Values
18/// returned in `i32` containers retain their source-depth scale, interleaved
19/// channel order and rate. This is a source API, not an audio sink or a claim
20/// about operating-system/device output. No DSP, FFmpeg or fallback is used.
21pub struct IntegerFileDecoder {
22    source: Source,
23    format: IntegerPcmFormat,
24    pending: Vec<i32>,
25    pending_offset: usize,
26    finished: bool,
27}
28
29enum Source {
30    Flac(IntegerFlacStream),
31    Wav(IntegerWavStream),
32}
33
34impl IntegerFileDecoder {
35    /// Inspect and decode the same opened regular file; extensions are not trusted.
36    pub fn open(path: &Path) -> Result<Self, Error> {
37        // Reuse the source preflight so a FIFO/device is refused before a
38        // potentially blocking open. Recheck the actual descriptor below.
39        crate::copy::validate_source(path).map_err(decode_error)?;
40        let mut file = File::open(path).map_err(decode_error)?;
41        if !file.metadata().map_err(decode_error)?.is_file() {
42            return Err(Error::UnsupportedIntegerSource(
43                "integer playback requires a local regular file",
44            ));
45        }
46        let mut magic = [0; 4];
47        file.read_exact(&mut magic).map_err(decode_error)?;
48        file.seek(SeekFrom::Start(0)).map_err(decode_error)?;
49        let (source, format) = if &magic == b"RIFF" {
50            let wav = IntegerWavStream::open(file)?;
51            let format = IntegerPcmFormat::new(
52                wav.sample_rate(),
53                wav.channel_layout(),
54                wav.bits_per_sample(),
55            )
56            .map_err(|_| Error::InvalidFormat)?;
57            if format.channels() != wav.channels() {
58                return Err(Error::InvalidFormat);
59            }
60            (Source::Wav(wav), format)
61        } else if &magic == b"fLaC" || &magic[..3] == b"ID3" {
62            let flac = IntegerFlacStream::open_integer(file)?;
63            let format =
64                IntegerPcmFormat::new(flac.sample_rate, flac.layout, flac.bits_per_sample())
65                    .map_err(|_| Error::InvalidFormat)?;
66            (Source::Flac(flac), format)
67        } else {
68            return Err(Error::UnsupportedIntegerSource(
69                "integer playback requires native 16/24-bit mono/stereo FLAC or PCM WAV",
70            ));
71        };
72        Ok(Self {
73            source,
74            format,
75            pending: Vec::new(),
76            pending_offset: 0,
77            finished: false,
78        })
79    }
80
81    /// Original rate, valid precision and channel association.
82    pub fn format(&self) -> IntegerPcmFormat {
83        self.format
84    }
85
86    /// FLAC verification status for this decode pass, finalized only at EOF.
87    pub fn flac_md5_status(&self) -> Option<FlacMd5Status> {
88        match &self.source {
89            Source::Flac(flac) => Some(flac.md5_status()),
90            Source::Wav(_) => None,
91        }
92    }
93
94    /// Read complete interleaved frames into a caller-owned integer buffer.
95    ///
96    /// A short read is normal; only `Ok(0)` establishes successful EOF. Empty
97    /// or non-frame-aligned buffers are rejected without advancing the source
98    /// or mutating the buffer. Unused output elements stay unchanged.
99    pub fn read_frames(&mut self, output: &mut [i32]) -> Result<usize, Error> {
100        let channels = usize::from(self.format.channels());
101        if output.is_empty() || !output.len().is_multiple_of(channels) {
102            return Err(Error::InvalidBuffer);
103        }
104        if !self.fill_pending()? {
105            return Ok(0);
106        }
107        let count = output.len().min(self.pending.len() - self.pending_offset);
108        output[..count]
109            .copy_from_slice(&self.pending[self.pending_offset..self.pending_offset + count]);
110        self.pending_offset += count;
111        Ok(count / channels)
112    }
113
114    /// Progressively discard source frames for a later explicitly requested seek.
115    ///
116    /// No output, DSP or listening history is produced. FLAC verification
117    /// includes the discarded prefix. Working memory stays bounded, but work
118    /// grows with the traversed audio. Cancellation is polled before decoding
119    /// and after at most 4096 discarded frames; a decoder/I/O call may block.
120    /// Discard this decoder after cancellation or failure rather than treating
121    /// a partially advanced position as a successful seek.
122    pub fn skip_frames(
123        &mut self,
124        frames: u64,
125        mut cancelled: impl FnMut() -> bool,
126    ) -> Result<SeekResult, Error> {
127        let channels = usize::from(self.format.channels());
128        let mut skipped = 0;
129        loop {
130            if cancelled() {
131                return Err(Error::SeekCancelled);
132            }
133            if skipped == frames {
134                return Ok(SeekResult {
135                    frames: skipped,
136                    reached_eof: false,
137                });
138            }
139            if !self.fill_pending()? {
140                return Ok(SeekResult {
141                    frames: skipped,
142                    reached_eof: true,
143                });
144            }
145            let available = (self.pending.len() - self.pending_offset) / channels;
146            let count = available.min((frames - skipped).min(4096) as usize);
147            self.pending_offset += count * channels;
148            skipped += count as u64;
149        }
150    }
151
152    fn fill_pending(&mut self) -> Result<bool, Error> {
153        if self.finished {
154            return Ok(false);
155        }
156        while self.pending_offset == self.pending.len() {
157            let chunk = match &mut self.source {
158                Source::Flac(flac) => {
159                    let chunk = flac.next_chunk()?;
160                    // Symphonia's S32 FLAC samples are already left-aligned;
161                    // undo that alignment once, keeping the original low bits.
162                    chunk.map(|mut samples| {
163                        let shift = 32 - self.format.bits_per_sample();
164                        for sample in &mut samples {
165                            *sample >>= shift;
166                        }
167                        samples
168                    })
169                }
170                Source::Wav(wav) => wav.next_chunk()?,
171            };
172            let Some(samples) = chunk else {
173                self.finished = true;
174                return Ok(false);
175            };
176            self.pending = samples;
177            self.pending_offset = 0;
178        }
179        Ok(true)
180    }
181}
182
183fn decode_error(error: impl std::fmt::Display) -> Error {
184    Error::Decode(AnalysisError::Decode(error.to_string()))
185}
186
187#[cfg(test)]
188#[path = "integer_tests.rs"]
189mod tests;