audio_samples/traits.rs
1use bytemuck::NoUninit;
2use ndarray::ScalarOperand;
3use num_traits::{FromPrimitive, Num, NumCast, One, ToBytes, Zero};
4use serde::{Deserialize, Serialize};
5
6use crate::repr::SampleType;
7use crate::{AudioSamples, I24};
8use std::fmt::{Debug, Display};
9use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
10
11/// Performs a raw numeric cast from type `S` into `Self`.
12///
13/// Unlike [`ConvertTo`] and [`ConvertFrom`], casts apply standard numeric
14/// conversion rules equivalent to an `as` expression — no audio-aware
15/// scaling, rounding, or saturation is applied. This trait is used
16/// internally to move values between numeric types for arithmetic or
17/// indexing purposes.
18///
19/// For audio-meaningful conversions (e.g. `i16` → `f32` in the range
20/// `[-1.0, 1.0]`), use [`ConvertTo`] or [`ConvertFrom`] instead.
21pub trait CastFrom<S>: Sized {
22 /// Casts a value of type `S` into `Self` using raw numeric conversion.
23 ///
24 /// # Arguments
25 ///
26 /// – `value` — the source value to cast.
27 ///
28 /// # Returns
29 ///
30 /// The cast value as `Self`. No audio-aware scaling is applied.
31 fn cast_from(value: S) -> Self;
32}
33
34/// Performs a raw numeric cast from `Self` into type `T`.
35///
36/// This is the directional complement of [`CastFrom`]. A blanket
37/// implementation delegates to `T::cast_from(self)`, so this trait does
38/// not need to be implemented directly.
39///
40/// For audio-meaningful conversions, use [`ConvertTo`] instead.
41pub trait CastInto<T>: Sized
42where
43 Self: CastFrom<T>,
44{
45 /// Casts `self` into type `T` using raw numeric conversion.
46 ///
47 /// # Returns
48 ///
49 /// The cast value as `T`. No audio-aware scaling is applied.
50 fn cast_into(self) -> T;
51}
52
53/// Convenience supertrait asserting that a type can be raw-cast into all
54/// supported audio sample types: `u8`, `i16`, [`I24`](i24::I24), `i32`, `f32`, and
55/// `f64`.
56///
57/// A type satisfies this bound automatically when it implements [`CastInto`]
58/// for all six target types; no manual implementation is required.
59pub trait Castable:
60 CastInto<u8> + CastInto<i16> + CastInto<I24> + CastInto<i32> + CastInto<f32> + CastInto<f64>
61{
62}
63impl<T> Castable for T where
64 T: CastInto<u8> + CastInto<i16> + CastInto<I24> + CastInto<i32> + CastInto<f32> + CastInto<f64>
65{
66}
67
68mod sealed {
69 pub trait Sealed {}
70}
71use sealed::Sealed;
72
73/// Zero-sized marker type parameterised by byte width `N`.
74///
75/// Used together with [`SupportedByteSize`] as a compile-time guard on
76/// [`AudioSample::as_bytes`] to restrict byte serialisation to widths that
77/// are valid for the supported sample types: 1, 2, 3, 4, and 8.
78#[non_exhaustive]
79pub struct SampleByteSize<const N: usize>;
80
81/// Sealed marker trait indicating that byte width `N` is a supported audio
82/// sample size.
83///
84/// Implemented for [`SampleByteSize<N>`] where `N` is 1, 2, 3, 4, or 8,
85/// corresponding to the byte widths of `u8`, `i16`, `I24`, `i32`/`f32`,
86/// and `f64` respectively.
87///
88/// This trait is sealed and cannot be implemented outside this crate.
89pub trait SupportedByteSize: Sealed {}
90
91impl<const N: usize> Sealed for SampleByteSize<N> {}
92
93impl SupportedByteSize for SampleByteSize<1> {}
94impl SupportedByteSize for SampleByteSize<2> {}
95impl SupportedByteSize for SampleByteSize<3> {}
96impl SupportedByteSize for SampleByteSize<4> {}
97impl SupportedByteSize for SampleByteSize<8> {}
98
99/// Core trait for all audio sample types.
100///
101/// `AudioSample` is the foundational constraint that every audio sample
102/// numeric type in this crate must satisfy. It provides a uniform interface
103/// for arithmetic, conversions, byte serialisation, and interoperability
104/// with `ndarray` scalar operations.
105///
106/// ## Supported Types
107///
108/// | Type | Representation | Bit depth |
109/// |---------|-----------------------------|-----------|
110/// | `u8` | Unsigned PCM (silence = 128)| 8 |
111/// | `i16` | Signed integer PCM | 16 |
112/// | [`I24`](i24::I24) | Signed integer PCM (24-bit) | 24 |
113/// | `i32` | Signed integer PCM | 32 |
114/// | `f32` | Normalised float | 32 |
115/// | `f64` | Normalised float | 64 |
116///
117/// For `u8`, the silence level is 128 (mid-scale unsigned PCM convention).
118/// For `f32` and `f64`, `MAX` and `MIN` are `1.0` and `-1.0`.
119///
120/// # Safety
121///
122/// All implementors must satisfy [`bytemuck::NoUninit`], guaranteeing that
123/// the byte representation contains no uninitialised or padding bytes. This
124/// is required for safe byte-level serialisation via [`AudioSample::to_bytes`]
125/// and [`AudioSample::as_bytes`].
126///
127/// ## Examples
128///
129/// ```
130/// use audio_samples::AudioSample;
131///
132/// let sample: f32 = 0.5;
133/// let bytes = sample.to_bytes();
134/// assert_eq!(bytes.len(), 4); // f32 is 4 bytes
135///
136/// // Raw numeric cast (not audio-normalised)
137/// let raw: f64 = sample.as_float();
138/// assert_eq!(raw, 0.5_f64);
139/// ```
140pub trait AudioSample:
141 // Standard library traits
142 Copy
143 + Sized
144 + Default
145 + Display
146 + Debug
147 + Sync
148 + Send
149 + PartialEq
150 + PartialOrd
151 + Add<Output = Self>
152 + AddAssign<Self>
153 + Sub<Output = Self>
154 + SubAssign<Self>
155 + Mul<Output = Self>
156 + MulAssign<Self>
157 + Div<Output = Self>
158 + DivAssign<Self>
159 + Rem<Output = Self>
160 + RemAssign<Self>
161 + Into<Self>
162 + From<Self>
163 + ToString
164
165 // External crate traits
166 + NoUninit // bytemuck trait to ensure no uninitialized bytes
167 + Num // num-traits trait for numeric operations
168 + One // num-traits trait for 1 value
169 + Zero // num-traits trait for 0 value
170 + ToBytes // num-traits trait for byte conversion
171 + Serialize // serde trait for serialization
172 + Deserialize<'static> //serde trait for deserialisation // Need to make these optional.
173 + FromPrimitive // num-traits trait for conversion from primitive types
174 + NumCast // num-traits trait for casting between numeric types
175 + ScalarOperand // ndarray trait for scalar operations
176
177 // Library-specific traits. Most of which are below.
178 // They define how to convert between types depending on the context.
179 // Sometimes we are dealing with audio samples and float representations between -1.0 and 1.0, sometimes we are dealing with raw integer representations that we need to cast to floats for specific operations, but not -1.0 to 1.0, for various operations.
180 + ConvertTo<Self> // "I can convert to myself" trait
181 + ConvertTo<u8> // "I can convert to u8" trait
182 + ConvertTo<i16> // "I can convert to i16" trait
183 + ConvertTo<I24> // "I can convert to I24" trait
184 + ConvertTo<i32> // "I can convert to i32" trait
185 + ConvertTo<f32> // "I can convert to f32" trait
186 + ConvertTo<f64> // "I can convert to f64" trait
187 + CastFrom<usize> // "I can cast from a usize"
188 + Castable // "I can be cast into supported types"
189{
190 /// Returns this sample value unchanged.
191 ///
192 /// This is an identity method provided for API uniformity. It is useful
193 /// in generic contexts where a value must be consumed through a trait
194 /// interface but should pass through unmodified.
195 ///
196 /// # Returns
197 ///
198 /// `self` unchanged.
199 ///
200 /// ## Examples
201 ///
202 /// ```
203 /// use audio_samples::AudioSample;
204 ///
205 /// let s: f32 = 0.75;
206 /// assert_eq!(s.into_inner(), 0.75_f32);
207 /// ```
208 #[inline]
209 #[must_use]
210 fn into_inner(self) -> Self {
211 self
212 }
213
214 /// Converts this sample into a byte vector in native-endian order.
215 ///
216 /// Each sample is serialised to its native byte representation using
217 /// the platform's endianness. The returned vector has length
218 /// `Self::BYTES as usize`.
219 ///
220 /// For converting a whole slice at once, prefer
221 /// [`AudioSample::slice_to_bytes`].
222 ///
223 /// # Returns
224 ///
225 /// A `Vec<u8>` of length `Self::BYTES as usize` containing the
226 /// native-endian byte representation of this sample.
227 ///
228 /// ## Examples
229 ///
230 /// ```
231 /// use audio_samples::AudioSample;
232 ///
233 /// let sample: i16 = 256;
234 /// let bytes = sample.to_bytes();
235 /// assert_eq!(bytes.len(), 2);
236 /// ```
237 #[inline]
238 #[must_use]
239 fn to_bytes(self) -> Vec<u8> {
240 self.to_ne_bytes().as_ref().to_vec()
241 }
242
243 /// Converts this sample into a fixed-size byte array in native-endian order.
244 ///
245 /// The const generic parameter `N` must equal the byte size of `Self`
246 /// (e.g. `N = 4` for `f32` or `i32`). Supported widths — 1, 2, 3, 4,
247 /// and 8 — are enforced at compile time via the [`SupportedByteSize`]
248 /// bound.
249 ///
250 /// # Arguments
251 ///
252 /// – `N` (const generic) — the number of bytes in the output array.
253 /// Must match `Self::BYTES`; mismatches are compile errors.
254 ///
255 /// # Returns
256 ///
257 /// A `[u8; N]` containing the native-endian byte representation of
258 /// this sample.
259 ///
260 /// ## Examples
261 ///
262 /// ```
263 /// use audio_samples::AudioSample;
264 ///
265 /// let sample: i16 = 0x0100_i16;
266 /// let bytes: [u8; 2] = sample.as_bytes::<2>();
267 /// assert_eq!(bytes.len(), 2);
268 /// ```
269 #[inline]
270 fn as_bytes<const N: usize>(&self) -> [u8; N]
271 where SampleByteSize<N>: SupportedByteSize,
272 {
273 let bytes_ref = self.to_ne_bytes();
274 let bytes_slice: &[u8] = bytes_ref.as_ref();
275 let mut result = [0u8; N];
276 result.copy_from_slice(bytes_slice);
277 result
278 }
279
280 #[inline]
281 /// Converts a slice of samples into a byte vector in native-endian order.
282 ///
283 /// Uses [`bytemuck`] to reinterpret the slice as bytes. This avoids
284 /// element-wise copying when the alignment allows it.
285 ///
286 /// # Arguments
287 ///
288 /// – `samples` — a slice of samples to serialise.
289 ///
290 /// # Returns
291 ///
292 /// A `Vec<u8>` of length `samples.len() * Self::BYTES as usize`.
293 ///
294 /// ## Examples
295 ///
296 /// ```
297 /// use audio_samples::AudioSample;
298 ///
299 /// let samples = [1_i16, 2, 3];
300 /// let bytes = i16::slice_to_bytes(&samples);
301 /// assert_eq!(bytes.len(), 6); // 3 samples × 2 bytes each
302 /// ```
303 fn slice_to_bytes(samples: &[Self]) -> Vec<u8> {
304 Vec::from(bytemuck::cast_slice(samples))
305 }
306
307 #[inline]
308 /// Casts this sample value to `f64` without audio-aware scaling.
309 ///
310 /// This is a raw numeric cast equivalent to `self as f64`. No
311 /// normalisation into `[-1.0, 1.0]` is applied. For integer sample
312 /// types, the raw integer magnitude is preserved.
313 ///
314 /// For audio-aware conversion that scales integer samples into the
315 /// floating-point range, use [`ConvertTo::<f64>::convert_to`] instead.
316 ///
317 /// # Returns
318 ///
319 /// The sample value cast to `f64`, preserving the raw numeric magnitude.
320 ///
321 /// ## Examples
322 ///
323 /// ```
324 /// use audio_samples::AudioSample;
325 ///
326 /// // Raw cast: the integer value 32767 becomes 32767.0, not 1.0.
327 /// let sample: i16 = 32767;
328 /// assert_eq!(sample.as_float(), 32767.0_f64);
329 ///
330 /// // For float types the value is unchanged.
331 /// let f: f32 = 0.5;
332 /// assert_eq!(f.as_float(), 0.5_f64);
333 /// ```
334 fn as_float(self) -> f64
335 {
336 self.cast_into()
337 }
338
339 /// Maximum representable amplitude value for this sample type.
340 ///
341 /// For integer types this is the type's integer maximum (e.g. `32767`
342 /// for `i16`, `255` for `u8`). For float types (`f32`, `f64`) this
343 /// is `1.0`.
344 const MAX: Self;
345
346 /// Minimum representable amplitude value for this sample type.
347 ///
348 /// For integer types this is the type's integer minimum (e.g. `-32768`
349 /// for `i16`, `0` for `u8`). For float types this is `-1.0`.
350 const MIN: Self;
351
352 /// Bit depth of this sample type (e.g. `8` for `u8`, `16` for `i16`).
353 const BITS: u8;
354
355 /// Byte width of this sample type, derived as `BITS / 8`.
356 const BYTES: u32 = Self::BITS as u32 / 8;
357
358 /// Human-readable label for this sample type, used for display and
359 /// plotting. Examples: `"u8"`, `"i16"`, `"I24"`, `"f32"`, `"f64"`.
360 const LABEL: &'static str;
361
362 /// Enum variant identifying this sample type at runtime.
363 ///
364 /// Corresponds to a variant of [`SampleType`].
365 const SAMPLE_TYPE: SampleType;
366}
367
368/// Supertrait combining [`AudioSample`] with full bidirectional conversion
369/// support across all standard sample types.
370///
371/// A type satisfies `StandardSample` automatically when it implements
372/// [`AudioSample`] together with [`CastInto<f64>`], [`CastFrom<f64>`], and
373/// [`ConvertFrom`] for every standard sample type (`u8`, `i16`, [`I24`](i24::I24),
374/// `i32`, `f32`, `f64`). No manual implementation is required.
375///
376/// The supported standard sample types are `u8`, `i16`, [`I24`](i24::I24), `i32`,
377/// `f32`, and `f64`. Most audio processing operations in this crate are
378/// generic over `T: StandardSample`.
379///
380/// Prefer this bound over [`AudioSample`] when bidirectional conversions
381/// between sample types are required.
382pub trait StandardSample:
383 AudioSample
384 + CastInto<f64>
385 + CastFrom<f64>
386 + ConvertFrom<Self>
387 + ConvertFrom<u8>
388 + ConvertFrom<i16>
389 + ConvertFrom<I24>
390 + ConvertFrom<i32>
391 + ConvertFrom<f32>
392 + ConvertFrom<f64>
393 + Castable
394{
395}
396
397impl<T> StandardSample for T where
398 T: AudioSample
399 + CastInto<f64>
400 + CastFrom<f64>
401 + ConvertFrom<Self>
402 + ConvertFrom<u8>
403 + ConvertFrom<i16>
404 + ConvertFrom<I24>
405 + ConvertFrom<i32>
406 + ConvertFrom<f32>
407 + ConvertFrom<f64>
408{
409}
410
411/// Trait for converting one sample type to another with audio-aware scaling.
412///
413/// `ConvertTo` performs conversions that are intended for audio sample values rather than raw
414/// numeric casts.
415///
416/// ## Conversion Behavior
417/// - **Integer ↔ Integer**: PCM-style bit-depth scaling (e.g. `i16::MAX` maps to `0x7FFF0000i32`)
418/// - **Integer ↔ Float**: normalized scaling into $[-1.0, 1.0]$ using asymmetric endpoints
419/// - **Float ↔ Integer**: clamp to $[-1.0, 1.0]$, then scale, round, and saturate
420/// - **I24 Special Handling**: conversions treat `I24` as a 24-bit signed PCM integer
421///
422/// ## Example
423/// ```rust
424/// use audio_samples::ConvertTo;
425///
426/// let sample_i16: i16 = 16384; // approximately half-scale
427/// let sample_f32: f32 = sample_i16.convert_to();
428/// assert!((sample_f32 - 0.5).abs() < 1e-4);
429///
430/// let sample_i32: i32 = sample_i16.convert_to();
431/// assert_eq!(sample_i32, 0x4000_0000);
432/// ```
433pub trait ConvertTo<Dst> {
434 /// Converts this sample into `Dst` using audio-aware scaling.
435 ///
436 /// # Returns
437 ///
438 /// The converted sample as `Dst`. Conversion semantics follow the rules
439 /// documented on [`ConvertTo`]: integer-to-float converts to
440 /// `[-1.0, 1.0]`; float-to-integer clamps, scales, and rounds;
441 /// integer-to-integer shifts bit depth with saturation.
442 fn convert_to(self) -> Dst;
443}
444
445/// Audio-aware conversion from a source sample type into `Self`.
446///
447/// This is the blanket-implemented inverse of [`ConvertTo`]. Implementing
448/// `ConvertFrom<Src>` for `Dst` automatically provides `ConvertTo<Dst>`
449/// for `Src` via the blanket implementation.
450///
451/// Conversion semantics are the same as documented on [`ConvertTo`]:
452/// integer ↔ float conversions apply asymmetric normalised scaling;
453/// integer ↔ integer conversions use PCM-style bit-depth shifting with
454/// saturation; `u8` uses mid-scale unsigned PCM conventions.
455pub trait ConvertFrom<Src> {
456 /// Converts a sample of type `Src` into `Self`.
457 ///
458 /// # Arguments
459 ///
460 /// – `source` — the source sample to convert.
461 ///
462 /// # Returns
463 ///
464 /// The converted sample as `Self`.
465 fn convert_from(source: Src) -> Self;
466}
467
468impl<Src, Dst> ConvertTo<Dst> for Src
469where
470 Dst: ConvertFrom<Src>,
471{
472 #[inline]
473 fn convert_to(self) -> Dst {
474 Dst::convert_from(self)
475 }
476}
477
478// Identity
479macro_rules! impl_identity_conversion {
480 ($ty:ty) => {
481 impl ConvertFrom<$ty> for $ty {
482 #[inline]
483 fn convert_from(source: $ty) -> Self {
484 source
485 }
486 }
487 };
488}
489
490// Integer -> Integer, saturating (with bit-shift scaling if needed)
491macro_rules! impl_int_to_int_conversion {
492 ($from:ty, $to:ty) => {
493 impl ConvertFrom<$from> for $to {
494 #[inline]
495 fn convert_from(source: $from) -> Self {
496 let from_bits = <$from>::BITS as i32;
497 let to_bits = <$to>::BITS as i32;
498
499 let v = <i128 as From<$from>>::from(source);
500 let scaled = if from_bits < to_bits {
501 v << (to_bits - from_bits)
502 } else if from_bits > to_bits {
503 v >> (from_bits - to_bits)
504 } else {
505 v
506 };
507
508 let min = <i128 as From<$to>>::from(<$to>::MIN);
509 let max = <i128 as From<$to>>::from(<$to>::MAX);
510
511 if scaled < min {
512 <$to>::MIN
513 } else if scaled > max {
514 <$to>::MAX
515 } else {
516 scaled as $to
517 }
518 }
519 }
520 };
521}
522
523// I24 -> Integer (any standard integer), saturating
524macro_rules! impl_i24_to_int {
525 ($to:ty) => {
526 impl ConvertFrom<I24> for $to {
527 #[inline]
528 fn convert_from(source: I24) -> Self {
529 let to_bits = <$to>::BITS as i32;
530 let v = <i128 as From<i32>>::from(source.to_i32());
531
532 let shift = to_bits - 24;
533 let scaled = if shift >= 0 {
534 v << shift
535 } else {
536 v >> (-shift)
537 };
538
539 let min = <i128 as From<$to>>::from(<$to>::MIN);
540 let max = <i128 as From<$to>>::from(<$to>::MAX);
541 if scaled < min {
542 <$to>::MIN
543 } else if scaled > max {
544 <$to>::MAX
545 } else {
546 scaled as $to
547 }
548 }
549 }
550 };
551}
552
553// Integer -> I24, saturating
554macro_rules! impl_int_to_i24 {
555 ($from:ty) => {
556 impl ConvertFrom<$from> for I24 {
557 #[inline]
558 fn convert_from(source: $from) -> Self {
559 let from_bits = <$from>::BITS as i32;
560 let v = <i128 as From<$from>>::from(source);
561
562 let shift = 24 - from_bits;
563 let scaled = if shift >= 0 {
564 v << shift
565 } else {
566 v >> (-shift)
567 };
568
569 let min = <i128 as From<i32>>::from(I24::MIN.to_i32());
570 let max = <i128 as From<i32>>::from(I24::MAX.to_i32());
571 let clamped = if scaled < min {
572 min as i32
573 } else if scaled > max {
574 max as i32
575 } else {
576 scaled as i32
577 };
578
579 I24::saturating_from_i32(clamped)
580 }
581 }
582 };
583}
584
585// I24 -> Float (normalised)
586macro_rules! impl_i24_to_float {
587 ($to:ty) => {
588 impl ConvertFrom<I24> for $to {
589 #[inline]
590 #[allow(clippy::cast_lossless)]
591 fn convert_from(source: I24) -> Self {
592 let v = source.to_i32() as $to;
593 let max = I24::MAX.to_i32() as $to;
594 let min = I24::MIN.to_i32() as $to;
595 if v < 0.0 { v / -min } else { v / max }
596 }
597 }
598 };
599}
600
601// Integer -> Float (normalised)
602macro_rules! impl_int_to_float {
603 ($from:ty, $to:ty) => {
604 impl ConvertFrom<$from> for $to {
605 #[inline]
606 fn convert_from(source: $from) -> Self {
607 let v = source;
608 if v < 0 {
609 (v as $to) / (-(<$from>::MIN as $to))
610 } else {
611 (v as $to) / (<$from>::MAX as $to)
612 }
613 }
614 }
615 };
616}
617
618// Float -> Integer (clamp + scale + round + saturating)
619macro_rules! impl_float_to_int {
620 ($from:ty, $to:ty) => {
621 impl ConvertFrom<$from> for $to {
622 #[inline]
623 fn convert_from(source: $from) -> Self {
624 let v = source.clamp(-1.0, 1.0);
625 let scaled = if v < 0.0 {
626 v * (-(<$to>::MIN as $from))
627 } else {
628 v * (<$to>::MAX as $from)
629 };
630 let rounded = scaled.round();
631 if rounded < (<$to>::MIN as $from) {
632 <$to>::MIN
633 } else if rounded > (<$to>::MAX as $from) {
634 <$to>::MAX
635 } else {
636 rounded as $to
637 }
638 }
639 }
640 };
641}
642
643// Float -> I24 (clamp + scale + round + saturating)
644macro_rules! impl_float_to_i24 {
645 ($from:ty) => {
646 impl ConvertFrom<$from> for I24 {
647 #[inline]
648 fn convert_from(source: $from) -> Self {
649 let v = source.clamp(-1.0, 1.0);
650 let scaled = if v < 0.0 {
651 v * (-(I24::MIN.to_i32() as $from))
652 } else {
653 v * (I24::MAX.to_i32() as $from)
654 };
655 let rounded = scaled.round();
656 let clamped = if rounded < (I24::MIN.to_i32() as $from) {
657 I24::MIN.to_i32()
658 } else if rounded > (I24::MAX.to_i32() as $from) {
659 I24::MAX.to_i32()
660 } else {
661 rounded as i32
662 };
663 I24::saturating_from_i32(clamped)
664 }
665 }
666 };
667}
668
669// Float -> Float
670macro_rules! impl_float_to_float {
671 ($from:ty, $to:ty) => {
672 impl ConvertFrom<$from> for $to {
673 #[inline]
674 fn convert_from(source: $from) -> Self {
675 source as $to
676 }
677 }
678 };
679}
680
681// ========================
682// u8 <-> Signed Integer / I24 / Float
683// ========================
684//
685// Conventions:
686// - u8 is unsigned PCM with 128 as zero.
687// - Centred value c = (u8 as i32) - 128 in [-128, 127].
688// - Asymmetric scaling:
689// negative side uses divisor 128 (so 0 maps to -1.0 exactly)
690// positive side uses divisor 127 (so 255 maps to +1.0 exactly)
691//
692// - For u8 -> signed-int/I24: scale c into destination integer range using the
693// same asymmetric idea (negative uses abs(min), positive uses max).
694// - For signed-int/I24 -> u8: invert scaling, round-to-nearest, clamp to [0,255].
695//
696// Notes:
697// - All intermediate arithmetic is done in i128 to avoid overflow for i32::MIN abs.
698// - Rounding is symmetric "nearest" for integer division (positive numerators only).
699
700#[inline]
701const fn div_round_nearest_i128(num: i128, den: i128) -> i128 {
702 // Assumes den > 0 and num >= 0
703 (num + (den / 2)) / den
704}
705
706// u8 -> signed integer (i16/i32), saturating + asymmetric scaling
707macro_rules! impl_u8_to_int {
708 ($to:ty) => {
709 impl ConvertFrom<u8> for $to {
710 #[inline]
711 #[allow(clippy::cast_lossless)]
712 fn convert_from(source: u8) -> Self {
713 let c: i128 = (source as i128) - 128; // [-128, 127]
714
715 let scaled: i128 = if c < 0 {
716 // Map -128 -> MIN exactly
717 // c is negative, abs range is 128
718 c * (-(<$to>::MIN as i128)) / 128
719 } else {
720 // Map +127 -> MAX exactly
721 c * (<$to>::MAX as i128) / 127
722 };
723
724 // Should already be in-range, but keep the same saturating style.
725 let min = <$to>::MIN as i128;
726 let max = <$to>::MAX as i128;
727 if scaled < min {
728 <$to>::MIN
729 } else if scaled > max {
730 <$to>::MAX
731 } else {
732 scaled as $to
733 }
734 }
735 }
736 };
737}
738
739// signed integer -> u8, saturating + asymmetric scaling + rounding
740macro_rules! impl_int_to_u8 {
741 ($from:ty) => {
742 impl ConvertFrom<$from> for u8 {
743 #[inline]
744 #[allow(clippy::cast_lossless)]
745 fn convert_from(source: $from) -> Self {
746 let v: i128 = source as i128;
747
748 let out_i128: i128 = if v < 0 {
749 // v in [MIN, -1]
750 let mag = (-v) as i128; // positive
751 let den = (-(<$from>::MIN as i128)); // abs(min), e.g. 32768 for i16
752 let scaled = div_round_nearest_i128(mag * 128, den); // 0..128
753 128 - scaled
754 } else {
755 // v in [0, MAX]
756 let den = (<$from>::MAX as i128);
757 let scaled = div_round_nearest_i128(v * 127, den); // 0..127
758 128 + scaled
759 };
760
761 // Clamp to [0, 255]
762 if out_i128 < 0 {
763 0
764 } else if out_i128 > 255 {
765 255
766 } else {
767 out_i128 as u8
768 }
769 }
770 }
771 };
772}
773
774// u8 -> I24 (asymmetric scaling), saturating
775macro_rules! impl_u8_to_i24 {
776 () => {
777 impl ConvertFrom<u8> for I24 {
778 #[inline]
779 fn convert_from(source: u8) -> Self {
780 let c: i128 = (<i128 as From<u8>>::from(source)) - 128; // [-128, 127]
781 let min = <i128 as From<i32>>::from(I24::MIN.to_i32());
782 let max = <i128 as From<i32>>::from(I24::MAX.to_i32());
783
784 let scaled: i128 = if c < 0 {
785 c * (-min) / 128
786 } else {
787 c * max / 127
788 };
789
790 // Clamp into i24 range, then saturating construct.
791 let clamped: i32 = if scaled < min {
792 min as i32
793 } else if scaled > max {
794 max as i32
795 } else {
796 scaled as i32
797 };
798
799 I24::saturating_from_i32(clamped)
800 }
801 }
802 };
803}
804
805// I24 -> u8 (invert asymmetric scaling), saturating + rounding
806macro_rules! impl_i24_to_u8 {
807 () => {
808 impl ConvertFrom<I24> for u8 {
809 #[inline]
810 fn convert_from(source: I24) -> Self {
811 let v: i128 = <i128 as From<i32>>::from(source.to_i32());
812 let min = <i128 as From<i32>>::from(I24::MIN.to_i32()); // negative
813 let max = <i128 as From<i32>>::from(I24::MAX.to_i32()); // positive
814
815 let out_i128: i128 = if v < 0 {
816 let mag = <i128 as From<i128>>::from(-v);
817 let den = <i128 as From<i128>>::from(-min); // abs(min) = 8388608
818 let scaled = div_round_nearest_i128(mag * 128, den); // 0..128
819 128 - scaled
820 } else {
821 let den = <i128 as From<i128>>::from(max); // 8388607
822 let scaled = div_round_nearest_i128(v * 127, den); // 0..127
823 128 + scaled
824 };
825
826 if out_i128 < 0 {
827 0
828 } else if out_i128 > 255 {
829 255
830 } else {
831 out_i128 as u8
832 }
833 }
834 }
835 };
836}
837
838// u8 -> float (normalised, asymmetric endpoints)
839macro_rules! impl_u8_to_float {
840 ($to:ty) => {
841 impl ConvertFrom<u8> for $to {
842 #[inline]
843 fn convert_from(source: u8) -> Self {
844 let c: i32 = (<i32 as From<u8>>::from(source)) - 128; // [-128, 127]
845 let v = c as $to;
846 if c < 0 {
847 v / (128.0 as $to)
848 } else {
849 v / (127.0 as $to)
850 }
851 }
852 }
853 };
854}
855
856// float -> u8 (clamp + asymmetric scale + round + saturate)
857macro_rules! impl_float_to_u8 {
858 ($from:ty) => {
859 impl ConvertFrom<$from> for u8 {
860 #[inline]
861 fn convert_from(source: $from) -> Self {
862 let v = source.clamp(-1.0, 1.0);
863
864 // Convert float to centred integer c in [-128, 127] with asymmetric scaling.
865 // Negative maps to [-128, 0], positive maps to [0, 127].
866 let c: i128 = if v < 0.0 {
867 // -1.0 -> -128 exactly
868 (v * (128.0 as $from)).round() as i128
869 } else {
870 // +1.0 -> +127 exactly
871 (v * (127.0 as $from)).round() as i128
872 };
873
874 let out = 128i128 + c;
875
876 if out < 0 {
877 0
878 } else if out > 255 {
879 255
880 } else {
881 out as u8
882 }
883 }
884 }
885 };
886}
887
888// ========================
889// u8 Identity
890// ========================
891
892impl_identity_conversion!(u8);
893
894// ========================
895// u8 <-> Integer
896// ========================
897
898impl_u8_to_int!(i16);
899impl_u8_to_int!(i32);
900
901impl_int_to_u8!(i16);
902impl_int_to_u8!(i32);
903
904// ========================
905// u8 <-> I24
906// ========================
907
908impl_u8_to_i24!();
909impl_i24_to_u8!();
910
911// ========================
912// u8 <-> Float
913// ========================
914
915impl_u8_to_float!(f32);
916impl_u8_to_float!(f64);
917
918impl_float_to_u8!(f32);
919impl_float_to_u8!(f64);
920
921// ========================
922// Identity
923// ========================
924
925impl_identity_conversion!(i16);
926impl_identity_conversion!(I24);
927impl_identity_conversion!(i32);
928impl_identity_conversion!(f32);
929impl_identity_conversion!(f64);
930
931// ========================
932// Integer <-> Integer (Saturating, No Normalisation)
933// ========================
934
935impl_int_to_int_conversion!(i16, i32);
936impl_int_to_int_conversion!(i32, i16);
937
938// ========================
939// I24 <-> Integer
940// ========================
941
942impl_i24_to_int!(i16);
943impl_i24_to_int!(i32);
944
945impl_int_to_i24!(i16);
946impl_int_to_i24!(i32);
947
948// ========================
949// Integer -> Float (Normalised +- 1.0)
950// ========================
951
952impl_int_to_float!(i16, f32);
953impl_int_to_float!(i16, f64);
954
955impl_int_to_float!(i32, f32);
956impl_int_to_float!(i32, f64);
957
958// ========================
959// I24 -> Float (Normalised +- 1.0)
960// ========================
961
962impl_i24_to_float!(f32);
963impl_i24_to_float!(f64);
964
965// ========================
966// Float -> Integer (Clamped, Rounded, Saturating)
967// ========================
968
969impl_float_to_int!(f32, i16);
970impl_float_to_int!(f64, i16);
971
972impl_float_to_int!(f32, i32);
973impl_float_to_int!(f64, i32);
974
975// ========================
976// Float -> I24 (Clamped, Rounded, Saturating)
977// ========================
978
979impl_float_to_i24!(f32);
980impl_float_to_i24!(f64);
981
982// ========================
983// Float <-> Float
984// ========================
985
986impl_float_to_float!(f32, f64);
987impl_float_to_float!(f64, f32);
988
989// ========================
990// AudioSample Implementations
991// ========================
992impl AudioSample for u8 {
993 const MAX: Self = Self::MAX;
994 const MIN: Self = Self::MIN;
995 const BITS: Self = 8;
996 const LABEL: &'static str = "u8";
997 const SAMPLE_TYPE: SampleType = SampleType::U8;
998}
999
1000impl AudioSample for i16 {
1001 const MAX: Self = Self::MAX;
1002 const MIN: Self = Self::MIN;
1003 const BITS: u8 = 16;
1004 const LABEL: &'static str = "i16";
1005 const SAMPLE_TYPE: SampleType = SampleType::I16;
1006}
1007
1008impl AudioSample for I24 {
1009 #[inline]
1010 fn slice_to_bytes(samples: &[Self]) -> Vec<u8> {
1011 Self::write_i24s_ne(samples)
1012 }
1013
1014 const MAX: Self = Self::MAX;
1015 const MIN: Self = Self::MIN;
1016 const BITS: u8 = 24;
1017 const LABEL: &'static str = "I24";
1018 const SAMPLE_TYPE: SampleType = SampleType::I24;
1019}
1020
1021impl AudioSample for i32 {
1022 const MAX: Self = Self::MAX;
1023 const MIN: Self = Self::MIN;
1024 const BITS: u8 = 32;
1025 const LABEL: &'static str = "i32";
1026 const SAMPLE_TYPE: SampleType = SampleType::I32;
1027}
1028
1029impl AudioSample for f32 {
1030 const MAX: Self = 1.0;
1031 const MIN: Self = -1.0;
1032 const BITS: u8 = 32;
1033 const LABEL: &'static str = "f32";
1034 const SAMPLE_TYPE: SampleType = SampleType::F32;
1035}
1036
1037impl AudioSample for f64 {
1038 const MAX: Self = 1.0;
1039 const MIN: Self = -1.0;
1040 const BITS: u8 = 64;
1041 const LABEL: &'static str = "f64";
1042 const SAMPLE_TYPE: SampleType = SampleType::F64;
1043}
1044
1045// ========================
1046// Generate All Conversions
1047// ========================
1048
1049macro_rules! impl_cast_from {
1050 ($src:ty => [$($dst:ty),+]) => {
1051 $(
1052 impl CastFrom<$src> for $dst {
1053 #[inline]
1054 fn cast_from(value: $src) -> Self {
1055 value as $dst
1056 }
1057 }
1058 )+
1059 };
1060}
1061
1062impl_cast_from!(u8 => [u8, i16, i32, f32, f64]);
1063impl_cast_from!(i16 => [u8, i16, i32, f32, f64]);
1064impl_cast_from!(i32 => [u8, i16, i32, f32, f64]);
1065impl_cast_from!(f64 => [u8, i16, i32, f32, f64]);
1066impl_cast_from!(f32 => [u8, i16, i32, f32, f64]);
1067
1068/// Macro to implement the `CastFrom` trait for multiple type pairs
1069macro_rules! impl_cast_from_i24 {
1070 // Simple direct casts (no clamping or special logic)
1071 ($src:ty => $dst:ty) => {
1072 impl CastFrom<$src> for $dst {
1073 #[inline]
1074 #[allow(clippy::cast_lossless)]
1075 fn cast_from(value: $src) -> Self {
1076 value as $dst
1077 }
1078 }
1079 };
1080
1081 // Clamped casts for usize -> integer
1082 (clamp_usize $src:ty => $dst:ty, $max:expr) => {
1083 impl CastFrom<$src> for $dst {
1084 #[inline]
1085 fn cast_from(value: $src) -> Self {
1086 if value > $max as $src {
1087 $max
1088 } else {
1089 value as $dst
1090 }
1091 }
1092 }
1093 };
1094
1095 // usize -> I24 with clamping and try_from_i32
1096 (usize_to_i24 $src:ty => $dst:ty) => {
1097 impl CastFrom<$src> for $dst {
1098 #[inline]
1099 fn cast_from(value: $src) -> Self {
1100 if value > I24::MAX.to_i32() as $src {
1101 I24::MAX
1102 } else {
1103 match I24::try_from_i32(value as i32) {
1104 Some(x) => x,
1105 None => I24::MIN,
1106 }
1107 }
1108 }
1109 }
1110 };
1111
1112 // I24 -> primitive
1113 (i24_to_primitive $src:ty => $dst:ty) => {
1114 impl CastFrom<$src> for $dst {
1115 #[inline]
1116 #[allow(clippy::cast_lossless)]
1117 fn cast_from(value: $src) -> Self {
1118 value.to_i32() as $dst
1119 }
1120 }
1121 };
1122
1123 // primitive -> I24
1124 (primitive_to_i24 $src:ty => $dst:ty) => {
1125 impl CastFrom<$src> for $dst {
1126 #[inline]
1127 #[allow(clippy::cast_lossless)]
1128 fn cast_from(value: $src) -> Self {
1129 I24::try_from_i32(value as i32).unwrap_or(I24::MIN)
1130 }
1131 }
1132 };
1133
1134 // identity
1135 (identity $t:ty) => {
1136 impl CastFrom<$t> for $t {
1137 #[inline]
1138 fn cast_from(value: $t) -> Self {
1139 value
1140 }
1141 }
1142 };
1143}
1144
1145// usize to primitives
1146impl_cast_from_i24!(clamp_usize usize => u8, u8::MAX);
1147impl_cast_from_i24!(clamp_usize usize => i16, i16::MAX);
1148impl_cast_from_i24!(usize_to_i24 usize => I24);
1149impl_cast_from_i24!(clamp_usize usize => i32, i32::MAX);
1150impl_cast_from_i24!(usize => f32);
1151impl_cast_from_i24!(usize => f64);
1152
1153// I24 to primitives
1154impl_cast_from_i24!(i24_to_primitive I24 => u8);
1155impl_cast_from_i24!(i24_to_primitive I24 => i16);
1156impl_cast_from_i24!(identity I24);
1157impl_cast_from_i24!(i24_to_primitive I24 => i32);
1158impl_cast_from_i24!(i24_to_primitive I24 => f32);
1159impl_cast_from_i24!(i24_to_primitive I24 => f64);
1160
1161// primitives to I24
1162impl_cast_from_i24!(primitive_to_i24 u8 => I24);
1163impl_cast_from_i24!(primitive_to_i24 i16 => I24);
1164impl_cast_from_i24!(primitive_to_i24 i32 => I24);
1165impl_cast_from_i24!(primitive_to_i24 f32 => I24);
1166impl_cast_from_i24!(primitive_to_i24 f64 => I24);
1167
1168macro_rules! impl_cast_into {
1169 ($src:ty => [$($dst:ty),+]) => {
1170 $(
1171 impl CastInto<$dst> for $src {
1172 #[inline]
1173 fn cast_into(self) -> $dst {
1174 <$dst>::cast_from(self)
1175 }
1176 }
1177 )+
1178 };
1179}
1180impl_cast_into!(u8 => [u8, i16, i32, f32, f64]);
1181impl_cast_into!(i16 => [u8, i16, i32, f32, f64]);
1182impl_cast_into!(i32 => [u8, i16, i32, f32, f64]);
1183impl_cast_into!(f64 => [u8, i16, i32, f32, f64]);
1184impl_cast_into!(f32 => [u8, i16, i32, f32, f64]);
1185
1186/// Macro to implement the `CastInto` trait for multiple type pairs
1187macro_rules! impl_cast_into_i24 {
1188 // I24 -> primitive (via to_i32)
1189 (i24_to_primitive $src:ty => $dst:ty) => {
1190 impl CastInto<$dst> for $src {
1191 #[inline]
1192 fn cast_into(self) -> $dst {
1193 self.to_i32() as $dst
1194 }
1195 }
1196 };
1197
1198 // primitive -> I24 (via CastFrom)
1199 (primitive_to_i24 $src:ty => $dst:ty) => {
1200 impl CastInto<$dst> for $src {
1201 #[inline]
1202 fn cast_into(self) -> $dst {
1203 <$dst as CastFrom<$src>>::cast_from(self)
1204 }
1205 }
1206 };
1207
1208 // identity
1209 (identity $t:ty) => {
1210 impl CastInto<$t> for $t {
1211 #[inline]
1212 fn cast_into(self) -> $t {
1213 self
1214 }
1215 }
1216 };
1217}
1218// I24 to primitives
1219
1220impl_cast_into_i24!(i24_to_primitive I24 => u8);
1221impl_cast_into_i24!(i24_to_primitive I24 => i16);
1222impl_cast_into_i24!(identity I24);
1223impl_cast_into_i24!(i24_to_primitive I24 => i32);
1224impl_cast_into_i24!(i24_to_primitive I24 => f32);
1225impl_cast_into_i24!(i24_to_primitive I24 => f64);
1226
1227// primitives to I24
1228impl_cast_into_i24!(primitive_to_i24 u8 => I24);
1229impl_cast_into_i24!(primitive_to_i24 i16 => I24);
1230impl_cast_into_i24!(primitive_to_i24 i32 => I24);
1231impl_cast_into_i24!(primitive_to_i24 f32 => I24);
1232impl_cast_into_i24!(primitive_to_i24 f64 => I24);
1233
1234/// Audio sample conversion and casting operations for [`AudioSamples`].
1235///
1236/// This trait defines the public conversion surface for transforming an
1237/// [`AudioSamples`] value from one sample representation to another.
1238///
1239/// ## Purpose
1240///
1241/// Audio data is commonly represented using both integer PCM formats and
1242/// floating-point formats. This trait provides two explicit conversion modes:
1243///
1244/// - *Audio-aware conversion* for interpreting numeric values as audio samples,
1245/// applying the appropriate scaling and clamping when moving between integer
1246/// and floating-point representations.
1247/// - *Raw numeric casting* for transforming values using standard numeric rules
1248/// without audio-specific scaling.
1249///
1250/// The two modes are intentionally distinct and must be selected explicitly by
1251/// the caller.
1252///
1253/// ## Behavioural Guarantees
1254///
1255/// - All operations return a new owned [`AudioSamples`] value.
1256/// - Sample rate, channel structure, and sample ordering are preserved.
1257/// - The source audio is not modified.
1258/// - Conversions are total and do not return `Result`.
1259///
1260/// When converting from floating-point to fixed-width integer formats, values
1261/// outside the representable range are clamped.
1262///
1263/// ## Assumptions
1264///
1265/// The conversion behaviour is defined by the conversion traits implemented for
1266/// the involved sample types. This trait is implemented for `AudioSamples<T>`
1267/// where those conversions are available.
1268pub trait AudioTypeConversion: Sized {
1269 /// The source sample type of the audio being converted.
1270 type Sample: StandardSample;
1271
1272 /// Converts the audio to a different sample type using audio-aware scaling.
1273 ///
1274 /// Performs an audio-aware conversion from `Self::Sample` to `O`. The
1275 /// amplitude meaning of each sample is preserved: integer-to-float
1276 /// conversions normalise into `[-1.0, 1.0]`; float-to-integer conversions
1277 /// clamp, scale, and round; integer-to-integer conversions shift bit depth
1278 /// with saturation. For `u8`, the mid-scale unsigned PCM convention is
1279 /// applied.
1280 ///
1281 /// The source audio is not modified; a new owned value is returned.
1282 ///
1283 /// # Returns
1284 ///
1285 /// A new owned [`AudioSamples<'static, O>`] with samples converted from
1286 /// `Self::Sample` to `O`. Sample rate, channel count, and temporal
1287 /// ordering are preserved.
1288 ///
1289 /// ## Examples
1290 ///
1291 /// ```
1292 /// use audio_samples::{AudioSamples, sample_rate, AudioTypeConversion};
1293 /// use ndarray::array;
1294 ///
1295 /// let audio = AudioSamples::new_mono(
1296 /// array![0i16, i16::MAX],
1297 /// sample_rate!(44100),
1298 /// ).unwrap();
1299 ///
1300 /// let float_audio = audio.to_format::<f32>();
1301 /// let samples = float_audio.as_mono().unwrap();
1302 /// assert_eq!(samples[0], 0.0_f32);
1303 /// assert!((samples[1] - 1.0_f32).abs() < 1e-4);
1304 /// ```
1305 fn to_format<O>(&self) -> AudioSamples<'static, O>
1306 where
1307 Self::Sample: ConvertTo<O> + ConvertFrom<O>,
1308 O: StandardSample;
1309
1310 /// Converts the audio to a different sample type, consuming the source.
1311 ///
1312 /// This is the consuming counterpart to [`AudioTypeConversion::to_format`].
1313 /// It performs the same audio-aware conversion, but takes ownership of the
1314 /// input value. Prefer this method over `to_format` when the source is no
1315 /// longer needed, to avoid an unnecessary clone.
1316 ///
1317 /// # Returns
1318 ///
1319 /// A new owned [`AudioSamples<'static, O>`] with samples converted from
1320 /// `Self::Sample` to `O`. Sample rate, channel count, and temporal
1321 /// ordering are preserved.
1322 ///
1323 /// ## Examples
1324 ///
1325 /// ```
1326 /// use audio_samples::{AudioSamples, AudioTypeConversion, sample_rate};
1327 /// use ndarray::array;
1328 ///
1329 /// let audio = AudioSamples::new_mono(array![0i16, i16::MAX], sample_rate!(44100)).unwrap();
1330 /// let audio_f32: AudioSamples<'static, f32> = audio.to_type::<f32>();
1331 /// assert_eq!(audio_f32[0], 0.0);
1332 /// assert!((audio_f32[1] - 1.0).abs() < 1e-4);
1333 /// ```
1334 fn to_type<O>(self) -> AudioSamples<'static, O>
1335 where
1336 Self::Sample: ConvertTo<O> + ConvertFrom<O>,
1337 O: StandardSample;
1338
1339 /// Casts the audio to a different sample type without audio-aware scaling.
1340 ///
1341 /// Performs a raw numeric cast from `Self::Sample` to `O`, equivalent to
1342 /// an `as` cast applied element-wise. No normalisation, clamping, or
1343 /// bit-depth scaling is applied. Integer values are preserved as their
1344 /// raw numeric magnitude.
1345 ///
1346 /// Use [`AudioTypeConversion::to_format`] when amplitude meaning must be
1347 /// preserved across sample types.
1348 ///
1349 /// # Returns
1350 ///
1351 /// A new owned [`AudioSamples<'static, O>`] containing raw-cast samples.
1352 /// The source audio is unchanged. Sample rate, channel count, and temporal
1353 /// ordering are preserved.
1354 ///
1355 /// ## Examples
1356 ///
1357 /// ```
1358 /// use audio_samples::{AudioSamples, AudioTypeConversion, sample_rate};
1359 /// use ndarray::array;
1360 ///
1361 /// // Raw cast: i16 value 1000 becomes f32 value 1000.0, not 0.030518...
1362 /// let audio = AudioSamples::new_mono(array![1000i16, -500], sample_rate!(44100)).unwrap();
1363 /// let raw = audio.cast_as::<f32>();
1364 /// assert_eq!(raw[0], 1000.0_f32);
1365 /// assert_eq!(raw[1], -500.0_f32);
1366 /// ```
1367 fn cast_as<O>(&self) -> AudioSamples<'static, O>
1368 where
1369 Self::Sample: CastInto<O> + ConvertTo<O>,
1370 O: StandardSample;
1371
1372 /// Casts the audio to a different sample type without audio-aware scaling,
1373 /// consuming the source.
1374 ///
1375 /// This is the consuming counterpart to [`AudioTypeConversion::cast_as`].
1376 /// It performs the same raw numeric cast but takes ownership of the input.
1377 ///
1378 /// # Returns
1379 ///
1380 /// A new owned [`AudioSamples<'static, O>`] containing raw-cast samples.
1381 /// Sample rate, channel count, and ordering are preserved.
1382 ///
1383 /// ## Examples
1384 ///
1385 /// ```
1386 /// use audio_samples::{AudioSamples, AudioTypeConversion, sample_rate};
1387 /// use ndarray::array;
1388 ///
1389 /// let audio = AudioSamples::new_mono(array![255u8, 128u8, 0u8], sample_rate!(44100)).unwrap();
1390 /// let raw: AudioSamples<'static, i16> = audio.cast_to::<i16>();
1391 /// assert_eq!(raw[0], 255);
1392 /// assert_eq!(raw[1], 128);
1393 /// assert_eq!(raw[2], 0);
1394 /// ```
1395 fn cast_to<O>(self) -> AudioSamples<'static, O>
1396 where
1397 Self::Sample: CastInto<O> + ConvertTo<O>,
1398 O: StandardSample;
1399
1400 /// Casts the audio to `f64` without audio-aware scaling.
1401 ///
1402 /// Each sample value is raw-cast to `f64` as a number, preserving its
1403 /// numeric magnitude without normalisation. For integer sample types,
1404 /// the integer value (e.g. `32767`) is cast directly to `f64`, not
1405 /// scaled to `1.0`.
1406 ///
1407 /// For audio-aware conversion into `[-1.0, 1.0]`, use
1408 /// [`AudioTypeConversion::as_f64`] instead.
1409 ///
1410 /// # Returns
1411 ///
1412 /// A new owned [`AudioSamples<'static, f64>`] with raw-cast sample values.
1413 #[inline]
1414 fn cast_as_f64(&self) -> AudioSamples<'static, f64> {
1415 self.cast_as::<f64>()
1416 }
1417
1418 /// Converts the audio to `f64` using audio-aware scaling.
1419 ///
1420 /// Integer sample values are normalised into `[-1.0, 1.0]` according to
1421 /// the [`ConvertTo`] rules for the source type. For `u8` audio, value
1422 /// `128` maps to `0.0`. For float sources the values are widened unchanged.
1423 ///
1424 /// # Returns
1425 ///
1426 /// A new owned [`AudioSamples<'static, f64>`] with normalised sample values.
1427 #[inline]
1428 fn as_float(&self) -> AudioSamples<'static, f64> {
1429 self.to_format::<f64>()
1430 }
1431
1432 /// Converts the audio to `f64` using audio-aware scaling.
1433 ///
1434 /// Equivalent to [`AudioTypeConversion::as_float`]. Integer sample values
1435 /// are normalised into `[-1.0, 1.0]`.
1436 ///
1437 /// # Returns
1438 ///
1439 /// A new owned [`AudioSamples<'static, f64>`] with normalised sample values.
1440 #[inline]
1441 fn as_f64(&self) -> AudioSamples<'static, f64> {
1442 self.to_format::<f64>()
1443 }
1444
1445 /// Converts the audio to `f32` using audio-aware scaling.
1446 ///
1447 /// Integer sample values are normalised into `[-1.0, 1.0]`. For `u8`
1448 /// audio, value `128` maps to `0.0`.
1449 ///
1450 /// # Returns
1451 ///
1452 /// A new owned [`AudioSamples<'static, f32>`] with normalised sample values.
1453 #[inline]
1454 fn as_f32(&self) -> AudioSamples<'static, f32> {
1455 self.to_format::<f32>()
1456 }
1457
1458 /// Converts the audio to 32-bit signed integer PCM using audio-aware
1459 /// scaling.
1460 ///
1461 /// Float samples in `[-1.0, 1.0]` are scaled and rounded to the `i32`
1462 /// range with saturation. For `u8` audio, mid-scale value `128` maps to
1463 /// `0`.
1464 ///
1465 /// # Returns
1466 ///
1467 /// A new owned [`AudioSamples<'static, i32>`] in signed 32-bit PCM format.
1468 #[inline]
1469 fn as_i32(&self) -> AudioSamples<'static, i32> {
1470 self.to_format::<i32>()
1471 }
1472
1473 /// Converts the audio to 16-bit signed integer PCM using audio-aware
1474 /// scaling.
1475 ///
1476 /// Float samples in `[-1.0, 1.0]` are scaled and rounded to the `i16`
1477 /// range with saturation. For `u8` audio, mid-scale value `128` maps to
1478 /// `0`.
1479 ///
1480 /// # Returns
1481 ///
1482 /// A new owned [`AudioSamples<'static, i16>`] in signed 16-bit PCM format.
1483 #[inline]
1484 fn as_i16(&self) -> AudioSamples<'static, i16> {
1485 self.to_format::<i16>()
1486 }
1487
1488 /// Converts the audio to 24-bit signed integer PCM using audio-aware
1489 /// scaling.
1490 ///
1491 /// Float samples in `[-1.0, 1.0]` are scaled and rounded to the [`I24`](i24::I24)
1492 /// range with saturation. For `u8` audio, mid-scale value `128` maps to
1493 /// `0`.
1494 ///
1495 /// # Returns
1496 ///
1497 /// A new owned [`AudioSamples<'static, I24>`] in signed 24-bit PCM format.
1498 #[inline]
1499 fn as_i24(&self) -> AudioSamples<'static, I24> {
1500 self.to_format::<I24>()
1501 }
1502
1503 /// Converts the audio to 8-bit unsigned PCM format using audio-aware
1504 /// scaling.
1505 ///
1506 /// Float samples in `[-1.0, 1.0]` and signed integer samples are scaled
1507 /// to the `u8` range with mid-scale silence at `128`. Negative full-scale
1508 /// maps to `0`; positive full-scale maps to `255`.
1509 ///
1510 /// # Returns
1511 ///
1512 /// A new owned [`AudioSamples<'static, u8>`] in unsigned 8-bit PCM format.
1513 #[inline]
1514 fn as_u8(&self) -> AudioSamples<'static, u8> {
1515 self.to_format::<u8>()
1516 }
1517}
1518
1519#[cfg(test)]
1520mod conversion_tests {
1521 use super::*;
1522
1523 use i24::i24;
1524
1525 macro_rules! assert_approx_eq {
1526 ($left:expr, $right:expr, $tolerance:expr) => {
1527 assert!(
1528 ($left - $right).abs() < $tolerance,
1529 "assertion failed: `{} ≈ {}` (tolerance: {})",
1530 $left,
1531 $right,
1532 $tolerance
1533 );
1534 };
1535 }
1536
1537 #[test]
1538 fn u8_tests() {
1539 let zero: u8 = 0;
1540 let mid: u8 = 128;
1541 let max: u8 = 255;
1542 let neg_one_f32: f32 = -1.0;
1543 let zero_f32: f32 = 0.0;
1544 let one_f32: f32 = 1.0;
1545
1546 let zero_to_neg_one: f32 = zero.convert_to();
1547 let mid_to_zero: f32 = mid.convert_to();
1548 let max_to_one: f32 = max.convert_to();
1549 assert_approx_eq!(zero_to_neg_one as f64, -1.0, 1e-10);
1550 assert_approx_eq!(mid_to_zero as f64, 0.0, 1e-10);
1551 assert_approx_eq!(max_to_one as f64, 1.0, 1e-10);
1552
1553 let neg_one_to_u8: u8 = neg_one_f32.convert_to();
1554 let zero_to_u8: u8 = zero_f32.convert_to();
1555 let one_to_u8: u8 = one_f32.convert_to();
1556
1557 assert_eq!(neg_one_to_u8, 0);
1558 assert_eq!(zero_to_u8, 128);
1559 assert_eq!(one_to_u8, 255);
1560 }
1561
1562 // Edge cases for i16 conversions
1563 #[test]
1564 fn i16_edge_cases() {
1565 // Test minimum value
1566 let min_i16: i16 = i16::MIN;
1567 let min_i16_to_f32: f32 = min_i16.convert_to();
1568 // Use higher epsilon for floating point comparison
1569 assert_approx_eq!(min_i16_to_f32 as f64, -1.0, 1e-5);
1570
1571 let min_i16_to_i32: i32 = min_i16.convert_to();
1572 assert_eq!(min_i16_to_i32, i32::MIN);
1573
1574 let min_i16_to_i24: I24 = min_i16.convert_to();
1575 let expected_i24_min = i24!(i32::MIN >> 8);
1576 assert_eq!(min_i16_to_i24.to_i32(), expected_i24_min.to_i32());
1577
1578 // Test maximum value
1579 let max_i16: i16 = i16::MAX;
1580 let max_i16_to_f32: f32 = max_i16.convert_to();
1581 assert_approx_eq!(max_i16_to_f32 as f64, 1.0, 1e-4);
1582
1583 let max_i16_to_i32: i32 = max_i16.convert_to();
1584 assert_eq!(max_i16_to_i32, 0x7FFF0000);
1585
1586 // Test zero
1587 let zero_i16: i16 = 0;
1588 let zero_i16_to_f32: f32 = zero_i16.convert_to();
1589 assert_approx_eq!(zero_i16_to_f32 as f64, 0.0, 1e-10);
1590
1591 let zero_i16_to_i32: i32 = zero_i16.convert_to();
1592 assert_eq!(zero_i16_to_i32, 0);
1593
1594 let zero_i16_to_i24: I24 = zero_i16.convert_to();
1595 assert_eq!(zero_i16_to_i24.to_i32(), 0);
1596
1597 // Test mid-range positive
1598 let half_max_i16: i16 = i16::MAX / 2;
1599 let half_max_i16_to_f32: f32 = half_max_i16.convert_to();
1600 // Use higher epsilon for floating point comparison of half values
1601 assert_approx_eq!(half_max_i16_to_f32 as f64, 0.5, 1e-4);
1602
1603 let half_max_i16_to_i32: i32 = half_max_i16.convert_to();
1604 assert_eq!(half_max_i16_to_i32, 0x3FFF0000);
1605
1606 // Test mid-range negative
1607 let half_min_i16: i16 = i16::MIN / 2;
1608 let half_min_i16_to_f32: f32 = half_min_i16.convert_to();
1609 assert_approx_eq!(half_min_i16_to_f32 as f64, -0.5, 1e-4);
1610
1611 // let half_min_i16_to_i32: i32 = half_min_i16.convert_to();
1612 // assert_eq!(half_min_i16_to_i32, 0xC0010000); // i16::MIN/2 == -16384
1613 }
1614
1615 // Edge cases for i32 conversions
1616 #[test]
1617 fn i32_edge_cases() {
1618 // Test minimum value
1619 let min_i32: i32 = i32::MIN;
1620 let min_i32_to_f32: f32 = min_i32.convert_to();
1621 assert_approx_eq!(min_i32_to_f32 as f64, -1.0, 1e-6);
1622
1623 let min_i32_to_f64: f64 = min_i32.convert_to();
1624 assert_approx_eq!(min_i32_to_f64, -1.0, 1e-12);
1625
1626 let min_i32_to_i16: i16 = min_i32.convert_to();
1627 assert_eq!(min_i32_to_i16, i16::MIN);
1628
1629 // Test maximum value
1630 let max_i32: i32 = i32::MAX;
1631 let max_i32_to_f32: f32 = max_i32.convert_to();
1632 assert_approx_eq!(max_i32_to_f32 as f64, 1.0, 1e-6);
1633
1634 let max_i32_to_f64: f64 = max_i32.convert_to();
1635 assert_approx_eq!(max_i32_to_f64, 1.0, 1e-12);
1636
1637 let max_i32_to_i16: i16 = max_i32.convert_to();
1638 assert_eq!(max_i32_to_i16, i16::MAX);
1639
1640 // Test zero
1641 let zero_i32: i32 = 0;
1642 let zero_i32_to_f32: f32 = zero_i32.convert_to();
1643 assert_approx_eq!(zero_i32_to_f32 as f64, 0.0, 1e-10);
1644
1645 let zero_i32_to_f64: f64 = zero_i32.convert_to();
1646 assert_approx_eq!(zero_i32_to_f64, 0.0, 1e-12);
1647
1648 let zero_i32_to_i16: i16 = zero_i32.convert_to();
1649 assert_eq!(zero_i32_to_i16, 0);
1650
1651 // Test quarter-range values
1652 let quarter_max_i32: i32 = i32::MAX / 4;
1653 let quarter_max_i32_to_f32: f32 = quarter_max_i32.convert_to();
1654 assert_approx_eq!(quarter_max_i32_to_f32 as f64, 0.25, 1e-6);
1655
1656 let quarter_min_i32: i32 = i32::MIN / 4;
1657 let quarter_min_i32_to_f32: f32 = quarter_min_i32.convert_to();
1658 assert_approx_eq!(quarter_min_i32_to_f32 as f64, -0.25, 1e-6);
1659 }
1660
1661 // Edge cases for f32 conversions
1662 #[test]
1663 fn f32_edge_cases() {
1664 // Test -1.0 (minimum valid value)
1665 let min_f32: f32 = -1.0;
1666 let min_f32_to_i16: i16 = min_f32.convert_to();
1667 // For exact -1.0, we can get -32767 due to rounding in the implementation
1668 // This is acceptable since it's only 1 bit off from the true min
1669 assert!(
1670 min_f32_to_i16 == i16::MIN || min_f32_to_i16 == -32767,
1671 "Expected either -32768 or -32767, got {}",
1672 min_f32_to_i16
1673 );
1674
1675 let min_f32_to_i32: i32 = min_f32.convert_to();
1676 assert!(
1677 min_f32_to_i32 == i32::MIN || min_f32_to_i32 == -2147483647,
1678 "Expected either i32::MIN or -2147483647, got {}",
1679 min_f32_to_i32
1680 );
1681
1682 let min_f32_to_i24: I24 = min_f32.convert_to();
1683 let expected_i24 = I24::MIN;
1684 let diff = (min_f32_to_i24.to_i32() - expected_i24.to_i32()).abs();
1685 assert!(diff <= 1, "I24 values differ by more than 1, {}", diff);
1686
1687 // Test 1.0 (maximum valid value)
1688 let max_f32: f32 = 1.0;
1689 let max_f32_to_i16: i16 = max_f32.convert_to();
1690 println!("DEBUG: f32 -> i16 conversion for 1.0");
1691 println!(
1692 "Input: {}, Output: {}, Expected: {}",
1693 max_f32,
1694 max_f32_to_i16,
1695 i16::MAX
1696 );
1697 assert_eq!(max_f32_to_i16, i16::MAX);
1698
1699 let max_f32_to_i32: i32 = max_f32.convert_to();
1700 println!("DEBUG: f32 -> i32 conversion for 1.0");
1701 println!(
1702 "Input: {}, Output: {}, Expected: {}",
1703 max_f32,
1704 max_f32_to_i32,
1705 i32::MAX
1706 );
1707 assert_eq!(max_f32_to_i32, i32::MAX);
1708
1709 // Test 0.0
1710 let zero_f32: f32 = 0.0;
1711 let zero_f32_to_i16: i16 = zero_f32.convert_to();
1712 println!("DEBUG: f32 -> i16 conversion for 0.0");
1713 println!(
1714 "Input: {}, Output: {}, Expected: 0",
1715 zero_f32, zero_f32_to_i16
1716 );
1717 assert_eq!(zero_f32_to_i16, 0);
1718
1719 let zero_f32_to_i32: i32 = zero_f32.convert_to();
1720 println!("DEBUG: f32 -> i32 conversion for 0.0");
1721 println!(
1722 "Input: {}, Output: {}, Expected: 0",
1723 zero_f32, zero_f32_to_i32
1724 );
1725 assert_eq!(zero_f32_to_i32, 0);
1726
1727 let zero_f32_to_i24: I24 = zero_f32.convert_to();
1728 println!("DEBUG: f32 -> I24 conversion for 0.0");
1729 println!(
1730 "Input: {}, Output: {} (i32 value), Expected: 0",
1731 zero_f32,
1732 zero_f32_to_i24.to_i32()
1733 );
1734 assert_eq!(zero_f32_to_i24.to_i32(), 0);
1735
1736 // Test clamping of out-of-range values
1737 let large_f32: f32 = 2.0;
1738 let large_f32_to_i16: i16 = large_f32.convert_to();
1739 assert_eq!(large_f32_to_i16, i16::MAX);
1740
1741 let neg_large_f32: f32 = -2.0;
1742 let neg_large_f32_to_i16: i16 = neg_large_f32.convert_to();
1743 assert!(
1744 neg_large_f32_to_i16 == i16::MIN || neg_large_f32_to_i16 == -32767,
1745 "Expected either -32768 or -32767, got {}",
1746 neg_large_f32_to_i16
1747 );
1748
1749 let large_f32_to_i32: i32 = large_f32.convert_to();
1750 assert_eq!(large_f32_to_i32, i32::MAX);
1751
1752 let neg_large_f32_to_i32: i32 = neg_large_f32.convert_to();
1753 assert!(
1754 neg_large_f32_to_i32 == i32::MIN || neg_large_f32_to_i32 == -2147483647,
1755 "Expected either i32::MIN or -2147483647, got {}",
1756 neg_large_f32_to_i32
1757 );
1758
1759 // Test small values
1760 let small_value: f32 = 1.0e-6;
1761 let small_value_to_i16: i16 = small_value.convert_to();
1762 assert_eq!(small_value_to_i16, 0);
1763
1764 let small_value_to_i32: i32 = small_value.convert_to();
1765 assert_eq!(small_value_to_i32, 2147); // 1.0e-6 * 2147483647 rounded to nearest
1766
1767 // Test values near 0.5
1768 let half_f32: f32 = 0.5;
1769 let half_f32_to_i16: i16 = half_f32.convert_to();
1770 assert_eq!(half_f32_to_i16, 16384); // 0.5 * 32767 rounded to nearest
1771
1772 let neg_half_f32: f32 = -0.5;
1773 let neg_half_f32_to_i16: i16 = neg_half_f32.convert_to();
1774 assert_eq!(neg_half_f32_to_i16, -16384);
1775 }
1776
1777 // Edge cases for f64 conversions
1778 #[test]
1779 fn f64_edge_cases() {
1780 // Test -1.0 (minimum valid value)
1781 let min_f64: f64 = -1.0;
1782 let min_f64_to_i16: i16 = min_f64.convert_to();
1783
1784 println!("DEBUG: f64 -> i16 conversion for -1.0");
1785 println!(
1786 "Input: {}, Output: {}, Expected: {} or {}",
1787 min_f64,
1788 min_f64_to_i16,
1789 i16::MIN,
1790 -32767
1791 );
1792
1793 // Due to rounding in the implementation, sometimes -1.0 can convert to -32767
1794 // This is acceptable since it's only 1 bit off from the true min
1795 assert!(
1796 min_f64_to_i16 == i16::MIN || min_f64_to_i16 == -32767,
1797 "Expected either -32768 or -32767, got {}",
1798 min_f64_to_i16
1799 );
1800
1801 let min_f64_to_i32: i32 = min_f64.convert_to();
1802
1803 println!("DEBUG: f64 -> i32 conversion for -1.0");
1804 println!(
1805 "Input: {}, Output: {}, Expected: {} or {}",
1806 min_f64,
1807 min_f64_to_i32,
1808 i32::MIN,
1809 -2147483647
1810 );
1811
1812 assert!(
1813 min_f64_to_i32 == i32::MIN || min_f64_to_i32 == -2147483647,
1814 "Expected either i32::MIN or -2147483647, got {}",
1815 min_f64_to_i32
1816 );
1817
1818 let min_f64_to_f32: f32 = min_f64.convert_to();
1819
1820 println!("DEBUG: f64 -> f32 conversion for -1.0");
1821 println!(
1822 "Input: {}, Output: {}, Expected: -1.0",
1823 min_f64, min_f64_to_f32
1824 );
1825
1826 assert_approx_eq!(min_f64_to_f32 as f64, -1.0, 1e-6);
1827
1828 // Test 1.0 (maximum valid value)
1829 let max_f64: f64 = 1.0;
1830 let max_f64_to_i16: i16 = max_f64.convert_to();
1831 assert_eq!(max_f64_to_i16, i16::MAX);
1832
1833 let max_f64_to_i32: i32 = max_f64.convert_to();
1834 assert_eq!(max_f64_to_i32, i32::MAX);
1835
1836 let max_f64_to_f32: f32 = max_f64.convert_to();
1837 assert_approx_eq!(max_f64_to_f32 as f64, 1.0, 1e-6);
1838
1839 // Test 0.0
1840 let zero_f64: f64 = 0.0;
1841 let zero_f64_to_i16: i16 = zero_f64.convert_to();
1842 assert_eq!(zero_f64_to_i16, 0);
1843
1844 let zero_f64_to_i32: i32 = zero_f64.convert_to();
1845 assert_eq!(zero_f64_to_i32, 0);
1846
1847 let zero_f64_to_f32: f32 = zero_f64.convert_to();
1848 assert_approx_eq!(zero_f64_to_f32 as f64, 0.0, 1e-10);
1849
1850 // Test clamping of out-of-range values
1851 let large_f64: f64 = 2.0;
1852 let large_f64_to_i16: i16 = large_f64.convert_to();
1853 assert_eq!(large_f64_to_i16, i16::MAX);
1854
1855 let neg_large_f64: f64 = -2.0;
1856 let neg_large_f64_to_i16: i16 = neg_large_f64.convert_to();
1857 assert!(
1858 neg_large_f64_to_i16 == i16::MIN || neg_large_f64_to_i16 == -32767,
1859 "Expected either -32768 or -32767, got {}",
1860 neg_large_f64_to_i16
1861 );
1862
1863 // Test very small values
1864 let tiny_value: f64 = 1.0e-12;
1865 let tiny_value_to_i16: i16 = tiny_value.convert_to();
1866 assert_eq!(tiny_value_to_i16, 0);
1867
1868 let tiny_value_to_i32: i32 = tiny_value.convert_to();
1869 assert_eq!(tiny_value_to_i32, 0);
1870
1871 let tiny_value_to_f32: f32 = tiny_value.convert_to();
1872 assert_approx_eq!(tiny_value_to_f32 as f64, 0.0, 1e-10);
1873 }
1874
1875 // Tests for I24 conversions
1876 #[test]
1877 fn i24_conversion_tests() {
1878 // Create an I24 with a known value
1879 let i24_value = i24!(4660 << 8); // So converting back to i16 gives 4660
1880 println!(
1881 "DEBUG: Created I24 value from 4660 << 8 = {}",
1882 i24_value.to_i32()
1883 );
1884
1885 // Test I24 to i16
1886 let i24_to_i16: i16 = i24_value.convert_to();
1887 let expected_i16 = 0x1234_i16;
1888 println!("DEBUG: I24 -> i16 conversion");
1889 println!(
1890 "I24 (as i32): {}, i16: {}, Expected: {}",
1891 i24_value.to_i32(),
1892 i24_to_i16,
1893 expected_i16
1894 );
1895 assert_eq!(i24_to_i16, expected_i16);
1896
1897 // Test I24 to f32
1898 let i24_to_f32: f32 = i24_value.convert_to();
1899 let expected_f32 = (0x123456 as f32) / (I24::MAX.to_i32() as f32);
1900 println!("DEBUG: I24 -> f32 conversion");
1901 println!(
1902 "I24 (as i32): {}, f32: {}, Expected: {}",
1903 i24_value.to_i32(),
1904 i24_to_f32,
1905 expected_f32
1906 );
1907 // Print the difference to help debug
1908 println!("DEBUG: Difference: {}", (i24_to_f32 - expected_f32).abs());
1909 assert_approx_eq!(i24_to_f32 as f64, expected_f32 as f64, 1e-4);
1910
1911 // Test I24 to f64
1912 let i24_to_f64: f64 = i24_value.convert_to();
1913 let expected_f64 = (0x123456 as f64) / (I24::MAX.to_i32() as f64);
1914 println!("DEBUG: I24 -> f64 conversion");
1915 println!(
1916 "I24 (as i32): {}, f64: {}, Expected: {}",
1917 i24_value.to_i32(),
1918 i24_to_f64,
1919 expected_f64
1920 );
1921 // Print the difference to help debug
1922 println!("DEBUG: Difference: {}", (i24_to_f64 - expected_f64).abs());
1923 assert_approx_eq!(i24_to_f64, expected_f64, 1e-4);
1924 }
1925
1926 // Tests for convert_from functionality
1927 #[test]
1928 fn convert_from_tests() {
1929 // Test i16::convert_from with different source types
1930 let f32_source: f32 = 0.5;
1931 let i16_result: i16 = i16::convert_from(f32_source);
1932 assert_eq!(i16_result, 16384); // 0.5 * 32767 rounded
1933
1934 let i32_source: i32 = 65536;
1935 let i16_result: i16 = i16::convert_from(i32_source);
1936 assert_eq!(i16_result, 1); // 65536 >> 16 = 1
1937
1938 // Test f32::convert_from with different source types
1939 let i16_source: i16 = 16384;
1940 let f32_result: f32 = f32::convert_from(i16_source);
1941 assert_approx_eq!(f32_result as f64, 0.5, 1e-4);
1942
1943 let i32_source: i32 = i32::MAX / 2;
1944 let f32_result: f32 = f32::convert_from(i32_source);
1945 assert_approx_eq!(f32_result as f64, 0.5, 1e-4);
1946
1947 // Test I24::convert_from
1948 let i16_source: i16 = 4660; // 0x1234
1949 let i24_result: I24 = I24::convert_from(i16_source);
1950 assert_eq!(i24_result.to_i32(), 4660 << 8); // Should be shifted left by 8 bits
1951
1952 // Test with zero values
1953 let zero_f32: f32 = 0.0;
1954 let zero_i16: i16 = i16::convert_from(zero_f32);
1955 assert_eq!(zero_i16, 0);
1956
1957 let zero_i16_source: i16 = 0;
1958 let zero_f32_result: f32 = f32::convert_from(zero_i16_source);
1959 assert_approx_eq!(zero_f32_result as f64, 0.0, 1e-10);
1960 }
1961
1962 // Tests for round trip conversions
1963 #[test]
1964 fn round_trip_conversions() {
1965 // i16 -> f32 -> i16
1966 for sample in [-32768, -16384, 0, 16384, 32767].iter() {
1967 let original = *sample;
1968 let intermediate: f32 = original.convert_to();
1969 let round_tripped: i16 = intermediate.convert_to();
1970
1971 println!("DEBUG: i16->f32->i16 conversion");
1972 println!(
1973 "Original i16: {}, f32: {}, Round trip i16: {}",
1974 original, intermediate, round_tripped
1975 );
1976
1977 assert!(
1978 (original - round_tripped).abs() <= 1,
1979 "Expected {}, got {}",
1980 original,
1981 round_tripped
1982 );
1983 }
1984
1985 // i32 -> f32 -> i32 (will lose precision)
1986 for &sample in &[i32::MIN, i32::MIN / 2, 0, i32::MAX / 2, i32::MAX] {
1987 let original = sample;
1988 let intermediate: f32 = original.convert_to();
1989 let round_tripped: i32 = intermediate.convert_to();
1990
1991 // Special case for extreme values
1992 if original == i32::MIN {
1993 // Allow off-by-one for MIN value
1994 assert!(
1995 round_tripped == i32::MIN || round_tripped == -2147483647,
1996 "Expected either i32::MIN or -2147483647, got {}",
1997 round_tripped
1998 );
1999 } else if original == i32::MAX || original == 0 {
2000 assert_eq!(
2001 original, round_tripped,
2002 "Failed in i32->f32->i32 with extreme value {}",
2003 original
2004 );
2005 } else {
2006 // For other values, we expect close but not exact due to precision
2007 let ratio = (round_tripped as f64) / (original as f64);
2008 assert!(
2009 ratio > 0.999 && ratio < 1.001,
2010 "Round trip error too large: {} -> {}",
2011 original,
2012 round_tripped
2013 );
2014 }
2015 }
2016
2017 // f32 -> i16 -> f32
2018 for &sample in &[-1.0, -0.5, 0.0, 0.5, 1.0] {
2019 let original: f32 = sample;
2020 let intermediate: i16 = original.convert_to();
2021 let round_tripped: f32 = intermediate.convert_to();
2022
2023 // For all values, we check approximately but with a more generous epsilon
2024 assert_approx_eq!(original as f64, round_tripped as f64, 1e-4);
2025 }
2026
2027 // i16 -> I24 -> i16
2028 for &sample in &[i16::MIN, -16384, 0, 16384, i16::MAX] {
2029 let original = sample;
2030 let intermediate: I24 = original.convert_to();
2031 let round_tripped: i16 = intermediate.convert_to();
2032
2033 // For extreme negative values, allow 1-bit difference
2034 if original == i16::MIN {
2035 assert!(
2036 round_tripped == i16::MIN || round_tripped == -32767,
2037 "Expected either -32768 or -32767, got {}",
2038 round_tripped
2039 );
2040 } else {
2041 assert_eq!(
2042 original, round_tripped,
2043 "Failed in i16->I24->i16 with value {}",
2044 original
2045 );
2046 }
2047 }
2048 }
2049}