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    /// Clamps `self` to the closed interval `[min, max]`.
215    ///
216    /// This is a per-type hook used internally by [`clip`][crate::AudioProcessing::clip]
217    /// to select the most efficient clamping implementation.  Integer types
218    /// use [`Ord::clamp`]; floating-point types use their inherent `.clamp()`
219    /// method which the compiler maps to `VMAXPS`/`VMINPS` in vectorised
220    /// loops.  The default implementation uses explicit comparisons and is
221    /// correct for all types.
222    ///
223    /// # Arguments
224    /// - `min` — lower bound (inclusive).
225    /// - `max` — upper bound (inclusive).
226    ///
227    /// # Returns
228    /// `min` if `self < min`, `max` if `self > max`, otherwise `self`.
229    #[inline]
230    #[must_use]
231    fn clamp_to(self, min: Self, max: Self) -> Self {
232        if self < min { min } else if self > max { max } else { self }
233    }
234
235    /// Returns the absolute-value maximum of a slice using a type-specific
236    /// fast path when available, or `None` to signal fallback to the generic
237    /// scalar path.
238    ///
239    /// The default returns `None`.  Types with hardware-specific
240    /// implementations (e.g. `f32` on x86-64 with AVX2) override this.
241    #[doc(hidden)]
242    #[inline]
243    fn avx2_abs_max(_slice: &[Self]) -> Option<Self> { None }
244
245    /// Converts this sample into a byte vector in native-endian order.
246    ///
247    /// Each sample is serialised to its native byte representation using
248    /// the platform's endianness. The returned vector has length
249    /// `Self::BYTES as usize`.
250    ///
251    /// For converting a whole slice at once, prefer
252    /// [`AudioSample::slice_to_bytes`].
253    ///
254    /// # Returns
255    ///
256    /// A `Vec<u8>` of length `Self::BYTES as usize` containing the
257    /// native-endian byte representation of this sample.
258    ///
259    /// ## Examples
260    ///
261    /// ```
262    /// use audio_samples::AudioSample;
263    ///
264    /// let sample: i16 = 256;
265    /// let bytes = sample.to_bytes();
266    /// assert_eq!(bytes.len(), 2);
267    /// ```
268    #[inline]
269    #[must_use]
270    fn to_bytes(self) -> Vec<u8> {
271        self.to_ne_bytes().as_ref().to_vec()
272    }
273
274    /// Converts this sample into a fixed-size byte array in native-endian order.
275    ///
276    /// The const generic parameter `N` must equal the byte size of `Self`
277    /// (e.g. `N = 4` for `f32` or `i32`). Supported widths — 1, 2, 3, 4,
278    /// and 8 — are enforced at compile time via the [`SupportedByteSize`]
279    /// bound.
280    ///
281    /// # Arguments
282    ///
283    /// – `N` (const generic) — the number of bytes in the output array.
284    ///   Must match `Self::BYTES`; mismatches are compile errors.
285    ///
286    /// # Returns
287    ///
288    /// A `[u8; N]` containing the native-endian byte representation of
289    /// this sample.
290    ///
291    /// ## Examples
292    ///
293    /// ```
294    /// use audio_samples::AudioSample;
295    ///
296    /// let sample: i16 = 0x0100_i16;
297    /// let bytes: [u8; 2] = sample.as_bytes::<2>();
298    /// assert_eq!(bytes.len(), 2);
299    /// ```
300    #[inline]
301    fn as_bytes<const N: usize>(&self) -> [u8; N]
302        where SampleByteSize<N>: SupportedByteSize,
303    {
304        let bytes_ref = self.to_ne_bytes();
305        let bytes_slice: &[u8] = bytes_ref.as_ref();
306        let mut result = [0u8; N];
307        result.copy_from_slice(bytes_slice);
308        result
309    }
310
311    #[inline]
312    /// Converts a slice of samples into a byte vector in native-endian order.
313    ///
314    /// Uses [`bytemuck`] to reinterpret the slice as bytes. This avoids
315    /// element-wise copying when the alignment allows it.
316    ///
317    /// # Arguments
318    ///
319    /// – `samples` — a slice of samples to serialise.
320    ///
321    /// # Returns
322    ///
323    /// A `Vec<u8>` of length `samples.len() * Self::BYTES as usize`.
324    ///
325    /// ## Examples
326    ///
327    /// ```
328    /// use audio_samples::AudioSample;
329    ///
330    /// let samples = [1_i16, 2, 3];
331    /// let bytes = i16::slice_to_bytes(&samples);
332    /// assert_eq!(bytes.len(), 6); // 3 samples × 2 bytes each
333    /// ```
334    fn slice_to_bytes(samples: &[Self]) -> Vec<u8> {
335        Vec::from(bytemuck::cast_slice(samples))
336    }
337
338    #[inline]
339    /// Casts this sample value to `f64` without audio-aware scaling.
340    ///
341    /// This is a raw numeric cast equivalent to `self as f64`. No
342    /// normalisation into `[-1.0, 1.0]` is applied. For integer sample
343    /// types, the raw integer magnitude is preserved.
344    ///
345    /// For audio-aware conversion that scales integer samples into the
346    /// floating-point range, use [`ConvertTo::<f64>::convert_to`] instead.
347    ///
348    /// # Returns
349    ///
350    /// The sample value cast to `f64`, preserving the raw numeric magnitude.
351    ///
352    /// ## Examples
353    ///
354    /// ```
355    /// use audio_samples::AudioSample;
356    ///
357    /// // Raw cast: the integer value 32767 becomes 32767.0, not 1.0.
358    /// let sample: i16 = 32767;
359    /// assert_eq!(sample.as_float(), 32767.0_f64);
360    ///
361    /// // For float types the value is unchanged.
362    /// let f: f32 = 0.5;
363    /// assert_eq!(f.as_float(), 0.5_f64);
364    /// ```
365    fn as_float(self) -> f64
366    {
367        self.cast_into()
368    }
369
370    /// Maximum representable amplitude value for this sample type.
371    ///
372    /// For integer types this is the type's integer maximum (e.g. `32767`
373    /// for `i16`, `255` for `u8`). For float types (`f32`, `f64`) this
374    /// is `1.0`.
375    const MAX: Self;
376
377    /// Minimum representable amplitude value for this sample type.
378    ///
379    /// For integer types this is the type's integer minimum (e.g. `-32768`
380    /// for `i16`, `0` for `u8`). For float types this is `-1.0`.
381    const MIN: Self;
382
383    /// Bit depth of this sample type (e.g. `8` for `u8`, `16` for `i16`).
384    const BITS: u8;
385
386    /// Byte width of this sample type, derived as `BITS / 8`.
387    const BYTES: u32 = Self::BITS as u32 / 8;
388
389    /// Human-readable label for this sample type, used for display and
390    /// plotting. Examples: `"u8"`, `"i16"`, `"I24"`, `"f32"`, `"f64"`.
391    const LABEL: &'static str;
392
393    /// Enum variant identifying this sample type at runtime.
394    ///
395    /// Corresponds to a variant of [`SampleType`].
396    const SAMPLE_TYPE: SampleType;
397}
398
399/// Supertrait combining [`AudioSample`] with full bidirectional conversion
400/// support across all standard sample types.
401///
402/// A type satisfies `StandardSample` automatically when it implements
403/// [`AudioSample`] together with [`CastInto<f64>`], [`CastFrom<f64>`], and
404/// [`ConvertFrom`] for every standard sample type (`u8`, `i16`, [`I24`](i24::I24),
405/// `i32`, `f32`, `f64`). No manual implementation is required.
406///
407/// The supported standard sample types are `u8`, `i16`, [`I24`](i24::I24), `i32`,
408/// `f32`, and `f64`. Most audio processing operations in this crate are
409/// generic over `T: StandardSample`.
410///
411/// Prefer this bound over [`AudioSample`] when bidirectional conversions
412/// between sample types are required.
413pub trait StandardSample:
414    AudioSample
415    + CastInto<f64>
416    + CastFrom<f64>
417    + ConvertFrom<Self>
418    + ConvertFrom<u8>
419    + ConvertFrom<i16>
420    + ConvertFrom<I24>
421    + ConvertFrom<i32>
422    + ConvertFrom<f32>
423    + ConvertFrom<f64>
424    + Castable
425{
426}
427
428impl<T> StandardSample for T where
429    T: AudioSample
430        + CastInto<f64>
431        + CastFrom<f64>
432        + ConvertFrom<Self>
433        + ConvertFrom<u8>
434        + ConvertFrom<i16>
435        + ConvertFrom<I24>
436        + ConvertFrom<i32>
437        + ConvertFrom<f32>
438        + ConvertFrom<f64>
439{
440}
441
442/// Trait for converting one sample type to another with audio-aware scaling.
443///
444/// `ConvertTo` performs conversions that are intended for audio sample values rather than raw
445/// numeric casts.
446///
447/// ## Conversion Behavior
448/// - **Integer ↔ Integer**: PCM-style bit-depth scaling (e.g. `i16::MAX` maps to `0x7FFF0000i32`)
449/// - **Integer ↔ Float**: normalized scaling into $[-1.0, 1.0]$ using asymmetric endpoints
450/// - **Float ↔ Integer**: clamp to $[-1.0, 1.0]$, then scale, round, and saturate
451/// - **I24 Special Handling**: conversions treat `I24` as a 24-bit signed PCM integer
452///
453/// ## Example
454/// ```rust
455/// use audio_samples::ConvertTo;
456///
457/// let sample_i16: i16 = 16384; // approximately half-scale
458/// let sample_f32: f32 = sample_i16.convert_to();
459/// assert!((sample_f32 - 0.5).abs() < 1e-4);
460///
461/// let sample_i32: i32 = sample_i16.convert_to();
462/// assert_eq!(sample_i32, 0x4000_0000);
463/// ```
464pub trait ConvertTo<Dst> {
465    /// Converts this sample into `Dst` using audio-aware scaling.
466    ///
467    /// # Returns
468    ///
469    /// The converted sample as `Dst`. Conversion semantics follow the rules
470    /// documented on [`ConvertTo`]: integer-to-float converts to
471    /// `[-1.0, 1.0]`; float-to-integer clamps, scales, and rounds;
472    /// integer-to-integer shifts bit depth with saturation.
473    fn convert_to(self) -> Dst;
474}
475
476/// Audio-aware conversion from a source sample type into `Self`.
477///
478/// This is the blanket-implemented inverse of [`ConvertTo`]. Implementing
479/// `ConvertFrom<Src>` for `Dst` automatically provides `ConvertTo<Dst>`
480/// for `Src` via the blanket implementation.
481///
482/// Conversion semantics are the same as documented on [`ConvertTo`]:
483/// integer ↔ float conversions apply asymmetric normalised scaling;
484/// integer ↔ integer conversions use PCM-style bit-depth shifting with
485/// saturation; `u8` uses mid-scale unsigned PCM conventions.
486pub trait ConvertFrom<Src> {
487    /// Converts a sample of type `Src` into `Self`.
488    ///
489    /// # Arguments
490    ///
491    /// – `source` — the source sample to convert.
492    ///
493    /// # Returns
494    ///
495    /// The converted sample as `Self`.
496    fn convert_from(source: Src) -> Self;
497}
498
499impl<Src, Dst> ConvertTo<Dst> for Src
500where
501    Dst: ConvertFrom<Src>,
502{
503    #[inline]
504    fn convert_to(self) -> Dst {
505        Dst::convert_from(self)
506    }
507}
508
509// Identity
510macro_rules! impl_identity_conversion {
511    ($ty:ty) => {
512        impl ConvertFrom<$ty> for $ty {
513            #[inline]
514            fn convert_from(source: $ty) -> Self {
515                source
516            }
517        }
518    };
519}
520
521// Integer -> Integer, saturating (with bit-shift scaling if needed)
522macro_rules! impl_int_to_int_conversion {
523    ($from:ty, $to:ty) => {
524        impl ConvertFrom<$from> for $to {
525            #[inline]
526            fn convert_from(source: $from) -> Self {
527                let from_bits = <$from>::BITS as i32;
528                let to_bits = <$to>::BITS as i32;
529
530                let v = <i128 as From<$from>>::from(source);
531                let scaled = if from_bits < to_bits {
532                    v << (to_bits - from_bits)
533                } else if from_bits > to_bits {
534                    v >> (from_bits - to_bits)
535                } else {
536                    v
537                };
538
539                let min = <i128 as From<$to>>::from(<$to>::MIN);
540                let max = <i128 as From<$to>>::from(<$to>::MAX);
541
542                if scaled < min {
543                    <$to>::MIN
544                } else if scaled > max {
545                    <$to>::MAX
546                } else {
547                    scaled as $to
548                }
549            }
550        }
551    };
552}
553
554// I24 -> Integer (any standard integer), saturating
555macro_rules! impl_i24_to_int {
556    ($to:ty) => {
557        impl ConvertFrom<I24> for $to {
558            #[inline]
559            fn convert_from(source: I24) -> Self {
560                let to_bits = <$to>::BITS as i32;
561                let v = <i128 as From<i32>>::from(source.to_i32());
562
563                let shift = to_bits - 24;
564                let scaled = if shift >= 0 {
565                    v << shift
566                } else {
567                    v >> (-shift)
568                };
569
570                let min = <i128 as From<$to>>::from(<$to>::MIN);
571                let max = <i128 as From<$to>>::from(<$to>::MAX);
572                if scaled < min {
573                    <$to>::MIN
574                } else if scaled > max {
575                    <$to>::MAX
576                } else {
577                    scaled as $to
578                }
579            }
580        }
581    };
582}
583
584// Integer -> I24, saturating
585macro_rules! impl_int_to_i24 {
586    ($from:ty) => {
587        impl ConvertFrom<$from> for I24 {
588            #[inline]
589            fn convert_from(source: $from) -> Self {
590                let from_bits = <$from>::BITS as i32;
591                let v = <i128 as From<$from>>::from(source);
592
593                let shift = 24 - from_bits;
594                let scaled = if shift >= 0 {
595                    v << shift
596                } else {
597                    v >> (-shift)
598                };
599
600                let min = <i128 as From<i32>>::from(I24::MIN.to_i32());
601                let max = <i128 as From<i32>>::from(I24::MAX.to_i32());
602                let clamped = if scaled < min {
603                    min as i32
604                } else if scaled > max {
605                    max as i32
606                } else {
607                    scaled as i32
608                };
609
610                I24::saturating_from_i32(clamped)
611            }
612        }
613    };
614}
615
616// I24 -> Float (normalised)
617macro_rules! impl_i24_to_float {
618    ($to:ty) => {
619        impl ConvertFrom<I24> for $to {
620            #[inline]
621            #[allow(clippy::cast_lossless)]
622            fn convert_from(source: I24) -> Self {
623                let v = source.to_i32() as $to;
624                let max = I24::MAX.to_i32() as $to;
625                let min = I24::MIN.to_i32() as $to;
626                if v < 0.0 { v / -min } else { v / max }
627            }
628        }
629    };
630}
631
632// Integer -> Float (normalised)
633macro_rules! impl_int_to_float {
634    ($from:ty, $to:ty) => {
635        impl ConvertFrom<$from> for $to {
636            #[inline]
637            fn convert_from(source: $from) -> Self {
638                let v = source;
639                if v < 0 {
640                    (v as $to) / (-(<$from>::MIN as $to))
641                } else {
642                    (v as $to) / (<$from>::MAX as $to)
643                }
644            }
645        }
646    };
647}
648
649// Float -> Integer (clamp + scale + truncating cast)
650//
651// Uses IEEE max/min (f32::max / f32::min) instead of clamp so that NaN
652// inputs produce a defined finite value: max(NaN, -1.0) = -1.0 per IEEE
653// 754-2008 maxNum semantics (returns the non-NaN operand). This lets LLVM
654// prove the intermediate value is finite, eliminating the NaN-handling
655// branches that Rust's saturating `as i32` would otherwise generate.
656// Without those branches LLVM can emit vcvttps2dq + vpackssdw (vectorised
657// f32→i32→i16) instead of the scalar vcvttss2si path.
658//
659// Semantic differences from a fully-IEEE-correct implementation:
660// - NaN input → −MAX (e.g. −32767) instead of 0 or ±MAX.
661// - Uses truncation rather than round-to-nearest (error ≤ 1 LSB, inaudible).
662// - −1.0 maps to −MAX (e.g. −32767 for i16), not MIN (−32768).  The one-
663//   code-unit asymmetry at full negative scale is inaudible in practice.
664// Used for i16 (and similarly small integer types) where $to::MAX is exactly
665// representable in f32/f64 (32767 < 2^23). `to_int_unchecked` removes the
666// NaN/overflow branches that `as i32` would otherwise emit, letting LLVM
667// vectorise the loop with vcvttps2dq + vpackssdw.
668macro_rules! impl_float_to_int {
669    ($from:ty, $to:ty) => {
670        impl ConvertFrom<$from> for $to {
671            #[inline]
672            fn convert_from(source: $from) -> Self {
673                // IEEE max/min: NaN input → -1.0 (non-NaN). v ∈ [-1.0, 1.0].
674                let v = source.max(-1.0).min(1.0);
675                // scaled ∈ [−MAX, MAX]. For i16, MAX = 32767 which is exactly
676                // representable in f32, so scaled is strictly within i32 range.
677                let scaled = v * (<$to>::MAX as $from);
678                // SAFETY: scaled is finite and |scaled| ≤ $to::MAX ≤ 32767,
679                // which fits comfortably in i32. No NaN/overflow is possible.
680                let as_i32: i32 = unsafe { scaled.to_int_unchecked() };
681                as_i32 as $to
682            }
683        }
684    };
685}
686
687// Used for i32 where i32::MAX = 2147483647 rounds up to 2147483648.0 in f32
688// (not exactly representable). Using `to_int_unchecked` there would be UB for
689// source = 1.0, so we use the saturating `as $to` cast instead.
690macro_rules! impl_float_to_large_int {
691    ($from:ty, $to:ty) => {
692        impl ConvertFrom<$from> for $to {
693            #[inline]
694            fn convert_from(source: $from) -> Self {
695                let v = source.max(-1.0).min(1.0);
696                let scaled = v * (<$to>::MAX as $from);
697                // Saturating cast handles the case where $to::MAX as $from
698                // rounds up (e.g. i32::MAX → 2147483648.0f32).
699                scaled.clamp(<$to>::MIN as $from, <$to>::MAX as $from) as $to
700            }
701        }
702    };
703}
704
705// Float -> I24 (clamp + scale + round + saturating)
706macro_rules! impl_float_to_i24 {
707    ($from:ty) => {
708        impl ConvertFrom<$from> for I24 {
709            #[inline]
710            fn convert_from(source: $from) -> Self {
711                let v = source.clamp(-1.0, 1.0);
712                let scaled = if v < 0.0 {
713                    v * (-(I24::MIN.to_i32() as $from))
714                } else {
715                    v * (I24::MAX.to_i32() as $from)
716                };
717                let rounded = scaled.round();
718                let clamped = if rounded < (I24::MIN.to_i32() as $from) {
719                    I24::MIN.to_i32()
720                } else if rounded > (I24::MAX.to_i32() as $from) {
721                    I24::MAX.to_i32()
722                } else {
723                    rounded as i32
724                };
725                I24::saturating_from_i32(clamped)
726            }
727        }
728    };
729}
730
731// Float -> Float
732macro_rules! impl_float_to_float {
733    ($from:ty, $to:ty) => {
734        impl ConvertFrom<$from> for $to {
735            #[inline]
736            fn convert_from(source: $from) -> Self {
737                source as $to
738            }
739        }
740    };
741}
742
743// ========================
744// u8 <-> Signed Integer / I24 / Float
745// ========================
746//
747// Conventions:
748// - u8 is unsigned PCM with 128 as zero.
749// - Centred value c = (u8 as i32) - 128  in [-128, 127].
750// - Asymmetric scaling:
751//     negative side uses divisor 128 (so 0 maps to -1.0 exactly)
752//     positive side uses divisor 127 (so 255 maps to +1.0 exactly)
753//
754// - For u8 -> signed-int/I24: scale c into destination integer range using the
755//   same asymmetric idea (negative uses abs(min), positive uses max).
756// - For signed-int/I24 -> u8: invert scaling, round-to-nearest, clamp to [0,255].
757//
758// Notes:
759// - All intermediate arithmetic is done in i128 to avoid overflow for i32::MIN abs.
760// - Rounding is symmetric "nearest" for integer division (positive numerators only).
761
762#[inline]
763const fn div_round_nearest_i128(num: i128, den: i128) -> i128 {
764    // Assumes den > 0 and num >= 0
765    (num + (den / 2)) / den
766}
767
768// u8 -> signed integer (i16/i32), saturating + asymmetric scaling
769macro_rules! impl_u8_to_int {
770    ($to:ty) => {
771        impl ConvertFrom<u8> for $to {
772            #[inline]
773            #[allow(clippy::cast_lossless)]
774            fn convert_from(source: u8) -> Self {
775                let c: i128 = (source as i128) - 128; // [-128, 127]
776
777                let scaled: i128 = if c < 0 {
778                    // Map -128 -> MIN exactly
779                    // c is negative, abs range is 128
780                    c * (-(<$to>::MIN as i128)) / 128
781                } else {
782                    // Map +127 -> MAX exactly
783                    c * (<$to>::MAX as i128) / 127
784                };
785
786                // Should already be in-range, but keep the same saturating style.
787                let min = <$to>::MIN as i128;
788                let max = <$to>::MAX as i128;
789                if scaled < min {
790                    <$to>::MIN
791                } else if scaled > max {
792                    <$to>::MAX
793                } else {
794                    scaled as $to
795                }
796            }
797        }
798    };
799}
800
801// signed integer -> u8, saturating + asymmetric scaling + rounding
802macro_rules! impl_int_to_u8 {
803    ($from:ty) => {
804        impl ConvertFrom<$from> for u8 {
805            #[inline]
806            #[allow(clippy::cast_lossless)]
807            fn convert_from(source: $from) -> Self {
808                let v: i128 = source as i128;
809
810                let out_i128: i128 = if v < 0 {
811                    // v in [MIN, -1]
812                    let mag = (-v) as i128; // positive
813                    let den = (-(<$from>::MIN as i128)); // abs(min), e.g. 32768 for i16
814                    let scaled = div_round_nearest_i128(mag * 128, den); // 0..128
815                    128 - scaled
816                } else {
817                    // v in [0, MAX]
818                    let den = (<$from>::MAX as i128);
819                    let scaled = div_round_nearest_i128(v * 127, den); // 0..127
820                    128 + scaled
821                };
822
823                // Clamp to [0, 255]
824                if out_i128 < 0 {
825                    0
826                } else if out_i128 > 255 {
827                    255
828                } else {
829                    out_i128 as u8
830                }
831            }
832        }
833    };
834}
835
836// u8 -> I24 (asymmetric scaling), saturating
837macro_rules! impl_u8_to_i24 {
838    () => {
839        impl ConvertFrom<u8> for I24 {
840            #[inline]
841            fn convert_from(source: u8) -> Self {
842                let c: i128 = (<i128 as From<u8>>::from(source)) - 128; // [-128, 127]
843                let min = <i128 as From<i32>>::from(I24::MIN.to_i32());
844                let max = <i128 as From<i32>>::from(I24::MAX.to_i32());
845
846                let scaled: i128 = if c < 0 {
847                    c * (-min) / 128
848                } else {
849                    c * max / 127
850                };
851
852                // Clamp into i24 range, then saturating construct.
853                let clamped: i32 = if scaled < min {
854                    min as i32
855                } else if scaled > max {
856                    max as i32
857                } else {
858                    scaled as i32
859                };
860
861                I24::saturating_from_i32(clamped)
862            }
863        }
864    };
865}
866
867// I24 -> u8 (invert asymmetric scaling), saturating + rounding
868macro_rules! impl_i24_to_u8 {
869    () => {
870        impl ConvertFrom<I24> for u8 {
871            #[inline]
872            fn convert_from(source: I24) -> Self {
873                let v: i128 = <i128 as From<i32>>::from(source.to_i32());
874                let min = <i128 as From<i32>>::from(I24::MIN.to_i32()); // negative
875                let max = <i128 as From<i32>>::from(I24::MAX.to_i32()); // positive
876
877                let out_i128: i128 = if v < 0 {
878                    let mag = <i128 as From<i128>>::from(-v);
879                    let den = <i128 as From<i128>>::from(-min); // abs(min) = 8388608
880                    let scaled = div_round_nearest_i128(mag * 128, den); // 0..128
881                    128 - scaled
882                } else {
883                    let den = <i128 as From<i128>>::from(max); // 8388607
884                    let scaled = div_round_nearest_i128(v * 127, den); // 0..127
885                    128 + scaled
886                };
887
888                if out_i128 < 0 {
889                    0
890                } else if out_i128 > 255 {
891                    255
892                } else {
893                    out_i128 as u8
894                }
895            }
896        }
897    };
898}
899
900// u8 -> float (normalised, asymmetric endpoints)
901macro_rules! impl_u8_to_float {
902    ($to:ty) => {
903        impl ConvertFrom<u8> for $to {
904            #[inline]
905            fn convert_from(source: u8) -> Self {
906                let c: i32 = (<i32 as From<u8>>::from(source)) - 128; // [-128, 127]
907                let v = c as $to;
908                if c < 0 {
909                    v / (128.0 as $to)
910                } else {
911                    v / (127.0 as $to)
912                }
913            }
914        }
915    };
916}
917
918// float -> u8 (clamp + asymmetric scale + round + saturate)
919macro_rules! impl_float_to_u8 {
920    ($from:ty) => {
921        impl ConvertFrom<$from> for u8 {
922            #[inline]
923            fn convert_from(source: $from) -> Self {
924                let v = source.clamp(-1.0, 1.0);
925
926                // Convert float to centred integer c in [-128, 127] with asymmetric scaling.
927                // Negative maps to [-128, 0], positive maps to [0, 127].
928                let c: i128 = if v < 0.0 {
929                    // -1.0 -> -128 exactly
930                    (v * (128.0 as $from)).round() as i128
931                } else {
932                    // +1.0 -> +127 exactly
933                    (v * (127.0 as $from)).round() as i128
934                };
935
936                let out = 128i128 + c;
937
938                if out < 0 {
939                    0
940                } else if out > 255 {
941                    255
942                } else {
943                    out as u8
944                }
945            }
946        }
947    };
948}
949
950// ========================
951// u8 Identity
952// ========================
953
954impl_identity_conversion!(u8);
955
956// ========================
957// u8 <-> Integer
958// ========================
959
960impl_u8_to_int!(i16);
961impl_u8_to_int!(i32);
962
963impl_int_to_u8!(i16);
964impl_int_to_u8!(i32);
965
966// ========================
967// u8 <-> I24
968// ========================
969
970impl_u8_to_i24!();
971impl_i24_to_u8!();
972
973// ========================
974// u8 <-> Float
975// ========================
976
977impl_u8_to_float!(f32);
978impl_u8_to_float!(f64);
979
980impl_float_to_u8!(f32);
981impl_float_to_u8!(f64);
982
983// ========================
984// Identity
985// ========================
986
987impl_identity_conversion!(i16);
988impl_identity_conversion!(I24);
989impl_identity_conversion!(i32);
990impl_identity_conversion!(f32);
991impl_identity_conversion!(f64);
992
993// ========================
994// Integer <-> Integer (Saturating, No Normalisation)
995// ========================
996
997impl_int_to_int_conversion!(i16, i32);
998impl_int_to_int_conversion!(i32, i16);
999
1000// ========================
1001// I24 <-> Integer
1002// ========================
1003
1004impl_i24_to_int!(i16);
1005impl_i24_to_int!(i32);
1006
1007impl_int_to_i24!(i16);
1008impl_int_to_i24!(i32);
1009
1010// ========================
1011// Integer -> Float (Normalised +- 1.0)
1012// ========================
1013
1014impl_int_to_float!(i16, f32);
1015impl_int_to_float!(i16, f64);
1016
1017impl_int_to_float!(i32, f32);
1018impl_int_to_float!(i32, f64);
1019
1020// ========================
1021// I24 -> Float (Normalised +- 1.0)
1022// ========================
1023
1024impl_i24_to_float!(f32);
1025impl_i24_to_float!(f64);
1026
1027// ========================
1028// Float -> Integer (Clamped, Rounded, Saturating)
1029// ========================
1030
1031impl_float_to_int!(f32, i16);
1032impl_float_to_int!(f64, i16);
1033
1034impl_float_to_large_int!(f32, i32);
1035impl_float_to_large_int!(f64, i32);
1036
1037// ========================
1038// Float -> I24 (Clamped, Rounded, Saturating)
1039// ========================
1040
1041impl_float_to_i24!(f32);
1042impl_float_to_i24!(f64);
1043
1044// ========================
1045// Float <-> Float
1046// ========================
1047
1048impl_float_to_float!(f32, f64);
1049impl_float_to_float!(f64, f32);
1050
1051// ========================
1052// AVX2 fast path for f32 abs-max scan
1053// ========================
1054
1055/// Computes the maximum absolute value of a contiguous f32 slice using AVX2.
1056///
1057/// Processes 8 f32 samples per iteration using `_mm256_andnot_ps` to mask
1058/// the sign bit (branchless abs) and `_mm256_max_ps` to track the running
1059/// maximum across 8 independent lanes.  A horizontal reduction folds the
1060/// lanes to a single scalar at the end.
1061///
1062/// # Safety
1063/// Caller must ensure that the AVX2 feature is available
1064/// (e.g. via `is_x86_feature_detected!("avx2")`).
1065#[cfg(target_arch = "x86_64")]
1066#[target_feature(enable = "avx2")]
1067unsafe fn abs_max_f32_avx2(slice: &[f32]) -> f32 {
1068    use std::arch::x86_64::{
1069        _mm_cvtss_f32, _mm_max_ps, _mm_max_ss, _mm_movehl_ps, _mm_shuffle_ps, _mm256_andnot_ps,
1070        _mm256_castps256_ps128, _mm256_extractf128_ps, _mm256_loadu_ps, _mm256_max_ps,
1071        _mm256_set1_ps, _mm256_setzero_ps,
1072    };
1073
1074    // Bit-mask that strips the IEEE 754 sign bit from every lane.
1075    let sign_mask = _mm256_set1_ps(-0.0_f32);
1076    // Eight independent max accumulators — no sequential dependency.
1077    let mut max_v = _mm256_setzero_ps();
1078
1079    let chunks = slice.len() / 8;
1080    let ptr = slice.as_ptr();
1081
1082    for i in 0..chunks {
1083        // SAFETY: i * 8 + 7 < slice.len() because i < chunks = slice.len() / 8.
1084        unsafe {
1085            let v = _mm256_loadu_ps(ptr.add(i * 8));
1086            let abs_v = _mm256_andnot_ps(sign_mask, v); // abs via bitmask, no branch
1087            max_v = _mm256_max_ps(max_v, abs_v);
1088        }
1089    }
1090
1091    // Horizontal reduction: fold 8 f32 lanes down to one scalar.
1092    let hi128 = _mm256_extractf128_ps(max_v, 1); // lanes [4..7]
1093    let lo128 = _mm256_castps256_ps128(max_v); // lanes [0..3]
1094    let max128 = _mm_max_ps(lo128, hi128);
1095    let shuf = _mm_movehl_ps(max128, max128);
1096    let max64 = _mm_max_ps(max128, shuf);
1097    let shuf2 = _mm_shuffle_ps(max64, max64, 0x1);
1098    let max32 = _mm_max_ss(max64, shuf2);
1099    let mut result = _mm_cvtss_f32(max32);
1100
1101    // Scalar tail for the remaining < 8 samples.
1102    for &x in &slice[chunks * 8..] {
1103        let ax = x.abs();
1104        if ax > result {
1105            result = ax;
1106        }
1107    }
1108    result
1109}
1110
1111// ========================
1112// AudioSample Implementations
1113// ========================
1114impl AudioSample for u8 {
1115    const MAX: Self = Self::MAX;
1116    const MIN: Self = Self::MIN;
1117    const BITS: Self = 8;
1118    const LABEL: &'static str = "u8";
1119    const SAMPLE_TYPE: SampleType = SampleType::U8;
1120    #[inline]
1121    fn clamp_to(self, min: Self, max: Self) -> Self {
1122        Ord::clamp(self, min, max)
1123    }
1124}
1125
1126impl AudioSample for i16 {
1127    const MAX: Self = Self::MAX;
1128    const MIN: Self = Self::MIN;
1129    const BITS: u8 = 16;
1130    const LABEL: &'static str = "i16";
1131    const SAMPLE_TYPE: SampleType = SampleType::I16;
1132    #[inline]
1133    fn clamp_to(self, min: Self, max: Self) -> Self {
1134        Ord::clamp(self, min, max)
1135    }
1136}
1137
1138impl AudioSample for I24 {
1139    #[inline]
1140    fn slice_to_bytes(samples: &[Self]) -> Vec<u8> {
1141        Self::write_i24s_ne(samples)
1142    }
1143
1144    const MAX: Self = Self::MAX;
1145    const MIN: Self = Self::MIN;
1146    const BITS: u8 = 24;
1147    const LABEL: &'static str = "I24";
1148    const SAMPLE_TYPE: SampleType = SampleType::I24;
1149    #[inline]
1150    fn clamp_to(self, min: Self, max: Self) -> Self {
1151        Ord::clamp(self, min, max)
1152    }
1153}
1154
1155impl AudioSample for i32 {
1156    const MAX: Self = Self::MAX;
1157    const MIN: Self = Self::MIN;
1158    const BITS: u8 = 32;
1159    const LABEL: &'static str = "i32";
1160    const SAMPLE_TYPE: SampleType = SampleType::I32;
1161    #[inline]
1162    fn clamp_to(self, min: Self, max: Self) -> Self {
1163        Ord::clamp(self, min, max)
1164    }
1165}
1166
1167impl AudioSample for f32 {
1168    const MAX: Self = 1.0;
1169    const MIN: Self = -1.0;
1170    const BITS: u8 = 32;
1171    const LABEL: &'static str = "f32";
1172    const SAMPLE_TYPE: SampleType = SampleType::F32;
1173    /// Uses `f32::clamp` — compiles to `VMAXSS`/`VMINSS` (and `VMAXPS`/`VMINPS` in
1174    /// vectorised loops), matching what C achieves with `-ffast-math`.
1175    #[inline]
1176    fn clamp_to(self, min: Self, max: Self) -> Self {
1177        self.clamp(min, max)
1178    }
1179
1180    #[inline]
1181    fn avx2_abs_max(slice: &[Self]) -> Option<Self> {
1182        #[cfg(target_arch = "x86_64")]
1183        if is_x86_feature_detected!("avx2") {
1184            // safety: caller must ensure AVX2 is available, which we check with is_x86_feature_detected.
1185            return Some(unsafe { abs_max_f32_avx2(slice) });
1186        }
1187        None
1188    }
1189}
1190
1191impl AudioSample for f64 {
1192    const MAX: Self = 1.0;
1193    const MIN: Self = -1.0;
1194    const BITS: u8 = 64;
1195    const LABEL: &'static str = "f64";
1196    const SAMPLE_TYPE: SampleType = SampleType::F64;
1197    /// Uses `f64::clamp` — compiles to `VMAXSD`/`VMINSD` in vectorised loops.
1198    #[inline]
1199    fn clamp_to(self, min: Self, max: Self) -> Self {
1200        self.clamp(min, max)
1201    }
1202}
1203
1204// ========================
1205// Generate All Conversions
1206// ========================
1207
1208macro_rules! impl_cast_from {
1209    ($src:ty => [$($dst:ty),+]) => {
1210        $(
1211            impl CastFrom<$src> for $dst {
1212                #[inline]
1213                fn cast_from(value: $src) -> Self {
1214                    value as $dst
1215                }
1216            }
1217        )+
1218    };
1219}
1220
1221impl_cast_from!(u8 => [u8, i16, i32, f32, f64]);
1222impl_cast_from!(i16 => [u8, i16, i32, f32, f64]);
1223impl_cast_from!(i32 => [u8, i16, i32, f32, f64]);
1224impl_cast_from!(f64 => [u8, i16, i32, f32, f64]);
1225impl_cast_from!(f32 => [u8, i16, i32, f32, f64]);
1226
1227/// Macro to implement the `CastFrom` trait for multiple type pairs
1228macro_rules! impl_cast_from_i24 {
1229    // Simple direct casts (no clamping or special logic)
1230    ($src:ty => $dst:ty) => {
1231        impl CastFrom<$src> for $dst {
1232            #[inline]
1233            #[allow(clippy::cast_lossless)]
1234            fn cast_from(value: $src) -> Self {
1235                value as $dst
1236            }
1237        }
1238    };
1239
1240    // Clamped casts for usize -> integer
1241    (clamp_usize $src:ty => $dst:ty, $max:expr) => {
1242        impl CastFrom<$src> for $dst {
1243            #[inline]
1244            fn cast_from(value: $src) -> Self {
1245                if value > $max as $src {
1246                    $max
1247                } else {
1248                    value as $dst
1249                }
1250            }
1251        }
1252    };
1253
1254    // usize -> I24 with clamping and try_from_i32
1255    (usize_to_i24 $src:ty => $dst:ty) => {
1256        impl CastFrom<$src> for $dst {
1257            #[inline]
1258            fn cast_from(value: $src) -> Self {
1259                if value > I24::MAX.to_i32() as $src {
1260                    I24::MAX
1261                } else {
1262                    match I24::try_from_i32(value as i32) {
1263                        Some(x) => x,
1264                        None => I24::MIN,
1265                    }
1266                }
1267            }
1268        }
1269    };
1270
1271    // I24 -> primitive
1272    (i24_to_primitive $src:ty => $dst:ty) => {
1273        impl CastFrom<$src> for $dst {
1274            #[inline]
1275            #[allow(clippy::cast_lossless)]
1276            fn cast_from(value: $src) -> Self {
1277                value.to_i32() as $dst
1278            }
1279        }
1280    };
1281
1282    // primitive -> I24
1283    (primitive_to_i24 $src:ty => $dst:ty) => {
1284        impl CastFrom<$src> for $dst {
1285            #[inline]
1286            #[allow(clippy::cast_lossless)]
1287            fn cast_from(value: $src) -> Self {
1288                I24::try_from_i32(value as i32).unwrap_or(I24::MIN)
1289            }
1290        }
1291    };
1292
1293    // identity
1294    (identity $t:ty) => {
1295        impl CastFrom<$t> for $t {
1296            #[inline]
1297            fn cast_from(value: $t) -> Self {
1298                value
1299            }
1300        }
1301    };
1302}
1303
1304// usize to primitives
1305impl_cast_from_i24!(clamp_usize usize => u8, u8::MAX);
1306impl_cast_from_i24!(clamp_usize usize => i16, i16::MAX);
1307impl_cast_from_i24!(usize_to_i24 usize => I24);
1308impl_cast_from_i24!(clamp_usize usize => i32, i32::MAX);
1309impl_cast_from_i24!(usize => f32);
1310impl_cast_from_i24!(usize => f64);
1311
1312// I24 to primitives
1313impl_cast_from_i24!(i24_to_primitive I24 => u8);
1314impl_cast_from_i24!(i24_to_primitive I24 => i16);
1315impl_cast_from_i24!(identity I24);
1316impl_cast_from_i24!(i24_to_primitive I24 => i32);
1317impl_cast_from_i24!(i24_to_primitive I24 => f32);
1318impl_cast_from_i24!(i24_to_primitive I24 => f64);
1319
1320// primitives to I24
1321impl_cast_from_i24!(primitive_to_i24 u8 => I24);
1322impl_cast_from_i24!(primitive_to_i24 i16 => I24);
1323impl_cast_from_i24!(primitive_to_i24 i32 => I24);
1324impl_cast_from_i24!(primitive_to_i24 f32 => I24);
1325impl_cast_from_i24!(primitive_to_i24 f64 => I24);
1326
1327macro_rules! impl_cast_into {
1328    ($src:ty => [$($dst:ty),+]) => {
1329        $(
1330            impl CastInto<$dst> for $src {
1331                #[inline]
1332                fn cast_into(self) -> $dst {
1333                    <$dst>::cast_from(self)
1334                }
1335            }
1336        )+
1337    };
1338}
1339impl_cast_into!(u8 => [u8, i16, i32, f32, f64]);
1340impl_cast_into!(i16 => [u8, i16, i32, f32, f64]);
1341impl_cast_into!(i32 => [u8, i16, i32, f32, f64]);
1342impl_cast_into!(f64 => [u8, i16, i32, f32, f64]);
1343impl_cast_into!(f32 => [u8, i16, i32, f32, f64]);
1344
1345/// Macro to implement the `CastInto` trait for multiple type pairs
1346macro_rules! impl_cast_into_i24 {
1347    // I24 -> primitive (via to_i32)
1348    (i24_to_primitive $src:ty => $dst:ty) => {
1349        impl CastInto<$dst> for $src {
1350            #[inline]
1351            fn cast_into(self) -> $dst {
1352                self.to_i32() as $dst
1353            }
1354        }
1355    };
1356
1357    // primitive -> I24 (via CastFrom)
1358    (primitive_to_i24 $src:ty => $dst:ty) => {
1359        impl CastInto<$dst> for $src {
1360            #[inline]
1361            fn cast_into(self) -> $dst {
1362                <$dst as CastFrom<$src>>::cast_from(self)
1363            }
1364        }
1365    };
1366
1367    // identity
1368    (identity $t:ty) => {
1369        impl CastInto<$t> for $t {
1370            #[inline]
1371            fn cast_into(self) -> $t {
1372                self
1373            }
1374        }
1375    };
1376}
1377// I24 to primitives
1378
1379impl_cast_into_i24!(i24_to_primitive I24 => u8);
1380impl_cast_into_i24!(i24_to_primitive I24 => i16);
1381impl_cast_into_i24!(identity I24);
1382impl_cast_into_i24!(i24_to_primitive I24 => i32);
1383impl_cast_into_i24!(i24_to_primitive I24 => f32);
1384impl_cast_into_i24!(i24_to_primitive I24 => f64);
1385
1386// primitives to I24
1387impl_cast_into_i24!(primitive_to_i24 u8 => I24);
1388impl_cast_into_i24!(primitive_to_i24 i16 => I24);
1389impl_cast_into_i24!(primitive_to_i24 i32 => I24);
1390impl_cast_into_i24!(primitive_to_i24 f32 => I24);
1391impl_cast_into_i24!(primitive_to_i24 f64 => I24);
1392
1393/// Audio sample conversion and casting operations for [`AudioSamples`].
1394///
1395/// This trait defines the public conversion surface for transforming an
1396/// [`AudioSamples`] value from one sample representation to another.
1397///
1398/// ## Purpose
1399///
1400/// Audio data is commonly represented using both integer PCM formats and
1401/// floating-point formats. This trait provides two explicit conversion modes:
1402///
1403/// - *Audio-aware conversion* for interpreting numeric values as audio samples,
1404///   applying the appropriate scaling and clamping when moving between integer
1405///   and floating-point representations.
1406/// - *Raw numeric casting* for transforming values using standard numeric rules
1407///   without audio-specific scaling.
1408///
1409/// The two modes are intentionally distinct and must be selected explicitly by
1410/// the caller.
1411///
1412/// ## Behavioural Guarantees
1413///
1414/// - All operations return a new owned [`AudioSamples`] value.
1415/// - Sample rate, channel structure, and sample ordering are preserved.
1416/// - The source audio is not modified.
1417/// - Conversions are total and do not return `Result`.
1418///
1419/// When converting from floating-point to fixed-width integer formats, values
1420/// outside the representable range are clamped.
1421///
1422/// ## Assumptions
1423///
1424/// The conversion behaviour is defined by the conversion traits implemented for
1425/// the involved sample types. This trait is implemented for `AudioSamples<T>`
1426/// where those conversions are available.
1427pub trait AudioTypeConversion: Sized {
1428    /// The source sample type of the audio being converted.
1429    type Sample: StandardSample;
1430
1431    /// Converts the audio to a different sample type using audio-aware scaling.
1432    ///
1433    /// Performs an audio-aware conversion from `Self::Sample` to `O`. The
1434    /// amplitude meaning of each sample is preserved: integer-to-float
1435    /// conversions normalise into `[-1.0, 1.0]`; float-to-integer conversions
1436    /// clamp, scale, and round; integer-to-integer conversions shift bit depth
1437    /// with saturation. For `u8`, the mid-scale unsigned PCM convention is
1438    /// applied.
1439    ///
1440    /// The source audio is not modified; a new owned value is returned.
1441    ///
1442    /// # Returns
1443    ///
1444    /// A new owned [`AudioSamples<'static, O>`] with samples converted from
1445    /// `Self::Sample` to `O`. Sample rate, channel count, and temporal
1446    /// ordering are preserved.
1447    ///
1448    /// ## Examples
1449    ///
1450    /// ```
1451    /// use audio_samples::{AudioSamples, sample_rate, AudioTypeConversion};
1452    /// use ndarray::array;
1453    ///
1454    /// let audio = AudioSamples::new_mono(
1455    ///     array![0i16, i16::MAX],
1456    ///     sample_rate!(44100),
1457    /// ).unwrap();
1458    ///
1459    /// let float_audio = audio.to_format::<f32>();
1460    /// let samples = float_audio.as_mono().unwrap();
1461    /// assert_eq!(samples[0], 0.0_f32);
1462    /// assert!((samples[1] - 1.0_f32).abs() < 1e-4);
1463    /// ```
1464    fn to_format<O>(&self) -> AudioSamples<'static, O>
1465    where
1466        Self::Sample: ConvertTo<O> + ConvertFrom<O>,
1467        O: StandardSample;
1468
1469    /// Converts the audio to a different sample type, consuming the source.
1470    ///
1471    /// This is the consuming counterpart to [`AudioTypeConversion::to_format`].
1472    /// It performs the same audio-aware conversion, but takes ownership of the
1473    /// input value. Prefer this method over `to_format` when the source is no
1474    /// longer needed, to avoid an unnecessary clone.
1475    ///
1476    /// # Returns
1477    ///
1478    /// A new owned [`AudioSamples<'static, O>`] with samples converted from
1479    /// `Self::Sample` to `O`. Sample rate, channel count, and temporal
1480    /// ordering are preserved.
1481    ///
1482    /// ## Examples
1483    ///
1484    /// ```
1485    /// use audio_samples::{AudioSamples, AudioTypeConversion, sample_rate};
1486    /// use ndarray::array;
1487    ///
1488    /// let audio = AudioSamples::new_mono(array![0i16, i16::MAX], sample_rate!(44100)).unwrap();
1489    /// let audio_f32: AudioSamples<'static, f32> = audio.to_type::<f32>();
1490    /// assert_eq!(audio_f32[0], 0.0);
1491    /// assert!((audio_f32[1] - 1.0).abs() < 1e-4);
1492    /// ```
1493    fn to_type<O>(self) -> AudioSamples<'static, O>
1494    where
1495        Self::Sample: ConvertTo<O> + ConvertFrom<O>,
1496        O: StandardSample;
1497
1498    /// Casts the audio to a different sample type without audio-aware scaling.
1499    ///
1500    /// Performs a raw numeric cast from `Self::Sample` to `O`, equivalent to
1501    /// an `as` cast applied element-wise. No normalisation, clamping, or
1502    /// bit-depth scaling is applied. Integer values are preserved as their
1503    /// raw numeric magnitude.
1504    ///
1505    /// Use [`AudioTypeConversion::to_format`] when amplitude meaning must be
1506    /// preserved across sample types.
1507    ///
1508    /// # Returns
1509    ///
1510    /// A new owned [`AudioSamples<'static, O>`] containing raw-cast samples.
1511    /// The source audio is unchanged. Sample rate, channel count, and temporal
1512    /// ordering are preserved.
1513    ///
1514    /// ## Examples
1515    ///
1516    /// ```
1517    /// use audio_samples::{AudioSamples, AudioTypeConversion, sample_rate};
1518    /// use ndarray::array;
1519    ///
1520    /// // Raw cast: i16 value 1000 becomes f32 value 1000.0, not 0.030518...
1521    /// let audio = AudioSamples::new_mono(array![1000i16, -500], sample_rate!(44100)).unwrap();
1522    /// let raw = audio.cast_as::<f32>();
1523    /// assert_eq!(raw[0], 1000.0_f32);
1524    /// assert_eq!(raw[1], -500.0_f32);
1525    /// ```
1526    fn cast_as<O>(&self) -> AudioSamples<'static, O>
1527    where
1528        Self::Sample: CastInto<O> + ConvertTo<O>,
1529        O: StandardSample;
1530
1531    /// Casts the audio to a different sample type without audio-aware scaling,
1532    /// consuming the source.
1533    ///
1534    /// This is the consuming counterpart to [`AudioTypeConversion::cast_as`].
1535    /// It performs the same raw numeric cast but takes ownership of the input.
1536    ///
1537    /// # Returns
1538    ///
1539    /// A new owned [`AudioSamples<'static, O>`] containing raw-cast samples.
1540    /// Sample rate, channel count, and ordering are preserved.
1541    ///
1542    /// ## Examples
1543    ///
1544    /// ```
1545    /// use audio_samples::{AudioSamples, AudioTypeConversion, sample_rate};
1546    /// use ndarray::array;
1547    ///
1548    /// let audio = AudioSamples::new_mono(array![255u8, 128u8, 0u8], sample_rate!(44100)).unwrap();
1549    /// let raw: AudioSamples<'static, i16> = audio.cast_to::<i16>();
1550    /// assert_eq!(raw[0], 255);
1551    /// assert_eq!(raw[1], 128);
1552    /// assert_eq!(raw[2], 0);
1553    /// ```
1554    fn cast_to<O>(self) -> AudioSamples<'static, O>
1555    where
1556        Self::Sample: CastInto<O> + ConvertTo<O>,
1557        O: StandardSample;
1558
1559    /// Casts the audio to `f64` without audio-aware scaling.
1560    ///
1561    /// Each sample value is raw-cast to `f64` as a number, preserving its
1562    /// numeric magnitude without normalisation. For integer sample types,
1563    /// the integer value (e.g. `32767`) is cast directly to `f64`, not
1564    /// scaled to `1.0`.
1565    ///
1566    /// For audio-aware conversion into `[-1.0, 1.0]`, use
1567    /// [`AudioTypeConversion::as_f64`] instead.
1568    ///
1569    /// # Returns
1570    ///
1571    /// A new owned [`AudioSamples<'static, f64>`] with raw-cast sample values.
1572    #[inline]
1573    fn cast_as_f64(&self) -> AudioSamples<'static, f64> {
1574        self.cast_as::<f64>()
1575    }
1576
1577    /// Converts the audio to `f64` using audio-aware scaling.
1578    ///
1579    /// Integer sample values are normalised into `[-1.0, 1.0]` according to
1580    /// the [`ConvertTo`] rules for the source type. For `u8` audio, value
1581    /// `128` maps to `0.0`. For float sources the values are widened unchanged.
1582    ///
1583    /// # Returns
1584    ///
1585    /// A new owned [`AudioSamples<'static, f64>`] with normalised sample values.
1586    #[inline]
1587    fn as_float(&self) -> AudioSamples<'static, f64> {
1588        self.to_format::<f64>()
1589    }
1590
1591    /// Converts the audio to `f64` using audio-aware scaling.
1592    ///
1593    /// Equivalent to [`AudioTypeConversion::as_float`]. Integer sample values
1594    /// are normalised into `[-1.0, 1.0]`.
1595    ///
1596    /// # Returns
1597    ///
1598    /// A new owned [`AudioSamples<'static, f64>`] with normalised sample values.
1599    #[inline]
1600    fn as_f64(&self) -> AudioSamples<'static, f64> {
1601        self.to_format::<f64>()
1602    }
1603
1604    /// Converts the audio to `f32` using audio-aware scaling.
1605    ///
1606    /// Integer sample values are normalised into `[-1.0, 1.0]`. For `u8`
1607    /// audio, value `128` maps to `0.0`.
1608    ///
1609    /// # Returns
1610    ///
1611    /// A new owned [`AudioSamples<'static, f32>`] with normalised sample values.
1612    #[inline]
1613    fn as_f32(&self) -> AudioSamples<'static, f32> {
1614        self.to_format::<f32>()
1615    }
1616
1617    /// Converts the audio to 32-bit signed integer PCM using audio-aware
1618    /// scaling.
1619    ///
1620    /// Float samples in `[-1.0, 1.0]` are scaled and rounded to the `i32`
1621    /// range with saturation. For `u8` audio, mid-scale value `128` maps to
1622    /// `0`.
1623    ///
1624    /// # Returns
1625    ///
1626    /// A new owned [`AudioSamples<'static, i32>`] in signed 32-bit PCM format.
1627    #[inline]
1628    fn as_i32(&self) -> AudioSamples<'static, i32> {
1629        self.to_format::<i32>()
1630    }
1631
1632    /// Converts the audio to 16-bit signed integer PCM using audio-aware
1633    /// scaling.
1634    ///
1635    /// Float samples in `[-1.0, 1.0]` are scaled and rounded to the `i16`
1636    /// range with saturation. For `u8` audio, mid-scale value `128` maps to
1637    /// `0`.
1638    ///
1639    /// # Returns
1640    ///
1641    /// A new owned [`AudioSamples<'static, i16>`] in signed 16-bit PCM format.
1642    #[inline]
1643    fn as_i16(&self) -> AudioSamples<'static, i16> {
1644        self.to_format::<i16>()
1645    }
1646
1647    /// Converts the audio to 24-bit signed integer PCM using audio-aware
1648    /// scaling.
1649    ///
1650    /// Float samples in `[-1.0, 1.0]` are scaled and rounded to the [`I24`](i24::I24)
1651    /// range with saturation. For `u8` audio, mid-scale value `128` maps to
1652    /// `0`.
1653    ///
1654    /// # Returns
1655    ///
1656    /// A new owned [`AudioSamples<'static, I24>`] in signed 24-bit PCM format.
1657    #[inline]
1658    fn as_i24(&self) -> AudioSamples<'static, I24> {
1659        self.to_format::<I24>()
1660    }
1661
1662    /// Converts the audio to 8-bit unsigned PCM format using audio-aware
1663    /// scaling.
1664    ///
1665    /// Float samples in `[-1.0, 1.0]` and signed integer samples are scaled
1666    /// to the `u8` range with mid-scale silence at `128`. Negative full-scale
1667    /// maps to `0`; positive full-scale maps to `255`.
1668    ///
1669    /// # Returns
1670    ///
1671    /// A new owned [`AudioSamples<'static, u8>`] in unsigned 8-bit PCM format.
1672    #[inline]
1673    fn as_u8(&self) -> AudioSamples<'static, u8> {
1674        self.to_format::<u8>()
1675    }
1676}
1677
1678#[cfg(test)]
1679mod conversion_tests {
1680    use super::*;
1681
1682    use i24::i24;
1683
1684    macro_rules! assert_approx_eq {
1685        ($left:expr, $right:expr, $tolerance:expr) => {
1686            assert!(
1687                ($left - $right).abs() < $tolerance,
1688                "assertion failed: `{} ≈ {}` (tolerance: {})",
1689                $left,
1690                $right,
1691                $tolerance
1692            );
1693        };
1694    }
1695
1696    #[test]
1697    fn u8_tests() {
1698        let zero: u8 = 0;
1699        let mid: u8 = 128;
1700        let max: u8 = 255;
1701        let neg_one_f32: f32 = -1.0;
1702        let zero_f32: f32 = 0.0;
1703        let one_f32: f32 = 1.0;
1704
1705        let zero_to_neg_one: f32 = zero.convert_to();
1706        let mid_to_zero: f32 = mid.convert_to();
1707        let max_to_one: f32 = max.convert_to();
1708        assert_approx_eq!(zero_to_neg_one as f64, -1.0, 1e-10);
1709        assert_approx_eq!(mid_to_zero as f64, 0.0, 1e-10);
1710        assert_approx_eq!(max_to_one as f64, 1.0, 1e-10);
1711
1712        let neg_one_to_u8: u8 = neg_one_f32.convert_to();
1713        let zero_to_u8: u8 = zero_f32.convert_to();
1714        let one_to_u8: u8 = one_f32.convert_to();
1715
1716        assert_eq!(neg_one_to_u8, 0);
1717        assert_eq!(zero_to_u8, 128);
1718        assert_eq!(one_to_u8, 255);
1719    }
1720
1721    // Edge cases for i16 conversions
1722    #[test]
1723    fn i16_edge_cases() {
1724        // Test minimum value
1725        let min_i16: i16 = i16::MIN;
1726        let min_i16_to_f32: f32 = min_i16.convert_to();
1727        // Use higher epsilon for floating point comparison
1728        assert_approx_eq!(min_i16_to_f32 as f64, -1.0, 1e-5);
1729
1730        let min_i16_to_i32: i32 = min_i16.convert_to();
1731        assert_eq!(min_i16_to_i32, i32::MIN);
1732
1733        let min_i16_to_i24: I24 = min_i16.convert_to();
1734        let expected_i24_min = i24!(i32::MIN >> 8);
1735        assert_eq!(min_i16_to_i24.to_i32(), expected_i24_min.to_i32());
1736
1737        // Test maximum value
1738        let max_i16: i16 = i16::MAX;
1739        let max_i16_to_f32: f32 = max_i16.convert_to();
1740        assert_approx_eq!(max_i16_to_f32 as f64, 1.0, 1e-4);
1741
1742        let max_i16_to_i32: i32 = max_i16.convert_to();
1743        assert_eq!(max_i16_to_i32, 0x7FFF0000);
1744
1745        // Test zero
1746        let zero_i16: i16 = 0;
1747        let zero_i16_to_f32: f32 = zero_i16.convert_to();
1748        assert_approx_eq!(zero_i16_to_f32 as f64, 0.0, 1e-10);
1749
1750        let zero_i16_to_i32: i32 = zero_i16.convert_to();
1751        assert_eq!(zero_i16_to_i32, 0);
1752
1753        let zero_i16_to_i24: I24 = zero_i16.convert_to();
1754        assert_eq!(zero_i16_to_i24.to_i32(), 0);
1755
1756        // Test mid-range positive
1757        let half_max_i16: i16 = i16::MAX / 2;
1758        let half_max_i16_to_f32: f32 = half_max_i16.convert_to();
1759        // Use higher epsilon for floating point comparison of half values
1760        assert_approx_eq!(half_max_i16_to_f32 as f64, 0.5, 1e-4);
1761
1762        let half_max_i16_to_i32: i32 = half_max_i16.convert_to();
1763        assert_eq!(half_max_i16_to_i32, 0x3FFF0000);
1764
1765        // Test mid-range negative
1766        let half_min_i16: i16 = i16::MIN / 2;
1767        let half_min_i16_to_f32: f32 = half_min_i16.convert_to();
1768        assert_approx_eq!(half_min_i16_to_f32 as f64, -0.5, 1e-4);
1769
1770        // let half_min_i16_to_i32: i32 = half_min_i16.convert_to();
1771        // assert_eq!(half_min_i16_to_i32, 0xC0010000); // i16::MIN/2 == -16384
1772    }
1773
1774    // Edge cases for i32 conversions
1775    #[test]
1776    fn i32_edge_cases() {
1777        // Test minimum value
1778        let min_i32: i32 = i32::MIN;
1779        let min_i32_to_f32: f32 = min_i32.convert_to();
1780        assert_approx_eq!(min_i32_to_f32 as f64, -1.0, 1e-6);
1781
1782        let min_i32_to_f64: f64 = min_i32.convert_to();
1783        assert_approx_eq!(min_i32_to_f64, -1.0, 1e-12);
1784
1785        let min_i32_to_i16: i16 = min_i32.convert_to();
1786        assert_eq!(min_i32_to_i16, i16::MIN);
1787
1788        // Test maximum value
1789        let max_i32: i32 = i32::MAX;
1790        let max_i32_to_f32: f32 = max_i32.convert_to();
1791        assert_approx_eq!(max_i32_to_f32 as f64, 1.0, 1e-6);
1792
1793        let max_i32_to_f64: f64 = max_i32.convert_to();
1794        assert_approx_eq!(max_i32_to_f64, 1.0, 1e-12);
1795
1796        let max_i32_to_i16: i16 = max_i32.convert_to();
1797        assert_eq!(max_i32_to_i16, i16::MAX);
1798
1799        // Test zero
1800        let zero_i32: i32 = 0;
1801        let zero_i32_to_f32: f32 = zero_i32.convert_to();
1802        assert_approx_eq!(zero_i32_to_f32 as f64, 0.0, 1e-10);
1803
1804        let zero_i32_to_f64: f64 = zero_i32.convert_to();
1805        assert_approx_eq!(zero_i32_to_f64, 0.0, 1e-12);
1806
1807        let zero_i32_to_i16: i16 = zero_i32.convert_to();
1808        assert_eq!(zero_i32_to_i16, 0);
1809
1810        // Test quarter-range values
1811        let quarter_max_i32: i32 = i32::MAX / 4;
1812        let quarter_max_i32_to_f32: f32 = quarter_max_i32.convert_to();
1813        assert_approx_eq!(quarter_max_i32_to_f32 as f64, 0.25, 1e-6);
1814
1815        let quarter_min_i32: i32 = i32::MIN / 4;
1816        let quarter_min_i32_to_f32: f32 = quarter_min_i32.convert_to();
1817        assert_approx_eq!(quarter_min_i32_to_f32 as f64, -0.25, 1e-6);
1818    }
1819
1820    // Edge cases for f32 conversions
1821    #[test]
1822    fn f32_edge_cases() {
1823        // Test -1.0 (minimum valid value)
1824        let min_f32: f32 = -1.0;
1825        let min_f32_to_i16: i16 = min_f32.convert_to();
1826        // For exact -1.0, we can get -32767 due to rounding in the implementation
1827        // This is acceptable since it's only 1 bit off from the true min
1828        assert!(
1829            min_f32_to_i16 == i16::MIN || min_f32_to_i16 == -32767,
1830            "Expected either -32768 or -32767, got {}",
1831            min_f32_to_i16
1832        );
1833
1834        let min_f32_to_i32: i32 = min_f32.convert_to();
1835        assert!(
1836            min_f32_to_i32 == i32::MIN || min_f32_to_i32 == -2147483647,
1837            "Expected either i32::MIN or -2147483647, got {}",
1838            min_f32_to_i32
1839        );
1840
1841        let min_f32_to_i24: I24 = min_f32.convert_to();
1842        let expected_i24 = I24::MIN;
1843        let diff = (min_f32_to_i24.to_i32() - expected_i24.to_i32()).abs();
1844        assert!(diff <= 1, "I24 values differ by more than 1, {}", diff);
1845
1846        // Test 1.0 (maximum valid value)
1847        let max_f32: f32 = 1.0;
1848        let max_f32_to_i16: i16 = max_f32.convert_to();
1849        println!("DEBUG: f32 -> i16 conversion for 1.0");
1850        println!(
1851            "Input: {}, Output: {}, Expected: {}",
1852            max_f32,
1853            max_f32_to_i16,
1854            i16::MAX
1855        );
1856        assert_eq!(max_f32_to_i16, i16::MAX);
1857
1858        let max_f32_to_i32: i32 = max_f32.convert_to();
1859        println!("DEBUG: f32 -> i32 conversion for 1.0");
1860        println!(
1861            "Input: {}, Output: {}, Expected: {}",
1862            max_f32,
1863            max_f32_to_i32,
1864            i32::MAX
1865        );
1866        assert_eq!(max_f32_to_i32, i32::MAX);
1867
1868        // Test 0.0
1869        let zero_f32: f32 = 0.0;
1870        let zero_f32_to_i16: i16 = zero_f32.convert_to();
1871        println!("DEBUG: f32 -> i16 conversion for 0.0");
1872        println!(
1873            "Input: {}, Output: {}, Expected: 0",
1874            zero_f32, zero_f32_to_i16
1875        );
1876        assert_eq!(zero_f32_to_i16, 0);
1877
1878        let zero_f32_to_i32: i32 = zero_f32.convert_to();
1879        println!("DEBUG: f32 -> i32 conversion for 0.0");
1880        println!(
1881            "Input: {}, Output: {}, Expected: 0",
1882            zero_f32, zero_f32_to_i32
1883        );
1884        assert_eq!(zero_f32_to_i32, 0);
1885
1886        let zero_f32_to_i24: I24 = zero_f32.convert_to();
1887        println!("DEBUG: f32 -> I24 conversion for 0.0");
1888        println!(
1889            "Input: {}, Output: {} (i32 value), Expected: 0",
1890            zero_f32,
1891            zero_f32_to_i24.to_i32()
1892        );
1893        assert_eq!(zero_f32_to_i24.to_i32(), 0);
1894
1895        // Test clamping of out-of-range values
1896        let large_f32: f32 = 2.0;
1897        let large_f32_to_i16: i16 = large_f32.convert_to();
1898        assert_eq!(large_f32_to_i16, i16::MAX);
1899
1900        let neg_large_f32: f32 = -2.0;
1901        let neg_large_f32_to_i16: i16 = neg_large_f32.convert_to();
1902        assert!(
1903            neg_large_f32_to_i16 == i16::MIN || neg_large_f32_to_i16 == -32767,
1904            "Expected either -32768 or -32767, got {}",
1905            neg_large_f32_to_i16
1906        );
1907
1908        let large_f32_to_i32: i32 = large_f32.convert_to();
1909        assert_eq!(large_f32_to_i32, i32::MAX);
1910
1911        let neg_large_f32_to_i32: i32 = neg_large_f32.convert_to();
1912        assert!(
1913            neg_large_f32_to_i32 == i32::MIN || neg_large_f32_to_i32 == -2147483647,
1914            "Expected either i32::MIN or -2147483647, got {}",
1915            neg_large_f32_to_i32
1916        );
1917
1918        // Test small values
1919        let small_value: f32 = 1.0e-6;
1920        let small_value_to_i16: i16 = small_value.convert_to();
1921        assert_eq!(small_value_to_i16, 0);
1922
1923        let small_value_to_i32: i32 = small_value.convert_to();
1924        assert_eq!(small_value_to_i32, 2147); // 1.0e-6 * 2147483647 rounded to nearest
1925
1926        // Test values near 0.5
1927        let half_f32: f32 = 0.5;
1928        let half_f32_to_i16: i16 = half_f32.convert_to();
1929        assert_eq!(half_f32_to_i16, 16383); // 0.5 * 32767 = 16383.5, truncated to 16383
1930
1931        let neg_half_f32: f32 = -0.5;
1932        let neg_half_f32_to_i16: i16 = neg_half_f32.convert_to();
1933        assert_eq!(neg_half_f32_to_i16, -16383); // -0.5 * 32767 = -16383.5, truncated toward zero
1934    }
1935
1936    // Edge cases for f64 conversions
1937    #[test]
1938    fn f64_edge_cases() {
1939        // Test -1.0 (minimum valid value)
1940        let min_f64: f64 = -1.0;
1941        let min_f64_to_i16: i16 = min_f64.convert_to();
1942
1943        println!("DEBUG: f64 -> i16 conversion for -1.0");
1944        println!(
1945            "Input: {}, Output: {}, Expected: {} or {}",
1946            min_f64,
1947            min_f64_to_i16,
1948            i16::MIN,
1949            -32767
1950        );
1951
1952        // Due to rounding in the implementation, sometimes -1.0 can convert to -32767
1953        // This is acceptable since it's only 1 bit off from the true min
1954        assert!(
1955            min_f64_to_i16 == i16::MIN || min_f64_to_i16 == -32767,
1956            "Expected either -32768 or -32767, got {}",
1957            min_f64_to_i16
1958        );
1959
1960        let min_f64_to_i32: i32 = min_f64.convert_to();
1961
1962        println!("DEBUG: f64 -> i32 conversion for -1.0");
1963        println!(
1964            "Input: {}, Output: {}, Expected: {} or {}",
1965            min_f64,
1966            min_f64_to_i32,
1967            i32::MIN,
1968            -2147483647
1969        );
1970
1971        assert!(
1972            min_f64_to_i32 == i32::MIN || min_f64_to_i32 == -2147483647,
1973            "Expected either i32::MIN or -2147483647, got {}",
1974            min_f64_to_i32
1975        );
1976
1977        let min_f64_to_f32: f32 = min_f64.convert_to();
1978
1979        println!("DEBUG: f64 -> f32 conversion for -1.0");
1980        println!(
1981            "Input: {}, Output: {}, Expected: -1.0",
1982            min_f64, min_f64_to_f32
1983        );
1984
1985        assert_approx_eq!(min_f64_to_f32 as f64, -1.0, 1e-6);
1986
1987        // Test 1.0 (maximum valid value)
1988        let max_f64: f64 = 1.0;
1989        let max_f64_to_i16: i16 = max_f64.convert_to();
1990        assert_eq!(max_f64_to_i16, i16::MAX);
1991
1992        let max_f64_to_i32: i32 = max_f64.convert_to();
1993        assert_eq!(max_f64_to_i32, i32::MAX);
1994
1995        let max_f64_to_f32: f32 = max_f64.convert_to();
1996        assert_approx_eq!(max_f64_to_f32 as f64, 1.0, 1e-6);
1997
1998        // Test 0.0
1999        let zero_f64: f64 = 0.0;
2000        let zero_f64_to_i16: i16 = zero_f64.convert_to();
2001        assert_eq!(zero_f64_to_i16, 0);
2002
2003        let zero_f64_to_i32: i32 = zero_f64.convert_to();
2004        assert_eq!(zero_f64_to_i32, 0);
2005
2006        let zero_f64_to_f32: f32 = zero_f64.convert_to();
2007        assert_approx_eq!(zero_f64_to_f32 as f64, 0.0, 1e-10);
2008
2009        // Test clamping of out-of-range values
2010        let large_f64: f64 = 2.0;
2011        let large_f64_to_i16: i16 = large_f64.convert_to();
2012        assert_eq!(large_f64_to_i16, i16::MAX);
2013
2014        let neg_large_f64: f64 = -2.0;
2015        let neg_large_f64_to_i16: i16 = neg_large_f64.convert_to();
2016        assert!(
2017            neg_large_f64_to_i16 == i16::MIN || neg_large_f64_to_i16 == -32767,
2018            "Expected either -32768 or -32767, got {}",
2019            neg_large_f64_to_i16
2020        );
2021
2022        // Test very small values
2023        let tiny_value: f64 = 1.0e-12;
2024        let tiny_value_to_i16: i16 = tiny_value.convert_to();
2025        assert_eq!(tiny_value_to_i16, 0);
2026
2027        let tiny_value_to_i32: i32 = tiny_value.convert_to();
2028        assert_eq!(tiny_value_to_i32, 0);
2029
2030        let tiny_value_to_f32: f32 = tiny_value.convert_to();
2031        assert_approx_eq!(tiny_value_to_f32 as f64, 0.0, 1e-10);
2032    }
2033
2034    // Tests for I24 conversions
2035    #[test]
2036    fn i24_conversion_tests() {
2037        // Create an I24 with a known value
2038        let i24_value = i24!(4660 << 8); //  So converting back to i16 gives 4660
2039        println!(
2040            "DEBUG: Created I24 value from 4660 << 8 = {}",
2041            i24_value.to_i32()
2042        );
2043
2044        // Test I24 to i16
2045        let i24_to_i16: i16 = i24_value.convert_to();
2046        let expected_i16 = 0x1234_i16;
2047        println!("DEBUG: I24 -> i16 conversion");
2048        println!(
2049            "I24 (as i32): {}, i16: {}, Expected: {}",
2050            i24_value.to_i32(),
2051            i24_to_i16,
2052            expected_i16
2053        );
2054        assert_eq!(i24_to_i16, expected_i16);
2055
2056        // Test I24 to f32
2057        let i24_to_f32: f32 = i24_value.convert_to();
2058        let expected_f32 = (0x123456 as f32) / (I24::MAX.to_i32() as f32);
2059        println!("DEBUG: I24 -> f32 conversion");
2060        println!(
2061            "I24 (as i32): {}, f32: {}, Expected: {}",
2062            i24_value.to_i32(),
2063            i24_to_f32,
2064            expected_f32
2065        );
2066        // Print the difference to help debug
2067        println!("DEBUG: Difference: {}", (i24_to_f32 - expected_f32).abs());
2068        assert_approx_eq!(i24_to_f32 as f64, expected_f32 as f64, 1e-4);
2069
2070        // Test I24 to f64
2071        let i24_to_f64: f64 = i24_value.convert_to();
2072        let expected_f64 = (0x123456 as f64) / (I24::MAX.to_i32() as f64);
2073        println!("DEBUG: I24 -> f64 conversion");
2074        println!(
2075            "I24 (as i32): {}, f64: {}, Expected: {}",
2076            i24_value.to_i32(),
2077            i24_to_f64,
2078            expected_f64
2079        );
2080        // Print the difference to help debug
2081        println!("DEBUG: Difference: {}", (i24_to_f64 - expected_f64).abs());
2082        assert_approx_eq!(i24_to_f64, expected_f64, 1e-4);
2083    }
2084
2085    // Tests for convert_from functionality
2086    #[test]
2087    fn convert_from_tests() {
2088        // Test i16::convert_from with different source types
2089        let f32_source: f32 = 0.5;
2090        let i16_result: i16 = i16::convert_from(f32_source);
2091        assert_eq!(i16_result, 16383); // 0.5 * 32767 = 16383.5, truncated to 16383
2092
2093        let i32_source: i32 = 65536;
2094        let i16_result: i16 = i16::convert_from(i32_source);
2095        assert_eq!(i16_result, 1); // 65536 >> 16 = 1
2096
2097        // Test f32::convert_from with different source types
2098        let i16_source: i16 = 16384;
2099        let f32_result: f32 = f32::convert_from(i16_source);
2100        assert_approx_eq!(f32_result as f64, 0.5, 1e-4);
2101
2102        let i32_source: i32 = i32::MAX / 2;
2103        let f32_result: f32 = f32::convert_from(i32_source);
2104        assert_approx_eq!(f32_result as f64, 0.5, 1e-4);
2105
2106        // Test I24::convert_from
2107        let i16_source: i16 = 4660; // 0x1234
2108        let i24_result: I24 = I24::convert_from(i16_source);
2109        assert_eq!(i24_result.to_i32(), 4660 << 8); // Should be shifted left by 8 bits
2110
2111        // Test with zero values
2112        let zero_f32: f32 = 0.0;
2113        let zero_i16: i16 = i16::convert_from(zero_f32);
2114        assert_eq!(zero_i16, 0);
2115
2116        let zero_i16_source: i16 = 0;
2117        let zero_f32_result: f32 = f32::convert_from(zero_i16_source);
2118        assert_approx_eq!(zero_f32_result as f64, 0.0, 1e-10);
2119    }
2120
2121    // Tests for round trip conversions
2122    #[test]
2123    fn round_trip_conversions() {
2124        // i16 -> f32 -> i16
2125        for sample in [-32768, -16384, 0, 16384, 32767].iter() {
2126            let original = *sample;
2127            let intermediate: f32 = original.convert_to();
2128            let round_tripped: i16 = intermediate.convert_to();
2129
2130            println!("DEBUG: i16->f32->i16 conversion");
2131            println!(
2132                "Original i16: {}, f32: {}, Round trip i16: {}",
2133                original, intermediate, round_tripped
2134            );
2135
2136            assert!(
2137                (original - round_tripped).abs() <= 1,
2138                "Expected {}, got {}",
2139                original,
2140                round_tripped
2141            );
2142        }
2143
2144        // i32 -> f32 -> i32 (will lose precision)
2145        for &sample in &[i32::MIN, i32::MIN / 2, 0, i32::MAX / 2, i32::MAX] {
2146            let original = sample;
2147            let intermediate: f32 = original.convert_to();
2148            let round_tripped: i32 = intermediate.convert_to();
2149
2150            // Special case for extreme values
2151            if original == i32::MIN {
2152                // Allow off-by-one for MIN value
2153                assert!(
2154                    round_tripped == i32::MIN || round_tripped == -2147483647,
2155                    "Expected either i32::MIN or -2147483647, got {}",
2156                    round_tripped
2157                );
2158            } else if original == i32::MAX || original == 0 {
2159                assert_eq!(
2160                    original, round_tripped,
2161                    "Failed in i32->f32->i32 with extreme value {}",
2162                    original
2163                );
2164            } else {
2165                // For other values, we expect close but not exact due to precision
2166                let ratio = (round_tripped as f64) / (original as f64);
2167                assert!(
2168                    ratio > 0.999 && ratio < 1.001,
2169                    "Round trip error too large: {} -> {}",
2170                    original,
2171                    round_tripped
2172                );
2173            }
2174        }
2175
2176        // f32 -> i16 -> f32
2177        for &sample in &[-1.0, -0.5, 0.0, 0.5, 1.0] {
2178            let original: f32 = sample;
2179            let intermediate: i16 = original.convert_to();
2180            let round_tripped: f32 = intermediate.convert_to();
2181
2182            // For all values, we check approximately but with a more generous epsilon
2183            assert_approx_eq!(original as f64, round_tripped as f64, 1e-4);
2184        }
2185
2186        // i16 -> I24 -> i16
2187        for &sample in &[i16::MIN, -16384, 0, 16384, i16::MAX] {
2188            let original = sample;
2189            let intermediate: I24 = original.convert_to();
2190            let round_tripped: i16 = intermediate.convert_to();
2191
2192            // For extreme negative values, allow 1-bit difference
2193            if original == i16::MIN {
2194                assert!(
2195                    round_tripped == i16::MIN || round_tripped == -32767,
2196                    "Expected either -32768 or -32767, got {}",
2197                    round_tripped
2198                );
2199            } else {
2200                assert_eq!(
2201                    original, round_tripped,
2202                    "Failed in i16->I24->i16 with value {}",
2203                    original
2204                );
2205            }
2206        }
2207    }
2208}