1use std::collections::TryReserveError;
9use std::fmt;
10
11use aede_dsp::ChannelLayout;
12
13use super::format::{IntegerPcmFormat, PcmStreamFormat};
14
15pub const MAX_EXACT_BLOCK_FRAMES: usize = 4096;
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum ExactSampleRepresentation {
21 Signed16Le,
23 PackedSigned24Le,
25 Signed32Le,
27 Float32Le,
29 Float64Le,
31}
32
33impl ExactSampleRepresentation {
34 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#[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 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 pub fn sample_rate(self) -> u32 {
101 self.pcm.sample_rate()
102 }
103
104 pub fn layout(self) -> ChannelLayout {
106 self.pcm.layout()
107 }
108
109 pub fn channels(self) -> u16 {
111 self.pcm.channels()
112 }
113
114 pub fn representation(self) -> ExactSampleRepresentation {
116 self.representation
117 }
118
119 pub fn effective_bits(self) -> Option<u32> {
121 self.effective_bits
122 }
123}
124
125#[derive(Debug)]
127pub enum ExactOutputError {
128 InvalidOutputFormat,
130 InvalidEffectivePrecision(u32),
132 UnknownEffectivePrecision,
134 RateMismatch {
136 source: u32,
138 output: u32,
140 },
141 LayoutMismatch {
143 source: ChannelLayout,
145 output: ChannelLayout,
147 },
148 InsufficientPrecision {
150 source: u32,
152 output: u32,
154 },
155 IncompleteFrame,
157 BlockTooLarge,
159 SampleOutOfRange {
161 index: usize,
163 sample: i32,
165 bits: u32,
167 },
168 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub struct ExactPcmAdapter {
235 source: IntegerPcmFormat,
236 output: ExactOutputFormat,
237}
238
239impl ExactPcmAdapter {
240 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 pub fn source_format(self) -> IntegerPcmFormat {
275 self.source
276 }
277
278 pub fn output_format(self) -> ExactOutputFormat {
280 self.output
281 }
282
283 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 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;