aede_core/playback/
exact_output.rs

1//! Exact software representations of original-depth integer PCM.
2//!
3//! These adapters describe and check a caller-selected output format. They do
4//! not negotiate a device or establish that its mixer, driver or DAC preserves
5//! the programme. Encoding runs outside the real-time callback and never uses
6//! the DSP session, output guard or quantizer.
7
8use std::collections::TryReserveError;
9use std::fmt;
10
11use aede_dsp::ChannelLayout;
12
13use super::format::{IntegerPcmFormat, PcmStreamFormat};
14
15/// Maximum number of complete frames encoded by one bounded block operation.
16pub const MAX_EXACT_BLOCK_FRAMES: usize = 4096;
17
18/// An explicit little-endian, interleaved output representation.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum ExactSampleRepresentation {
21    /// Full-width signed 16-bit PCM.
22    Signed16Le,
23    /// Full-width signed 24-bit PCM packed into three bytes per sample.
24    PackedSigned24Le,
25    /// Full-width signed 32-bit PCM, including exact left-aligned widening.
26    Signed32Le,
27    /// Normalized IEEE 754 binary32 samples.
28    Float32Le,
29    /// Normalized IEEE 754 binary64 samples.
30    Float64Le,
31}
32
33impl ExactSampleRepresentation {
34    /// Number of bytes occupied by one encoded channel sample.
35    pub fn bytes_per_sample(self) -> usize {
36        match self {
37            Self::Signed16Le => 2,
38            Self::PackedSigned24Le => 3,
39            Self::Signed32Le | Self::Float32Le => 4,
40            Self::Float64Le => 8,
41        }
42    }
43
44    fn signed_container_bits(self) -> Option<u32> {
45        match self {
46            Self::Signed16Le => Some(16),
47            Self::PackedSigned24Le => Some(24),
48            Self::Signed32Le => Some(32),
49            Self::Float32Le | Self::Float64Le => None,
50        }
51    }
52}
53
54/// Explicit output facts supplied by a caller, without device certification.
55///
56/// `effective_bits` is the known downstream signed-PCM precision, not the byte
57/// width of the transport. For example, a normalized float transport can feed
58/// a known 24-bit destination. Unknown precision can be retained for diagnosis
59/// but is refused when constructing an exact adapter.
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub struct ExactOutputFormat {
62    pcm: PcmStreamFormat,
63    representation: ExactSampleRepresentation,
64    effective_bits: Option<u32>,
65}
66
67impl ExactOutputFormat {
68    /// Validate explicit rate, speaker positions and precision metadata.
69    ///
70    /// Effective precision must be between 1 and 64 bits when known, and must
71    /// fit a signed integer transport's container. A float representation does
72    /// not by itself prove the precision of any subsequent conversion.
73    pub fn new(
74        sample_rate: u32,
75        layout: ChannelLayout,
76        representation: ExactSampleRepresentation,
77        effective_bits: Option<u32>,
78    ) -> Result<Self, ExactOutputError> {
79        if layout.mask().is_none() {
80            return Err(ExactOutputError::InvalidOutputFormat);
81        }
82        let pcm = PcmStreamFormat::with_layout(sample_rate, layout)
83            .map_err(|_| ExactOutputError::InvalidOutputFormat)?;
84        if let Some(bits) = effective_bits
85            && (!(1..=64).contains(&bits)
86                || representation
87                    .signed_container_bits()
88                    .is_some_and(|container| bits > container))
89        {
90            return Err(ExactOutputError::InvalidEffectivePrecision(bits));
91        }
92        Ok(Self {
93            pcm,
94            representation,
95            effective_bits,
96        })
97    }
98
99    /// Explicit output frames per second.
100    pub fn sample_rate(self) -> u32 {
101        self.pcm.sample_rate()
102    }
103
104    /// Explicit interleaved speaker positions.
105    pub fn layout(self) -> ChannelLayout {
106        self.pcm.layout()
107    }
108
109    /// Number of interleaved samples in each output frame.
110    pub fn channels(self) -> u16 {
111        self.pcm.channels()
112    }
113
114    /// Selected transport representation and byte order.
115    pub fn representation(self) -> ExactSampleRepresentation {
116        self.representation
117    }
118
119    /// Caller-supplied downstream precision; no measurement is implied.
120    pub fn effective_bits(self) -> Option<u32> {
121        self.effective_bits
122    }
123}
124
125/// A refusal to represent source samples exactly in a selected output format.
126#[derive(Debug)]
127pub enum ExactOutputError {
128    /// Rate or channel positions are invalid or unknown.
129    InvalidOutputFormat,
130    /// Claimed effective precision is invalid for its representation.
131    InvalidEffectivePrecision(u32),
132    /// The downstream precision has not been established by the caller.
133    UnknownEffectivePrecision,
134    /// Exact output cannot resample to another rate.
135    RateMismatch {
136        /// Original source frames per second.
137        source: u32,
138        /// Selected output frames per second.
139        output: u32,
140    },
141    /// Exact output cannot mix or remap source speaker positions.
142    LayoutMismatch {
143        /// Original source speaker positions and interleaved order.
144        source: ChannelLayout,
145        /// Selected output speaker positions and interleaved order.
146        output: ChannelLayout,
147    },
148    /// The representation or downstream precision would discard source bits.
149    InsufficientPrecision {
150        /// Original signed-PCM valid bit depth.
151        source: u32,
152        /// Available effective precision after representation constraints.
153        output: u32,
154    },
155    /// A source block ends inside an interleaved frame.
156    IncompleteFrame,
157    /// A source block exceeds the bounded encoding operation.
158    BlockTooLarge,
159    /// A canonical source integer is outside its declared valid-bit range.
160    SampleOutOfRange {
161        /// Zero-based interleaved sample position in the submitted block.
162        index: usize,
163        /// Invalid canonical source integer.
164        sample: i32,
165        /// Declared signed-PCM valid bit depth.
166        bits: u32,
167    },
168    /// The caller's encoded buffer cannot reserve the needed capacity.
169    Allocation(TryReserveError),
170}
171
172impl fmt::Display for ExactOutputError {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        match self {
175            Self::InvalidOutputFormat => {
176                f.write_str("exact output needs a positive rate and explicit speaker positions")
177            }
178            Self::InvalidEffectivePrecision(bits) => {
179                write!(f, "invalid exact-output effective precision: {bits} bits")
180            }
181            Self::UnknownEffectivePrecision => {
182                f.write_str("exact output needs known downstream precision")
183            }
184            Self::RateMismatch { source, output } => write!(
185                f,
186                "exact output cannot change the source rate from {source} to {output} Hz"
187            ),
188            Self::LayoutMismatch { source, output } => write!(
189                f,
190                "exact output cannot change the source layout from {} to {}",
191                source.name(),
192                output.name()
193            ),
194            Self::InsufficientPrecision { source, output } => write!(
195                f,
196                "exact output cannot preserve {source}-bit source PCM with {output} effective bits"
197            ),
198            Self::IncompleteFrame => {
199                f.write_str("exact source block must contain complete interleaved frames")
200            }
201            Self::BlockTooLarge => write!(
202                f,
203                "exact source block exceeds {MAX_EXACT_BLOCK_FRAMES} frames"
204            ),
205            Self::SampleOutOfRange {
206                index,
207                sample,
208                bits,
209            } => write!(
210                f,
211                "exact source sample {index} ({sample}) is outside the signed {bits}-bit range"
212            ),
213            Self::Allocation(error) => write!(f, "cannot reserve exact-output block: {error}"),
214        }
215    }
216}
217
218impl std::error::Error for ExactOutputError {
219    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
220        match self {
221            Self::Allocation(error) => Some(error),
222            _ => None,
223        }
224    }
225}
226
227/// A validated, stateless mapping from canonical source integers to bytes.
228///
229/// No source rate, precision, speaker position or normalized sample value is
230/// changed. Output integers are full-width PCM: widening shifts source values
231/// towards the most significant bit, rather than merely sign-extending them.
232/// Float scaling is exact for every source value in the 16/24-bit profile.
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub struct ExactPcmAdapter {
235    source: IntegerPcmFormat,
236    output: ExactOutputFormat,
237}
238
239impl ExactPcmAdapter {
240    /// Refuse rate/layout conversion and unknown or insufficient precision.
241    pub fn new(
242        source: IntegerPcmFormat,
243        output: ExactOutputFormat,
244    ) -> Result<Self, ExactOutputError> {
245        if source.sample_rate() != output.sample_rate() {
246            return Err(ExactOutputError::RateMismatch {
247                source: source.sample_rate(),
248                output: output.sample_rate(),
249            });
250        }
251        if source.layout() != output.layout() {
252            return Err(ExactOutputError::LayoutMismatch {
253                source: source.layout(),
254                output: output.layout(),
255            });
256        }
257        let effective = output
258            .effective_bits()
259            .ok_or(ExactOutputError::UnknownEffectivePrecision)?;
260        let available = output
261            .representation()
262            .signed_container_bits()
263            .map_or(effective, |container| effective.min(container));
264        if available < source.bits_per_sample() {
265            return Err(ExactOutputError::InsufficientPrecision {
266                source: source.bits_per_sample(),
267                output: available,
268            });
269        }
270        Ok(Self { source, output })
271    }
272
273    /// Original unscaled signed source format accepted by this adapter.
274    pub fn source_format(self) -> IntegerPcmFormat {
275        self.source
276    }
277
278    /// Software output representation accepted by this adapter.
279    pub fn output_format(self) -> ExactOutputFormat {
280        self.output
281    }
282
283    /// Replace `encoded` with one bounded, complete interleaved block.
284    ///
285    /// Return the encoded frame count. An empty block produces no bytes. All
286    /// source samples, frame alignment and capacity are validated before the
287    /// existing encoded bytes are modified. Allocation is permitted here, so
288    /// callers must encode outside the output callback and may reuse capacity.
289    pub fn encode_block(
290        self,
291        samples: &[i32],
292        encoded: &mut Vec<u8>,
293    ) -> Result<usize, ExactOutputError> {
294        let channels = usize::from(self.source.channels());
295        if !samples.len().is_multiple_of(channels) {
296            return Err(ExactOutputError::IncompleteFrame);
297        }
298        let frames = samples.len() / channels;
299        if frames > MAX_EXACT_BLOCK_FRAMES {
300            return Err(ExactOutputError::BlockTooLarge);
301        }
302        let bits = self.source.bits_per_sample();
303        let negative_limit = -(1 << (bits - 1));
304        let positive_limit = (1 << (bits - 1)) - 1;
305        for (index, &sample) in samples.iter().enumerate() {
306            if !(negative_limit..=positive_limit).contains(&sample) {
307                return Err(ExactOutputError::SampleOutOfRange {
308                    index,
309                    sample,
310                    bits,
311                });
312            }
313        }
314        // The frame bound and mono/stereo source admission make this product
315        // bounded by 65536 bytes, independently of caller-owned vector size.
316        let bytes = samples.len() * self.output.representation().bytes_per_sample();
317        encoded
318            .try_reserve(bytes.saturating_sub(encoded.len()))
319            .map_err(ExactOutputError::Allocation)?;
320        encoded.clear();
321        for &sample in samples {
322            match self.output.representation() {
323                ExactSampleRepresentation::Signed16Le => {
324                    encoded.extend_from_slice(&(sample as i16).to_le_bytes());
325                }
326                ExactSampleRepresentation::PackedSigned24Le => {
327                    let widened = sample << (24 - bits);
328                    encoded.extend_from_slice(&widened.to_le_bytes()[..3]);
329                }
330                ExactSampleRepresentation::Signed32Le => {
331                    encoded.extend_from_slice(&(sample << (32 - bits)).to_le_bytes());
332                }
333                ExactSampleRepresentation::Float32Le => {
334                    let normalized = sample as f32 / (1_u32 << (bits - 1)) as f32;
335                    encoded.extend_from_slice(&normalized.to_le_bytes());
336                }
337                ExactSampleRepresentation::Float64Le => {
338                    let normalized = f64::from(sample) / f64::from(1_u32 << (bits - 1));
339                    encoded.extend_from_slice(&normalized.to_le_bytes());
340                }
341            }
342        }
343        Ok(frames)
344    }
345}
346
347#[cfg(test)]
348#[path = "exact_output_tests.rs"]
349mod tests;