Skip to main content

ahx_rs/
stream.rs

1use core::num::NonZeroU32;
2
3use crate::{
4    error::{Error, InvalidData, Result},
5    input::{Input, SliceInput},
6    packet::frame_buffer,
7    Metadata, PacketDecoder, SAMPLES_PER_FRAME,
8};
9
10const FOOTER: &[u8; 17] = b"\0\x80\x01\0\x0cAHXE(c)CRI\0\0";
11
12/// The mutually exclusive states of a complete-file decoder.
13///
14/// Returned by [`SliceDecoder::state`] and the standard I/O decoder's `state` method.
15/// A decoding stream always has samples remaining. `Finished` is reached only
16/// after validating the end marker; `Failed` cannot resume decoding.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum DecoderState {
19    /// More declared PCM remains to be decoded.
20    Decoding {
21        /// Mono PCM16 samples still to be returned, including the next block.
22        remaining_samples: NonZeroU32,
23    },
24    /// All declared samples and the end marker were successfully decoded.
25    Finished,
26    /// An error invalidated the stream. Construct a new decoder to restart.
27    Failed,
28}
29
30struct Stream<S, P> {
31    input: S,
32    metadata: Metadata,
33    state: DecoderState,
34    codec: PacketDecoder,
35    pcm: P,
36}
37
38impl<S: Input, P: AsMut<[i16]>> Stream<S, P> {
39    fn new(mut input: S, mut pcm: P) -> Result<Self> {
40        frame_buffer(pcm.as_mut())?;
41        let metadata = Metadata::from_input(&mut input)?;
42        Ok(Self {
43            input,
44            metadata,
45            state: DecoderState::Decoding {
46                remaining_samples: metadata.sample_count(),
47            },
48            codec: PacketDecoder::new(),
49            pcm,
50        })
51    }
52
53    fn next_block(&mut self) -> Result<Option<&[i16]>> {
54        let remaining = match self.state {
55            DecoderState::Decoding { remaining_samples } => remaining_samples,
56            DecoderState::Finished => return Ok(None),
57            DecoderState::Failed => return Err(Error::Failed),
58        };
59        // Commit a resumable state only on success. This also poisons the stream
60        // if caller-provided I/O or storage panics and the caller catches the unwind.
61        self.state = DecoderState::Failed;
62        let pcm = frame_buffer(self.pcm.as_mut())?;
63        self.codec.decode_from(&mut self.input, pcm)?;
64        let count = remaining.get().min(SAMPLES_PER_FRAME as u32);
65        let next = match NonZeroU32::new(remaining.get() - count) {
66            Some(remaining_samples) => DecoderState::Decoding { remaining_samples },
67            None => {
68                let mut footer = [0; FOOTER.len()];
69                self.input.read_exact(&mut footer)?;
70                if &footer != FOOTER {
71                    return Err(Error::Invalid(InvalidData::EndMarker));
72                }
73                DecoderState::Finished
74            }
75        };
76        self.state = next;
77        Ok(Some(&pcm[..count as usize]))
78    }
79}
80
81/// Allocation-free decoder over a complete AHX byte slice.
82///
83/// [`Self::new`] owns a fixed PCM array. [`Self::with_buffer`] accepts caller-owned
84/// storage instead. Neither constructor requires `std` or `alloc`. Input is
85/// consumed only as frames are decoded; trailing bytes after the footer remain unread.
86pub struct SliceDecoder<'input, P = [i16; SAMPLES_PER_FRAME]> {
87    inner: Stream<SliceInput<'input>, P>,
88}
89
90impl<P> core::fmt::Debug for SliceDecoder<'_, P> {
91    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
92        f.debug_struct("SliceDecoder")
93            .field("metadata", &self.inner.metadata)
94            .field("state", &self.inner.state)
95            .field("remaining_bytes", &self.inner.input.0.len())
96            .finish_non_exhaustive()
97    }
98}
99
100impl<'input, P> SliceDecoder<'input, P> {
101    /// Validated file timing and header layout.
102    pub const fn metadata(&self) -> Metadata {
103        self.inner.metadata
104    }
105
106    /// Current decoding progress, successful completion, or terminal failure.
107    pub const fn state(&self) -> DecoderState {
108        self.inner.state
109    }
110
111    /// Recover unconsumed input and PCM storage without rewinding.
112    /// After successful EOF the input begins immediately after the AHX end marker.
113    pub fn into_inner(self) -> (&'input [u8], P) {
114        (self.inner.input.0, self.inner.pcm)
115    }
116}
117
118impl<'input> SliceDecoder<'input> {
119    /// Validate the header and initialize fixed synthesis and PCM arrays.
120    pub fn new(input: &'input [u8]) -> Result<Self> {
121        Self::with_buffer(input, [0; SAMPLES_PER_FRAME])
122    }
123}
124
125impl<'input, P: AsMut<[i16]>> SliceDecoder<'input, P> {
126    /// Use caller-owned storage with at least [`SAMPLES_PER_FRAME`] `i16` elements.
127    ///
128    /// Short buffers are rejected before parsing input or modifying output. Borrow
129    /// a slice to retain ownership on constructor failure. Storage length is checked
130    /// again on each block; shorter views return an error and invalidate the decoder.
131    pub fn with_buffer(input: &'input [u8], pcm: P) -> Result<Self> {
132        Ok(Self {
133            inner: Stream::new(SliceInput(input), pcm)?,
134        })
135    }
136
137    /// Decode the next borrowed mono PCM16 block, or `None` at validated EOF.
138    ///
139    /// The final block is trimmed to the declared sample count; its footer is
140    /// checked before the block is returned. Calls after successful EOF return
141    /// `None`. Any error invalidates the reader; later calls return [`Error::Failed`].
142    /// An unwinding panic in caller-provided storage also leaves it failed.
143    pub fn next_block(&mut self) -> Result<Option<&[i16]>> {
144        self.inner.next_block()
145    }
146}
147
148/// Streaming AHX decoder with fixed synthesis and PCM arrays, available with `std`.
149///
150/// No allocation or seeking is performed by this decoder. Wrap files in
151/// `std::io::BufReader` for efficient small reads. Reader-owned buffering and
152/// output handling are outside the decoder's allocation guarantee.
153#[cfg(feature = "std")]
154pub struct Decoder<R> {
155    inner: Stream<crate::input::ReaderInput<R>, [i16; SAMPLES_PER_FRAME]>,
156}
157
158#[cfg(feature = "std")]
159impl<R> core::fmt::Debug for Decoder<R> {
160    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
161        f.debug_struct("Decoder")
162            .field("metadata", &self.inner.metadata)
163            .field("state", &self.inner.state)
164            .finish_non_exhaustive()
165    }
166}
167
168#[cfg(feature = "std")]
169impl<R> Decoder<R> {
170    /// Validated file timing and header layout.
171    pub const fn metadata(&self) -> Metadata {
172        self.inner.metadata
173    }
174
175    /// Current decoding progress, successful completion, or terminal failure.
176    pub const fn state(&self) -> DecoderState {
177        self.inner.state
178    }
179
180    /// Recover the reader at its current position, without rewinding.
181    pub fn into_inner(self) -> R {
182        self.inner.input.0
183    }
184}
185
186#[cfg(feature = "std")]
187impl<R: std::io::Read> Decoder<R> {
188    /// Validate the header and initialize fixed-size storage without allocating.
189    pub fn new(reader: R) -> Result<Self> {
190        Ok(Self {
191            inner: Stream::new(crate::input::ReaderInput(reader), [0; SAMPLES_PER_FRAME])?,
192        })
193    }
194
195    /// Decode up to [`SAMPLES_PER_FRAME`] mono PCM16 elements into borrowed storage.
196    ///
197    /// No startup samples are discarded and no resampling occurs. The final block
198    /// is trimmed to the declared duration after validating the footer. Errors
199    /// invalidate the reader; calls after an error return [`Error::Failed`].
200    /// An unwinding panic in caller I/O also leaves it failed.
201    /// Calls after successful EOF return `None` without reading further input.
202    pub fn next_block(&mut self) -> Result<Option<&[i16]>> {
203        self.inner.next_block()
204    }
205}