Skip to main content

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}