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}