Skip to main content

audio_samples/
repr.rs

1//! Core audio sample representation and data structures. is this module?
2//!
3//! This module defines the foundational data types used throughout the `audio_samples`
4//! library: the storage primitives [`MonoData`] / [`MultiData`], the channel-agnostic
5//! enum [`AudioData`], and the primary user-facing container [`AudioSamples`].
6//!
7//! [`AudioSamples<T>`] pairs raw PCM data (backed by `ndarray`) with essential metadata –
8//! sample rate, channel count, and memory layout – so that all downstream operations have
9//! access to the full audio context without passing it separately. does this module exist?
10//!
11//! Separating the storage layer from the processing API keeps the representation stable
12//! and independently testable. Internal storage types (`MonoData`, `MultiData`) can
13//! borrow or own their ndarray buffers without exposing that detail to callers; the
14//! promote-on-write pattern means read-only paths are always zero-copy. should it be used?
15//!
16//! Construct audio via the `AudioSamples` constructors (`new_mono`, `new_multi_channel`,
17//! `from_mono_vec`, …) or via the signal generators in [`utils::generation`](crate::utils::generation).
18//! Then use the trait methods from the `operations` module for processing.
19//!
20//! ```rust
21//! use audio_samples::{AudioSamples, sample_rate};
22//! use ndarray::array;
23//!
24//! // Mono audio from a 1-D array
25//! let mono = AudioSamples::new_mono(array![0.1f32, 0.2, 0.3, 0.4, 0.5], sample_rate!(44100)).unwrap();
26//!
27//! assert_eq!(mono.num_channels().get(), 1);
28//! assert_eq!(mono.samples_per_channel().get(), 5);
29//! assert_eq!(mono.sample_rate(), sample_rate!(44100));
30//!
31//! // Stereo audio from a 2-D array (channels × samples)
32//! let stereo = AudioSamples::new_multi_channel(
33//!     array![[0.1f32, 0.2, 0.3], [0.4f32, 0.5, 0.6]],
34//!     sample_rate!(48000),
35//! ).unwrap();
36//!
37//! assert_eq!(stereo.num_channels().get(), 2);
38//! assert_eq!(stereo.samples_per_channel().get(), 3);
39//! ```
40//!
41//! ## Supported sample types
42//!
43//! All six concrete types that implement [`AudioSample`] are supported:
44//! `u8`, `i16`, [`I24`](i24::I24), `i32`, `f32`, and `f64`.
45//!
46//! ```rust
47//! use audio_samples::{AudioSamples, sample_rate};
48//! use ndarray::array;
49//!
50//! // 8-bit unsigned (mid-scale 128 = silence)
51//! let u8_audio  = AudioSamples::new_mono(array![128u8, 160, 96], sample_rate!(8000)).unwrap();
52//! // 16-bit signed (CD quality)
53//! let i16_audio = AudioSamples::new_mono(array![1000i16, 2000, 3000], sample_rate!(44100)).unwrap();
54//! // 64-bit float (high-precision processing)
55//! let f64_audio = AudioSamples::new_mono(array![0.1f64, 0.2, 0.3], sample_rate!(96000)).unwrap();
56//! ```
57//!
58//! ## Sample-wise transformations
59//!
60//! [`AudioSamples`] provides in-place and mapping methods for sample-wise operations:
61//!
62//! ```rust
63//! use audio_samples::{AudioSamples, sample_rate};
64//! use ndarray::array;
65//!
66//! let mut audio = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0], sample_rate!(44100)).unwrap();
67//!
68//! // Apply a uniform gain
69//! audio.apply(|s| s * 0.5);
70//!
71//! // Apply a position-dependent fade-out
72//! audio.apply_with_index(|i, s| s * (1.0 - i as f32 * 0.1));
73//! ```
74//!
75//! ## Memory layout
76//!
77//! | Variant | Storage | Shape |
78//! |---|---|---|
79//! | [`AudioData::Mono`] | `Array1<T>` | `[samples]` |
80//! | [`AudioData::Multi`] | `Array2<T>` | `[channels, samples_per_channel]` |
81//!
82//! Multi-channel audio is stored in planar layout (each channel is a contiguous row).
83//! The [`ChannelLayout`] field on [`AudioSamples`] records the *intended* layout for
84//! serialisation purposes (e.g. when converting to an interleaved `Vec<T>`), but does
85//! not affect how the internal array is laid out.
86//!
87//! [`AudioSample`]: crate::AudioSample
88//! [`I24`](i24::I24): crate::I24
89//! [`ChannelLayout`]: crate::ChannelLayout
90use core::fmt::{Display, Formatter, Result as FmtResult};
91
92use core::num::{NonZeroU32, NonZeroUsize};
93use ndarray::iter::AxisIterMut;
94use ndarray::{
95    Array1, Array2, ArrayView1, ArrayView2, ArrayViewMut1, ArrayViewMut2, Axis, Ix1, Ix2, SliceArg,
96    s,
97};
98use non_empty_iter::{IntoNonEmptyIterator, NonEmptyIterator};
99use non_empty_slice::{NonEmptyByteVec, NonEmptyBytes, NonEmptySlice, NonEmptyVec};
100#[cfg(feature = "resampling")]
101use rubato::audioadapter::Adapter;
102use std::any::TypeId;
103use std::num::NonZeroU8;
104use std::ops::{Bound, Deref, DerefMut, Index, IndexMut, Mul, Neg, RangeBounds};
105
106/// Creates a [`NonZeroU32`] sample rate from a compile-time constant.
107///
108/// This macro provides zero-cost construction of sample rates with compile-time
109/// validation that the value is non-zero.
110///
111/// # Examples
112///
113/// ```rust
114/// use audio_samples::sample_rate;
115///
116/// // Common sample rates - validated at compile time
117/// let cd_rate = sample_rate!(44100);
118/// let dvd_rate = sample_rate!(48000);
119/// let high_res = sample_rate!(96000);
120///
121/// // Use directly in AudioSamples constructors
122/// use audio_samples::AudioSamples;
123/// use ndarray::array;
124/// let audio = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0], sample_rate!(44100)).unwrap();
125/// ```
126///
127/// # Compile-time Safety
128///
129/// The macro will cause a compile error if passed zero:
130///
131/// ```compile_fail
132/// use audio_samples::sample_rate;
133/// let invalid = sample_rate!(0); // Compile error!
134/// ```
135#[macro_export]
136macro_rules! sample_rate {
137    ($rate:expr) => {{
138        const RATE: u32 = $rate;
139        const { assert!(RATE > 0, "sample rate must be greater than 0") };
140        // SAFETY: We just asserted RATE > 0 at compile time
141        unsafe { ::core::num::NonZeroU32::new_unchecked(RATE) }
142    }};
143}
144
145/// Creates a [`NonZeroU32`] channel count from a compile-time constant.
146///
147/// Provides zero-cost, compile-time-validated construction of channel counts for use
148/// with [`AudioSamples`] constructors and channel-aware operations.
149///
150/// # Examples
151///
152/// ```rust
153/// use audio_samples::channels;
154///
155/// let mono   = channels!(1);
156/// let stereo = channels!(2);
157/// let surround = channels!(6); // 5.1
158/// ```
159///
160/// # Compile-time Safety
161///
162/// Passing zero causes a compile error:
163///
164/// ```compile_fail
165/// use audio_samples::channels;
166/// let invalid = channels!(0); // Compile error: channel count must be greater than 0
167/// ```
168#[macro_export]
169macro_rules! channels {
170    ($count:expr) => {{
171        const COUNT: u32 = $count;
172        const { assert!(COUNT > 0, "channel count must be greater than 0") };
173        // SAFETY: We just asserted COUNT > 0 at compile time
174        unsafe { ::core::num::NonZeroU32::new_unchecked(COUNT) }
175    }};
176}
177
178/// Sample rate in Hz, guaranteed to be non-zero at compile time.
179///
180/// Use the [`sample_rate!`] macro to construct values at compile time or
181/// [`NonZeroU32::new`] for runtime construction.
182pub type SampleRate = NonZeroU32;
183
184/// Number of audio channels, guaranteed to be non-zero.
185///
186/// Use the [`channels!`] macro to construct values at compile time or
187/// [`NonZeroU32::new`] for runtime construction.
188pub type ChannelCount = NonZeroU32;
189
190use crate::traits::{ConvertFrom, StandardSample};
191use crate::{
192    AudioSampleError, AudioSampleResult, CastInto, ConvertTo, I24, LayoutError, ParameterError,
193};
194
195/// Borrowed-or-owned byte view returned by `AudioData::bytes`.
196#[derive(Debug, Clone)]
197pub enum AudioBytes<'a> {
198    /// Zero-copy view into the underlying contiguous buffer.
199    Borrowed(&'a NonEmptyBytes),
200    /// Owned buffer when a borrow is impossible (e.g., I24 packing).
201    Owned(NonEmptyByteVec),
202}
203
204impl AudioBytes<'_> {
205    /// Returns the byte slice regardless of ownership mode.
206    #[inline]
207    #[must_use]
208    pub const fn as_slice(&self) -> &[u8] {
209        match self {
210            AudioBytes::Borrowed(b) => b.as_slice(),
211            AudioBytes::Owned(v) => v.as_slice(),
212        }
213    }
214
215    /// Returns an owned `Vec<u8>`, cloning if necessary.
216    #[inline]
217    #[must_use]
218    pub fn into_owned(self) -> NonEmptyByteVec {
219        match self {
220            AudioBytes::Borrowed(b) => b.to_owned(),
221            AudioBytes::Owned(v) => v,
222        }
223    }
224}
225
226/// Identifies the numeric type used to represent individual audio samples.
227///
228/// ## Purpose
229///
230/// `SampleType` provides a runtime representation of the sample format that can
231/// be inspected, serialised, and used for dispatch without needing generic type
232/// parameters. It mirrors the set of types that implement [`AudioSample`].
233///
234/// ## Intended Usage
235///
236/// Use `SampleType` when the sample format must be described at runtime – for
237/// example when reading audio file headers, serialising audio metadata, or
238/// routing to SIMD dispatch paths via [`SampleType::from_type_id`].
239///
240/// ## Variants
241///
242/// | Variant | Type | Width | Notes |
243/// |---|---|---|---|
244/// | `U8`  | `u8`  | 8-bit unsigned  | Mid-scale 128 = silence |
245/// | `I16` | `i16` | 16-bit signed   | CD-quality |
246/// | `I24` | [`I24`](i24::I24) | 24-bit signed | From the `i24` crate |
247/// | `I32` | `i32` | 32-bit signed   | |
248/// | `F32` | `f32` | 32-bit float    | Native DSP format |
249/// | `F64` | `f64` | 64-bit float    | High-precision |
250///
251/// [`AudioSample`]: crate::AudioSample
252/// [`I24`](i24::I24): crate::I24
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
254#[non_exhaustive]
255pub enum SampleType {
256    /// 8-bit unsigned integer (`u8`). Mid-scale value 128 represents silence.
257    U8,
258    /// 16-bit signed integer (`i16`). CD-quality audio.
259    I16,
260    /// 24-bit signed integer ([`I24`](i24::I24)(crate::I24)). Common in studio audio.
261    I24,
262    /// 32-bit signed integer (`i32`). High-dynamic-range integer audio.
263    I32,
264    /// 32-bit floating point (`f32`). Native format for most DSP operations.
265    F32,
266    /// 64-bit floating point (`f64`). High-precision processing.
267    F64,
268}
269
270impl SampleType {
271    /// Returns the canonical short name (machine-oriented)
272    #[inline]
273    #[must_use]
274    pub const fn as_str(self) -> &'static str {
275        match self {
276            Self::U8 => "u8",
277            Self::I16 => "i16",
278            Self::I24 => "i24",
279            Self::I32 => "i32",
280            Self::F32 => "f32",
281            Self::F64 => "f64",
282        }
283    }
284
285    /// Returns a human-readable descriptive name
286    #[inline]
287    #[must_use]
288    pub const fn description(self) -> &'static str {
289        match self {
290            Self::U8 => "8-bit unsigned integer",
291            Self::I16 => "16-bit signed integer",
292            Self::I24 => "24-bit signed integer",
293            Self::I32 => "32-bit signed integer",
294            Self::F32 => "32-bit floating point",
295            Self::F64 => "64-bit floating point",
296        }
297    }
298
299    /// Returns the bit-depth of the format, if defined
300    #[inline]
301    #[must_use]
302    pub const fn bits(self) -> Option<u32> {
303        match self {
304            Self::U8 => Some(8),
305            Self::I16 => Some(16),
306            Self::I24 => Some(24),
307            Self::I32 | Self::F32 => Some(32),
308            Self::F64 => Some(64),
309        }
310    }
311
312    /// Returns the size in bytes of one sample, if defined
313    #[inline]
314    #[must_use]
315    pub const fn bytes(self) -> Option<usize> {
316        match self {
317            Self::U8 => Some(1),
318            Self::I16 => Some(2),
319            Self::I24 => Some(3),
320            Self::I32 | Self::F32 => Some(4),
321            Self::F64 => Some(8),
322        }
323    }
324
325    /// Returns `true` if this is an integer-based sample format (`U8`, `I16`, `I24`, or `I32`).
326    #[inline]
327    #[must_use]
328    pub const fn is_integer(self) -> bool {
329        matches!(self, Self::U8 | Self::I16 | Self::I24 | Self::I32)
330    }
331
332    /// Returns `true` if this is a signed integer format (`I16`, `I24`, or `I32`).
333    ///
334    /// Note: `U8` is an integer type but is *unsigned*, so this returns `false` for it.
335    #[inline]
336    #[must_use]
337    pub const fn is_signed(self) -> bool {
338        matches!(self, Self::I16 | Self::I24 | Self::I32)
339    }
340
341    /// Returns `true` if this is an unsigned integer format (currently only `U8`).
342    #[inline]
343    #[must_use]
344    pub const fn is_unsigned(self) -> bool {
345        matches!(self, Self::U8)
346    }
347
348    /// True if this is a floating-point sample format
349    #[inline]
350    #[must_use]
351    pub const fn is_float(self) -> bool {
352        matches!(self, Self::F32 | Self::F64)
353    }
354
355    /// True if this format is usable for DSP operations without conversion
356    #[inline]
357    #[must_use]
358    pub const fn is_dsp_native(self) -> bool {
359        matches!(self, Self::F32 | Self::F64)
360    }
361}
362
363impl SampleType {
364    /// Returns the SampleType corresponding to a TypeId, or None if unrecognized.
365    ///
366    /// This is useful for SIMD dispatch code that needs to determine the
367    /// concrete sample type at runtime.
368    ///
369    /// # Example
370    /// ```
371    /// use audio_samples::SampleType;
372    /// use std::any::TypeId;
373    ///
374    /// assert_eq!(SampleType::from_type_id(TypeId::of::<f32>()), Some(SampleType::F32));
375    /// assert_eq!(SampleType::from_type_id(TypeId::of::<String>()), None);
376    /// ```
377    #[inline]
378    #[must_use]
379    pub fn from_type_id(type_id: std::any::TypeId) -> Option<Self> {
380        use std::any::TypeId;
381        if type_id == TypeId::of::<u8>() {
382            Some(Self::U8)
383        } else if type_id == TypeId::of::<i16>() {
384            Some(Self::I16)
385        } else if type_id == TypeId::of::<I24>() {
386            Some(Self::I24)
387        } else if type_id == TypeId::of::<i32>() {
388            Some(Self::I32)
389        } else if type_id == TypeId::of::<f32>() {
390            Some(Self::F32)
391        } else if type_id == TypeId::of::<f64>() {
392            Some(Self::F64)
393        } else {
394            None
395        }
396    }
397
398    /// Creates a sample type from a number of bits per sample.
399    ///
400    /// # Panics
401    ///
402    /// Panics if the bits value does not correspond to a supported sample type.
403    #[inline]
404    #[must_use]
405    pub const fn from_bits(bits: u16) -> Self {
406        match bits {
407            8 => Self::U8,
408            16 => Self::I16,
409            24 => Self::I24,
410            32 => Self::I32,
411            64 => Self::F64,
412            _ => panic!("Unsupported bits per sample"),
413        }
414    }
415}
416
417/// Formats the sample type as a string.
418///
419/// The standard format (`{}`) uses the short machine-readable identifier (e.g. `"f32"`).
420/// The alternate format (`{:#}`) uses the human-readable description (e.g. `"32-bit floating point"`).
421///
422/// # Examples
423///
424/// ```rust
425/// use audio_samples::SampleType;
426///
427/// assert_eq!(format!("{}", SampleType::F32), "f32");
428/// assert_eq!(format!("{:#}", SampleType::F32), "32-bit floating point");
429/// ```
430impl Display for SampleType {
431    #[inline]
432    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
433        if f.alternate() {
434            write!(f, "{}", self.description())
435        } else {
436            write!(f, "{}", self.as_str())
437        }
438    }
439}
440
441/// Parses a [`SampleType`] from its canonical short-name string.
442///
443/// Accepts `"u8"`, `"i16"`, `"i24"`, `"i32"`, `"f32"`, and `"f64"`.
444/// Returns `Err(())` for any unrecognised string.
445///
446/// # Examples
447///
448/// ```rust
449/// use audio_samples::SampleType;
450///
451/// assert_eq!(SampleType::try_from("f32"), Ok(SampleType::F32));
452/// assert_eq!(SampleType::try_from("i16"), Ok(SampleType::I16));
453/// assert!(SampleType::try_from("unknown").is_err());
454/// ```
455impl TryFrom<&str> for SampleType {
456    type Error = ();
457
458    #[inline]
459    fn try_from(value: &str) -> Result<Self, Self::Error> {
460        match value {
461            "u8" => Ok(Self::U8),
462            "i16" => Ok(Self::I16),
463            "i24" => Ok(Self::I24),
464            "i32" => Ok(Self::I32),
465            "f32" => Ok(Self::F32),
466            "f64" => Ok(Self::F64),
467            _ => Err(()),
468        }
469    }
470}
471
472/// Internal storage variant for single-channel (mono) audio data.
473///
474/// `MonoRepr` is the innermost representation used by [`MonoData`]. Callers should
475/// interact with [`MonoData`] rather than this enum directly; it is public only to
476/// allow construction in adjacent modules.
477///
478/// ## Variants
479///
480/// - `Borrowed` – immutable borrow of an existing ndarray view. Zero-copy reads.
481/// - `BorrowedMut` – mutable borrow. Zero-copy reads and writes.
482/// - `Owned` – heap-allocated `Array1<T>`. Enables all operations without lifetime constraints.
483///
484/// ## Promote-on-write
485///
486/// When a mutable operation (e.g. `mapv_inplace`) is called on a `Borrowed` variant,
487/// [`MonoData`] promotes it to `Owned` by cloning the slice. `BorrowedMut` and `Owned`
488/// are passed through without allocation.
489#[derive(Debug, PartialEq)]
490pub enum MonoRepr<'a, T>
491where
492    T: StandardSample,
493{
494    /// Immutable borrow of an existing 1-D ndarray view.
495    Borrowed(ArrayView1<'a, T>),
496    /// Mutable borrow of an existing 1-D ndarray view.
497    BorrowedMut(ArrayViewMut1<'a, T>),
498    /// Heap-allocated, owned 1-D array.
499    Owned(Array1<T>),
500}
501
502/// Storage wrapper for mono (single-channel) audio samples.
503///
504/// `MonoData` can either **borrow** an `ndarray::ArrayView1` / `ArrayViewMut1` or **own** an
505/// `ndarray::Array1`. Methods that require mutation will promote borrowed data to an owned
506/// buffer when necessary.
507///
508/// # Indexing
509/// Indexing uses the sample index (`usize`).
510///
511/// # Allocation behavior
512/// - Pure reads are zero-copy.
513/// - Some mutable operations (e.g. indexing mutably when currently borrowed immutably) may
514///   allocate by cloning into an owned buffer.
515#[derive(Debug, PartialEq)]
516pub struct MonoData<'a, T>(MonoRepr<'a, T>)
517where
518    T: StandardSample;
519
520impl<'a, T> MonoData<'a, T>
521where
522    T: StandardSample,
523{
524    /// Creates self from an Array1.
525    ///
526    /// # Safety
527    ///
528    /// Caller must ensure that array is not empty
529    ///
530    /// # Returns
531    ///
532    /// An owned version of Self
533    #[inline]
534    pub const unsafe fn from_array1(array: Array1<T>) -> Self {
535        MonoData(MonoRepr::Owned(array))
536    }
537
538    /// Creates a borrowed version of Self from an Array1View
539    ///
540    /// # Safety
541    ///
542    /// Caller must ensure that view is not empty
543    ///
544    /// # Returns
545    ///
546    /// A borrowed version of Self
547    pub const unsafe fn from_array_view<'b>(view: ArrayView1<'b, T>) -> Self
548    where
549        'b: 'a,
550    {
551        MonoData(MonoRepr::Borrowed(view))
552    }
553
554    /// Creates a borrowed version of Self, from self
555    ///
556    /// # Returns
557    ///
558    /// A borrowed version of self
559    #[inline]
560    pub fn borrow(&'a self) -> Self {
561        MonoData(MonoRepr::Borrowed(self.as_view()))
562    }
563
564    /// Returns an `ndarray` view of the samples.
565    ///
566    /// This is always a zero-copy operation.
567    #[inline]
568    pub fn as_view(&self) -> ArrayView1<'_, T> {
569        match &self.0 {
570            MonoRepr::Borrowed(v) => *v,
571            MonoRepr::BorrowedMut(v) => v.view(),
572            MonoRepr::Owned(a) => a.view(),
573        }
574    }
575
576    /// Promotes immutably-borrowed data to owned data.
577    ///
578    /// If this `MonoData` is already mutable-borrowed or owned, this is a no-op.
579    /// If it is immutably borrowed, this allocates and clones the underlying samples.
580    #[inline]
581    pub fn promote(&mut self) {
582        if let MonoRepr::Borrowed(v) = &self.0 {
583            self.0 = MonoRepr::Owned(v.to_owned());
584        }
585    }
586
587    /// Creates a borrowed `MonoData` from an immutable 1-D ndarray view.
588    ///
589    /// # Arguments
590    ///
591    /// – `view` – an immutable view whose lifetime `'b` must outlive `'a`.
592    ///
593    /// # Returns
594    ///
595    /// `Ok(MonoData)` wrapping the view.
596    ///
597    /// # Errors
598    ///
599    /// Errors if the view is empty
600    #[inline]
601    pub fn from_view<'b>(view: ArrayView1<'b, T>) -> AudioSampleResult<Self>
602    where
603        'b: 'a,
604    {
605        if view.is_empty() {
606            return Err(AudioSampleError::EmptyData);
607        }
608        Ok(MonoData(MonoRepr::Borrowed(view)))
609    }
610
611    /// Unsafe version of [`from_view`] that does not check for empty input.
612    ///
613    /// # Arguments
614    ///
615    /// – `view` – an immutable view whose lifetime `'b` must outlive `'a`.
616    /// # Returns
617    ///
618    /// `MonoData` wrapping the view. This constructor never fails; the `Result`
619    /// wrapper exists for API consistency with the other `from_*` constructors.
620    #[inline]
621    pub const unsafe fn from_view_unchecked<'b>(view: ArrayView1<'b, T>) -> Self
622    where
623        'b: 'a,
624    {
625        MonoData(MonoRepr::Borrowed(view))
626    }
627
628    /// Creates a mutably-borrowed `MonoData` from a mutable 1-D ndarray view.
629    ///
630    /// # Arguments
631    ///
632    /// – `view` – a mutable view whose lifetime `'b` must outlive `'a`.
633    ///
634    /// # Returns
635    ///
636    /// `Ok(MonoData)` wrapping the mutable view.
637    ///
638    /// # Errors
639    ///
640    /// Returns [`AudioSampleError::EmptyData`] if `view` is empty.
641    #[inline]
642    pub fn from_view_mut<'b>(view: ArrayViewMut1<'b, T>) -> AudioSampleResult<Self>
643    where
644        'b: 'a,
645    {
646        if view.is_empty() {
647            return Err(AudioSampleError::EmptyData);
648        }
649        Ok(MonoData(MonoRepr::BorrowedMut(view)))
650    }
651
652    /// Unchecked version of [`from_view_mut`]
653    ///
654    /// # Arguments
655    ///
656    /// – `view` – a mutable view whose lifetime `'b` must outlive `'a`.
657    /// # Returns
658    ///
659    /// `MonoData` wrapping the mutable view. This constructor never fails; the `Result`
660    /// wrapper exists for API consistency with the other `from_*` constructors.
661    #[inline]
662    pub const unsafe fn from_view_mut_unchecked<'b>(view: ArrayViewMut1<'b, T>) -> Self
663    where
664        'b: 'a,
665    {
666        MonoData(MonoRepr::BorrowedMut(view))
667    }
668
669    /// Creates an owned `MonoData` from an `Array1`.
670    ///
671    /// # Arguments
672    ///
673    /// – `array` – the owned 1-D array to wrap.
674    ///
675    /// # Returns
676    ///
677    /// `Ok(MonoData)` taking ownership of the array.
678    ///
679    /// # Errors
680    ///
681    /// Returns [`AudioSampleError::EmptyData`] if `array` is empty.
682    #[inline]
683    pub fn from_owned(array: Array1<T>) -> AudioSampleResult<Self> {
684        if array.is_empty() {
685            return Err(AudioSampleError::EmptyData);
686        }
687        Ok(MonoData(MonoRepr::Owned(array)))
688    }
689
690    /// Creates an owned `MonoData` from an `Array1`.
691    ///
692    /// # Arguments
693    ///
694    /// – `array` – the owned 1-D array to wrap.
695    ///
696    /// # Returns
697    ///
698    /// `MonoData` taking ownership of the array.
699    ///
700    /// # Safety
701    ///
702    /// Don't pass an empty array
703    pub const unsafe fn from_owned_unchecked(array: Array1<T>) -> Self {
704        MonoData(MonoRepr::Owned(array))
705    }
706
707    /// Get a mutable view of the audio data, converting to owned if necessary.
708    fn to_mut(&mut self) -> ArrayViewMut1<'_, T> {
709        if let MonoRepr::Borrowed(v) = self.0 {
710            // Convert borrowed to owned for mutability
711            self.0 = MonoRepr::Owned(v.to_owned());
712        }
713
714        match &mut self.0 {
715            MonoRepr::BorrowedMut(view) => view.view_mut(), // If the data  is already mutable borrowed then we do not need to convert to owned, this variant says "we have mutable access"
716            MonoRepr::Owned(a) => a.view_mut(),
717            MonoRepr::Borrowed(_) => {
718                unreachable!("Self should have been converted to owned by now")
719            }
720        }
721    }
722
723    fn into_owned<'b>(self) -> MonoData<'b, T> {
724        match self.0 {
725            MonoRepr::Borrowed(v) => MonoData(MonoRepr::Owned(v.to_owned())),
726            MonoRepr::BorrowedMut(v) => MonoData(MonoRepr::Owned(v.to_owned())),
727            MonoRepr::Owned(a) => MonoData(MonoRepr::Owned(a)),
728        }
729    }
730
731    // Delegation methods for ndarray operations
732
733    /// Returns the number of samples.
734    #[inline]
735    pub fn len(&self) -> NonZeroUsize {
736        NonZeroUsize::new(self.as_view().len()).expect("Array is guaranteed to be non-empty")
737    }
738
739    /// Returns an `ndarray` view of the samples.
740    #[inline]
741    pub fn view(&self) -> ArrayView1<'_, T> {
742        self.as_view()
743    }
744
745    /// Returns the arithmetic mean of the samples.
746    #[inline]
747    pub fn mean(&self) -> T {
748        self.as_view()
749            .mean()
750            .expect("Array is guaranteed to be non-empty")
751    }
752
753    /// Returns the population variance of the samples.
754    #[inline]
755    pub fn variance(&self) -> f64 {
756        self.variance_with_ddof(0)
757    }
758
759    /// Returns the variance with a custom delta degrees of freedom.
760    ///
761    /// The divisor used in the calculation is `N - ddof`, where `N` is the number of
762    /// samples. `ddof = 0` gives the population variance; `ddof = 1` gives the
763    /// unbiased sample variance.
764    ///
765    /// # Arguments
766    ///
767    /// – `ddof` – delta degrees of freedom. Must be less than `self.len()`.
768    #[inline]
769    pub fn variance_with_ddof(&self, ddof: usize) -> f64 {
770        let degrees_of_freedom = (self.len().get() - ddof) as f64;
771        let mean: f64 = self.mean().cast_into();
772
773        self.iter()
774            .map(|&x| {
775                let diff: f64 = <T as CastInto<f64>>::cast_into(x) - mean;
776                diff * diff
777            })
778            .sum::<f64>()
779            / degrees_of_freedom
780    }
781
782    /// Returns the standard deviation of the samples
783    #[inline]
784    pub fn stddev(&self) -> f64 {
785        self.stddev_with_ddof(0)
786    }
787
788    /// Returns the standard deviation of the samples with specified delta degrees of freedom.
789    #[inline]
790    pub fn stddev_with_ddof(&self, ddof: usize) -> f64 {
791        self.variance_with_ddof(ddof).sqrt()
792    }
793
794    /// Returns the sum of the samples.
795    #[inline]
796    pub fn sum(&self) -> T {
797        self.as_view().sum()
798    }
799
800    /// Folds over the samples.
801    #[inline]
802    pub fn fold<F>(&self, init: T, f: F) -> T
803    where
804        F: FnMut(T, &T) -> T,
805    {
806        self.iter().fold(init, f)
807    }
808
809    /// Returns a slice view of the audio data based on the provided slicing information.
810    #[inline]
811    pub fn slice<I>(&self, info: I) -> ArrayView1<'_, T>
812    where
813        I: SliceArg<Ix1, OutDim = Ix1>,
814    {
815        match &self.0 {
816            MonoRepr::Borrowed(v) => v.slice(info),
817            MonoRepr::BorrowedMut(v) => v.slice(info),
818            MonoRepr::Owned(a) => a.slice(info),
819        }
820    }
821
822    /// Returns a mutable slice view of the audio data based on the provided slicing information.
823    /// NOTE: This function promotes to owned data if the current representation is borrowed.
824    #[inline]
825    pub fn slice_mut<I>(&mut self, info: I) -> ArrayViewMut1<'_, T>
826    where
827        I: ndarray::SliceArg<Ix1, OutDim = Ix1>,
828    {
829        self.promote();
830        match &mut self.0 {
831            MonoRepr::BorrowedMut(a) => a.slice_mut(info),
832            MonoRepr::Owned(a) => a.slice_mut(info),
833            MonoRepr::Borrowed(_) => {
834                unreachable!("Self should have been converted to owned by now")
835            }
836        }
837    }
838
839    /// Returns an iterator over the samples.
840    #[inline]
841    pub fn iter(&self) -> ndarray::iter::Iter<'_, T, Ix1> {
842        match &self.0 {
843            MonoRepr::Borrowed(v) => v.iter(),
844            MonoRepr::BorrowedMut(v) => v.iter(),
845            MonoRepr::Owned(a) => a.iter(),
846        }
847    }
848
849    /// Returns a mutable iterator over the samples.
850    ///
851    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
852    #[inline]
853    pub fn iter_mut(&mut self) -> ndarray::iter::IterMut<'_, T, Ix1> {
854        if let MonoRepr::Borrowed(a) = &mut self.0 {
855            self.0 = MonoRepr::Owned(a.to_owned());
856        }
857
858        match &mut self.0 {
859            MonoRepr::BorrowedMut(b) => b.iter_mut(),
860            MonoRepr::Owned(a) => a.iter_mut(),
861            MonoRepr::Borrowed(_) => {
862                unreachable!("Self should have been converted to owned by now")
863            }
864        }
865    }
866
867    /// Applies a value-mapping function in-place.
868    ///
869    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
870    #[inline]
871    pub fn mapv_inplace<F>(&mut self, f: F)
872    where
873        F: FnMut(T) -> T,
874    {
875        self.to_mut().mapv_inplace(f);
876    }
877
878    /// Returns a mutable slice of the underlying samples.
879    ///
880    /// # Panics
881    /// Panics if the underlying `ndarray` storage is not contiguous.
882    #[inline]
883    pub fn as_slice_mut(&mut self) -> &mut [T] {
884        self.promote();
885        match &mut self.0 {
886            MonoRepr::BorrowedMut(a) => a
887                .as_slice_mut()
888                .expect("Structures backing audio samples should be contiguous in memory"),
889            MonoRepr::Owned(a) => a
890                .as_slice_mut()
891                .expect("Structures backing audio samples should be contiguous in memory"),
892            MonoRepr::Borrowed(_) => {
893                unreachable!("Self should have been converted to owned by now")
894            }
895        }
896    }
897
898    /// Returns a shared slice if the underlying storage is contiguous.
899    #[inline]
900    pub fn as_slice(&self) -> Option<&[T]> {
901        match &self.0 {
902            MonoRepr::Borrowed(v) => v.as_slice(),
903            MonoRepr::BorrowedMut(v) => v.as_slice(),
904            MonoRepr::Owned(a) => a.as_slice(),
905        }
906    }
907
908    /// Returns the shape of the underlying `ndarray` buffer.
909    #[inline]
910    pub fn shape(&self) -> &[usize] {
911        match &self.0 {
912            MonoRepr::Borrowed(v) => v.shape(),
913            MonoRepr::BorrowedMut(v) => v.shape(),
914            MonoRepr::Owned(a) => a.shape(),
915        }
916    }
917
918    /// Maps each sample into a new `Array1`.
919    #[inline]
920    pub fn mapv<U, F>(&self, f: F) -> Array1<U>
921    where
922        F: Fn(T) -> U,
923        U: Clone,
924    {
925        self.as_view().mapv(f)
926    }
927
928    /// Returns a mutable pointer to the underlying buffer.
929    ///
930    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
931    #[inline]
932    pub fn as_mut_ptr(&mut self) -> *mut T {
933        self.to_mut().as_mut_ptr()
934    }
935
936    /// Collects the samples into a `Vec<T>`.
937    #[inline]
938    pub fn to_vec(&self) -> Vec<T> {
939        self.as_view().to_vec()
940    }
941
942    /// Fills all samples with `value`.
943    ///
944    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
945    #[inline]
946    pub fn fill(&mut self, value: T) {
947        self.to_mut().fill(value);
948    }
949
950    /// Converts into a raw `Vec<T>` and an offset.
951    ///
952    /// For borrowed data, this allocates and clones.
953    #[inline]
954    pub fn into_raw_vec_and_offset(self) -> (Vec<T>, usize) {
955        match self.0 {
956            MonoRepr::Borrowed(v) => {
957                let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
958                (vec, offset.unwrap_or(0))
959            }
960            MonoRepr::BorrowedMut(v) => {
961                let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
962                (vec, offset.unwrap_or(0))
963            }
964            MonoRepr::Owned(a) => {
965                let (vec, offset) = a.into_raw_vec_and_offset();
966                (vec, offset.unwrap_or(0))
967            }
968        }
969    }
970
971    /// Converts this wrapper into an owned `Array1<T>`.
972    ///
973    /// For borrowed data, this allocates and clones.
974    #[inline]
975    pub fn take(self) -> Array1<T> {
976        match self.0 {
977            MonoRepr::Borrowed(v) => v.to_owned(),
978            MonoRepr::BorrowedMut(v) => v.to_owned(),
979            MonoRepr::Owned(a) => a,
980        }
981    }
982}
983
984/// Compares `MonoData` to an `Array1` sample-by-sample.
985impl<T> PartialEq<Array1<T>> for MonoData<'_, T>
986where
987    T: StandardSample,
988{
989    #[inline]
990    fn eq(&self, other: &Array1<T>) -> bool {
991        self.as_view() == other.view()
992    }
993}
994
995/// Compares an `Array1` to a `MonoData` sample-by-sample.
996impl<'a, T> PartialEq<MonoData<'a, T>> for Array1<T>
997where
998    T: StandardSample,
999{
1000    #[inline]
1001    fn eq(&self, other: &MonoData<'a, T>) -> bool {
1002        self.view() == other.as_view()
1003    }
1004}
1005
1006/// Iterates over shared references to each sample in the mono buffer.
1007impl<'a, T> IntoIterator for &'a MonoData<'_, T>
1008where
1009    T: StandardSample,
1010{
1011    type Item = &'a T;
1012    type IntoIter = ndarray::iter::Iter<'a, T, ndarray::Ix1>;
1013
1014    #[inline]
1015    fn into_iter(self) -> Self::IntoIter {
1016        self.as_view().into_iter()
1017    }
1018}
1019
1020/// Indexes the mono buffer by sample position.
1021///
1022/// # Panics
1023///
1024/// Panics if `idx` is out of bounds.
1025impl<T> Index<usize> for MonoData<'_, T>
1026where
1027    T: StandardSample,
1028{
1029    type Output = T;
1030
1031    #[inline]
1032    fn index(&self, idx: usize) -> &Self::Output
1033    where
1034        T: StandardSample,
1035    {
1036        match &self.0 {
1037            MonoRepr::Borrowed(arr) => &arr[idx],
1038            MonoRepr::BorrowedMut(arr) => &arr[idx],
1039            MonoRepr::Owned(arr) => &arr[idx],
1040        }
1041    }
1042}
1043
1044/// Mutably indexes the mono buffer by sample position.
1045///
1046/// If the data is currently immutably borrowed, this promotes to owned (allocates).
1047///
1048/// # Panics
1049///
1050/// Panics if `idx` is out of bounds.
1051impl<T> IndexMut<usize> for MonoData<'_, T>
1052where
1053    T: StandardSample,
1054{
1055    #[inline]
1056    fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
1057        self.promote();
1058        match &mut self.0 {
1059            MonoRepr::BorrowedMut(arr) => &mut arr[idx],
1060            MonoRepr::Owned(arr) => &mut arr[idx],
1061            MonoRepr::Borrowed(_) => {
1062                unreachable!("Self should have been converted to owned by now")
1063            }
1064        }
1065    }
1066}
1067
1068/// Internal storage variant for multi-channel audio data.
1069///
1070/// `MultiRepr` is the innermost representation used by [`MultiData`]. The same
1071/// promote-on-write semantics as [`MonoRepr`] apply: immutable borrows are promoted
1072/// to owned allocations on the first mutable operation.
1073///
1074/// The array shape is always `(channels, samples_per_channel)` (channels are rows).
1075///
1076/// ## Variants
1077///
1078/// - `Borrowed` – immutable borrow of an existing 2-D ndarray view. Zero-copy reads.
1079/// - `BorrowedMut` – mutable borrow. Zero-copy reads and writes.
1080/// - `Owned` – heap-allocated `Array2<T>`. Enables all operations without lifetime constraints.
1081#[derive(Debug, PartialEq)]
1082pub enum MultiRepr<'a, T>
1083where
1084    T: StandardSample,
1085{
1086    /// Immutable borrow of an existing 2-D ndarray view.
1087    Borrowed(ArrayView2<'a, T>),
1088    /// Mutable borrow of an existing 2-D ndarray view.
1089    BorrowedMut(ArrayViewMut2<'a, T>),
1090    /// Heap-allocated, owned 2-D array with shape `(channels, samples_per_channel)`.
1091    Owned(Array2<T>),
1092}
1093
1094/// Storage wrapper for multi-channel audio samples.
1095///
1096/// `MultiData` can either **borrow** an `ndarray::ArrayView2` / `ArrayViewMut2` or **own** an
1097/// `ndarray::Array2`.
1098///
1099/// The shape is `(channels, samples_per_channel)` (channels are rows).
1100///
1101/// # Indexing
1102/// Indexing uses `(channel, sample)`.
1103#[derive(Debug, PartialEq)]
1104pub struct MultiData<'a, T>(MultiRepr<'a, T>)
1105where
1106    T: StandardSample;
1107
1108/// Indexes the multi-channel buffer by `(channel, sample)` tuple.
1109///
1110/// # Panics
1111///
1112/// Panics if either index is out of bounds.
1113impl<T> Index<(usize, usize)> for MultiData<'_, T>
1114where
1115    T: StandardSample,
1116{
1117    type Output = T;
1118
1119    #[inline]
1120    fn index(&self, (ch, s): (usize, usize)) -> &Self::Output {
1121        match &self.0 {
1122            MultiRepr::Borrowed(arr) => &arr[[ch, s]],
1123            MultiRepr::BorrowedMut(arr) => &arr[[ch, s]],
1124            MultiRepr::Owned(arr) => &arr[[ch, s]],
1125        }
1126    }
1127}
1128
1129/// Mutably indexes the multi-channel buffer by `(channel, sample)` tuple.
1130///
1131/// If the data is currently immutably borrowed, this promotes to owned (allocates).
1132///
1133/// # Panics
1134///
1135/// Panics if either index is out of bounds.
1136impl<T> IndexMut<(usize, usize)> for MultiData<'_, T>
1137where
1138    T: StandardSample,
1139{
1140    #[inline]
1141    fn index_mut(&mut self, (ch, s): (usize, usize)) -> &mut Self::Output {
1142        self.promote();
1143        match &mut self.0 {
1144            MultiRepr::BorrowedMut(arr) => &mut arr[[ch, s]],
1145            MultiRepr::Owned(arr) => &mut arr[[ch, s]],
1146            MultiRepr::Borrowed(_) => {
1147                unreachable!("Self should have been converted to owned by now")
1148            }
1149        }
1150    }
1151}
1152
1153/// Indexes the multi-channel buffer by `[channel, sample]` array.
1154///
1155/// # Panics
1156///
1157/// Panics if either index is out of bounds.
1158impl<T> Index<[usize; 2]> for MultiData<'_, T>
1159where
1160    T: StandardSample,
1161{
1162    type Output = T;
1163
1164    #[inline]
1165    fn index(&self, index: [usize; 2]) -> &Self::Output {
1166        match &self.0 {
1167            MultiRepr::Borrowed(arr) => &arr[index],
1168            MultiRepr::BorrowedMut(arr) => &arr[index],
1169            MultiRepr::Owned(arr) => &arr[index],
1170        }
1171    }
1172}
1173
1174/// Mutably indexes the multi-channel buffer by `[channel, sample]` array.
1175///
1176/// If the data is currently immutably borrowed, this promotes to owned (allocates).
1177///
1178/// # Panics
1179///
1180/// Panics if either index is out of bounds.
1181impl<T> IndexMut<[usize; 2]> for MultiData<'_, T>
1182where
1183    T: StandardSample,
1184{
1185    #[inline]
1186    fn index_mut(&mut self, index: [usize; 2]) -> &mut Self::Output {
1187        self.promote();
1188        match &mut self.0 {
1189            MultiRepr::BorrowedMut(arr) => &mut arr[index],
1190            MultiRepr::Owned(arr) => &mut arr[index],
1191            MultiRepr::Borrowed(_) => {
1192                unreachable!("Self should have been converted to owned by now")
1193            }
1194        }
1195    }
1196}
1197
1198impl<'a, T> MultiData<'a, T>
1199where
1200    T: StandardSample,
1201{
1202    /// Creates a borrowed version of Self from an Array2
1203    ///
1204    /// # Arguments
1205    ///
1206    /// `array` - A non-empty Array2
1207    ///
1208    /// # Safety
1209    ///
1210    /// Caller must ensure that array is not empty
1211    ///
1212    /// # Returns
1213    ///
1214    /// A borrowed version of Self
1215    #[inline]
1216    pub const unsafe fn from_array2(array: Array2<T>) -> Self {
1217        MultiData(MultiRepr::Owned(array))
1218    }
1219
1220    /// Creates a borrowed version of Self from an Array2View
1221    ///
1222    /// # Arguments
1223    ///
1224    /// `view` - A non-empty 2D ArrayView
1225    ///
1226    /// # Safety
1227    ///
1228    /// Caller must ensure that view is not empty
1229    ///
1230    /// # Returns
1231    ///
1232    /// A borrowed version of Self which is tied to the owner of `view`
1233    #[inline]
1234    pub const unsafe fn from_array_view<'b>(view: ArrayView2<'b, T>) -> Self
1235    where
1236        'b: 'a,
1237    {
1238        MultiData(MultiRepr::Borrowed(view))
1239    }
1240
1241    /// Creates a borrowed version of Self, from self
1242    ///
1243    /// # Returns
1244    ///
1245    /// A borrowed version of self tied to the lifetime of Self
1246    #[inline]
1247    pub fn borrow(&'a self) -> Self {
1248        MultiData(MultiRepr::Borrowed(self.as_view()))
1249    }
1250
1251    /// Returns an `ndarray` view of the samples with shape `(channels, samples_per_channel)`.
1252    ///
1253    /// This is always a zero-copy operation.
1254    #[inline]
1255    pub fn as_view(&self) -> ArrayView2<'_, T> {
1256        match &self.0 {
1257            MultiRepr::Borrowed(a) => *a,
1258            MultiRepr::BorrowedMut(a) => a.view(),
1259            MultiRepr::Owned(a) => a.view(),
1260        }
1261    }
1262
1263    /// Promotes immutably-borrowed data to owned data.
1264    ///
1265    /// If this `MultiData` is already mutable-borrowed or owned, this is a no-op.
1266    /// If it is immutably borrowed, this allocates and clones the underlying samples.
1267    #[inline]
1268    pub fn promote(&mut self) {
1269        if let MultiRepr::Borrowed(v) = &self.0 {
1270            self.0 = MultiRepr::Owned(v.to_owned());
1271        }
1272    }
1273
1274    /// Creates a borrowed `MultiData` from an immutable 2-D ndarray view.
1275    ///
1276    /// # Arguments
1277    ///
1278    /// – `view` – an immutable view with shape `(channels, samples_per_channel)`.
1279    ///   The lifetime `'b` must outlive `'a`.
1280    ///
1281    /// # Returns
1282    ///
1283    /// `Ok(MultiData)` wrapping the view.
1284    ///
1285    /// # Errors
1286    ///
1287    /// Returns [`AudioSampleError::EmptyData`] if `view` is empty.
1288    #[inline]
1289    pub fn from_view<'b>(view: ArrayView2<'b, T>) -> AudioSampleResult<Self>
1290    where
1291        'b: 'a,
1292    {
1293        if view.is_empty() {
1294            return Err(AudioSampleError::EmptyData);
1295        }
1296        Ok(MultiData(MultiRepr::Borrowed(view)))
1297    }
1298
1299    /// Creates a mutably-borrowed `MultiData` from a mutable 2-D ndarray view.
1300    ///
1301    /// # Arguments
1302    ///
1303    /// – `view` – a mutable view with shape `(channels, samples_per_channel)`.
1304    ///   The lifetime `'b` must outlive `'a`.
1305    ///
1306    /// # Returns
1307    ///
1308    /// `Ok(MultiData)` wrapping the mutable view.
1309    ///
1310    /// # Errors
1311    ///
1312    /// Returns [`AudioSampleError::EmptyData`] if `view` is empty.
1313    #[inline]
1314    pub fn from_view_mut<'b>(view: ArrayViewMut2<'b, T>) -> AudioSampleResult<Self>
1315    where
1316        'b: 'a,
1317    {
1318        if view.is_empty() {
1319            return Err(AudioSampleError::EmptyData);
1320        }
1321        Ok(MultiData(MultiRepr::BorrowedMut(view)))
1322    }
1323
1324    /// Creates an owned `MultiData` from an `Array2`.
1325    ///
1326    /// # Arguments
1327    ///
1328    /// – `array` – the owned 2-D array with shape `(channels, samples_per_channel)`.
1329    ///
1330    /// # Returns
1331    ///
1332    /// `Ok(MultiData)` taking ownership of the array.
1333    ///
1334    /// # Errors
1335    ///
1336    /// Returns [`AudioSampleError::EmptyData`] if `array` is empty.
1337    #[inline]
1338    pub fn from_owned(array: Array2<T>) -> AudioSampleResult<Self> {
1339        if array.is_empty() {
1340            return Err(AudioSampleError::EmptyData);
1341        }
1342        Ok(MultiData(MultiRepr::Owned(array)))
1343    }
1344
1345    /// Unchecked version of ['from_owned]
1346    /// # Returns
1347    ///
1348    /// `MultiData` taking ownership of the array.
1349    ///
1350    /// # Safety
1351    ///
1352    /// Don't pass an empty array
1353    #[inline]
1354    pub const unsafe fn from_owned_unchecked(array: Array2<T>) -> Self {
1355        MultiData(MultiRepr::Owned(array))
1356    }
1357
1358    fn to_mut(&mut self) -> ArrayViewMut2<'_, T> {
1359        self.promote();
1360        match &mut self.0 {
1361            MultiRepr::BorrowedMut(a) => a.view_mut(),
1362            MultiRepr::Owned(a) => a.view_mut(),
1363            MultiRepr::Borrowed(_) => {
1364                unreachable!("Self should have been converted to owned by now")
1365            }
1366        }
1367    }
1368
1369    fn into_owned<'b>(self) -> MultiData<'b, T> {
1370        match self.0 {
1371            MultiRepr::Borrowed(v) => MultiData(MultiRepr::Owned(v.to_owned())),
1372            MultiRepr::BorrowedMut(v) => MultiData(MultiRepr::Owned(v.to_owned())),
1373            MultiRepr::Owned(a) => MultiData(MultiRepr::Owned(a)),
1374        }
1375    }
1376
1377    // Delegation methods for ndarray operations
1378
1379    /// Returns the number of channels (rows).
1380    #[inline]
1381    pub fn nrows(&self) -> NonZeroUsize {
1382        // safety: self is guaranteed non-empty
1383        unsafe { NonZeroUsize::new_unchecked(self.as_view().nrows()) }
1384    }
1385    /// Returns the number of columns (samples per channel) in multi-channel audio data.
1386    #[inline]
1387    pub fn ncols(&self) -> NonZeroUsize {
1388        // safety: self is guaranteed non-empty
1389        unsafe { NonZeroUsize::new_unchecked(self.as_view().ncols()) }
1390    }
1391
1392    /// Returns `(channels, samples_per_channel)`.
1393    #[inline]
1394    pub fn dim(&self) -> (NonZeroUsize, NonZeroUsize) {
1395        let (r, c) = self.as_view().dim();
1396        // safety: self is guaranteed non-empty
1397        unsafe {
1398            (
1399                NonZeroUsize::new_unchecked(r),
1400                NonZeroUsize::new_unchecked(c),
1401            )
1402        }
1403    }
1404
1405    /// Returns the arithmetic mean along `axis`
1406    #[inline]
1407    pub fn mean_axis(&self, axis: Axis) -> Array1<T> {
1408        self.as_view()
1409            .mean_axis(axis)
1410            .expect("self is guaranteed non-empty")
1411    }
1412
1413    /// Returns the arithmetic mean across all samples.
1414    #[inline]
1415    pub fn mean(&self) -> T {
1416        self.as_view().mean().expect("self is guaranteed non-empty")
1417    }
1418
1419    /// Returns the population variances across all the specificed axix
1420    #[inline]
1421    pub fn variance_axis(&self, axis: Axis) -> Array1<f64> {
1422        self.variance_axis_ddof(axis, 0)
1423    }
1424
1425    /// Returns the variance with respect to the specified delta degrees of freedom across the specified axis
1426    #[inline]
1427    pub fn variance_axis_ddof(&self, axis: Axis, ddof: usize) -> Array1<f64> {
1428        let view = self.as_view();
1429        let degrees_of_freedom = (view.len() - ddof) as f64;
1430        let means = self.mean_axis(axis);
1431
1432        view.outer_iter()
1433            .map(|lane| {
1434                lane.iter()
1435                    .zip(means.iter())
1436                    .map(|(&x, &mean)| {
1437                        let mean: f64 = mean.cast_into();
1438                        let diff: f64 = <T as CastInto<f64>>::cast_into(x) - mean;
1439                        diff * diff
1440                    })
1441                    .sum::<f64>()
1442                    / degrees_of_freedom
1443            })
1444            .collect::<Array1<f64>>()
1445    }
1446
1447    /// Returns the population variances across all samples.
1448    #[inline]
1449    pub fn variance(&self) -> Array1<f64> {
1450        self.variance_axis(Axis(0))
1451    }
1452
1453    /// Returns the standard deviations across the specified axis
1454    #[inline]
1455    pub fn stddev_axis(&self, axis: Axis) -> Array1<f64> {
1456        self.stddev_axis_ddof(axis, 0)
1457    }
1458
1459    /// Returns the standard deviations with respect to the specified delta degrees of freedom across the specified axis
1460    #[inline]
1461    pub fn stddev_axis_ddof(&self, axis: Axis, ddof: usize) -> Array1<f64> {
1462        self.variance_axis_ddof(axis, ddof).mapv(f64::sqrt)
1463    }
1464
1465    /// Returns the standard deviations across all samples.
1466    #[inline]
1467    pub fn stddev(&self) -> Array1<f64> {
1468        self.stddev_axis(Axis(0))
1469    }
1470
1471    /// Returns the sum across all samples.
1472    #[inline]
1473    pub fn sum(&self) -> T {
1474        self.as_view().sum()
1475    }
1476
1477    /// Returns a 1D view into `axis` at `index`.
1478    #[inline]
1479    pub fn index_axis(&self, axis: Axis, index: usize) -> ArrayView1<'_, T> {
1480        match &self.0 {
1481            MultiRepr::Borrowed(a) => a.index_axis(axis, index),
1482            MultiRepr::BorrowedMut(a) => a.index_axis(axis, index),
1483            MultiRepr::Owned(a) => a.index_axis(axis, index),
1484        }
1485    }
1486
1487    /// Returns a view of the column at `index`.
1488    #[inline]
1489    pub fn column(&self, index: usize) -> ArrayView1<'_, T> {
1490        match &self.0 {
1491            MultiRepr::Borrowed(v) => v.column(index),
1492            MultiRepr::BorrowedMut(v) => v.column(index),
1493            MultiRepr::Owned(a) => a.column(index),
1494        }
1495    }
1496
1497    /// Returns a sliced 2D view.
1498    #[inline]
1499    pub fn slice<I>(&self, info: I) -> ArrayView2<'_, T>
1500    where
1501        I: ndarray::SliceArg<ndarray::Ix2, OutDim = ndarray::Ix2>,
1502    {
1503        match &self.0 {
1504            MultiRepr::Borrowed(v) => v.slice(info),
1505            MultiRepr::BorrowedMut(v) => v.slice(info),
1506            MultiRepr::Owned(a) => a.slice(info),
1507        }
1508    }
1509
1510    /// Returns a mutable sliced 2D view.
1511    ///
1512    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1513    #[inline]
1514    pub fn slice_mut<I>(&mut self, info: I) -> ArrayViewMut2<'_, T>
1515    where
1516        I: ndarray::SliceArg<ndarray::Ix2, OutDim = ndarray::Ix2>,
1517    {
1518        self.promote();
1519
1520        self.promote();
1521
1522        match &mut self.0 {
1523            MultiRepr::BorrowedMut(a) => a.slice_mut(info),
1524            MultiRepr::Owned(a) => a.slice_mut(info),
1525            MultiRepr::Borrowed(_) => {
1526                unreachable!("Self should have been converted to owned by now")
1527            }
1528        }
1529    }
1530
1531    /// Returns a 2D view of the samples.
1532    #[inline]
1533    pub fn view(&self) -> ArrayView2<'_, T> {
1534        self.as_view()
1535    }
1536
1537    /// Returns a mutable 2D view of the samples.
1538    ///
1539    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1540    #[inline]
1541    pub fn view_mut(&mut self) -> ArrayViewMut2<'_, T> {
1542        self.promote();
1543        match &mut self.0 {
1544            MultiRepr::BorrowedMut(a) => a.view_mut(),
1545            MultiRepr::Owned(a) => a.view_mut(),
1546            MultiRepr::Borrowed(_) => {
1547                unreachable!("Self should have been converted to owned by now")
1548            }
1549        }
1550    }
1551
1552    /// Swaps two axes in-place.
1553    #[inline]
1554    pub fn swap_axes(&mut self, a: usize, b: usize) {
1555        self.to_mut().swap_axes(a, b);
1556    }
1557
1558    /// Returns a mutable 1D view into `axis` at `index`.
1559    ///
1560    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1561    #[inline]
1562    pub fn index_axis_mut(&mut self, axis: Axis, index: usize) -> ArrayViewMut1<'_, T> {
1563        self.promote();
1564        self.promote();
1565        match &mut self.0 {
1566            MultiRepr::BorrowedMut(a) => a.index_axis_mut(axis, index),
1567            MultiRepr::Owned(a) => a.index_axis_mut(axis, index),
1568            MultiRepr::Borrowed(_) => {
1569                unreachable!("Self should have been converted to owned by now")
1570            }
1571        }
1572    }
1573
1574    /// Returns the shape of the underlying `ndarray` buffer.
1575    #[inline]
1576    pub fn shape(&self) -> &[usize] {
1577        match &self.0 {
1578            MultiRepr::Borrowed(v) => v.shape(),
1579            MultiRepr::BorrowedMut(v) => v.shape(),
1580            MultiRepr::Owned(a) => a.shape(),
1581        }
1582    }
1583
1584    /// Applies a value-mapping function in-place.
1585    ///
1586    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1587    #[inline]
1588    pub fn mapv_inplace<F>(&mut self, f: F)
1589    where
1590        F: FnMut(T) -> T,
1591    {
1592        self.to_mut().mapv_inplace(f);
1593    }
1594
1595    /// Returns a mutable iterator over 1D lanes along `axis`.
1596    ///
1597    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1598    #[inline]
1599    pub fn axis_iter_mut(&mut self, axis: Axis) -> AxisIterMut<'_, T, Ix1> {
1600        self.promote();
1601        match &mut self.0 {
1602            MultiRepr::BorrowedMut(a) => a.axis_iter_mut(axis),
1603            MultiRepr::Owned(a) => a.axis_iter_mut(axis),
1604            MultiRepr::Borrowed(_) => {
1605                unreachable!("Self should have been converted to owned by now")
1606            }
1607        }
1608    }
1609
1610    /// Returns a view of the row at `index`.
1611    #[inline]
1612    pub fn row(&self, index: usize) -> ArrayView1<'_, T> {
1613        match &self.0 {
1614            MultiRepr::Borrowed(v) => v.row(index),
1615            MultiRepr::BorrowedMut(v) => v.row(index),
1616            MultiRepr::Owned(a) => a.row(index),
1617        }
1618    }
1619
1620    /// Returns an iterator over all samples (row-major).
1621    #[inline]
1622    pub fn iter(&self) -> ndarray::iter::Iter<'_, T, Ix2> {
1623        match &self.0 {
1624            MultiRepr::Borrowed(v) => v.iter(),
1625            MultiRepr::BorrowedMut(v) => v.iter(),
1626            MultiRepr::Owned(a) => a.iter(),
1627        }
1628    }
1629
1630    /// Returns a mutable iterator over all samples (row-major).
1631    ///
1632    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1633    #[inline]
1634    pub fn iter_mut(&mut self) -> ndarray::iter::IterMut<'_, T, Ix2> {
1635        self.promote();
1636        match &mut self.0 {
1637            MultiRepr::BorrowedMut(a) => a.iter_mut(),
1638            MultiRepr::Owned(a) => a.iter_mut(),
1639            MultiRepr::Borrowed(_) => {
1640                unreachable!("Self should have been converted to owned by now")
1641            }
1642        }
1643    }
1644
1645    /// Returns the total number of samples across all channels.
1646    #[inline]
1647    pub fn len(&self) -> NonZeroUsize {
1648        NonZeroUsize::new(self.as_view().len()).expect("Array is guaranteed to be non-empty")
1649    }
1650
1651    /// Maps each sample into a new `Array2`.
1652    #[inline]
1653    pub fn mapv<U, F>(&self, f: F) -> Array2<U>
1654    where
1655        F: Fn(T) -> U,
1656        U: Clone,
1657    {
1658        self.as_view().mapv(f)
1659    }
1660
1661    /// Returns a mutable pointer to the underlying buffer.
1662    ///
1663    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1664    #[inline]
1665    pub fn as_mut_ptr(&mut self) -> *mut T {
1666        self.to_mut().as_mut_ptr()
1667    }
1668
1669    /// Returns a shared slice if the underlying storage is contiguous.
1670    #[inline]
1671    pub fn as_slice(&self) -> Option<&[T]> {
1672        match &self.0 {
1673            MultiRepr::Borrowed(v) => v.as_slice(),
1674            MultiRepr::BorrowedMut(v) => v.as_slice(),
1675            MultiRepr::Owned(a) => a.as_slice(),
1676        }
1677    }
1678
1679    /// Returns a mutable iterator over the outer axis (rows/channels).
1680    ///
1681    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1682    #[inline]
1683    pub fn outer_iter(&mut self) -> ndarray::iter::AxisIterMut<'_, T, ndarray::Ix1> {
1684        self.promote();
1685        match &mut self.0 {
1686            MultiRepr::BorrowedMut(a) => a.outer_iter_mut(),
1687            MultiRepr::Owned(a) => a.outer_iter_mut(),
1688            MultiRepr::Borrowed(_) => {
1689                unreachable!("Self should have been converted to owned by now")
1690            }
1691        }
1692    }
1693
1694    /// Returns a mutable slice if the underlying storage is contiguous.
1695    ///
1696    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1697    #[inline]
1698    pub fn as_slice_mut(&mut self) -> Option<&mut [T]> {
1699        self.promote();
1700        match &mut self.0 {
1701            MultiRepr::BorrowedMut(a) => a.as_slice_mut(),
1702            MultiRepr::Owned(a) => a.as_slice_mut(),
1703            MultiRepr::Borrowed(_) => {
1704                unreachable!("Self should have been converted to owned by now")
1705            }
1706        }
1707    }
1708
1709    /// Returns an iterator over 1D lanes along `axis`.
1710    #[inline]
1711    pub fn axis_iter(&self, axis: ndarray::Axis) -> ndarray::iter::AxisIter<'_, T, ndarray::Ix1> {
1712        match &self.0 {
1713            MultiRepr::Borrowed(v) => v.axis_iter(axis),
1714            MultiRepr::BorrowedMut(v) => v.axis_iter(axis),
1715            MultiRepr::Owned(a) => a.axis_iter(axis),
1716        }
1717    }
1718
1719    /// Returns the raw dimension.
1720    #[inline]
1721    pub fn raw_dim(&self) -> ndarray::Dim<[usize; 2]> {
1722        self.as_view().raw_dim()
1723    }
1724
1725    /// Returns a mutable view of the row at `index`.
1726    ///
1727    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1728    #[inline]
1729    pub fn row_mut(&mut self, index: usize) -> ndarray::ArrayViewMut1<'_, T> {
1730        self.promote();
1731        match &mut self.0 {
1732            MultiRepr::BorrowedMut(a) => a.row_mut(index),
1733            MultiRepr::Owned(a) => a.row_mut(index),
1734            MultiRepr::Borrowed(_) => {
1735                unreachable!("Self should have been converted to owned by now")
1736            }
1737        }
1738    }
1739
1740    /// Fills all samples with `value`.
1741    ///
1742    /// If the data is currently immutably borrowed, this promotes to owned (allocates).
1743    #[inline]
1744    pub fn fill(&mut self, value: T) {
1745        self.to_mut().fill(value);
1746    }
1747
1748    /// Converts into a raw `Vec<T>` and an offset.
1749    ///
1750    /// For borrowed data, this allocates and clones.
1751    #[inline]
1752    pub fn into_raw_vec_and_offset(self) -> (Vec<T>, usize) {
1753        match self.0 {
1754            MultiRepr::Borrowed(v) => {
1755                let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
1756                (vec, offset.unwrap_or(0))
1757            }
1758            MultiRepr::BorrowedMut(v) => {
1759                let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
1760                (vec, offset.unwrap_or(0))
1761            }
1762            MultiRepr::Owned(a) => {
1763                let (vec, offset) = a.into_raw_vec_and_offset();
1764                (vec, offset.unwrap_or(0))
1765            }
1766        }
1767    }
1768
1769    /// Folds over all samples (row-major).
1770    #[inline]
1771    pub fn fold<B, F>(&self, init: B, f: F) -> B
1772    where
1773        F: FnMut(B, &T) -> B,
1774    {
1775        self.as_view().iter().fold(init, f)
1776    }
1777
1778    /// Converts this wrapper into an owned `Array2<T>`.
1779    ///
1780    /// For borrowed data, this allocates and clones.
1781    #[inline]
1782    pub fn take(self) -> Array2<T> {
1783        match self.0 {
1784            MultiRepr::Borrowed(v) => v.to_owned(),
1785            MultiRepr::BorrowedMut(v) => v.to_owned(),
1786            MultiRepr::Owned(a) => a,
1787        }
1788    }
1789}
1790
1791/// Compares `MultiData` to an `Array2` element-by-element.
1792impl<T> PartialEq<Array2<T>> for MultiData<'_, T>
1793where
1794    T: StandardSample,
1795{
1796    #[inline]
1797    fn eq(&self, other: &Array2<T>) -> bool {
1798        self.as_view() == other.view()
1799    }
1800}
1801
1802/// Compares an `Array2` to a `MultiData` element-by-element.
1803impl<'a, T> PartialEq<MultiData<'a, T>> for Array2<T>
1804where
1805    T: StandardSample,
1806{
1807    #[inline]
1808    fn eq(&self, other: &MultiData<'a, T>) -> bool {
1809        self.view() == other.as_view()
1810    }
1811}
1812
1813/// Iterates over all samples in row-major order (`channel 0` first, then `channel 1`, …).
1814impl<'a, T> IntoIterator for &'a MultiData<'_, T>
1815where
1816    T: StandardSample,
1817{
1818    type Item = &'a T;
1819    type IntoIter = ndarray::iter::Iter<'a, T, ndarray::Ix2>;
1820
1821    #[inline]
1822    fn into_iter(self) -> Self::IntoIter {
1823        self.as_view().into_iter()
1824    }
1825}
1826
1827/// Channel-agnostic container for raw audio sample data.
1828///
1829/// ## Purpose
1830///
1831/// `AudioData` abstracts over the two storage shapes used by the library:
1832/// 1-D mono arrays and 2-D multi-channel arrays. All audio operations that
1833/// must work for both mono and multi-channel audio accept `AudioData<T>` or
1834/// the higher-level [`AudioSamples<T>`] that wraps it.
1835///
1836/// ## Intended Usage
1837///
1838/// Prefer constructing audio through [`AudioSamples`] constructors rather than
1839/// building `AudioData` directly. Use `AudioData` directly only when writing
1840/// low-level operations that need to inspect or replace the underlying array.
1841///
1842/// ## Invariants
1843///
1844/// - `Mono` always contains a non-empty `MonoData` (1-D array with ≥ 1 sample).
1845/// - `Multi` always contains a non-empty `MultiData` (2-D array with ≥ 1 channel
1846///   and ≥ 1 sample per channel).
1847///
1848/// ## Assumptions
1849///
1850/// Callers that use the unsafe constructors (`from_array1`, `from_array2`, …)
1851/// are responsible for upholding the non-empty invariant.
1852#[derive(Debug, PartialEq)]
1853#[allow(clippy::exhaustive_enums)]
1854pub enum AudioData<'a, T>
1855where
1856    T: StandardSample,
1857{
1858    /// Single-channel (mono) audio stored as a 1-D array.
1859    Mono(MonoData<'a, T>),
1860    /// Multi-channel audio stored as a 2-D array with shape `(channels, samples_per_channel)`.
1861    Multi(MultiData<'a, T>),
1862}
1863
1864impl<T> AudioData<'static, T>
1865where
1866    T: StandardSample,
1867{
1868    /// Converts any `AudioData` into a `'static`-lifetime owned instance.
1869    ///
1870    /// Borrowed variants are promoted to owned by cloning the underlying buffer.
1871    /// If the input is already owned this is a zero-copy move.
1872    ///
1873    /// # Arguments
1874    ///
1875    /// – `data` – any `AudioData`, regardless of lifetime.
1876    ///
1877    /// # Returns
1878    ///
1879    /// An `AudioData<'static, T>` with owned storage.
1880    #[inline]
1881    #[must_use]
1882    pub fn from_owned(data: AudioData<'_, T>) -> Self {
1883        data.into_owned()
1884    }
1885
1886    /// Returns the arithmetic mean across all samples.
1887    ///
1888    /// For mono data this is the mean of the single channel. For multi-channel data
1889    /// it is the mean computed across every sample in every channel.
1890    ///
1891    /// # Returns
1892    ///
1893    /// The mean value as type `T`.
1894    #[inline]
1895    #[must_use]
1896    pub fn mean(&self) -> T {
1897        match self {
1898            AudioData::Mono(m) => m.mean(),
1899            AudioData::Multi(m) => m.mean(),
1900        }
1901    }
1902
1903    /// Consumes this `AudioData` and returns the underlying `Array1<T>` if it is mono.
1904    ///
1905    /// # Returns
1906    ///
1907    /// `Some(Array1<T>)` if the variant is `Mono`; `None` if it is `Multi`.
1908    #[inline]
1909    #[must_use]
1910    pub fn into_mono_data(self) -> Option<Array1<T>> {
1911        if let AudioData::Mono(m) = self {
1912            Some(m.take())
1913        } else {
1914            None
1915        }
1916    }
1917
1918    /// Consumes this `AudioData` and returns the underlying `Array2<T>` if it is multi-channel.
1919    ///
1920    /// # Returns
1921    ///
1922    /// `Some(Array2<T>)` if the variant is `Multi`; `None` if it is `Mono`.
1923    #[inline]
1924    #[must_use]
1925    pub fn into_multi_data(self) -> Option<Array2<T>> {
1926        if let AudioData::Multi(m) = self {
1927            Some(m.take())
1928        } else {
1929            None
1930        }
1931    }
1932}
1933
1934/// Clones `AudioData` into an owned instance.
1935///
1936/// Borrowed variants are promoted to owned by cloning the underlying ndarray buffer.
1937/// The resulting clone always uses owned storage regardless of the original variant.
1938impl<T> Clone for AudioData<'_, T>
1939where
1940    T: StandardSample,
1941{
1942    #[inline]
1943    fn clone(&self) -> Self {
1944        match self {
1945            AudioData::Mono(m) => AudioData::Mono(
1946                MonoData::from_owned(m.as_view().to_owned())
1947                    .expect("self has already guaranteed non-emptiness"),
1948            ),
1949            AudioData::Multi(m) => AudioData::Multi(
1950                MultiData::from_owned(m.as_view().to_owned())
1951                    .expect("self has already guaranteed non-emptiness"),
1952            ),
1953        }
1954    }
1955}
1956
1957impl<T> AudioData<'_, T>
1958where
1959    T: StandardSample,
1960{
1961    /// Creates a new mono AudioData from an owned Array1.
1962    ///
1963    /// # Errors
1964    ///
1965    /// - If the data is empty
1966    #[inline]
1967    pub fn new_mono(data: Array1<T>) -> AudioSampleResult<AudioData<'static, T>> {
1968        if data.is_empty() {
1969            return Err(AudioSampleError::EmptyData);
1970        }
1971        Ok(AudioData::Mono(MonoData(MonoRepr::Owned(data))))
1972    }
1973
1974    /// Creates a new multi-channel AudioData from an owned Array2.
1975    ///
1976    /// # Errors
1977    ///
1978    /// - If the data is empty
1979    #[inline]
1980    pub fn new_multi(data: Array2<T>) -> AudioSampleResult<AudioData<'static, T>> {
1981        if data.is_empty() {
1982            return Err(AudioSampleError::EmptyData);
1983        }
1984        Ok(AudioData::Multi(MultiData(MultiRepr::Owned(data))))
1985    }
1986    /// Create a borrowed version of self
1987    #[inline]
1988    #[must_use]
1989    pub fn borrow(&self) -> AudioData<'_, T> {
1990        match self {
1991            AudioData::Mono(mono_data) => {
1992                AudioData::Mono(MonoData(MonoRepr::Borrowed(mono_data.as_view())))
1993            }
1994            AudioData::Multi(multi_data) => {
1995                AudioData::Multi(MultiData(MultiRepr::Borrowed(multi_data.as_view())))
1996            }
1997        }
1998    }
1999
2000    /// Returns all samples as a `Vec<T>`.
2001    ///
2002    /// For mono data the vec has `samples_per_channel` elements. For multi-channel
2003    /// data the samples are collected in row-major order: all samples for channel 0,
2004    /// then channel 1, etc. This allocates a new `Vec` on every call.
2005    ///
2006    /// # Returns
2007    ///
2008    /// A `Vec<T>` containing every sample.
2009    #[inline]
2010    #[must_use]
2011    pub fn as_vec(&self) -> Vec<T> {
2012        match &self {
2013            AudioData::Mono(mono_data) => mono_data.as_view().to_vec(),
2014            AudioData::Multi(multi_data) => multi_data.as_view().iter().copied().collect(),
2015        }
2016    }
2017
2018    /// Returns the number of frames (time steps) in the audio data.
2019    ///
2020    /// A *frame* contains one sample per channel at a given point in time.
2021    /// For mono data this equals the total number of samples. For multi-channel
2022    /// data it equals `samples_per_channel`.
2023    ///
2024    /// # Returns
2025    ///
2026    /// The frame count as a non-zero `usize`.
2027    #[inline]
2028    #[must_use]
2029    pub fn total_frames(&self) -> NonZeroUsize {
2030        match self {
2031            AudioData::Mono(_) => self.len(),
2032            AudioData::Multi(multi_data) => multi_data.ncols(),
2033        }
2034    }
2035
2036    /// Returns `true` if the underlying ndarray buffer uses standard (C) memory layout.
2037    ///
2038    /// Standard layout means elements are stored in row-major order with unit strides.
2039    /// Some operations (e.g. raw pointer access, SIMD paths) require standard layout.
2040    ///
2041    /// # Returns
2042    ///
2043    /// `true` if the storage is contiguous and row-major, `false` otherwise.
2044    #[inline]
2045    #[must_use]
2046    pub fn is_standard_layout(&self) -> bool {
2047        match &self {
2048            AudioData::Mono(m) => m.as_view().is_standard_layout(),
2049            AudioData::Multi(m) => m.as_view().is_standard_layout(),
2050        }
2051    }
2052
2053    /// Creates mono AudioData from a slice (borrowed).
2054    #[inline]
2055    #[must_use]
2056    pub fn from_slice(slice: &NonEmptySlice<T>) -> AudioData<'_, T> {
2057        let view = ArrayView1::from(slice);
2058        AudioData::Mono(MonoData(MonoRepr::Borrowed(view)))
2059    }
2060
2061    /// Creates mono AudioData from a mutable slice (borrowed).
2062    #[inline]
2063    #[must_use]
2064    pub fn from_slice_mut(slice: &mut NonEmptySlice<T>) -> AudioData<'_, T> {
2065        let view = ArrayViewMut1::from(slice);
2066        AudioData::Mono(MonoData(MonoRepr::BorrowedMut(view)))
2067    }
2068
2069    /// Creates multi-channel AudioData from a slice with specified channel count (borrowed).
2070    ///
2071    /// # Errors
2072    /// - If the slice cannot be reshaped into the desired shape
2073    #[inline]
2074    pub fn from_slice_multi(
2075        slice: &NonEmptySlice<T>,
2076        channels: ChannelCount,
2077    ) -> AudioSampleResult<AudioData<'_, T>> {
2078        let total_samples = slice.len().get();
2079        let samples_per_channel = total_samples / channels.get() as usize;
2080        let view = ArrayView2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
2081        Ok(AudioData::Multi(MultiData(MultiRepr::Borrowed(view))))
2082    }
2083
2084    /// Creates multi-channel AudioData from a mutable slice with specified channel count (borrowed).
2085    ///
2086    /// # Errors
2087    ///
2088    /// - If the slice cannot be reshaped into the desired shape
2089    #[inline]
2090    pub fn from_slice_multi_mut(
2091        slice: &mut NonEmptySlice<T>,
2092        channels: ChannelCount,
2093    ) -> AudioSampleResult<AudioData<'_, T>> {
2094        let total_samples = slice.len().get();
2095        let samples_per_channel = total_samples / channels.get() as usize;
2096        let view =
2097            ArrayViewMut2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
2098        Ok(AudioData::Multi(MultiData(MultiRepr::BorrowedMut(view))))
2099    }
2100
2101    /// Creates mono AudioData from a Vec (owned).
2102    #[inline]
2103    #[must_use]
2104    pub fn from_vec(vec: NonEmptyVec<T>) -> AudioData<'static, T> {
2105        AudioData::Mono(MonoData(MonoRepr::Owned(Array1::from(vec.to_vec()))))
2106    }
2107
2108    /// Creates multi-channel AudioData from a Vec with specified channel count (owned).
2109    ///
2110    /// # Errors
2111    ///
2112    /// - If the Vec cannot be reshaped into the desired shape
2113    #[inline]
2114    pub fn from_vec_multi(
2115        vec: NonEmptyVec<T>,
2116        channels: ChannelCount,
2117    ) -> AudioSampleResult<AudioData<'static, T>> {
2118        let total_samples = vec.len().get();
2119        let samples_per_channel = total_samples / channels.get() as usize;
2120        let arr =
2121            Array2::from_shape_vec((channels.get() as usize, samples_per_channel), vec.to_vec())?;
2122        Ok(AudioData::Multi(MultiData(MultiRepr::Owned(arr))))
2123    }
2124}
2125
2126// Main implementation block for AudioData
2127impl<'a, T> AudioData<'a, T>
2128where
2129    T: StandardSample,
2130{
2131    /// Creates mono AudioData from an owned Array1.
2132    ///
2133    /// # Safety
2134    ///
2135    /// Caller must ensure that the array is non-empty.
2136    #[inline]
2137    #[must_use]
2138    pub const unsafe fn from_array1(arr: Array1<T>) -> Self {
2139        AudioData::Mono(MonoData(MonoRepr::Owned(arr)))
2140    }
2141
2142    /// Creates multi-channel AudioData from an owned Array2.
2143    ///
2144    /// # Safety
2145    ///
2146    /// Caller must ensure that the array is non-empty.
2147    #[inline]
2148    #[must_use]
2149    pub const unsafe fn from_array2(arr: Array2<T>) -> Self {
2150        AudioData::Multi(MultiData(MultiRepr::Owned(arr)))
2151    }
2152
2153    /// Wraps a pre-validated [`MonoData`] in `AudioData::Mono`.
2154    ///
2155    /// # Safety
2156    ///
2157    /// `data` must be non-empty. Callers that construct `MonoData` through the safe
2158    /// `from_owned` / `from_view` / `from_view_mut` constructors already uphold this
2159    /// invariant.
2160    #[inline]
2161    #[must_use]
2162    pub const unsafe fn from_mono_data(data: MonoData<'a, T>) -> Self {
2163        AudioData::Mono(data)
2164    }
2165
2166    /// Wraps a pre-validated [`MultiData`] in `AudioData::Multi`.
2167    ///
2168    /// # Safety
2169    ///
2170    /// `data` must be non-empty. Callers that construct `MultiData` through the safe
2171    /// `from_owned` / `from_view` / `from_view_mut` constructors already uphold this
2172    /// invariant.
2173    #[inline]
2174    #[must_use]
2175    pub const unsafe fn from_multi_data(data: MultiData<'a, T>) -> Self {
2176        AudioData::Multi(data)
2177    }
2178
2179    /// Creates a borrowed mono `AudioData` from a 1-D ndarray view.
2180    ///
2181    /// # Safety
2182    ///
2183    /// `view` must be non-empty.
2184    #[inline]
2185    #[must_use]
2186    pub const unsafe fn from_array1_view(view: ArrayView1<'a, T>) -> Self {
2187        AudioData::Mono(MonoData(MonoRepr::Borrowed(view)))
2188    }
2189
2190    /// Creates a borrowed multi-channel `AudioData` from a 2-D ndarray view.
2191    ///
2192    /// # Safety
2193    ///
2194    /// `view` must be non-empty and have shape `(channels, samples_per_channel)`.
2195    #[inline]
2196    #[must_use]
2197    pub const unsafe fn from_array2_view(view: ArrayView2<'a, T>) -> Self {
2198        AudioData::Multi(MultiData(MultiRepr::Borrowed(view)))
2199    }
2200
2201    /// Converts this AudioData to owned data.
2202    #[inline]
2203    #[must_use]
2204    pub fn into_owned<'b>(self) -> AudioData<'b, T> {
2205        match self {
2206            AudioData::Mono(m) => AudioData::Mono(m.into_owned()),
2207            AudioData::Multi(m) => AudioData::Multi(m.into_owned()),
2208        }
2209    }
2210
2211    /// Creates a new AudioData instance from borrowed data.
2212    #[inline]
2213    #[must_use]
2214    pub fn from_borrowed(&self) -> AudioData<'_, T> {
2215        match self {
2216            AudioData::Mono(m) => AudioData::Mono(MonoData(MonoRepr::Borrowed(m.as_view()))),
2217            AudioData::Multi(m) => AudioData::Multi(MultiData(MultiRepr::Borrowed(m.as_view()))),
2218        }
2219    }
2220
2221    /// Creates AudioData from a borrowed mono array view.
2222    ///
2223    /// # Errors
2224    ///
2225    /// - Returns [`AudioSampleError::EmptyData`] if `view` is empty.
2226    #[inline]
2227    pub fn from_borrowed_array1(view: ArrayView1<'_, T>) -> AudioSampleResult<AudioData<'_, T>> {
2228        if view.is_empty() {
2229            return Err(AudioSampleError::EmptyData);
2230        }
2231        Ok(AudioData::Mono(MonoData(MonoRepr::Borrowed(view))))
2232    }
2233
2234    /// Creates a borrowed mono `AudioData` from an immutable 1-D view, without checking for emptiness.
2235    ///
2236    /// Prefer [`from_borrowed_array1`](AudioData::from_borrowed_array1) unless the view is
2237    /// already guaranteed non-empty and the check is measurably expensive.
2238    ///
2239    /// # Safety
2240    ///
2241    /// `view` must be non-empty.
2242    #[inline]
2243    #[must_use]
2244    pub const unsafe fn from_borrowed_array1_unchecked(
2245        view: ArrayView1<'_, T>,
2246    ) -> AudioData<'_, T> {
2247        AudioData::Mono(MonoData(MonoRepr::Borrowed(view)))
2248    }
2249
2250    /// Creates a mutably-borrowed mono `AudioData` from a mutable 1-D view.
2251    ///
2252    /// # Returns
2253    ///
2254    /// `Ok(AudioData::Mono)` wrapping the mutable view.
2255    ///
2256    /// # Errors
2257    ///
2258    /// Returns [`AudioSampleError::EmptyData`] if `view` is empty.
2259    #[inline]
2260    pub fn from_borrowed_array1_mut(
2261        view: ArrayViewMut1<'_, T>,
2262    ) -> AudioSampleResult<AudioData<'_, T>> {
2263        if view.is_empty() {
2264            return Err(AudioSampleError::EmptyData);
2265        }
2266        Ok(AudioData::Mono(MonoData(MonoRepr::BorrowedMut(view))))
2267    }
2268
2269    /// Creates a mutably-borrowed mono `AudioData` from a mutable 1-D view, without checking for emptiness.
2270    ///
2271    /// Prefer [`from_borrowed_array1_mut`](AudioData::from_borrowed_array1_mut) unless the
2272    /// view is already guaranteed non-empty.
2273    ///
2274    /// # Safety
2275    ///
2276    /// `view` must be non-empty.
2277    #[inline]
2278    #[must_use]
2279    pub const unsafe fn from_borrowed_array1_mut_unchecked(
2280        view: ArrayViewMut1<'_, T>,
2281    ) -> AudioData<'_, T> {
2282        AudioData::Mono(MonoData(MonoRepr::BorrowedMut(view)))
2283    }
2284
2285    /// Creates a borrowed multi-channel `AudioData` from an immutable 2-D view.
2286    ///
2287    /// # Returns
2288    ///
2289    /// `Ok(AudioData::Multi)` wrapping the view.
2290    ///
2291    /// # Errors
2292    ///
2293    /// Returns [`AudioSampleError::EmptyData`] if `view` is empty.
2294    #[inline]
2295    pub fn from_borrowed_array2(view: ArrayView2<'_, T>) -> AudioSampleResult<AudioData<'_, T>> {
2296        if view.is_empty() {
2297            return Err(AudioSampleError::EmptyData);
2298        }
2299        Ok(AudioData::Multi(MultiData(MultiRepr::Borrowed(view))))
2300    }
2301
2302    /// Creates a borrowed multi-channel `AudioData` from an immutable 2-D view, without checking for emptiness.
2303    ///
2304    /// Prefer [`from_borrowed_array2`](AudioData::from_borrowed_array2) unless the view is
2305    /// already guaranteed non-empty.
2306    ///
2307    /// # Safety
2308    ///
2309    /// `view` must be non-empty and have shape `(channels, samples_per_channel)`.
2310    #[inline]
2311    #[must_use]
2312    pub const unsafe fn from_borrowed_array2_unchecked(
2313        view: ArrayView2<'_, T>,
2314    ) -> AudioData<'_, T> {
2315        AudioData::Multi(MultiData(MultiRepr::Borrowed(view)))
2316    }
2317
2318    /// Creates a mutably-borrowed multi-channel `AudioData` from a mutable 2-D view.
2319    ///
2320    /// # Returns
2321    ///
2322    /// `Ok(AudioData::Multi)` wrapping the mutable view.
2323    ///
2324    /// # Errors
2325    ///
2326    /// Returns [`AudioSampleError::EmptyData`] if `view` is empty.
2327    #[inline]
2328    pub fn from_borrowed_array2_mut(
2329        view: ArrayViewMut2<'_, T>,
2330    ) -> AudioSampleResult<AudioData<'_, T>> {
2331        if view.is_empty() {
2332            return Err(AudioSampleError::EmptyData);
2333        }
2334        Ok(AudioData::Multi(MultiData(MultiRepr::BorrowedMut(view))))
2335    }
2336
2337    /// Creates a mutably-borrowed multi-channel `AudioData` from a mutable 2-D view, without checking for emptiness.
2338    ///
2339    /// Prefer [`from_borrowed_array2_mut`](AudioData::from_borrowed_array2_mut) unless the
2340    /// view is already guaranteed non-empty.
2341    ///
2342    /// # Safety
2343    ///
2344    /// `view` must be non-empty and have shape `(channels, samples_per_channel)`.
2345    #[inline]
2346    #[must_use]
2347    pub const unsafe fn from_borrowed_array2_mut_unchecked(
2348        view: ArrayViewMut2<'_, T>,
2349    ) -> AudioData<'_, T> {
2350        AudioData::Multi(MultiData(MultiRepr::BorrowedMut(view)))
2351    }
2352
2353    /// Returns the total number of samples in the audio data.
2354    #[inline]
2355    #[must_use]
2356    pub fn len(&self) -> NonZeroUsize {
2357        match self {
2358            AudioData::Mono(m) => m.len(),
2359            AudioData::Multi(m) => m.len(),
2360        }
2361    }
2362
2363    /// Returns the number of channels in the audio data.
2364    #[inline]
2365    #[must_use]
2366    pub fn num_channels(&self) -> ChannelCount {
2367        match self {
2368            AudioData::Mono(_) => channels!(1),
2369            // safety: self is non-empty therefore guaranteed to have at least one channel
2370            AudioData::Multi(m) => unsafe { ChannelCount::new_unchecked(m.shape()[0] as u32) },
2371        }
2372    }
2373
2374    /// Returns true if the audio data is mono (single channel).
2375    #[inline]
2376    #[must_use]
2377    pub const fn is_mono(&self) -> bool {
2378        matches!(self, AudioData::Mono(_))
2379    }
2380
2381    /// Returns true if the audio data has multiple channels.
2382    #[inline]
2383    #[must_use]
2384    pub const fn is_multi_channel(&self) -> bool {
2385        matches!(self, AudioData::Multi(_))
2386    }
2387
2388    /// Returns the shape of the underlying array data.
2389    #[inline]
2390    #[must_use]
2391    pub fn shape(&self) -> &[usize] {
2392        match &self {
2393            AudioData::Mono(m) => m.shape(),
2394            AudioData::Multi(m) => m.shape(),
2395        }
2396    }
2397
2398    /// Returns the number of samples per channel.
2399    #[inline]
2400    #[must_use]
2401    pub fn samples_per_channel(&self) -> NonZeroUsize {
2402        match self {
2403            AudioData::Mono(m) => {
2404                // safety: self is non-empty therefore len is NonZero
2405                unsafe { NonZeroUsize::new_unchecked(m.as_view().len()) }
2406            }
2407            // safety: self is non-empty therefore len is NonZero
2408            AudioData::Multi(m) => unsafe { NonZeroUsize::new_unchecked(m.as_view().shape()[1]) },
2409        }
2410    }
2411
2412    /// Returns audio data as a slice if contiguous.
2413    #[inline]
2414    #[must_use]
2415    pub fn as_slice(&self) -> Option<&[T]> {
2416        match &self {
2417            AudioData::Mono(m) => m.as_slice(),
2418            AudioData::Multi(m) => m.as_slice(),
2419        }
2420    }
2421
2422    /// Returns a contiguous byte view when possible, falling back to I24 packing when required.
2423    ///
2424    /// # Errors
2425    ///
2426    /// - if the underlying data is not in a standard layout and cannot be safely viewed as bytes.
2427    #[inline]
2428    pub fn bytes(&self) -> AudioSampleResult<AudioBytes<'_>> {
2429        let slice = self.as_slice().ok_or_else(|| {
2430            AudioSampleError::Layout(LayoutError::NonContiguous {
2431                operation: "bytes view".to_string(),
2432                layout_type: "non-contiguous audio data".to_string(),
2433            })
2434        })?;
2435
2436        if TypeId::of::<T>() == TypeId::of::<I24>() {
2437            // I24 is 4 bytes in memory but 3 bytes on disk; pack to owned bytes.
2438            // safety: self is non-empty
2439            let i24_slice: &[I24] =
2440                unsafe { core::slice::from_raw_parts(slice.as_ptr().cast::<I24>(), slice.len()) };
2441            let packed = I24::write_i24s_ne(i24_slice);
2442            // safety: self is non-empty
2443            let packed = unsafe { NonEmptyByteVec::new_unchecked(packed) };
2444            return Ok(AudioBytes::Owned(packed));
2445        }
2446
2447        let byte_len = std::mem::size_of_val(slice);
2448        let byte_ptr = slice.as_ptr().cast::<u8>();
2449        // safety: self is non-empty
2450        let bytes = unsafe { core::slice::from_raw_parts(byte_ptr, byte_len) };
2451        // safety: self is non-empty
2452        let bytes = unsafe { NonEmptySlice::new_unchecked(bytes) };
2453        Ok(AudioBytes::Borrowed(bytes))
2454    }
2455
2456    /// Returns the number of bytes per sample for the current sample type.
2457    #[inline]
2458    #[must_use]
2459    pub const fn bytes_per_sample(&self) -> NonZeroU32 {
2460        // Safety: T::BYTES is guaranteed to be non-zero for StandardSample types
2461        unsafe { NonZeroU32::new_unchecked(T::BYTES) }
2462    }
2463
2464    /// Returns the number of bits per sample for the current sample type.
2465    #[inline]
2466    #[must_use]
2467    pub const fn bits_per_sample(&self) -> NonZeroU8 {
2468        // Safety: T::BITS is guaranteed to be non-zero for StandardSample types
2469        unsafe { NonZeroU8::new_unchecked(T::BITS) }
2470    }
2471
2472    /// Returns an owned byte buffer.
2473    ///
2474    /// # Errors
2475    ///
2476    /// - If the underlying data is not in a standard layout and cannot be safely viewed as bytes.
2477    #[inline]
2478    pub fn into_bytes(&self) -> AudioSampleResult<NonEmptyByteVec> {
2479        self.bytes().map(AudioBytes::into_owned)
2480    }
2481
2482    /// Maps a function over each sample, returning new owned audio data.
2483    #[inline]
2484    pub fn mapv<U, F>(&self, f: F) -> AudioData<'static, U>
2485    where
2486        F: Fn(T) -> U,
2487        U: StandardSample,
2488    {
2489        match self {
2490            AudioData::Mono(m) => {
2491                let out = m.as_view().mapv(f);
2492                AudioData::Mono(MonoData(MonoRepr::Owned(out)))
2493            }
2494            AudioData::Multi(m) => {
2495                let out = m.as_view().mapv(f);
2496                AudioData::Multi(MultiData(MultiRepr::Owned(out)))
2497            }
2498        }
2499    }
2500
2501    /// Maps a function over each sample in place.
2502    ///
2503    /// # Arguments
2504    ///
2505    /// - `f` - a function that takes a sample of type `T` and returns a new sample of the same type.
2506    #[inline]
2507    pub fn mapv_inplace<F>(&mut self, f: F)
2508    where
2509        F: Fn(T) -> T,
2510    {
2511        match self {
2512            AudioData::Mono(m) => m.to_mut().iter_mut().for_each(|x| *x = f(*x)),
2513            AudioData::Multi(m) => m.to_mut().iter_mut().for_each(|x| *x = f(*x)),
2514        }
2515    }
2516
2517    /// Applies a function to each sample in place.
2518    ///
2519    /// # Arguments
2520    ///
2521    /// - `func` - a function that takes a sample of type `T` and returns a new sample of the same type.
2522    #[inline]
2523    pub fn apply<F>(&mut self, func: F)
2524    where
2525        F: Fn(T) -> T,
2526    {
2527        self.mapv_inplace(func);
2528    }
2529
2530    /// Applies a function to each sample with its index in place.
2531    ///
2532    /// # Arguments
2533    ///
2534    /// - `func` - a function that takes the index of the sample and the sample value, and returns a new sample of the same type.
2535    #[inline]
2536    pub fn apply_with_index<F>(&mut self, func: F)
2537    where
2538        F: Fn(usize, T) -> T,
2539    {
2540        match self {
2541            AudioData::Mono(m) => {
2542                for (i, x) in m.to_mut().iter_mut().enumerate() {
2543                    *x = func(i, *x);
2544                }
2545            }
2546            AudioData::Multi(m) => {
2547                // index by frame within channel
2548                for mut row in m.to_mut().rows_mut() {
2549                    for (i, x) in row.iter_mut().enumerate() {
2550                        *x = func(i, *x);
2551                    }
2552                }
2553            }
2554        }
2555    }
2556
2557    /// Applies a windowed function to the audio data with overlap processing.
2558    ///
2559    /// # Arguments
2560    ///
2561    /// - `window_size` - the size of the window to apply the function to.
2562    /// - `hop_size` - the number of samples to advance the window for each application
2563    /// - `func` - a function that takes the current window of samples and the previous window, and returns a new window of processed samples.
2564    ///
2565    /// # Errors
2566    ///
2567    /// - If the underlying data is not in a standard layout and cannot be safely viewed as slices.
2568    #[inline]
2569    pub fn apply_windowed<F>(
2570        &mut self,
2571        window_size: NonZeroUsize,
2572        hop_size: NonZeroUsize,
2573        func: F,
2574    ) -> AudioSampleResult<()>
2575    where
2576        F: Fn(&[T], &[T]) -> Vec<T>, // (current_window, prev_window) -> processed_window
2577    {
2578        let window_size = window_size.get();
2579        let hop_size = hop_size.get();
2580
2581        match self {
2582            AudioData::Mono(m) => {
2583                let data = m.as_view();
2584                let x = data.as_slice().ok_or_else(|| {
2585                    AudioSampleError::Layout(LayoutError::NonContiguous {
2586                        operation: "mono processing".to_string(),
2587                        layout_type: "non-contiguous mono data".to_string(),
2588                    })
2589                })?;
2590
2591                let n = x.len();
2592                if n < window_size {
2593                    return Ok(());
2594                }
2595
2596                let num_windows = (n - window_size) / hop_size + 1;
2597                let out_len = (num_windows - 1) * hop_size + window_size;
2598
2599                let mut result = vec![T::default(); out_len];
2600                let mut overlap = vec![0usize; out_len];
2601                let mut prev = vec![T::default(); window_size];
2602
2603                for w in 0..num_windows {
2604                    let pos = w * hop_size;
2605                    let win = &x[pos..pos + window_size];
2606                    let processed = func(win, &prev);
2607
2608                    // overlap-add
2609                    for (i, &s) in processed.iter().enumerate() {
2610                        let idx = pos + i;
2611                        result[idx] += s;
2612                        overlap[idx] += 1;
2613                    }
2614
2615                    prev.copy_from_slice(win);
2616                }
2617
2618                // normalise overlaps
2619                for (y, &c) in result.iter_mut().zip(&overlap) {
2620                    if c > 1 {
2621                        *y /= T::cast_from(c);
2622                    }
2623                }
2624
2625                // REPLACE THE VARIANT (don’t try to mutate inner binding)
2626                m.0 = MonoRepr::Owned(ndarray::Array1::from(result));
2627                Ok(())
2628            }
2629
2630            AudioData::Multi(m) => {
2631                let view = m.as_view();
2632                let (ch, spc) = view.dim();
2633                if spc < window_size {
2634                    return Ok(());
2635                }
2636
2637                let num_windows = (spc - window_size) / hop_size + 1;
2638                let out_len = (num_windows - 1) * hop_size + window_size;
2639
2640                let mut out = ndarray::Array2::from_elem((ch, out_len), T::default());
2641                let mut cnt = vec![0usize; out_len];
2642                let mut prev = vec![T::default(); window_size];
2643
2644                for c in 0..ch {
2645                    let row = view.row(c);
2646                    let x = row.as_slice().ok_or_else(|| {
2647                        AudioSampleError::Layout(LayoutError::NonContiguous {
2648                            operation: "multi-channel row processing".to_string(),
2649                            layout_type: "non-contiguous row data".to_string(),
2650                        })
2651                    })?;
2652
2653                    cnt.fill(0);
2654                    prev.fill(T::default());
2655
2656                    for w in 0..num_windows {
2657                        let pos = w * hop_size;
2658                        let win = &x[pos..pos + window_size];
2659                        let processed = func(win, &prev);
2660
2661                        for (i, &s) in processed.iter().enumerate() {
2662                            let idx = pos + i;
2663                            out[[c, idx]] += s;
2664                            cnt[idx] += 1;
2665                        }
2666                        prev.copy_from_slice(win);
2667                    }
2668
2669                    for i in 0..out_len {
2670                        if cnt[i] > 1 {
2671                            out[[c, i]] /= T::cast_from(cnt[i]);
2672                        }
2673                    }
2674                }
2675
2676                // REPLACE THE VARIANT
2677                m.0 = MultiRepr::Owned(out);
2678                Ok(())
2679            }
2680        }
2681    }
2682
2683    /// Applies a function to all samples in all channels.
2684    ///
2685    /// # Arguments
2686    ///
2687    /// - `f` - a function that takes a sample of type `T` and returns a new sample of the same type.
2688    #[inline]
2689    pub fn apply_to_all_channels<F>(&mut self, f: F)
2690    where
2691        F: Fn(T) -> T,
2692    {
2693        self.mapv_inplace(f);
2694    }
2695
2696    /// Applies a function to samples in specified channels only.
2697    ///
2698    /// # Arguments
2699    ///
2700    /// - `channels` - a slice of channel indices to apply the function to.
2701    /// - `f` - a function that takes a sample of type `T` and
2702    #[inline]
2703    pub fn apply_to_channels<F>(&mut self, channels: &[u32], f: F)
2704    where
2705        F: Fn(T) -> T,
2706    {
2707        match self {
2708            AudioData::Mono(m) => m.to_mut().iter_mut().for_each(|x| *x = f(*x)),
2709            AudioData::Multi(m) => {
2710                let mut a = m.to_mut();
2711                for (ch_idx, mut row) in a.axis_iter_mut(Axis(0)).enumerate() {
2712                    if channels.contains(&(ch_idx as u32)) {
2713                        for x in &mut row {
2714                            *x = f(*x);
2715                        }
2716                    }
2717                }
2718            }
2719        }
2720    }
2721    /// Converts the samples to another sample type.
2722    ///
2723    /// This is an audio-aware conversion using [`ConvertTo`], so it can clamp and scale
2724    /// as needed for the source/target sample formats.
2725    ///
2726    /// # Returns
2727    ///
2728    /// A new `AudioData` instance with the same shape but samples converted to type `O`.
2729    #[inline]
2730    #[must_use]
2731    pub fn convert_to<O>(&self) -> AudioData<'static, O>
2732    where
2733        O: StandardSample + ConvertTo<T> + ConvertFrom<T>,
2734    {
2735        match self {
2736            AudioData::Mono(m) => {
2737                let out = m.as_view().mapv(super::traits::ConvertTo::convert_to);
2738                AudioData::Mono(MonoData(MonoRepr::Owned(out)))
2739            }
2740            AudioData::Multi(m) => {
2741                let out = m.as_view().mapv(super::traits::ConvertTo::convert_to);
2742                AudioData::Multi(MultiData(MultiRepr::Owned(out)))
2743            }
2744        }
2745    }
2746    /// Converts the audio data to an interleaved vector, consuming the data.
2747    ///
2748    /// # Returns
2749    ///
2750    /// A `NonEmptyVec<T>` containing the interleaved samples.
2751    ///
2752    /// # Panics
2753    ///
2754    /// Only panics if self.num_channels does not divide self.len() which at this point is guaranteed
2755    #[inline]
2756    #[must_use]
2757    pub fn to_interleaved_vec(self) -> NonEmptyVec<T> {
2758        match self {
2759            // safety: Self is guaranteed to be non-empty by construction, and to_vec() preserves length
2760            AudioData::Mono(m) => match m.0 {
2761                // safety: Self is guaranteed to be non-empty by construction, and to_vec() preserves length
2762                MonoRepr::Borrowed(v) => unsafe { NonEmptyVec::new_unchecked(v.to_vec()) },
2763                // safety: Self is guaranteed to be non-empty by construction, and to_vec() preserves length
2764                MonoRepr::BorrowedMut(v) => unsafe { NonEmptyVec::new_unchecked(v.to_vec()) },
2765                // safety: Self is guaranteed to be non-empty by construction, and to_vec() preserves length
2766                MonoRepr::Owned(a) => unsafe { NonEmptyVec::new_unchecked(a.to_vec()) },
2767            },
2768            AudioData::Multi(m) => {
2769                let (ch, _spc) = m.as_view().dim();
2770                // Get planar data as contiguous slice
2771                let planar: Vec<T> = m
2772                    .as_view()
2773                    .as_slice()
2774                    .map_or_else(|| m.as_view().iter().copied().collect(), <[T]>::to_vec);
2775                // safety: Self is guaranteed to be non-empty by construction
2776                let planar = unsafe { NonEmptyVec::new_unchecked(planar) };
2777
2778                // safety: ch is non-zero by construction
2779                let ch = unsafe { NonZeroU32::new_unchecked(ch as u32) };
2780                // Use optimized interleave
2781                crate::simd_conversions::interleave_multi_vec(&planar, ch)
2782                    .expect("Interleave failed - this should not happen with valid input")
2783            }
2784        }
2785    }
2786
2787    /// Returns the audio data as an interleaved vector without consuming the data.
2788    ///
2789    /// # Returns
2790    ///
2791    /// A `NonEmptyVec<T>` containing the interleaved samples.
2792    ///
2793    /// # Panics
2794    ///
2795    /// Will only ever panic if self.num_channel does not divide into self.len()
2796    /// In this part of the codebase, this is impossible.
2797    #[inline]
2798    #[must_use]
2799    pub fn as_interleaved_vec(&self) -> NonEmptyVec<T> {
2800        match self {
2801            // safety: self is guaranteed non-empty
2802            AudioData::Mono(m) => unsafe { NonEmptyVec::new_unchecked(m.as_view().to_vec()) },
2803            AudioData::Multi(m) => {
2804                let v = m.as_view();
2805                let (ch, _spc) = v.dim();
2806                // Get planar data as contiguous slice
2807                let planar: Vec<T> = v
2808                    .as_slice()
2809                    .map_or_else(|| v.iter().copied().collect(), <[T]>::to_vec);
2810                // safety: non-empty by design
2811                let planar = unsafe { NonEmptyVec::new_unchecked(planar) };
2812                // safety: channels non-zero by design
2813                let ch = unsafe { NonZeroU32::new_unchecked(ch as u32) };
2814                crate::simd_conversions::interleave_multi_vec(&planar, ch)
2815                    .expect("Interleave failed - this should not happen with valid input")
2816            }
2817        }
2818    }
2819}
2820
2821/// Converts a borrowed 1-D view into a mono `AudioData`.
2822///
2823/// # Errors
2824///
2825/// Returns [`AudioSampleError::EmptyData`] if `arr` is empty.
2826impl<'a, T> TryFrom<ArrayView1<'a, T>> for AudioData<'a, T>
2827where
2828    T: StandardSample,
2829{
2830    type Error = AudioSampleError;
2831
2832    #[inline]
2833    fn try_from(arr: ArrayView1<'a, T>) -> Result<Self, Self::Error> {
2834        if arr.is_empty() {
2835            return Err(AudioSampleError::EmptyData);
2836        }
2837        Ok(AudioData::Mono(MonoData(MonoRepr::Borrowed(arr))))
2838    }
2839}
2840
2841/// Converts a mutable borrowed 1-D view into a mono `AudioData`.
2842///
2843/// # Errors
2844///
2845/// Returns [`AudioSampleError::EmptyData`] if `arr` is empty.
2846impl<'a, T> TryFrom<ArrayViewMut1<'a, T>> for AudioData<'a, T>
2847where
2848    T: StandardSample,
2849{
2850    type Error = AudioSampleError;
2851
2852    #[inline]
2853    fn try_from(arr: ArrayViewMut1<'a, T>) -> Result<Self, Self::Error> {
2854        if arr.is_empty() {
2855            return Err(AudioSampleError::EmptyData);
2856        }
2857        Ok(AudioData::Mono(MonoData(MonoRepr::BorrowedMut(arr))))
2858    }
2859}
2860
2861/// Converts a borrowed 2-D view into a multi-channel `AudioData`.
2862///
2863/// The view must have shape `(channels, samples_per_channel)`.
2864///
2865/// # Errors
2866///
2867/// Returns [`AudioSampleError::EmptyData`] if `arr` is empty.
2868impl<'a, T> TryFrom<ArrayView2<'a, T>> for AudioData<'a, T>
2869where
2870    T: StandardSample,
2871{
2872    type Error = AudioSampleError;
2873
2874    #[inline]
2875    fn try_from(arr: ArrayView2<'a, T>) -> Result<Self, Self::Error> {
2876        if arr.is_empty() {
2877            return Err(AudioSampleError::EmptyData);
2878        }
2879        Ok(AudioData::Multi(MultiData(MultiRepr::Borrowed(arr))))
2880    }
2881}
2882
2883/// Converts a mutable borrowed 2-D view into a multi-channel `AudioData`.
2884///
2885/// The view must have shape `(channels, samples_per_channel)`.
2886///
2887/// # Errors
2888///
2889/// Returns [`AudioSampleError::EmptyData`] if `arr` is empty.
2890impl<'a, T> TryFrom<ArrayViewMut2<'a, T>> for AudioData<'a, T>
2891where
2892    T: StandardSample,
2893{
2894    type Error = AudioSampleError;
2895
2896    #[inline]
2897    fn try_from(arr: ArrayViewMut2<'a, T>) -> Result<Self, Self::Error> {
2898        if arr.is_empty() {
2899            return Err(AudioSampleError::EmptyData);
2900        }
2901        Ok(AudioData::Multi(MultiData(MultiRepr::BorrowedMut(arr))))
2902    }
2903}
2904
2905/// Indexes `AudioData` by a flat sample index.
2906///
2907/// For mono data the index addresses samples directly. For multi-channel data the
2908/// index is linearised in row-major order: `sample[channel * samples_per_channel + i]`.
2909///
2910/// # Panics
2911///
2912/// Panics if `index` is out of bounds.
2913impl<T> Index<usize> for AudioData<'_, T>
2914where
2915    T: StandardSample,
2916{
2917    type Output = T;
2918
2919    #[inline]
2920    fn index(&self, index: usize) -> &Self::Output {
2921        match self {
2922            AudioData::Mono(arr) => &arr[index],
2923            AudioData::Multi(arr) => {
2924                let (channels, samples_per_channel) = arr.dim();
2925                let total_samples = channels.get() * samples_per_channel.get();
2926                assert!(
2927                    index < total_samples,
2928                    "Index {index} out of bounds for total samples {total_samples}"
2929                );
2930                let channel = index / samples_per_channel;
2931                let sample_idx = index % samples_per_channel;
2932                &arr[(channel, sample_idx)]
2933            }
2934        }
2935    }
2936}
2937
2938/// Converts an owned `Array1<T>` into a `MonoData`.
2939///
2940/// # Errors
2941///
2942/// Returns [`AudioSampleError::EmptyData`] if the array is empty.
2943impl<T> TryFrom<Array1<T>> for MonoData<'_, T>
2944where
2945    T: StandardSample,
2946{
2947    type Error = AudioSampleError;
2948
2949    #[inline]
2950    fn try_from(a: Array1<T>) -> Result<Self, Self::Error> {
2951        if a.is_empty() {
2952            return Err(AudioSampleError::EmptyData);
2953        }
2954        Ok(MonoData(MonoRepr::Owned(a)))
2955    }
2956}
2957
2958/// Converts an owned `Array2<T>` into a `MultiData`.
2959///
2960/// The array must have shape `(channels, samples_per_channel)`.
2961///
2962/// # Errors
2963///
2964/// Returns [`AudioSampleError::EmptyData`] if the array is empty.
2965impl<T> TryFrom<Array2<T>> for MultiData<'_, T>
2966where
2967    T: StandardSample,
2968{
2969    type Error = AudioSampleError;
2970
2971    #[inline]
2972    fn try_from(a: Array2<T>) -> Result<Self, Self::Error> {
2973        if a.is_empty() {
2974            return Err(AudioSampleError::EmptyData);
2975        }
2976        Ok(MultiData(MultiRepr::Owned(a)))
2977    }
2978}
2979
2980/// Converts an owned `Array1<T>` into a mono `AudioData`.
2981///
2982/// # Errors
2983///
2984/// Returns [`AudioSampleError::EmptyData`] if the array is empty.
2985impl<T> TryFrom<Array1<T>> for AudioData<'_, T>
2986where
2987    T: StandardSample,
2988{
2989    type Error = AudioSampleError;
2990
2991    #[inline]
2992    fn try_from(a: Array1<T>) -> Result<Self, Self::Error> {
2993        if a.is_empty() {
2994            return Err(AudioSampleError::EmptyData);
2995        }
2996        Ok(AudioData::Mono(a.try_into()?))
2997    }
2998}
2999
3000/// Converts an owned `Array2<T>` into a multi-channel `AudioData`.
3001///
3002/// The array must have shape `(channels, samples_per_channel)`.
3003///
3004/// # Errors
3005///
3006/// Returns [`AudioSampleError::EmptyData`] if the array is empty.
3007impl<T> TryFrom<Array2<T>> for AudioData<'_, T>
3008where
3009    T: StandardSample,
3010{
3011    type Error = AudioSampleError;
3012
3013    #[inline]
3014    fn try_from(a: Array2<T>) -> Result<Self, Self::Error> {
3015        if a.is_empty() {
3016            return Err(AudioSampleError::EmptyData);
3017        }
3018        Ok(AudioData::Multi(a.try_into()?))
3019    }
3020}
3021
3022macro_rules! impl_audio_data_ops {
3023    ($(
3024        $trait:ident, $method:ident,
3025        $assign_trait:ident, $assign_method:ident,
3026        $op:tt,
3027        $mono_err:literal,
3028        $multi_err:literal,
3029        $mismatch_err:literal
3030    );+ $(;)?) => {
3031        $(
3032            // =========================
3033            // Binary ops: AudioData ∘ AudioData  -> new AudioData
3034            // =========================
3035            impl<'a, T> std::ops::$trait<Self> for AudioData<'a, T>
3036                where
3037                    T: StandardSample,
3038
3039            {
3040                type Output = Self;
3041
3042
3043                #[inline]
3044                fn $method(self, rhs: Self) -> Self::Output {
3045                    match (self, rhs) {
3046                        (AudioData::Mono(lhs), AudioData::Mono(rhs)) => {
3047                            if lhs.len() != rhs.len() {
3048                                panic!($mono_err);
3049                            }
3050                            // operate on views; convert to owned to ensure operation works
3051                            let arr: Array1<T> = &lhs.as_view() $op &rhs.as_view();
3052                            // safety: arr is already non-empty
3053                            unsafe {
3054                                AudioData::Mono(MonoData::from_array1(arr))
3055                            }
3056                        }
3057                        (AudioData::Multi(lhs), AudioData::Multi(rhs)) => {
3058                            if lhs.as_view().dim() != rhs.as_view().dim() {
3059                                panic!($multi_err);
3060                            }
3061                // safety: input array is not empty therefore output wont be
3062
3063                            AudioData::Multi(unsafe { MultiData::from_array2(&lhs.as_view() $op &rhs.as_view()) })
3064                        }
3065                        _ => panic!($mismatch_err),
3066                    }
3067                }
3068            }
3069
3070            // =========================
3071            // Binary ops: AudioData ∘ scalar -> new AudioData
3072            // =========================
3073            impl<'a, T> std::ops::$trait<T> for AudioData<'a, T>
3074                where
3075                    T: StandardSample,
3076            {
3077                type Output = Self;
3078
3079
3080                #[inline]
3081                fn $method(self, rhs: T) -> Self::Output {
3082                    match self {
3083                        // safety: input array is not empty therefore output wont be
3084                        AudioData::Mono(a) => AudioData::Mono(unsafe { MonoData::from_array1(a.as_view().mapv(|x| x $op rhs)) }),
3085                        // safety: input array is not empty therefore output wont be
3086                        AudioData::Multi(a) => AudioData::Multi(unsafe { MultiData::from_array2(a.as_view().mapv(|x| x $op rhs)) }),
3087                    }
3088                }
3089            }
3090
3091            // =========================
3092            // Assignment ops: AudioData ∘= AudioData  (in-place, no Default)
3093            // =========================
3094            impl<'a, T> std::ops::$assign_trait<Self> for AudioData<'a, T>
3095            where
3096                T: StandardSample,
3097            {
3098
3099                #[inline]
3100                fn $assign_method(&mut self, rhs: Self) {
3101                    match (self, rhs) {
3102                        (AudioData::Mono(lhs), AudioData::Mono(rhs)) => {
3103                            if lhs.len() != rhs.len() {
3104                                panic!($mono_err);
3105                            }
3106                            // promote lhs to owned and apply in place against rhs view
3107                            let mut lhs_mut = lhs.to_mut();
3108                            let rhs_view = rhs.as_view();
3109                            // Use zip_mut_with for element-wise in-place operations
3110                            lhs_mut.zip_mut_with(&rhs_view, |a, &b| *a = *a $op b);
3111                        }
3112                        (AudioData::Multi(lhs), AudioData::Multi(rhs)) => {
3113                            if lhs.as_view().dim() != rhs.as_view().dim() {
3114                                panic!($multi_err);
3115                            }
3116                            let mut lhs_mut = lhs.to_mut();
3117                            let rhs_view = rhs.as_view();
3118                            lhs_mut.zip_mut_with(&rhs_view, |a, &b| *a = *a $op b);
3119                        }
3120                        _ => panic!($mismatch_err),
3121                    }
3122                }
3123            }
3124
3125            // =========================
3126            // Assignment ops: AudioData ∘= scalar  (in-place)
3127            // =========================
3128            impl<'a, T> std::ops::$assign_trait<T> for AudioData<'a, T>
3129            where
3130                T: StandardSample,
3131            {
3132
3133                #[inline]
3134                fn $assign_method(&mut self, rhs: T) {
3135                    match self {
3136                        AudioData::Mono(lhs) => {
3137                            let mut lhs_mut = lhs.to_mut();
3138                            // Use element-wise iteration for scalar assignment operations
3139                            lhs_mut.iter_mut().for_each(|x| *x = *x $op rhs);
3140                        }
3141                        AudioData::Multi(lhs) => {
3142                            let mut lhs_mut = lhs.to_mut();
3143                            lhs_mut.iter_mut().for_each(|x| *x = *x $op rhs);
3144                        }
3145                    }
3146                }
3147            }
3148        )+
3149    };
3150}
3151
3152impl_audio_data_ops!(
3153    Add, add, AddAssign, add_assign, +,
3154    "Cannot add mono audio with different lengths",
3155    "Cannot add multi-channel audio with different shapes",
3156    "Cannot add mono and multi-channel audio";
3157    Sub, sub, SubAssign, sub_assign, -,
3158    "Cannot subtract mono audio with different lengths",
3159    "Cannot subtract multi-channel audio with different shapes",
3160    "Cannot subtract mono and multi-channel audio";
3161    Mul, mul, MulAssign, mul_assign, *,
3162    "Cannot multiply mono audio with different lengths",
3163    "Cannot multiply multi-channel audio with different shapes",
3164    "Cannot multiply mono and multi-channel audio";
3165    Div, div, DivAssign, div_assign, /,
3166    "Cannot divide mono audio with different lengths",
3167    "Cannot divide multi-channel audio with different shapes",
3168    "Cannot divide mono and multi-channel audio";
3169);
3170
3171/// Negates every sample in the audio data.
3172///
3173/// Returns a new `AudioData` with all samples multiplied by −1. Requires the
3174/// sample type to implement `Neg`.
3175impl<T> Neg for AudioData<'_, T>
3176where
3177    T: StandardSample + Neg<Output = T> + ConvertTo<T> + ConvertFrom<T>,
3178{
3179    type Output = Self;
3180
3181    #[inline]
3182    fn neg(self) -> Self::Output {
3183        match self {
3184            AudioData::Mono(arr) => {
3185                // safety: input array is not empty therefore output wont be
3186                AudioData::Mono(unsafe { MonoData::from_array1(arr.as_view().mapv(|x| -x)) })
3187            }
3188            AudioData::Multi(arr) => {
3189                // safety: input array is not empty therefore output wont be
3190                AudioData::Multi(unsafe { MultiData::from_array2(arr.as_view().mapv(|x| -x)) })
3191            }
3192        }
3193    }
3194}
3195
3196/// Multiplies each sample in a mono `AudioSamples` by the corresponding value in a slice.
3197///
3198/// The multiplication is performed in the type `S`: each `T` sample is first converted to
3199/// `S` via [`ConvertTo`], multiplied, then converted back to `T` via [`ConvertFrom`].
3200///
3201/// # Returns
3202///
3203/// `Some(AudioSamples<T>)` on success; `None` if the audio is multi-channel or if the
3204/// slice length does not equal the number of samples.
3205impl<T, S> Mul<&[S]> for AudioSamples<'_, T>
3206where
3207    T: StandardSample + ConvertTo<S> + ConvertFrom<S>,
3208    S: StandardSample,
3209{
3210    type Output = Option<Self>;
3211
3212    #[inline]
3213    fn mul(self, rhs: &[S]) -> Self::Output {
3214        if self.is_multi_channel() || self.len().get() != rhs.len() {
3215            return None;
3216        }
3217        let mut out = self.into_owned();
3218        out.apply_with_index(|idx, x| {
3219            let x: S = T::convert_to(x);
3220            let res = x * rhs[idx];
3221            let res: T = S::convert_to(res);
3222            res
3223        });
3224
3225        Some(out)
3226    }
3227}
3228
3229/// Represents homogeneous audio samples with associated metadata.
3230///
3231/// Primary container for audio data combining raw sample values with essential
3232/// metadata including sample rate, and type information.
3233/// Supports both mono and multi-channel audio with unified interface.
3234///
3235/// # Fields
3236/// - `data`: Audio sample data in mono or multi-channel format
3237/// - `sample_rate`: Sampling frequency in Hz
3238///
3239/// # Examples
3240///
3241/// ```rust
3242/// use audio_samples::{AudioSamples, sample_rate, channels};
3243/// use ndarray::array;
3244///
3245/// let mono = AudioSamples::new_mono(array![0.1f32, 0.2, 0.3], sample_rate!(44100)).unwrap();
3246/// assert_eq!(mono.num_channels(), channels!(1));
3247///
3248/// let stereo = AudioSamples::new_multi_channel(
3249///     array![[0.1f32, 0.2], [0.3f32, 0.4]],
3250///     sample_rate!(48000),
3251/// ).unwrap();
3252/// assert_eq!(stereo.num_channels(), channels!(2));
3253/// ```
3254///
3255/// # Invariants
3256///
3257/// The following properties are guaranteed by construction:
3258/// - `sample_rate` is always > 0 (stored as [`NonZeroU32`])
3259/// - `num_channels()` is always ≥ 1
3260/// - `samples_per_channel()` is always ≥ 1 (empty audio is not allowed)
3261///
3262/// These invariants eliminate the need for runtime null-checks in downstream code.
3263#[derive(Debug, PartialEq)]
3264#[allow(clippy::exhaustive_structs)] // `AudioSamples` will likely not change from this 
3265pub struct AudioSamples<'a, T>
3266where
3267    T: StandardSample,
3268{
3269    /// The audio sample data.
3270    pub data: AudioData<'a, T>,
3271    /// Sample rate in Hz (guaranteed non-zero).
3272    pub sample_rate: SampleRate,
3273    // pub layout: ChannelLayout,
3274}
3275
3276/// Formats a human-readable summary of the audio samples.
3277///
3278/// The standard format (`{}`) prints a compact one-line header:
3279/// `AudioSamples<TYPE>: N ch × M samples @ R Hz (Layout)`.
3280///
3281/// The alternate format (`{:#}`) also includes the first and last 5 samples for
3282/// each channel, useful for quick inspection during debugging.
3283///
3284/// # Examples
3285///
3286/// ```rust
3287/// use audio_samples::{AudioSamples, sample_rate};
3288/// use ndarray::array;
3289///
3290/// let audio = AudioSamples::new_mono(array![0.1f32, 0.2, 0.3], sample_rate!(44100)).unwrap();
3291/// let s = format!("{}", audio);
3292/// assert!(s.contains("44100"));
3293/// ```
3294impl<T> Display for AudioSamples<'_, T>
3295where
3296    T: StandardSample,
3297{
3298    #[inline]
3299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3300        let type_name = std::any::type_name::<T>();
3301        let n_channels = self.num_channels();
3302        let n_samples = self.samples_per_channel();
3303        let rate = self.sample_rate;
3304
3305        // Compact header (always shown)
3306        writeln!(
3307            f,
3308            "AudioSamples<{type_name}>: {n_channels} ch x {n_samples} samples @ {rate} Hz"
3309        )?;
3310
3311        // Alternate (#) gives full details; otherwise, concise
3312        if f.alternate() {
3313            // Detailed alternate view
3314            match &self.data {
3315                AudioData::Mono(arr) => {
3316                    let len = arr.len();
3317                    let display_len = 5.min(len.get());
3318                    write!(f, "Mono Channel\n  First {display_len} samples: [")?;
3319                    for (i, val) in arr.iter().take(display_len).enumerate() {
3320                        write!(f, "{val:.4}")?;
3321                        if i < display_len - 1 {
3322                            write!(f, ", ")?;
3323                        }
3324                    }
3325                    write!(f, "]")?;
3326                    if len.get() > display_len {
3327                        write!(f, "\n  Last {display_len} samples: [")?;
3328                        for (i, val) in arr.iter().rev().take(display_len).rev().enumerate() {
3329                            write!(f, "{val:.4}")?;
3330                            if i < display_len - 1 {
3331                                write!(f, ", ")?;
3332                            }
3333                        }
3334                        write!(f, "]")?;
3335                    }
3336                }
3337                AudioData::Multi(arr) => {
3338                    let (channels, samples) = arr.dim();
3339                    for ch in 0..channels.get() {
3340                        let ch_data = arr.index_axis(ndarray::Axis(0), ch);
3341                        let len = samples.get();
3342                        let display_len = 5.min(len);
3343
3344                        write!(f, "\nChannel {ch}:")?;
3345                        write!(f, "\n  First {display_len} samples: [")?;
3346                        for (i, val) in ch_data.iter().take(display_len).enumerate() {
3347                            write!(f, "{val:.4}")?;
3348                            if i < display_len - 1 {
3349                                write!(f, ", ")?;
3350                            }
3351                        }
3352                        write!(f, "]")?;
3353
3354                        if len > display_len {
3355                            write!(f, "\n  Last {display_len} samples: [")?;
3356                            for (i, val) in ch_data.iter().rev().take(display_len).rev().enumerate()
3357                            {
3358                                write!(f, "{val:.4}")?;
3359                                if i < display_len - 1 {
3360                                    write!(f, ", ")?;
3361                                }
3362                            }
3363                            write!(f, "]")?;
3364                        }
3365                    }
3366                }
3367            }
3368        } else {
3369            // Compact summary
3370            match &self.data {
3371                AudioData::Mono(arr) => {
3372                    let len = arr.len();
3373                    let preview = 5.min(len.get());
3374                    write!(f, "[")?;
3375                    for (i, val) in arr.as_view().iter().take(preview).enumerate() {
3376                        write!(f, "{val:.4}")?;
3377                        if i < preview - 1 {
3378                            write!(f, ", ")?;
3379                        }
3380                    }
3381                    if len.get() > preview {
3382                        write!(f, ", ...")?;
3383                    }
3384                    write!(f, "]")?;
3385                }
3386                AudioData::Multi(arr) => {
3387                    let channels = arr.ncols().get();
3388                    for ch in 0..channels {
3389                        let ch_data = arr.index_axis(ndarray::Axis(0), ch);
3390                        let len = ch_data.len();
3391                        let preview = 3.min(len);
3392                        write!(f, "\nCh {ch}: [")?;
3393                        for (i, val) in ch_data.iter().take(preview).enumerate() {
3394                            write!(f, "{val:.4}")?;
3395                            if i < preview - 1 {
3396                                write!(f, ", ")?;
3397                            }
3398                        }
3399                        if len > preview {
3400                            write!(f, ", ...")?;
3401                        }
3402                        write!(f, "]")?;
3403                    }
3404                }
3405            }
3406        }
3407        Ok(())
3408    }
3409}
3410
3411impl<T> AudioSamples<'static, T>
3412where
3413    T: StandardSample,
3414{
3415    /// Creates AudioSamples from owned data.
3416    #[inline]
3417    #[must_use]
3418    pub fn from_owned(data: AudioData<'_, T>, sample_rate: SampleRate) -> Self {
3419        let owned = data.into_owned();
3420
3421        Self {
3422            data: owned,
3423            sample_rate,
3424        }
3425    }
3426
3427    /// Consumes self and returns the underlying AudioData
3428    #[inline]
3429    #[must_use]
3430    pub fn into_data(self) -> AudioData<'static, T> {
3431        self.data.into_owned()
3432    }
3433
3434    /// Consumes self and returns the underlying mono Array1 if applicable
3435    #[inline]
3436    #[must_use]
3437    pub fn into_array1(self) -> Option<Array1<T>> {
3438        match self.data {
3439            AudioData::Mono(m) => Some(m.take()),
3440            AudioData::Multi(_) => None,
3441        }
3442    }
3443
3444    /// Consumes self and returns the underlying multi-channel Array2 if applicable
3445    #[inline]
3446    #[must_use]
3447    pub fn into_array2(self) -> Option<Array2<T>> {
3448        match self.data {
3449            AudioData::Multi(m) => Some(m.take()),
3450            AudioData::Mono(_) => None,
3451        }
3452    }
3453}
3454
3455impl<'a, T> AudioSamples<'a, T>
3456where
3457    T: StandardSample,
3458{
3459    /// Creates a new AudioSamples with the given data and sample rate.
3460    ///
3461    /// This is a low-level constructor. Prefer `new_mono` or `new_multi_channel`
3462    /// for most use cases.
3463    #[inline]
3464    #[must_use]
3465    pub const fn new(data: AudioData<'a, T>, sample_rate: SampleRate) -> Self {
3466        Self { data, sample_rate }
3467    }
3468
3469    /// Borrows the audio data as an AudioSamples with the same lifetime.
3470    #[inline]
3471    #[must_use]
3472    pub fn borrow(&self) -> AudioSamples<'_, T> {
3473        AudioSamples {
3474            data: self.data.borrow(),
3475            sample_rate: self.sample_rate,
3476        }
3477    }
3478
3479    /// Returns all samples as a `Vec<T>`.
3480    ///
3481    /// For mono audio the vec contains `samples_per_channel` elements. For multi-channel
3482    /// audio the samples are in row-major order: all samples for channel 0, then channel 1, etc.
3483    /// This always allocates a new `Vec`.
3484    ///
3485    /// # Returns
3486    ///
3487    /// A `Vec<T>` containing every sample.
3488    #[inline]
3489    #[must_use]
3490    pub fn as_vec(&self) -> Vec<T> {
3491        self.data.as_vec()
3492    }
3493
3494    /// Returns `true` if the underlying ndarray buffer uses standard (C/row-major) memory layout.
3495    ///
3496    /// Some low-level operations (raw pointer access, SIMD paths) require standard layout.
3497    ///
3498    /// # Returns
3499    ///
3500    /// `true` if the storage is contiguous and row-major, `false` otherwise.
3501    #[inline]
3502    #[must_use]
3503    pub fn is_standard_layout(&self) -> bool {
3504        self.data.is_standard_layout()
3505    }
3506
3507    /// Creates AudioSamples from borrowed data.
3508    ///
3509    /// # Arguments
3510    ///
3511    /// - `data`: Audio sample data in mono or multi-channel format
3512    /// - `sample_rate`: Sample rate in Hz
3513    ///
3514    /// # Returns
3515    ///
3516    /// A new `AudioSamples` instance borrowing the provided data
3517    #[inline]
3518    #[must_use]
3519    pub const fn from_borrowed(data: AudioData<'a, T>, sample_rate: SampleRate) -> Self {
3520        Self { data, sample_rate }
3521    }
3522
3523    /// Creates AudioSamples from borrowed data.
3524    ///
3525    /// # Arguments
3526    ///
3527    /// - `data`: Audio sample data in mono or multi-channel format
3528    /// - `sample_rate`: Sample rate in Hz
3529    ///
3530    /// # Returns
3531    ///
3532    /// A new `AudioSamples` instance borrowing the provided data.
3533    #[inline]
3534    #[must_use]
3535    pub const fn from_borrowed_with_layout(
3536        data: AudioData<'a, T>,
3537        sample_rate: SampleRate,
3538    ) -> Self {
3539        Self { data, sample_rate }
3540    }
3541
3542    /// Convert audio samples to another sample type.
3543    ///
3544    /// # Returns
3545    ///
3546    /// A new `AudioSamples` instance with the same audio data converted to type `O`. The conversion is performed element-wise using the `ConvertTo` and `ConvertFrom` traits. This always allocates a new buffer for the converted samples.
3547    #[inline]
3548    pub fn convert_to<O>(&self) -> AudioSamples<'static, O>
3549    where
3550        T: ConvertTo<O>,
3551        O: StandardSample + ConvertFrom<T> + ConvertTo<O> + ConvertFrom<O>,
3552    {
3553        self.map_into(O::convert_from)
3554    }
3555
3556    /// Convert the AudioSamples struct into a vector of samples in interleaved format.
3557    #[inline]
3558    #[must_use]
3559    pub fn to_interleaved_vec(&self) -> NonEmptyVec<T> {
3560        self.data.as_interleaved_vec()
3561    }
3562
3563    /// Returns the number of frames (time steps) in the audio.
3564    ///
3565    /// A *frame* contains one sample per channel. For mono audio this equals
3566    /// `samples_per_channel()`. For multi-channel audio it equals `samples_per_channel()`.
3567    ///
3568    /// # Returns
3569    ///
3570    /// The total number of frames as a non-zero `usize`.
3571    #[inline]
3572    #[must_use]
3573    pub fn total_frames(&self) -> NonZeroUsize {
3574        self.data.total_frames()
3575    }
3576
3577    /// Returns a slice of the audio samples if the data is contiguous. ``None`` otherwise
3578    ///
3579    /// # Returns
3580    ///
3581    /// `Some(&[T])` if the audio data is stored contiguously in memory, otherwise `None`.
3582    #[inline]
3583    #[must_use]
3584    pub fn as_slice(&self) -> Option<&[T]> {
3585        match &self.data {
3586            AudioData::Mono(m) => m.as_slice(),
3587            AudioData::Multi(m) => m.as_slice(),
3588        }
3589    }
3590
3591    /// Returns a mutable slice of the audio samples if the data is contiguous. ``None`` otherwise
3592    ///
3593    /// # Returns
3594    ///
3595    /// `Some(&mut [T])` if the audio data is stored contiguously in memory, otherwise `None`.
3596    #[inline]
3597    pub fn as_slice_mut(&mut self) -> Option<&mut [T]> {
3598        match &mut self.data {
3599            AudioData::Mono(mono_data) => Some(mono_data.as_slice_mut()),
3600            AudioData::Multi(multi_data) => multi_data.as_slice_mut(),
3601        }
3602    }
3603
3604    /// Creates a new mono AudioSamples that owns its data.
3605    ///
3606    /// # Arguments
3607    /// * `data` - 1D array containing the audio samples
3608    /// * `sample_rate` - Sample rate in Hz
3609    ///
3610    /// # Returns
3611    /// A new mono AudioSamples instance that owns the provided data.
3612    ///
3613    /// # Errors
3614    ///
3615    /// - [`AudioSampleError::EmptyData`] if `data` is empty
3616    ///
3617    /// # Examples
3618    /// ```
3619    /// use audio_samples::{AudioSamples, sample_rate};
3620    /// use ndarray::array;
3621    ///
3622    /// let data = array![1.0f32, -1.0, 0.5, -0.5];
3623    /// let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
3624    /// assert_eq!(audio.num_channels().get(), 1);
3625    /// assert_eq!(audio.sample_rate().get(), 44100);
3626    /// ```
3627    /// Creates a new mono AudioSamples with the given data and sample rate.
3628    ///
3629    /// # Panics
3630    /// - If `data` is empty
3631    #[inline]
3632    pub fn new_mono<'b>(
3633        data: Array1<T>,
3634        sample_rate: SampleRate,
3635    ) -> AudioSampleResult<AudioSamples<'b, T>> {
3636        if data.is_empty() {
3637            return Err(AudioSampleError::EmptyData);
3638        }
3639
3640        Ok(AudioSamples {
3641            data: AudioData::Mono(MonoData(MonoRepr::Owned(data))),
3642            sample_rate,
3643        })
3644    }
3645
3646    /// Creates a new mono AudioSamples that owns its data without checking invariants.
3647    ///
3648    /// # Safety
3649    ///
3650    /// Make sure data is not empty
3651    #[inline]
3652    #[must_use]
3653    pub const unsafe fn new_mono_unchecked<'b>(
3654        data: Array1<T>,
3655        sample_rate: SampleRate,
3656    ) -> AudioSamples<'b, T> {
3657        AudioSamples {
3658            data: AudioData::Mono(MonoData(MonoRepr::Owned(data))),
3659            sample_rate,
3660        }
3661    }
3662
3663    /// Creates a new multi-channel AudioSamples with the given data and sample rate.
3664    ///
3665    /// # Arguments
3666    /// * `data` - 2D array where each row represents a channel and each column a sample
3667    /// * `sample_rate` - Sample rate in Hz
3668    ///
3669    /// # Returns
3670    /// A new multi-channel AudioSamples instance that owns the provided data.
3671    ///
3672    /// # Examples
3673    /// ```
3674    /// use audio_samples::{AudioSamples, sample_rate};
3675    /// use ndarray::array;
3676    ///
3677    /// let data = array![[1.0f32, -1.0], [0.5, -0.5]]; // 2 channels, 2 samples each
3678    /// let audio = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
3679    /// assert_eq!(audio.num_channels().get(), 2);
3680    /// assert_eq!(audio.samples_per_channel().get(), 2);
3681    /// ```
3682    /// Creates a new multi-channel AudioSamples with the given data and sample rate.
3683    ///
3684    /// # Errors
3685    /// - If `data` is empty
3686    #[inline]
3687    pub fn new_multi_channel<'b>(
3688        data: Array2<T>,
3689        sample_rate: SampleRate,
3690    ) -> AudioSampleResult<AudioSamples<'b, T>> {
3691        if data.is_empty() {
3692            return Err(AudioSampleError::EmptyData);
3693        }
3694
3695        Ok(AudioSamples {
3696            data: AudioData::Multi(MultiData(MultiRepr::Owned(data))),
3697            sample_rate,
3698        })
3699    }
3700
3701    /// Creates a new mono AudioSamples filled with zeros.
3702    ///
3703    /// # Arguments
3704    /// * `length` - Number of samples to create
3705    /// * `sample_rate` - Sample rate in Hz
3706    ///
3707    /// # Returns
3708    /// A new mono AudioSamples instance filled with zero values.
3709    ///
3710    /// # Examples
3711    /// ```
3712    /// use audio_samples::{AudioSamples, sample_rate, nzu};
3713    ///
3714    /// let audio = AudioSamples::<f32>::zeros_mono(nzu!(1024), sample_rate!(44100));
3715    /// assert_eq!(audio.samples_per_channel().get(), 1024);
3716    /// assert_eq!(audio.num_channels().get(), 1);
3717    /// ```
3718    #[inline]
3719    #[must_use]
3720    pub fn zeros_mono(length: NonZeroUsize, sample_rate: SampleRate) -> Self {
3721        Self {
3722            data: AudioData::Mono(MonoData(MonoRepr::Owned(Array1::zeros(length.get())))),
3723            sample_rate,
3724        }
3725    }
3726
3727    /// Creates a new multi-channel AudioSamples filled with zeros.
3728    ///
3729    /// # Arguments
3730    /// * `channels` - Number of channels to create
3731    /// * `length` - Number of samples per channel
3732    /// * `sample_rate` - Sample rate in Hz
3733    ///
3734    /// # Returns
3735    /// A new multi-channel AudioSamples instance filled with zero values.
3736    ///
3737    /// # Examples
3738    /// ```
3739    /// use audio_samples::{AudioSamples, sample_rate, channels, nzu};
3740    ///
3741    /// let audio = AudioSamples::<f32>::zeros_multi(channels!(2), nzu!(1024), sample_rate!(44100));
3742    /// assert_eq!(audio.num_channels().get(), 2);
3743    /// assert_eq!(audio.samples_per_channel().get(), 1024);
3744    /// ```
3745    #[inline]
3746    #[must_use]
3747    pub fn zeros_multi(
3748        channels: ChannelCount,
3749        length: NonZeroUsize,
3750        sample_rate: SampleRate,
3751    ) -> Self {
3752        Self {
3753            data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::zeros((
3754                channels.get() as usize,
3755                length.get(),
3756            ))))),
3757            sample_rate,
3758        }
3759    }
3760
3761    /// Creates a new multi-channel AudioSamples filled with zeros (static version)
3762    #[inline]
3763    #[must_use]
3764    pub fn zeros_multi_channel(
3765        channels: ChannelCount,
3766        length: NonZeroUsize,
3767        sample_rate: SampleRate,
3768    ) -> AudioSamples<'static, T> {
3769        AudioSamples {
3770            data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::zeros((
3771                channels.get() as usize,
3772                length.get(),
3773            ))))),
3774            sample_rate,
3775        }
3776    }
3777
3778    /// Creates a new mono AudioSamples filled with ones
3779    #[inline]
3780    #[must_use]
3781    pub fn ones_mono(length: NonZeroUsize, sample_rate: SampleRate) -> Self {
3782        Self {
3783            data: AudioData::Mono(MonoData(MonoRepr::Owned(Array1::ones(length.get())))),
3784            sample_rate,
3785        }
3786    }
3787
3788    /// Creates a new multi-channel AudioSamples filled with ones
3789    #[inline]
3790    #[must_use]
3791    pub fn ones_multi(
3792        channels: ChannelCount,
3793        length: NonZeroUsize,
3794        sample_rate: SampleRate,
3795    ) -> Self {
3796        Self {
3797            data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::ones((
3798                channels.get() as usize,
3799                length.get(),
3800            ))))),
3801            sample_rate,
3802        }
3803    }
3804
3805    /// Creates a new mono AudioSamples filled with the specified value
3806    #[inline]
3807    pub fn uniform_mono(length: NonZeroUsize, sample_rate: SampleRate, value: T) -> Self {
3808        Self {
3809            data: AudioData::Mono(MonoData(MonoRepr::Owned(Array1::from_elem(
3810                length.get(),
3811                value,
3812            )))),
3813            sample_rate,
3814        }
3815    }
3816
3817    /// Creates a new multi-channel AudioSamples filled with the specified value
3818    #[inline]
3819    pub fn uniform_multi(
3820        channels: ChannelCount,
3821        length: NonZeroUsize,
3822        sample_rate: SampleRate,
3823        value: T,
3824    ) -> Self {
3825        Self {
3826            data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::from_elem(
3827                (channels.get() as usize, length.get()),
3828                value,
3829            )))),
3830            sample_rate,
3831        }
3832    }
3833
3834    /// Returns basic info: (num_channels, samples_per_channel, duration_seconds, sample_rate, layout)
3835    ///
3836    /// # Returns
3837    ///
3838    /// A tuple containing the number of channels, the number of samples per channel,
3839    /// the duration in seconds, and the sample rate.
3840    #[inline]
3841    #[must_use]
3842    pub fn info(&self) -> (ChannelCount, NonZeroUsize, f64, NonZeroU32) {
3843        (
3844            self.num_channels(),
3845            self.samples_per_channel(),
3846            self.duration_seconds(),
3847            self.sample_rate,
3848        )
3849    }
3850
3851    /// Returns the sample rate in Hz as a [`NonZeroU32`].
3852    ///
3853    /// This is guaranteed to be non-zero by construction.
3854    ///
3855    /// # Examples
3856    ///
3857    /// ```rust
3858    /// use audio_samples::{AudioSamples, sample_rate};
3859    /// use ndarray::array;
3860    ///
3861    /// let audio = AudioSamples::new_mono(array![1.0f32, 2.0], sample_rate!(44100)).unwrap();
3862    /// assert_eq!(audio.sample_rate().get(), 44100);
3863    /// ```
3864    #[inline]
3865    #[must_use]
3866    pub const fn sample_rate(&self) -> SampleRate {
3867        self.sample_rate
3868    }
3869
3870    /// Returns the sample rate as a plain `f64`.
3871    ///
3872    /// This is a convenience method equivalent to `self.sample_rate().get()`.
3873    #[inline]
3874    #[must_use]
3875    pub const fn sample_rate_hz(&self) -> f64 {
3876        self.sample_rate.get() as f64
3877    }
3878
3879    /// Returns the number of channels.
3880    ///
3881    /// This is guaranteed to be ≥ 1 by construction.
3882    #[inline]
3883    #[must_use]
3884    pub fn num_channels(&self) -> ChannelCount {
3885        self.data.num_channels()
3886    }
3887
3888    /// Returns the number of samples per channel.
3889    ///
3890    /// This is guaranteed to be ≥ 1 by construction (empty audio is not allowed).
3891    #[inline]
3892    #[must_use]
3893    pub fn samples_per_channel(&self) -> NonZeroUsize {
3894        self.data.samples_per_channel()
3895    }
3896
3897    /// Returns the duration in seconds
3898    #[inline]
3899    #[must_use]
3900    pub fn duration_seconds(&self) -> f64 {
3901        self.samples_per_channel().get() as f64 / self.sample_rate_hz()
3902    }
3903
3904    /// Returns the total number of samples across all channels
3905    #[inline]
3906    #[must_use]
3907    pub fn total_samples(&self) -> NonZeroUsize {
3908        // Safety: A non-zero number * a non-zero number is always non-zero
3909        unsafe {
3910            NonZeroUsize::new_unchecked(
3911                self.num_channels().get() as usize * self.samples_per_channel().get(),
3912            )
3913        }
3914    }
3915
3916    /// Returns the number of bytes per sample for type T
3917    #[inline]
3918    #[must_use]
3919    pub const fn bytes_per_sample(&self) -> NonZeroU32 {
3920        self.data.bytes_per_sample()
3921    }
3922
3923    /// Returns the sample type as a string
3924    #[inline]
3925    #[must_use]
3926    pub const fn sample_type() -> SampleType {
3927        T::SAMPLE_TYPE
3928    }
3929
3930    /// Returns true if this is mono audio
3931    #[inline]
3932    #[must_use]
3933    pub const fn is_mono(&self) -> bool {
3934        self.data.is_mono()
3935    }
3936
3937    /// Returns true if this is multi-channel audio
3938    #[inline]
3939    #[must_use]
3940    pub const fn is_multi_channel(&self) -> bool {
3941        self.data.is_multi_channel()
3942    }
3943
3944    /// Returns the total number of samples.
3945    #[inline]
3946    #[must_use]
3947    pub fn len(&self) -> NonZeroUsize {
3948        self.data.len()
3949    }
3950
3951    /// Returns the shape of the audio data.
3952    #[inline]
3953    #[must_use]
3954    pub fn shape(&self) -> &[usize] {
3955        self.data.shape()
3956    }
3957
3958    /// Applies a function to each sample in the audio data in-place.
3959    ///
3960    /// This method applies the given function to every sample in the audio data,
3961    /// modifying the samples in-place. The function receives each sample and
3962    /// should return the transformed sample.
3963    ///
3964    /// # Arguments
3965    /// * `f` - A function that takes a sample and returns a transformed sample
3966    ///
3967    /// # Returns
3968    /// Nothing. This method is infallible.
3969    ///
3970    /// # Example
3971    /// ```rust,ignore
3972    /// // Halve the amplitude of all samples
3973    /// audio.apply(|sample| sample * 0.5);
3974    ///
3975    /// // Apply a simple distortion
3976    /// audio.apply(|sample| sample.clamp(-0.8, 0.8));
3977    /// ```
3978    #[inline]
3979    pub fn apply<F>(&mut self, f: F)
3980    where
3981        F: Fn(T) -> T,
3982    {
3983        self.data.apply(f);
3984    }
3985
3986    /// Apply a function to specific channels.
3987    #[inline]
3988    pub fn apply_to_channels<F>(&mut self, channels: &[u32], f: F)
3989    where
3990        F: Fn(T) -> T + Copy,
3991    {
3992        self.data.apply_to_channels(channels, f);
3993    }
3994
3995    /// Maps a function to each sample and returns a new AudioSamples instance.
3996    ///
3997    /// This is a functional-style version of `apply` that doesn't modify the original
3998    /// audio data but returns a new instance with the transformed samples.
3999    ///
4000    /// # Arguments
4001    /// * `f` - A function that takes a sample and returns a transformed sample
4002    ///
4003    /// # Returns
4004    /// A new AudioSamples instance with the transformed samples
4005    ///
4006    /// # Example
4007    /// ```rust,ignore
4008    /// // Create a new audio instance with halved amplitude
4009    /// let quieter_audio = audio.map(|sample| sample * 0.5);
4010    ///
4011    /// // Create a new audio instance with clipped samples
4012    /// let clipped_audio = audio.map(|sample| sample.clamp(-0.8, 0.8));
4013    /// ```
4014    #[inline]
4015    pub fn map<F>(&self, f: F) -> AudioSamples<'static, T>
4016    where
4017        F: Fn(T) -> T,
4018    {
4019        let new_data = self.data.mapv(f);
4020        AudioSamples::from_owned(new_data, self.sample_rate)
4021    }
4022
4023    /// Map each sample to a new type using a function.
4024    /// Does not care about in-domain or out-of-domain mapping.
4025    /// i.e. both convert_to and cast_from/into are acceptable.
4026    #[inline]
4027    pub fn map_into<O, F>(&self, f: F) -> AudioSamples<'static, O>
4028    where
4029        F: Fn(T) -> O,
4030        T: ConvertTo<O>,
4031        O: StandardSample,
4032    {
4033        let new_data = AudioData::from_owned(self.data.mapv(f));
4034        AudioSamples::from_owned(new_data, self.sample_rate)
4035    }
4036
4037    /// Applies a function to each sample with access to the sample index.
4038    ///
4039    /// This method is similar to `apply` but provides the sample index to the function,
4040    /// allowing for position-dependent transformations.
4041    ///
4042    /// # Arguments
4043    /// * `f` - A function that takes a sample index and sample value, returns transformed sample
4044    ///
4045    /// # Returns
4046    /// Nothing. This method is infallible.
4047    ///
4048    /// # Example
4049    /// ```rust,ignore
4050    /// // Apply a fade-in effect
4051    /// audio.apply_with_index(|index, sample| {
4052    ///     let fade_samples = 44100; // 1 second fade at 44.1kHz
4053    ///     let gain = if index < fade_samples {
4054    ///         index as f32 / fade_samples as f32
4055    ///     } else {
4056    ///         1.0
4057    ///     };
4058    ///     sample * gain
4059    /// });
4060    /// ```
4061    #[inline]
4062    pub fn apply_with_index<F>(&mut self, f: F)
4063    where
4064        F: Fn(usize, T) -> T,
4065    {
4066        self.data.apply_with_index(f);
4067    }
4068
4069    // ========================
4070    // Sample and channel slicing methods for Python bindings compatibility
4071    // ========================
4072
4073    /// Slice audio by sample range, keeping all channels.
4074    ///
4075    /// Creates a new AudioSamples instance containing samples in the specified range.
4076    ///
4077    /// # Arguments
4078    /// * `sample_range` - Range of samples to extract (e.g., 100..200)
4079    ///
4080    /// # Returns
4081    /// A new AudioSamples instance with the sliced samples
4082    ///
4083    /// # Errors
4084    /// Returns an error if the range is out of bounds.
4085    #[inline]
4086    pub fn slice_samples<R>(&self, sample_range: R) -> AudioSampleResult<AudioSamples<'_, T>>
4087    where
4088        R: RangeBounds<usize> + Clone,
4089    {
4090        let samples_per_channel = self.samples_per_channel();
4091
4092        let start = match sample_range.start_bound() {
4093            Bound::Included(&n) => n,
4094            Bound::Excluded(&n) => n + 1,
4095            Bound::Unbounded => 0,
4096        };
4097
4098        let end = match sample_range.end_bound() {
4099            Bound::Included(&n) => n + 1,
4100            Bound::Excluded(&n) => n,
4101            Bound::Unbounded => samples_per_channel.get(),
4102        };
4103
4104        if start >= samples_per_channel.get() || end > samples_per_channel.get() || start >= end {
4105            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4106                "sample_range",
4107                format!(
4108                    "Sample range {start}..{end} out of bounds for {samples_per_channel} samples"
4109                ),
4110            )));
4111        }
4112
4113        // no range condition
4114        if start == end {
4115            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4116                "sample_range",
4117                "Start and end of sample range cannot be equal".to_string(),
4118            )));
4119        }
4120        // guarantees a non-empty slice
4121
4122        match &self.data {
4123            AudioData::Mono(arr) => {
4124                // safety: slicing within bounds as we have guaranteed a non-empty slice
4125                let sliced =
4126                    unsafe { AudioData::from_array1_view(arr.slice(ndarray::s![start..end])) };
4127                Ok(AudioSamples::from_borrowed_with_layout(
4128                    sliced,
4129                    self.sample_rate(),
4130                ))
4131            }
4132            AudioData::Multi(arr) => {
4133                let sliced =
4134                    // safety: slicing within bounds as we have guaranteed a non-empty slice
4135                    unsafe { AudioData::from_array2_view(arr.slice(ndarray::s![.., start..end])) };
4136                Ok(AudioSamples::from_borrowed_with_layout(
4137                    sliced,
4138                    self.sample_rate(),
4139                ))
4140            }
4141        }
4142    }
4143
4144    /// Slice audio by channel range, keeping all samples.
4145    ///
4146    /// Creates a new AudioSamples instance containing only the specified channels.
4147    ///
4148    /// # Arguments
4149    /// * `channel_range` - Range of channels to extract (e.g., 0..2 for stereo)
4150    ///
4151    /// # Returns
4152    /// A new AudioSamples instance with the sliced channels
4153    ///
4154    /// # Errors
4155    /// Returns an error if the range is out of bounds.
4156    #[inline]
4157    pub fn slice_channels<'iter, R>(
4158        &'iter self,
4159        channel_range: R,
4160    ) -> AudioSampleResult<AudioSamples<'static, T>>
4161    where
4162        'iter: 'a,
4163        R: RangeBounds<usize> + Clone,
4164    {
4165        let num_channels = self.num_channels();
4166
4167        let start = match channel_range.start_bound() {
4168            Bound::Included(&n) => n,
4169            Bound::Excluded(&n) => n + 1,
4170            Bound::Unbounded => 0,
4171        };
4172
4173        let end = match channel_range.end_bound() {
4174            Bound::Included(&n) => n + 1,
4175            Bound::Excluded(&n) => n,
4176            Bound::Unbounded => num_channels.get() as usize,
4177        };
4178
4179        if start >= num_channels.get() as usize || end > num_channels.get() as usize || start >= end
4180        {
4181            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4182                "channel_range",
4183                format!(
4184                    "Channel range {}..{} out of bounds for {} channels",
4185                    start,
4186                    end,
4187                    num_channels.get()
4188                ),
4189            )));
4190        }
4191
4192        match &self.data {
4193            AudioData::Mono(arr) => {
4194                if start != 0 || end != 1 {
4195                    return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4196                        "channel_range",
4197                        format!(
4198                            "Channel range {start}..{end} invalid for mono audio (only 0..1 allowed)"
4199                        ),
4200                    )));
4201                }
4202                let audio_data = AudioData::try_from(arr.as_view())?;
4203                let audio = AudioSamples::from_owned(audio_data.into_owned(), self.sample_rate);
4204                Ok(audio)
4205            }
4206            AudioData::Multi(arr) => {
4207                let sliced = arr.slice(ndarray::s![start..end, ..]);
4208                if end - start == 1 {
4209                    // Single channel result - requires owned data due to temporary
4210                    let mono_data = sliced
4211                        .index_axis(ndarray::Axis(0), 0)
4212                        .to_owned()
4213                        .try_into()?;
4214                    let audio = AudioSamples::from_owned(mono_data, self.sample_rate);
4215                    Ok(audio)
4216                } else {
4217                    // Multi-channel result - return owned data for consistency
4218                    let audio =
4219                        AudioSamples::from_owned(sliced.to_owned().try_into()?, self.sample_rate);
4220                    Ok(audio)
4221                }
4222            }
4223        }
4224    }
4225
4226    /// Slice audio by both channel and sample ranges.
4227    ///
4228    /// Creates a new AudioSamples instance containing the intersection of
4229    /// the specified channel and sample ranges.
4230    ///
4231    /// # Arguments
4232    /// * `channel_range` - Range of channels to extract
4233    /// * `sample_range` - Range of samples to extract
4234    ///
4235    /// # Returns
4236    /// A new AudioSamples instance with the sliced data
4237    ///
4238    /// # Errors
4239    /// Returns an error if either range is out of bounds.
4240    #[inline]
4241    pub fn slice_both<CR, SR>(
4242        &self,
4243        channel_range: CR,
4244        sample_range: SR,
4245    ) -> AudioSampleResult<AudioSamples<'static, T>>
4246    where
4247        CR: RangeBounds<isize> + Clone,
4248        SR: RangeBounds<isize> + Clone,
4249    {
4250        let num_channels = self.num_channels().get() as isize;
4251        let samples_per_channel = self.samples_per_channel().get().cast_signed();
4252
4253        // --- Helper closure for normalising negative indices ---
4254        let norm = |idx: isize, len: isize| -> usize {
4255            if idx < 0 {
4256                (len + idx).max(0) as usize
4257            } else {
4258                idx.min(len) as usize
4259            }
4260        };
4261
4262        // --- Channel range ---
4263        let ch_start = match channel_range.start_bound() {
4264            Bound::Included(&n) => norm(n, num_channels),
4265            Bound::Excluded(&n) => norm(n + 1, num_channels),
4266            Bound::Unbounded => 0,
4267        };
4268        let ch_end = match channel_range.end_bound() {
4269            Bound::Included(&n) => norm(n + 1, num_channels),
4270            Bound::Excluded(&n) => norm(n, num_channels),
4271            Bound::Unbounded => num_channels as usize,
4272        };
4273
4274        // --- Sample range ---
4275        let s_start = match sample_range.start_bound() {
4276            Bound::Included(&n) => norm(n, samples_per_channel),
4277            Bound::Excluded(&n) => norm(n + 1, samples_per_channel),
4278            Bound::Unbounded => 0,
4279        };
4280        let s_end = match sample_range.end_bound() {
4281            Bound::Included(&n) => norm(n + 1, samples_per_channel),
4282            Bound::Excluded(&n) => norm(n, samples_per_channel),
4283            Bound::Unbounded => samples_per_channel as usize,
4284        };
4285
4286        // --- Validate computed ranges ---
4287        if ch_start >= num_channels as usize || ch_end > num_channels as usize || ch_start >= ch_end
4288        {
4289            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4290                "channel_range",
4291                format!(
4292                    "Channel range {ch_start}..{ch_end} out of bounds for {num_channels} channels"
4293                ),
4294            )));
4295        }
4296
4297        if s_start >= samples_per_channel as usize
4298            || s_end > samples_per_channel as usize
4299            || s_start >= s_end
4300        {
4301            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4302                "sample_range",
4303                format!(
4304                    "Sample range {s_start}..{s_end} out of bounds for {samples_per_channel} samples"
4305                ),
4306            )));
4307        }
4308
4309        // --- Perform actual slicing ---
4310        match &self.data {
4311            AudioData::Mono(arr) => {
4312                if ch_start != 0 || ch_end != 1 {
4313                    return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4314                        "channel_range",
4315                        format!(
4316                            "Channel range {ch_start}..{ch_end} invalid for mono audio (only 0..1 allowed)"
4317                        ),
4318                    )));
4319                }
4320                let sliced = arr
4321                    .slice(ndarray::s![s_start..s_end])
4322                    .to_owned()
4323                    .try_into()?;
4324                Ok(AudioSamples::from_owned(sliced, self.sample_rate))
4325            }
4326            AudioData::Multi(arr) => {
4327                let sliced = arr.slice(ndarray::s![ch_start..ch_end, s_start..s_end]);
4328                if ch_end - ch_start == 1 {
4329                    let mono_data: AudioData<_> = sliced
4330                        .index_axis(ndarray::Axis(0), 0)
4331                        .to_owned()
4332                        .try_into()?;
4333                    Ok(AudioSamples::from_owned(mono_data, self.sample_rate))
4334                } else {
4335                    Ok(AudioSamples::from_owned(
4336                        sliced.to_owned().try_into()?,
4337                        self.sample_rate,
4338                    ))
4339                }
4340            }
4341        }
4342    }
4343
4344    /// Returns a contiguous byte view when possible.
4345    ///
4346    /// # Errors
4347    ///
4348    /// - If the audio data is not stored contiguously in memory, an error is returned since a byte view cannot be created.
4349    #[inline]
4350    pub fn bytes(&self) -> AudioSampleResult<AudioBytes<'_>> {
4351        self.data.bytes()
4352    }
4353
4354    /// Convert audio samples to raw bytes. If the underlying data is not contiguous,
4355    /// this will return an error
4356    ///
4357    /// # Errors
4358    ///
4359    /// - If the audio data is not stored contiguously in memory, an error is returned since a byte view cannot be created.
4360    #[inline]
4361    pub fn into_bytes(&self) -> AudioSampleResult<NonEmptyByteVec> {
4362        self.data.into_bytes()
4363    }
4364
4365    /// Returns the total size in bytes of all audio data.
4366    ///
4367    /// This is equivalent to `self.data.len() * self.bytes_per_sample()`.
4368    ///
4369    /// # Examples
4370    /// ```
4371    /// use audio_samples::{AudioSamples, sample_rate};
4372    /// use ndarray::array;
4373    ///
4374    /// let audio = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0], sample_rate!(44100)).unwrap();
4375    /// assert_eq!(audio.total_byte_size().get(), 12); // 3 samples × 4 bytes per f32
4376    /// ```
4377    #[inline]
4378    #[must_use]
4379    pub fn total_byte_size(&self) -> NonZeroUsize {
4380        // safety: Totalbyte size will never be zero since both factors are non-zero
4381        unsafe {
4382            NonZeroUsize::new_unchecked(
4383                self.bytes_per_sample().get() as usize * self.data.len().get(),
4384            )
4385        }
4386    }
4387
4388    // Convert audio samples to bytes in the specified endianness.
4389    //
4390    // Returns a vector of bytes representing the audio data in the requested
4391    // byte order. For cross-platform compatibility and when working with
4392    // audio file formats that specify endianness.
4393    //
4394    // # Feature Gate
4395    // This method requires the `serialization` feature.
4396    //
4397    // # Examples
4398    // ```
4399    // # #[cfg(feature = "serialization")]
4400    // # {
4401    // use audio_samples::{AudioSamples, operations::Endianness};
4402    // use ndarray::array;
4403    //
4404    // let audio = AudioSamples::new_mono(array![1000i16, 2000, 3000], 44100).unwrap();
4405    //
4406    // let native_bytes = audio.as_bytes_with_endianness(Endianness::Native);
4407    // let big_endian_bytes = audio.as_bytes_with_endianness(Endianness::Big);
4408    // let little_endian_bytes = audio.as_bytes_with_endianness(Endianness::Little);
4409    //
4410    // assert_eq!(native_bytes.len(), 6); // 3 samples × 2 bytes per i16
4411    // # }
4412    // ```
4413    // #[cfg(feature = "serialization")]
4414    //
4415    // pub fn as_bytes_with_endianness(
4416    //     &self,
4417    //     endianness: crate::operations::Endianness,
4418    // ) -> AudioSampleResult<NonEmptyByteVec> {
4419    //     use crate::operations::Endianness;
4420
4421    //     match endianness {
4422    //         Endianness::Native => self.into_bytes(),
4423    //         Endianness::Big => {
4424    //             let mut result = Vec::with_capacity(self.total_byte_size().get());
4425
4426    //             match &self.data {
4427    //                 AudioData::Mono(m) => {
4428    //                     for sample in m.iter() {
4429    //                         result.extend_from_slice(sample.to_be_bytes().as_ref());
4430    //                     }
4431    //                 }
4432    //                 AudioData::Multi(m) => {
4433    //                     for sample in m.iter() {
4434    //                         result.extend_from_slice(sample.to_be_bytes().as_ref());
4435    //                     }
4436    //                 }
4437    //             }
4438
4439    //             NonEmptyByteVec::new(result).map_err(|_| AudioSampleError::EmptyData)
4440    //         }
4441    //         Endianness::Little => {
4442    //             let mut result = Vec::with_capacity(self.total_byte_size().get());
4443    //             match &self.data {
4444    //                 AudioData::Mono(m) => {
4445    //                     for sample in m.iter() {
4446    //                         result.extend_from_slice(sample.to_le_bytes().as_ref());
4447    //                     }
4448    //                 }
4449    //                 AudioData::Multi(m) => {
4450    //                     for sample in m.iter() {
4451    //                         result.extend_from_slice(sample.to_le_bytes().as_ref());
4452    //                     }
4453    //                 }
4454    //             }
4455    //             NonEmptyByteVec::new(result).map_err(|_| AudioSampleError::EmptyData)
4456    //         }
4457    //     }
4458    // }
4459
4460    /// Apply a windowed processing function to the audio data.
4461    ///
4462    /// This method processes the audio in overlapping windows, applying
4463    /// the provided function to each window and updating the audio data
4464    /// with the results.
4465    ///
4466    /// # Arguments
4467    /// * `window_size` - Size of each processing window in samples
4468    /// * `hop_size` - Number of samples to advance between windows
4469    /// * `func` - Function called for each window, receiving `(current_window, prev_window)`
4470    ///
4471    /// # Returns
4472    /// Returns `Ok(())` on success, or an error if parameters are invalid.
4473    ///
4474    /// # Errors
4475    ///
4476    /// - If the underlying array data cannot be accessed contiguously
4477    #[inline]
4478    pub fn apply_windowed<F>(
4479        &mut self,
4480        window_size: NonZeroUsize,
4481        hop_size: NonZeroUsize,
4482        func: F,
4483    ) -> AudioSampleResult<()>
4484    where
4485        F: Fn(&[T], &[T]) -> Vec<T>,
4486    {
4487        self.data.apply_windowed(window_size, hop_size, func)
4488    }
4489
4490    /// Converts this AudioSamples into an owned version with 'static lifetime.
4491    ///
4492    /// This method takes ownership of the AudioSamples and ensures all data is owned,
4493    /// allowing it to be moved freely without lifetime constraints.
4494    #[inline]
4495    #[must_use]
4496    pub fn into_owned<'b>(self) -> AudioSamples<'b, T> {
4497        AudioSamples {
4498            data: self.data.into_owned(),
4499            sample_rate: self.sample_rate,
4500        }
4501    }
4502
4503    /// Replaces the audio data while preserving sample rate.
4504    ///
4505    /// This method allows you to swap out the underlying audio data with new data
4506    /// of the same channel configuration, maintaining the existing sample rate.
4507    ///
4508    /// # Arguments
4509    /// * `new_data` - The new audio data to replace the current data
4510    ///
4511    /// # Returns
4512    /// Returns `Ok(())` on success, or an error if the new data has incompatible dimensions.
4513    ///
4514    /// # Errors
4515    /// - Returns `ParameterError` if the new data has a different number of channels
4516    ///
4517    /// # Examples
4518    /// ```rust
4519    /// use audio_samples::{AudioSamples, AudioData, sample_rate};
4520    /// use ndarray::array;
4521    ///
4522    /// // Create initial audio
4523    /// let initial_data = array![1.0f32, 2.0, 3.0];
4524    /// let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
4525    ///
4526    /// // Replace with new data
4527    /// let new_data = AudioData::new_mono(array![4.0f32, 5.0, 6.0, 7.0]).unwrap();
4528    /// audio.replace_data(new_data)?;
4529    ///
4530    /// assert_eq!(audio.samples_per_channel().get(), 4);
4531    /// assert_eq!(audio.sample_rate().get(), 44100); // Preserved
4532    /// # Ok::<(), Box<dyn std::error::Error>>(())
4533    /// ```
4534    #[inline]
4535    pub fn replace_data(&mut self, new_data: AudioData<'a, T>) -> AudioSampleResult<()> {
4536        // Validate channel count compatibility
4537        let current_channels = self.data.num_channels();
4538        let new_channels = new_data.num_channels();
4539
4540        if current_channels != new_channels {
4541            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4542                "new_data",
4543                format!(
4544                    "Channel count mismatch: existing audio has {current_channels} channels, new data has {new_channels} channels"
4545                ),
4546            )));
4547        }
4548
4549        // Validate mono/multi layout compatibility
4550        match (&self.data, &new_data) {
4551            (AudioData::Mono(_), AudioData::Multi(_)) => {
4552                return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4553                    "new_data",
4554                    "Cannot replace mono audio data with multi-channel data",
4555                )));
4556            }
4557            (AudioData::Multi(_), AudioData::Mono(_)) => {
4558                return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4559                    "new_data",
4560                    "Cannot replace multi-channel audio data with mono data",
4561                )));
4562            }
4563            _ => {} // Same layout types are fine
4564        }
4565
4566        // Replace the data while preserving metadata
4567        self.data = new_data;
4568        Ok(())
4569    }
4570
4571    /// Replaces the audio data with new mono data from an owned Array1.
4572    ///
4573    /// This is a convenience method for replacing mono audio data while preserving
4574    /// sample rate and layout metadata.
4575    ///
4576    /// # Arguments
4577    /// * `data` - New mono audio data as an owned Array1
4578    ///
4579    /// # Returns
4580    /// Returns `Ok(())` on success, or an error if the current audio is not mono.
4581    ///
4582    /// # Errors
4583    /// - Returns `ParameterError` if the current audio is not mono
4584    ///
4585    /// # Examples
4586    /// ```rust
4587    /// use audio_samples::{AudioSamples, sample_rate};
4588    /// use ndarray::array;
4589    ///
4590    /// // Create initial mono audio
4591    /// let mut audio = AudioSamples::new_mono(array![1.0f32, 2.0], sample_rate!(44100)).unwrap();
4592    ///
4593    /// // Replace with new mono data
4594    /// audio.replace_with_mono(array![3.0f32, 4.0, 5.0])?;
4595    ///
4596    /// assert_eq!(audio.samples_per_channel().get(), 3);
4597    /// assert_eq!(audio.sample_rate().get(), 44100); // Preserved
4598    /// # Ok::<(), Box<dyn std::error::Error>>(())
4599    /// ```
4600    #[inline]
4601    pub fn replace_with_mono(&mut self, data: Array1<T>) -> AudioSampleResult<()> {
4602        if data.is_empty() {
4603            return Err(AudioSampleError::EmptyData);
4604        }
4605        if !self.is_mono() {
4606            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4607                "audio_type",
4608                format!(
4609                    "Cannot replace multi-channel audio ({} channels) with mono data",
4610                    self.num_channels()
4611                ),
4612            )));
4613        }
4614
4615        let new_data = AudioData::try_from(data)?.into_owned();
4616        self.replace_data(new_data)
4617    }
4618
4619    /// Replaces the audio data with new multi-channel data from an owned Array2.
4620    ///
4621    /// This is a convenience method for replacing multi-channel audio data while preserving
4622    /// sample rate and layout metadata.
4623    ///
4624    /// # Arguments
4625    /// * `data` - New multi-channel audio data as an owned Array2 where rows are channels
4626    ///
4627    /// # Returns
4628    /// Returns `Ok(())` on success, or an error if channel count doesn't match.
4629    ///
4630    /// # Errors
4631    /// - Returns `ParameterError` if the current audio is mono
4632    /// - Returns `ParameterError` if the new data has different number of channels
4633    ///
4634    /// # Examples
4635    /// ```rust
4636    /// use audio_samples::{AudioSamples, sample_rate};
4637    /// use ndarray::array;
4638    ///
4639    /// // Create initial stereo audio
4640    /// let mut audio = AudioSamples::new_multi_channel(
4641    ///     array![[1.0f32, 2.0], [3.0, 4.0]], sample_rate!(44100)
4642    /// ).unwrap();
4643    ///
4644    /// // Replace with new stereo data (different length is OK)
4645    /// audio.replace_with_multi(array![[5.0f32, 6.0, 7.0], [8.0, 9.0, 10.0]])?;
4646    ///
4647    /// assert_eq!(audio.samples_per_channel().get(), 3);
4648    /// assert_eq!(audio.num_channels().get(), 2);
4649    /// assert_eq!(audio.sample_rate().get(), 44100); // Preserved
4650    /// # Ok::<(), Box<dyn std::error::Error>>(())
4651    /// ```
4652    #[inline]
4653    pub fn replace_with_multi(&mut self, data: Array2<T>) -> AudioSampleResult<()> {
4654        if data.is_empty() {
4655            return Err(AudioSampleError::EmptyData);
4656        }
4657        let new_channels = data.nrows();
4658        // safety: Channel count cannot be zero
4659        let new_channels = unsafe { ChannelCount::new_unchecked(new_channels as u32) };
4660
4661        if self.is_mono() {
4662            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4663                "audio_type",
4664                "Cannot replace mono audio with multi-channel data",
4665            )));
4666        }
4667
4668        if self.num_channels() != new_channels {
4669            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4670                "new_data",
4671                format!(
4672                    "Channel count mismatch: existing audio has {} channels, new data has {} channels",
4673                    self.num_channels(),
4674                    new_channels.get()
4675                ),
4676            )));
4677        }
4678
4679        let new_data = AudioData::try_from(data)?.into_owned();
4680        self.replace_data(new_data)
4681    }
4682
4683    /// Replaces the audio data with new data from a Vec.
4684    ///
4685    /// This is a convenience method for replacing audio data with samples from a Vec.
4686    /// The method infers whether to create mono or multi-channel data based on the
4687    /// current AudioSamples configuration.
4688    ///
4689    /// # Arguments
4690    /// * `samples` - Vector of samples to replace the current audio data
4691    ///
4692    /// # Returns
4693    /// Returns `Ok(())` on success, or an error if the sample count is incompatible.
4694    ///
4695    /// # Errors
4696    /// - Returns `ParameterError` if the sample count is not divisible by current channel count
4697    /// - Returns `ParameterError` if the resulting array cannot be created
4698    ///
4699    /// # Examples
4700    /// ```rust
4701    /// use audio_samples::{AudioSamples, sample_rate};
4702    /// use non_empty_slice::non_empty_vec;
4703    /// use ndarray::array;
4704    ///
4705    /// // Replace mono audio
4706    /// let mut mono_audio = AudioSamples::new_mono(array![1.0f32, 2.0], sample_rate!(44100)).unwrap();
4707    /// mono_audio.replace_with_vec(&non_empty_vec![3.0f32, 4.0, 5.0, 6.0])?;
4708    /// assert_eq!(mono_audio.samples_per_channel().get(), 4);
4709    ///
4710    /// // Replace stereo audio (interleaved samples)
4711    /// let mut stereo_audio = AudioSamples::new_multi_channel(
4712    ///     array![[1.0f32, 2.0], [3.0, 4.0]], sample_rate!(44100)
4713    /// ).unwrap();
4714    /// stereo_audio.replace_with_vec(&non_empty_vec![5.0f32, 6.0, 7.0, 8.0, 9.0, 10.0])?;
4715    /// assert_eq!(stereo_audio.samples_per_channel().get(), 3); // 6 samples ÷ 2 channels
4716    /// assert_eq!(stereo_audio.num_channels().get(), 2);
4717    /// # Ok::<(), Box<dyn std::error::Error>>(())
4718    /// ```
4719    #[inline]
4720    pub fn replace_with_vec(&mut self, samples: &NonEmptyVec<T>) -> AudioSampleResult<()> {
4721        let num_channels = self.num_channels().get() as usize;
4722
4723        if !samples.len().get().is_multiple_of(num_channels) {
4724            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4725                "samples",
4726                format!(
4727                    "Sample count {} is not divisible by channel count {}",
4728                    samples.len(),
4729                    num_channels
4730                ),
4731            )));
4732        }
4733
4734        if num_channels == 1 {
4735            // Mono case: create Array1 directly
4736            let array = Array1::from(samples.to_vec());
4737            self.replace_with_mono(array)
4738        } else {
4739            // Multi-channel case: reshape into (channels, samples_per_channel)
4740            let samples_per_channel = samples.len().get() / num_channels;
4741            let array =
4742                Array2::from_shape_vec((num_channels, samples_per_channel), samples.to_vec())
4743                    .map_err(|e| {
4744                    AudioSampleError::Parameter(ParameterError::invalid_value(
4745                        "samples",
4746                        format!(
4747                            "Failed to reshape samples into {num_channels}x{samples_per_channel} array: {e}"
4748                        ),
4749                    ))
4750                })?;
4751            self.replace_with_multi(array)
4752        }
4753    }
4754
4755    /// Replaces the audio data with new data from a Vec, validating against a specific channel count.
4756    ///
4757    /// This method is similar to `replace_with_vec` but validates the sample count against
4758    /// the provided `expected_channels` parameter rather than the current audio configuration.
4759    /// This is useful when the current audio might be a placeholder or when you want to
4760    /// ensure the data matches a specific channel configuration.
4761    ///
4762    /// # Arguments
4763    /// * `samples` - Vector of samples to replace the current audio data
4764    /// * `expected_channels` - Expected number of channels for validation
4765    ///
4766    /// # Returns
4767    /// Returns `Ok(())` on success, or an error if validation fails.
4768    ///
4769    /// # Errors
4770    /// - Returns `ParameterError` if the sample count is not divisible by `expected_channels`
4771    /// - Returns `ParameterError` if the current audio doesn't match `expected_channels`
4772    /// - Returns `ParameterError` if the resulting array cannot be created
4773    ///
4774    /// # Examples
4775    /// ```rust
4776    /// use audio_samples::{AudioSamples, sample_rate, channels};
4777    /// use non_empty_slice::non_empty_vec;
4778    /// use ndarray::array;
4779    ///
4780    /// // Create placeholder stereo audio
4781    /// let mut audio = AudioSamples::new_multi_channel(
4782    ///     array![[0.0f32, 0.0], [0.0, 0.0]], sample_rate!(44100)
4783    /// ).unwrap();
4784    ///
4785    /// // Replace with data that must be stereo (2 channels)
4786    /// let samples = non_empty_vec![1.0f32, 2.0, 3.0, 4.0]; // 4 samples = 2 samples per channel
4787    /// audio.replace_with_vec_channels(&samples, channels!(2))?;
4788    ///
4789    /// assert_eq!(audio.samples_per_channel().get(), 2);
4790    /// assert_eq!(audio.num_channels().get(), 2);
4791    /// # Ok::<(), Box<dyn std::error::Error>>(())
4792    /// ```
4793    #[inline]
4794    pub fn replace_with_vec_channels(
4795        &mut self,
4796        samples: &NonEmptyVec<T>,
4797        expected_channels: ChannelCount,
4798    ) -> AudioSampleResult<()> {
4799        // Validate sample count against expected channels
4800        if !samples
4801            .len()
4802            .get()
4803            .is_multiple_of(expected_channels.get() as usize)
4804        {
4805            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4806                "samples",
4807                format!(
4808                    "Sample count {} is not divisible by expected channel count {}",
4809                    samples.len(),
4810                    expected_channels.get()
4811                ),
4812            )));
4813        }
4814        // Validate that current audio matches expected channels
4815        if self.num_channels() != expected_channels {
4816            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
4817                "expected_channels",
4818                format!(
4819                    "Current audio has {} channels, but expected {} channels",
4820                    self.num_channels(),
4821                    expected_channels
4822                ),
4823            )));
4824        }
4825
4826        if expected_channels == channels!(1) {
4827            // Mono case: create Array1 directly
4828            let array: Array1<T> = Array1::from_vec(samples.to_vec());
4829            self.replace_with_mono(array)
4830        } else {
4831            // Multi-channel case: reshape into (channels, samples_per_channel)
4832            let samples_per_channel = self.samples_per_channel();
4833            let array = Array2::from_shape_vec(
4834                (expected_channels.get() as usize, samples_per_channel.get()),
4835                samples.to_vec(),
4836            )?;
4837            self.replace_with_multi(array)
4838        }
4839    }
4840
4841    /// Returns a reference to the underlying mono array if this is mono audio.
4842    ///
4843    /// # Returns
4844    /// Some reference to the mono array if this is mono audio, None otherwise.
4845    #[inline]
4846    #[must_use]
4847    pub const fn as_mono(&self) -> Option<&MonoData<'a, T>> {
4848        match &self.data {
4849            AudioData::Mono(arr) => Some(arr),
4850            AudioData::Multi(_) => None,
4851        }
4852    }
4853
4854    /// Returns a reference to the underlying multi-channel array if this is multi-channel audio.
4855    ///
4856    /// # Returns
4857    /// Some reference to the multi-channel array if this is multi-channel audio, None otherwise.
4858    #[inline]
4859    #[must_use]
4860    pub const fn as_multi_channel(&self) -> Option<&MultiData<'a, T>> {
4861        match &self.data {
4862            AudioData::Mono(_) => None,
4863            AudioData::Multi(arr) => Some(arr),
4864        }
4865    }
4866
4867    /// Returns a mutable reference to the underlying mono array if this is mono audio.
4868    ///
4869    /// # Returns
4870    /// Some mutable reference to the mono array if this is mono audio, None otherwise.
4871    #[inline]
4872    pub const fn as_mono_mut(&mut self) -> Option<&mut MonoData<'a, T>> {
4873        match &mut self.data {
4874            AudioData::Mono(arr) => Some(arr),
4875            AudioData::Multi(_) => None,
4876        }
4877    }
4878
4879    /// Returns a mutable reference to the underlying multi-channel array if this is multi-channel audio.
4880    ///
4881    /// # Returns
4882    /// Some mutable reference to the multi-channel array if this is multi-channel audio, None otherwise.
4883    #[inline]
4884    pub const fn as_multi_channel_mut(&mut self) -> Option<&mut MultiData<'a, T>> {
4885        match &mut self.data {
4886            AudioData::Mono(_) => None,
4887            AudioData::Multi(arr) => Some(arr),
4888        }
4889    }
4890
4891    /// Creates a new mono AudioSamples from a slice.
4892    ///
4893    /// # Arguments
4894    ///
4895    /// - `slice` - A non-empty slice containing the audio samples in row-major order (all samples for channel 0, then all samples for channel 1, etc.).
4896    /// - `sample_rate` - The sample rate of the audio.
4897    ///
4898    /// # Returns
4899    ///
4900    /// A new instance of `Self`
4901    #[inline]
4902    pub fn new_mono_from_slice(slice: &'a NonEmptySlice<T>, sample_rate: SampleRate) -> Self {
4903        let arr = ArrayView1::from(slice);
4904        // safety: We know slice to be non-empty
4905        let mono_data = unsafe { MonoData::from_view_unchecked(arr) };
4906        let audio_data = AudioData::Mono(mono_data);
4907        AudioSamples {
4908            data: audio_data,
4909            sample_rate,
4910        }
4911    }
4912
4913    /// Creates a new mono AudioSamples from a mutable slice.
4914    ///
4915    /// # Arguments
4916    ///
4917    /// - `slice` - A non-empty slice containing the audio samples in row-major order (all samples for channel 0, then all samples for channel 1, etc.).
4918    /// - `sample_rate` - The sample rate of the audio.
4919    ///
4920    /// # Returns
4921    ///
4922    /// A new instance of `Self`
4923    #[inline]
4924    pub fn new_mono_from_mut_slice(
4925        slice: &'a mut NonEmptySlice<T>,
4926        sample_rate: SampleRate,
4927    ) -> Self {
4928        let arr = ArrayViewMut1::from(slice);
4929        // safety: We know the slice is non-empty
4930        let mono_data = unsafe { MonoData::from_view_mut_unchecked(arr) };
4931        let audio_data = AudioData::Mono(mono_data);
4932        AudioSamples {
4933            data: audio_data,
4934            sample_rate,
4935        }
4936    }
4937
4938    /// Creates a new multi-channel AudioSamples from a slice.
4939    ///
4940    /// # Arguments
4941    ///
4942    /// - `slice` - A non-empty slice containing the audio samples in row-major order (all samples for channel 0, then all samples for channel 1, etc.).
4943    /// - `channels` - The number of channels in the audio data.
4944    /// - `sample_rate` - The sample rate of the audio data.
4945    ///
4946    /// # Returns
4947    ///
4948    /// Returns an `AudioSampleResult` containing the new `AudioSamples` instance if successful, or an error if the input data is invalid.
4949    ///
4950    /// # Errors
4951    ///
4952    /// - If the length of the slice is not divisible by the number of channels.
4953    /// - If the slice cannot be reshaped into the desired shape.
4954    #[inline]
4955    pub fn new_multi_channel_from_slice(
4956        slice: &'a NonEmptySlice<T>,
4957        channels: ChannelCount,
4958        sample_rate: SampleRate,
4959    ) -> AudioSampleResult<Self> {
4960        let total_samples = slice.len().get();
4961        let samples_per_channel = total_samples / channels.get() as usize;
4962        let arr = ArrayView2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
4963        let multi_data = MultiData::from_view(arr)?;
4964        let audio_data = AudioData::Multi(multi_data);
4965        Ok(AudioSamples {
4966            data: audio_data,
4967            sample_rate,
4968        })
4969    }
4970
4971    /// Creates a new multi-channel AudioSamples from a mutable slice.
4972    ///
4973    /// # Arguments
4974    ///
4975    /// - `slice` - A non-empty slice containing the audio samples in row-major order (all samples for channel 0, then all samples for channel 1, etc.).
4976    /// - `channels` - The number of channels in the audio data.
4977    /// - `sample_rate` - The sample rate of the audio data.
4978    ///
4979    /// # Returns
4980    ///
4981    /// Returns an `AudioSampleResult` containing the new `AudioSamples` instance if successful, or an error if the input data is invalid.
4982    ///
4983    /// # Errors
4984    /// - If the length of the slice is not divisible by the number of channels.
4985    /// - If the slice cannot be reshaped into the desired shape.
4986    #[inline]
4987    pub fn new_multi_channel_from_mut_slice(
4988        slice: &'a mut NonEmptySlice<T>,
4989        channels: ChannelCount,
4990        sample_rate: SampleRate,
4991    ) -> AudioSampleResult<Self> {
4992        let total_samples = slice.len().get();
4993        let samples_per_channel = total_samples / channels.get() as usize;
4994        let arr = ArrayViewMut2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
4995        let multi_data = MultiData::from_view_mut(arr)?;
4996        let audio_data = AudioData::Multi(multi_data);
4997        Ok(AudioSamples {
4998            data: audio_data,
4999            sample_rate,
5000        })
5001    }
5002
5003    /// Creates multi-channel AudioSamples from a flat non-empty vec (owned).
5004    ///
5005    /// # Arguments
5006    ///
5007    /// - `vec` - A non-empty vector containing the audio samples in row-major order (all samples for channel 0, then all samples for channel 1, etc.).
5008    /// - `channels` - The number of channels in the audio data.
5009    /// - `sample_rate` - The sample rate of the audio data.
5010    ///
5011    /// # Returns
5012    ///
5013    /// Returns an `AudioSampleResult` containing the new `AudioSamples` instance if successful, or an error if the input data is invalid.
5014    ///
5015    /// # Errors
5016    ///
5017    /// If the number of samples in the input Vec is not divisible by the number of channels, or if the resulting array cannot be created due to shape issues.
5018    #[inline]
5019    pub fn new_multi_channel_from_vec<O>(
5020        vec: NonEmptyVec<O>,
5021        channels: ChannelCount,
5022        sample_rate: SampleRate,
5023    ) -> AudioSampleResult<Self>
5024    where
5025        T: ConvertFrom<O>,
5026        O: StandardSample,
5027    {
5028        let total_samples = vec.len().get();
5029        if !total_samples.is_multiple_of(channels.get() as usize) {
5030            return Err(AudioSampleError::invalid_number_of_samples(
5031                total_samples,
5032                channels.get(),
5033            ));
5034        }
5035        let samples_per_channel = total_samples / channels.get() as usize;
5036        let vec: NonEmptyVec<T> = vec
5037            .into_non_empty_iter()
5038            .map(T::convert_from)
5039            .collect_non_empty();
5040        let arr =
5041            Array2::from_shape_vec((channels.get() as usize, samples_per_channel), vec.to_vec())?;
5042
5043        let multi_data = MultiData::from_owned(arr)?;
5044        let audio_data = AudioData::Multi(multi_data);
5045        Ok(AudioSamples {
5046            data: audio_data,
5047            sample_rate,
5048        })
5049    }
5050
5051    /// Creates mono AudioSamples from a Vec (owned).
5052    ///
5053    /// # Examples
5054    /// ```
5055    /// use audio_samples::{AudioSamples, sample_rate};
5056    /// use non_empty_slice::non_empty_vec;
5057    ///
5058    /// let samples = non_empty_vec![0.1f32, 0.2, 0.3, 0.4];
5059    /// let audio: AudioSamples<'static, f32> = AudioSamples::from_mono_vec(samples, sample_rate!(44100));
5060    /// assert_eq!(audio.num_channels().get(), 1);
5061    /// assert_eq!(audio.samples_per_channel().get(), 4);
5062    /// ```
5063    #[inline]
5064    pub fn from_mono_vec<O>(
5065        vec: NonEmptyVec<O>,
5066        sample_rate: SampleRate,
5067    ) -> AudioSamples<'static, T>
5068    where
5069        O: StandardSample,
5070        T: ConvertFrom<O>,
5071    {
5072        let vec: NonEmptyVec<T> = vec
5073            .into_non_empty_iter()
5074            .map(ConvertFrom::convert_from)
5075            .collect_non_empty();
5076        AudioSamples {
5077            data: AudioData::from_vec(vec),
5078            sample_rate,
5079        }
5080    }
5081
5082    /// Creates multi-channel AudioSamples from a Vec with specified channel count (owned).
5083    ///
5084    /// The samples in the Vec are assumed to be in row-major order (all samples for
5085    /// channel 0, then all samples for channel 1, etc.).
5086    ///
5087    /// # Examples
5088    /// ```
5089    /// use audio_samples::{AudioSamples, sample_rate, channels};
5090    /// use non_empty_slice::non_empty_vec;
5091    ///
5092    /// // 2 channels, 3 samples each: [ch0_s0, ch0_s1, ch0_s2, ch1_s0, ch1_s1, ch1_s2]
5093    /// let samples = non_empty_vec![0.1f32, 0.2, 0.3, 0.4, 0.5, 0.6];
5094    /// let audio = AudioSamples::from_vec_with_channels(samples, channels!(2), sample_rate!(44100)).unwrap();
5095    /// assert_eq!(audio.num_channels().get(), 2);
5096    /// assert_eq!(audio.samples_per_channel().get(), 3);
5097    /// ```
5098    ///
5099    /// # Errors
5100    ///
5101    /// If the number of samples in the input Vec is not divisible by the number of channels, or if the resulting array cannot be created due to shape issues.
5102    #[inline]
5103    pub fn from_vec_with_channels(
5104        vec: NonEmptyVec<T>,
5105        channels: ChannelCount,
5106        sample_rate: SampleRate,
5107    ) -> AudioSampleResult<AudioSamples<'static, T>> {
5108        let total_samples = vec.len().get();
5109
5110        if !total_samples.is_multiple_of(channels.get() as usize) {
5111            return Err(AudioSampleError::invalid_number_of_samples(
5112                total_samples,
5113                channels.get(),
5114            ));
5115        }
5116
5117        Ok(AudioSamples {
5118            data: AudioData::from_vec_multi(vec, channels)?,
5119            sample_rate,
5120        })
5121    }
5122
5123    /// Creates AudioSamples from interleaved sample data (owned).
5124    ///
5125    /// Interleaved format: [L0, R0, L1, R1, L2, R2, ...] for stereo.
5126    /// This method de-interleaves the data into the internal non-interleaved format.
5127    ///
5128    /// # Examples
5129    /// ```
5130    /// use audio_samples::{AudioSamples, sample_rate, channels};
5131    /// use non_empty_slice::non_empty_vec;
5132    ///
5133    /// // Interleaved stereo: [L0, R0, L1, R1]
5134    /// let interleaved = non_empty_vec![0.1f32, 0.4, 0.2, 0.5];
5135    /// let audio: AudioSamples<'static, f32> = AudioSamples::from_interleaved_vec(interleaved, channels!(2), sample_rate!(44100)).unwrap();
5136    /// assert_eq!(audio.num_channels().get(), 2);
5137    /// assert_eq!(audio.samples_per_channel().get(), 2);
5138    /// // After de-interleaving: channel 0 = [0.1, 0.2], channel 1 = [0.4, 0.5]
5139    /// ```
5140    ///
5141    /// # Errors
5142    ///
5143    /// If the number of samples in the input Vec is not divisible by the number of channels, or if the de-interleaving process fails due to shape issues.
5144    ///
5145    #[inline]
5146    pub fn from_interleaved_vec<O>(
5147        samples: NonEmptyVec<O>,
5148        channels: ChannelCount,
5149        sample_rate: SampleRate,
5150    ) -> AudioSampleResult<AudioSamples<'static, T>>
5151    where
5152        T: ConvertFrom<O>,
5153        O: StandardSample,
5154    {
5155        if channels.get() == 1 {
5156            return Ok(AudioSamples::from_mono_vec(samples, sample_rate));
5157        }
5158
5159        let deinterleaved = crate::simd_conversions::deinterleave_multi_vec(&samples, channels)?;
5160        AudioSamples::new_multi_channel_from_vec::<O>(deinterleaved, channels, sample_rate)
5161    }
5162
5163    /// Creates AudioSamples from interleaved slice data (borrowed).
5164    ///
5165    /// # Arguments
5166    ///
5167    /// - `samples`: A non-empty slice of interleaved audio samples.
5168    /// - `channels`: The number of channels in the interleaved data.
5169    /// - `sample_rate`: The sample rate of the audio data.
5170    ///
5171    /// # Errors
5172    ///
5173    /// If the `samples` slice cannot be turned into an 2D Array due to a shape error.
5174    #[inline]
5175    pub fn from_interleaved_slice(
5176        samples: &'a NonEmptySlice<T>,
5177        channels: ChannelCount,
5178        sample_rate: SampleRate,
5179    ) -> AudioSampleResult<Self> {
5180        if channels == channels!(1) {
5181            return Ok(AudioSamples::new_mono_from_slice(samples, sample_rate));
5182        }
5183
5184        AudioSamples::new_multi_channel_from_slice(samples, channels, sample_rate)
5185    }
5186
5187    /// Creates multi-channel AudioSamples from separate channel arrays.
5188    ///
5189    /// Each element in the vector represents one channel of audio data.
5190    /// All channels must have the same length.
5191    ///
5192    /// # Errors
5193    /// Returns an error if:
5194    /// - any channel is empty (has no samples)
5195    /// - channels have different lengths
5196    ///
5197    /// # Examples
5198    /// ```
5199    /// use audio_samples::{AudioSamples, sample_rate};
5200    /// use non_empty_slice::non_empty_vec;
5201    ///
5202    /// let left = non_empty_vec![0.1f32, 0.2, 0.3];
5203    /// let right = non_empty_vec![0.4, 0.5, 0.6];
5204    /// let audio: AudioSamples<'static, f32> = AudioSamples::from_channels(non_empty_vec![left, right], sample_rate!(44100)).unwrap();
5205    /// assert_eq!(audio.num_channels().get(), 2);
5206    /// assert_eq!(audio.samples_per_channel().get(), 3);
5207    /// ```
5208    #[inline]
5209    pub fn from_channels<O>(
5210        channels: NonEmptyVec<NonEmptyVec<O>>,
5211        sample_rate: SampleRate,
5212    ) -> AudioSampleResult<AudioSamples<'static, T>>
5213    where
5214        T: ConvertFrom<O>,
5215        O: StandardSample,
5216    {
5217        let num_channels = channels.len().get();
5218        let samples_per_channel = channels[0].len().get();
5219
5220        // Validate all channels have the same length
5221        for (idx, ch) in channels.iter().enumerate() {
5222            if ch.len().get() != samples_per_channel {
5223                return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
5224                    "channels",
5225                    format!(
5226                        "Channel {} has {} samples, but channel 0 has {} samples",
5227                        idx,
5228                        ch.len(),
5229                        samples_per_channel
5230                    ),
5231                )));
5232            }
5233        }
5234
5235        if num_channels == 1 {
5236            return Ok(AudioSamples::from_mono_vec(
5237                channels[0].clone(),
5238                sample_rate,
5239            ));
5240        }
5241
5242        // Flatten channels into row-major order
5243        let flat: NonEmptyVec<T> = channels
5244            .into_non_empty_iter()
5245            .flatten()
5246            .map(ConvertTo::convert_to)
5247            .collect_non_empty();
5248        let arr = Array2::from_shape_vec((num_channels, samples_per_channel), flat.to_vec())
5249            .map_err(|e| {
5250                AudioSampleError::Parameter(ParameterError::invalid_value(
5251                    "channels",
5252                    format!("Failed to create multi-channel array: {e}"),
5253                ))
5254            })?;
5255
5256        Ok(AudioSamples {
5257            data: AudioData::Multi(MultiData::from_owned(arr)?),
5258            sample_rate,
5259        })
5260    }
5261
5262    /// Creates multi-channel AudioSamples from separate mono AudioSamples.
5263    ///
5264    /// All input AudioSamples must be mono and have the same sample rate and length.
5265    ///
5266    /// # Errors
5267    /// Returns an error if:
5268    /// - Any input is not mono
5269    /// - Sample rates don't match
5270    /// - Lengths don't match
5271    ///
5272    /// # Examples
5273    /// ```
5274    /// use audio_samples::{AudioSamples, sample_rate};
5275    /// use non_empty_slice::non_empty_vec;
5276    /// use ndarray::array;
5277    ///
5278    /// let left = AudioSamples::new_mono(array![0.1f32, 0.2, 0.3], sample_rate!(44100)).unwrap();
5279    /// let right = AudioSamples::new_mono(array![0.4, 0.5, 0.6], sample_rate!(44100)).unwrap();
5280    /// let stereo: AudioSamples<'static, f32> = AudioSamples::from_mono_channels(non_empty_vec![left, right]).unwrap();
5281    /// assert_eq!(stereo.num_channels().get(), 2);
5282    /// ```
5283    #[inline]
5284    pub fn from_mono_channels<O>(
5285        channels: NonEmptyVec<AudioSamples<'_, O>>,
5286    ) -> AudioSampleResult<AudioSamples<'static, T>>
5287    where
5288        O: StandardSample + ConvertFrom<T> + ConvertTo<O> + ConvertFrom<O>,
5289        T: ConvertFrom<O>,
5290    {
5291        let sample_rate = channels[0].sample_rate();
5292        let samples_per_channel = channels[0].samples_per_channel();
5293
5294        // Validate all channels
5295        for (idx, ch) in channels.iter().enumerate() {
5296            if !ch.is_mono() {
5297                return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
5298                    "channels",
5299                    format!(
5300                        "Channel {} is not mono (has {} channels)",
5301                        idx,
5302                        ch.num_channels()
5303                    ),
5304                )));
5305            }
5306            if ch.sample_rate() != sample_rate {
5307                return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
5308                    "channels",
5309                    format!(
5310                        "Channel {} has sample rate {}, but channel 0 has sample rate {}",
5311                        idx,
5312                        ch.sample_rate(),
5313                        sample_rate
5314                    ),
5315                )));
5316            }
5317            if ch.samples_per_channel() != samples_per_channel {
5318                return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
5319                    "channels",
5320                    format!(
5321                        "Channel {} has {} samples, but channel 0 has {} samples",
5322                        idx,
5323                        ch.samples_per_channel(),
5324                        samples_per_channel
5325                    ),
5326                )));
5327            }
5328        }
5329
5330        // Extract data from each mono channel
5331        let channel_data: NonEmptyVec<NonEmptyVec<O>> = channels
5332            .into_non_empty_iter()
5333            .map(|ch| match ch.data {
5334                // Safety: m has already been verified as non-empty
5335                AudioData::Mono(m) => unsafe { NonEmptyVec::new_unchecked(m.to_vec()) },
5336                AudioData::Multi(_) => unreachable!("Already validated as mono"),
5337            })
5338            .collect_non_empty();
5339
5340        AudioSamples::from_channels(channel_data, sample_rate)
5341    }
5342    /// Creates AudioSamples from an iterator of samples (mono, owned).
5343    ///
5344    /// # Examples
5345    /// ```
5346    /// use audio_samples::{AudioSamples, sample_rate};
5347    /// use non_empty_slice::non_empty_vec;
5348    ///
5349    /// // NonEmptyVec implements IntoNonEmptyIterator
5350    /// let samples = non_empty_vec![0.0f32, 0.1, 0.2, 0.3, 0.4];
5351    /// let audio = AudioSamples::from_iter(samples, sample_rate!(44100));
5352    /// assert_eq!(audio.num_channels().get(), 1);
5353    /// assert_eq!(audio.samples_per_channel().get(), 5);
5354    /// ```
5355    #[inline]
5356    pub fn from_iter<I>(iter: I, sample_rate: SampleRate) -> AudioSamples<'static, T>
5357    where
5358        I: IntoNonEmptyIterator<Item = T>,
5359        NonEmptyVec<T>: non_empty_iter::FromNonEmptyIterator<T>,
5360    {
5361        // Convert the incoming type into a non-empty iterator
5362        let ne_iter = iter.into_non_empty_iter();
5363
5364        // Collect into your non-empty vector
5365        let ne_vec: NonEmptyVec<T> = ne_iter.collect_non_empty();
5366
5367        AudioSamples::from_mono_vec::<T>(ne_vec, sample_rate)
5368    }
5369
5370    /// Creates AudioSamples from an iterator with specified channel count (owned).
5371    ///
5372    /// Samples are collected and arranged in row-major order (all samples for
5373    /// channel 0, then channel 1, etc.).
5374    ///
5375    /// # Panics
5376    /// - If the collected sample count is not divisible by `channels`
5377    ///
5378    /// # Examples
5379    /// ```
5380    /// use audio_samples::{AudioSamples, sample_rate, channels};
5381    /// use non_empty_slice::non_empty_vec;
5382    ///
5383    /// // 2 channels, 3 samples each — NonEmptyVec implements IntoNonEmptyIterator
5384    /// let samples = non_empty_vec![0.0f32, 0.1, 0.2, 0.3, 0.4, 0.5];
5385    /// let audio = AudioSamples::from_iter_with_channels(samples, channels!(2), sample_rate!(44100));
5386    /// assert_eq!(audio.num_channels().get(), 2);
5387    /// assert_eq!(audio.samples_per_channel().get(), 3);
5388    /// ```
5389    #[inline]
5390    pub fn from_iter_with_channels<I>(
5391        iter: I,
5392        channels: ChannelCount,
5393        sample_rate: SampleRate,
5394    ) -> AudioSamples<'static, T>
5395    where
5396        I: IntoNonEmptyIterator<Item = T>,
5397        NonEmptyVec<T>: non_empty_iter::FromNonEmptyIterator<T>,
5398    {
5399        // Convert the incoming type into a non-empty iterator
5400        let ne_iter = iter.into_non_empty_iter();
5401
5402        // Collect into your non-empty vector
5403        let ne_vec: NonEmptyVec<T> = ne_iter.collect_non_empty();
5404
5405        AudioSamples::from_vec_with_channels(ne_vec, channels, sample_rate)
5406            .expect("Collected samples should be valid for the given channel count")
5407    }
5408
5409    /// Calculates the nyquist frequency of the signal
5410    #[inline]
5411    #[must_use]
5412    pub fn nyquist(&self) -> f64 {
5413        f64::from(self.sample_rate.get()) / 2.0
5414    }
5415
5416    /// Raises each sample to the specified exponent, optionally applying modulo.
5417    ///
5418    /// # Arguments
5419    ///
5420    /// * `exponent` - The exponent to raise each sample to.
5421    /// * `modulo` - Optional modulo value to apply after exponentiation.
5422    ///
5423    /// # Returns
5424    ///
5425    /// A new AudioSamples instance with each sample raised to the specified exponent.
5426    #[inline]
5427    #[must_use]
5428    pub fn powf(&self, exponent: f64, modulo: Option<T>) -> Self {
5429        let new_data = match &self.data {
5430            AudioData::Mono(mono) => {
5431                let powered = mono.mapv(|sample: T| {
5432                    let base: f64 = sample.cast_into();
5433                    let result = base.powf(exponent);
5434                    let powered_sample: T = T::cast_from(result);
5435                    modulo.map_or(powered_sample, |mod_val| powered_sample % mod_val)
5436                });
5437                // safety: self has already been validated as non-empty, therefore powered is non-empty
5438                unsafe { AudioData::Mono(MonoData::from_owned_unchecked(powered)) }
5439            }
5440            AudioData::Multi(multi) => {
5441                let powered = multi.mapv(|sample| {
5442                    let base: f64 = sample.cast_into();
5443                    let result = base.powf(exponent);
5444                    let powered_sample: T = T::cast_from(result);
5445                    modulo.map_or(powered_sample, |mod_val| powered_sample % mod_val)
5446                });
5447                // safety: self has already been validated as non-empty, therefore powered is non-empty
5448                unsafe { AudioData::Multi(MultiData::from_owned_unchecked(powered)) }
5449            }
5450        };
5451
5452        AudioSamples {
5453            data: new_data,
5454            sample_rate: self.sample_rate,
5455        }
5456    }
5457
5458    /// Calls `f` with a mutable *view* of the window.
5459    ///
5460    /// For mono: `ArrayViewMut1<T>` of length `len`.
5461    /// For multi: `ArrayViewMut2<T>` with shape:
5462    /// - (channels, len) if SamplesAreAxis1
5463    /// - (len, channels) if SamplesAreAxis0
5464    #[inline]
5465    pub fn with_window_mut<R>(
5466        &mut self,
5467        start: usize,
5468        len: NonZeroUsize,
5469        f: impl FnOnce(WindowMut<'_, T>) -> R,
5470    ) -> Option<R> {
5471        let len = len.get();
5472        let total = self.samples_per_channel().get();
5473        if start >= total {
5474            return None;
5475        }
5476
5477        let end = (start + len).min(total);
5478
5479        let out = match &mut self.data {
5480            AudioData::Mono(mono_data) => {
5481                let view = mono_data.slice_mut(s![start..end]);
5482                f(WindowMut::Mono(view))
5483            }
5484            AudioData::Multi(multi_data) => {
5485                // (channels, samples): slice along Axis(1)
5486                let view = multi_data.slice_mut(s![.., start..end]);
5487                f(WindowMut::Multi(view))
5488            }
5489        };
5490
5491        Some(out)
5492    }
5493}
5494
5495/// Mutable window view  over ``ndarray::ArrayViewMut1`` and ``ndarray::ArrayViewMut2`` for `with_window_mut` method.
5496#[derive(Debug)]
5497pub enum WindowMut<'a, T> {
5498    Mono(ArrayViewMut1<'a, T>),
5499    Multi(ArrayViewMut2<'a, T>),
5500}
5501
5502/// Indicates the time axis orientation in multi-channel audio data.
5503#[derive(Clone, Copy, Debug)]
5504#[allow(unused)]
5505pub enum TimeAxis {
5506    /// Shape: (channels, samples)
5507    SamplesAreAxis1,
5508    /// Shape: (samples, channels)
5509    SamplesAreAxis0,
5510}
5511
5512impl<T> Clone for AudioSamples<'_, T>
5513where
5514    T: StandardSample,
5515{
5516    #[inline]
5517    fn clone(&self) -> Self {
5518        AudioSamples {
5519            data: self.data.clone(),
5520            sample_rate: self.sample_rate,
5521        }
5522    }
5523}
5524
5525impl<T> TryInto<Array1<T>> for AudioSamples<'_, T>
5526where
5527    T: StandardSample,
5528{
5529    type Error = AudioSampleError;
5530    /// Convert mono AudioSamples into an owned Array1.
5531    ///
5532    /// # Errors
5533    /// Returns an error if the AudioSamples is not mono.
5534    #[inline]
5535    fn try_into(self) -> Result<Array1<T>, Self::Error> {
5536        match self.data {
5537            AudioData::Mono(mono) => Ok(mono.as_view().to_owned()),
5538            AudioData::Multi(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
5539                "audio_type",
5540                "Cannot convert multi-channel AudioSamples into Array1",
5541            ))),
5542        }
5543    }
5544}
5545
5546impl<T> TryInto<Array2<T>> for AudioSamples<'_, T>
5547where
5548    T: StandardSample,
5549{
5550    type Error = AudioSampleError;
5551    /// Convert multi-channel AudioSamples into an owned Array2.
5552    ///
5553    /// # Errors
5554    ///
5555    /// Returns an error if the AudioSamples is not multi-channel.
5556    #[inline]
5557    fn try_into(self) -> Result<Array2<T>, Self::Error> {
5558        match self.data {
5559            AudioData::Mono(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
5560                "audio_type",
5561                "Cannot convert mono AudioSamples into Array2",
5562            ))),
5563            AudioData::Multi(multi) => Ok(multi.as_view().to_owned()),
5564        }
5565    }
5566}
5567
5568impl<T> TryFrom<(ChannelCount, SampleRate, NonEmptyVec<T>)> for AudioSamples<'static, T>
5569where
5570    T: StandardSample,
5571{
5572    type Error = AudioSampleError;
5573
5574    /// Create AudioSamples from a sample rate, the number of channels and a vector of samples .
5575    #[inline]
5576    fn try_from(
5577        (channels, sample_rate, samples): (ChannelCount, SampleRate, NonEmptyVec<T>),
5578    ) -> Result<Self, Self::Error> {
5579        if channels == channels!(1) {
5580            Ok(AudioSamples::from_mono_vec(samples, sample_rate))
5581        } else {
5582            AudioSamples::from_vec_with_channels(samples, channels, sample_rate)
5583        }
5584    }
5585}
5586
5587/// Create owned mono AudioSamples from (`NonEmptyVec<T>`, sample_rate) tuple.
5588///
5589/// # Examples
5590/// ```
5591/// use audio_samples::{AudioSamples, sample_rate};
5592/// use non_empty_slice::non_empty_vec;
5593///
5594/// let audio = AudioSamples::from((non_empty_vec![0.1f32, 0.2, 0.3], sample_rate!(44100)));
5595/// assert_eq!(audio.num_channels().get(), 1);
5596/// assert_eq!(audio.samples_per_channel().get(), 3);
5597/// ```
5598impl<T> From<(NonEmptyVec<T>, SampleRate)> for AudioSamples<'static, T>
5599where
5600    T: StandardSample,
5601{
5602    #[inline]
5603    fn from((samples, sample_rate): (NonEmptyVec<T>, SampleRate)) -> Self {
5604        AudioSamples::from_mono_vec(samples, sample_rate)
5605    }
5606}
5607impl<T> From<(SampleRate, NonEmptyVec<T>)> for AudioSamples<'static, T>
5608where
5609    T: StandardSample,
5610{
5611    #[inline]
5612    fn from((sample_rate, samples): (SampleRate, NonEmptyVec<T>)) -> Self {
5613        AudioSamples::from_mono_vec(samples, sample_rate)
5614    }
5615}
5616
5617/// Create mono AudioSamples from (`&NonEmptySlice<T>`, sample_rate) tuple (borrows the data).
5618///
5619/// # Examples
5620/// ```
5621/// use audio_samples::{AudioSamples, sample_rate};
5622/// use non_empty_slice::NonEmptySlice;
5623///
5624/// let data = [0.1f32, 0.2, 0.3];
5625/// let ne_slice = NonEmptySlice::new(&data).unwrap();
5626/// let audio = AudioSamples::from((ne_slice, sample_rate!(44100)));
5627/// assert_eq!(audio.num_channels().get(), 1);
5628/// assert_eq!(audio.samples_per_channel().get(), 3);
5629/// ```
5630impl<'a, T> From<(&'a NonEmptySlice<T>, SampleRate)> for AudioSamples<'a, T>
5631where
5632    T: StandardSample,
5633{
5634    #[inline]
5635    fn from((samples, sample_rate): (&'a NonEmptySlice<T>, SampleRate)) -> Self {
5636        AudioSamples::new_mono_from_slice(samples, sample_rate)
5637    }
5638}
5639
5640// ========================
5641// Index and IndexMut implementations using ndarray delegation
5642// ========================
5643
5644impl<T> Index<usize> for AudioSamples<'_, T>
5645where
5646    T: StandardSample,
5647{
5648    type Output = T;
5649
5650    /// Index into mono audio samples by sample index.
5651    ///
5652    /// For mono audio, this returns the sample at the given index.
5653    /// For multi-channel audio, this will panic - use `[(channel, sample)]` instead.
5654    ///
5655    /// # Panics
5656    /// - If index is out of bounds
5657    /// - If used on multi-channel audio (use 2D indexing instead)
5658    ///
5659    /// # Examples
5660    /// ```rust
5661    /// use audio_samples::{AudioSamples, sample_rate};
5662    /// use ndarray::array;
5663    ///
5664    /// let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
5665    /// let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
5666    ///
5667    /// assert_eq!(audio[0], 1.0);
5668    /// assert_eq!(audio[2], 3.0);
5669    /// ```
5670    #[inline]
5671    fn index(&self, index: usize) -> &Self::Output {
5672        match &self.data {
5673            AudioData::Mono(arr) => &arr[index],
5674            AudioData::Multi(_) => {
5675                panic!(
5676                    "Cannot use single index on multi-channel audio. Use (channel, sample) indexing instead."
5677                );
5678            }
5679        }
5680    }
5681}
5682
5683impl<T> IndexMut<usize> for AudioSamples<'_, T>
5684where
5685    T: StandardSample,
5686{
5687    /// Mutable index into mono audio samples by sample index.
5688    ///
5689    /// For mono audio, this returns a mutable reference to the sample at the given index.
5690    /// For multi-channel audio, this will panic - use `[(channel, sample)]` instead.
5691    ///
5692    /// # Panics
5693    ///
5694    /// - If index is out of bounds
5695    /// - If used on multi-channel audio (use 2D indexing instead)
5696    #[inline]
5697    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
5698        match &mut self.data {
5699            AudioData::Mono(arr) => &mut arr[index],
5700            AudioData::Multi(_) => {
5701                panic!(
5702                    "Cannot use single index on multi-channel audio. Use (channel, sample) indexing instead."
5703                );
5704            }
5705        }
5706    }
5707}
5708
5709impl<T> Index<(usize, usize)> for AudioSamples<'_, T>
5710where
5711    T: StandardSample,
5712{
5713    type Output = T;
5714
5715    /// Index into audio samples by (channel, sample) coordinates.
5716    ///
5717    /// This works for both mono and multi-channel audio:
5718    /// - For mono: only `(0, sample_index)` is valid
5719    /// - For multi-channel: `(channel_index, sample_index)`
5720    ///
5721    /// # Panics
5722    /// - If channel or sample index is out of bounds
5723    ///
5724    /// # Examples
5725    /// ```rust
5726    /// use audio_samples::{AudioSamples, sample_rate};
5727    /// use ndarray::array;
5728    ///
5729    /// // Mono audio
5730    /// let mono_data = array![1.0f32, 2.0, 3.0];
5731    /// let mono_audio = AudioSamples::new_mono(mono_data, sample_rate!(44100)).unwrap();
5732    /// assert_eq!(mono_audio[(0, 1)], 2.0);
5733    ///
5734    /// // Multi-channel audio
5735    /// let stereo_data = array![[1.0f32, 2.0], [3.0, 4.0]];
5736    /// let stereo_audio = AudioSamples::new_multi_channel(stereo_data, sample_rate!(44100)).unwrap();
5737    /// assert_eq!(stereo_audio[(0, 1)], 2.0); // Channel 0, sample 1
5738    /// assert_eq!(stereo_audio[(1, 0)], 3.0); // Channel 1, sample 0
5739    /// ```
5740    #[inline]
5741    fn index(&self, index: (usize, usize)) -> &Self::Output {
5742        let (channel, sample) = index;
5743        match &self.data {
5744            AudioData::Mono(arr) => {
5745                assert!(
5746                    channel == 0,
5747                    "Channel index {channel} out of bounds for mono audio (only channel 0 exists)"
5748                );
5749                &arr[sample]
5750            }
5751            AudioData::Multi(arr) => &arr[(channel, sample)],
5752        }
5753    }
5754}
5755
5756impl<T> IndexMut<(usize, usize)> for AudioSamples<'_, T>
5757where
5758    T: StandardSample,
5759{
5760    /// Mutable index into audio samples by (channel, sample) coordinates.
5761    ///
5762    /// This works for both mono and multi-channel audio:
5763    /// - For mono: only `(0, sample_index)` is valid  
5764    /// - For multi-channel: `(channel_index, sample_index)`
5765    ///
5766    /// # Panics
5767    /// - If channel or sample index is out of bounds
5768    #[inline]
5769    fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
5770        let (channel, sample) = index;
5771        match &mut self.data {
5772            AudioData::Mono(arr) => {
5773                assert!(
5774                    channel == 0,
5775                    "Channel index {channel} out of bounds for mono audio (only channel 0 exists)"
5776                );
5777                &mut arr[sample]
5778            }
5779            AudioData::Multi(arr) => &mut arr[(channel, sample)],
5780        }
5781    }
5782}
5783
5784impl<T> Index<[usize; 2]> for AudioSamples<'_, T>
5785where
5786    T: StandardSample,
5787{
5788    type Output = T;
5789
5790    /// Index into audio samples by [channel, sample] coordinates.
5791    ///
5792    /// This works for both mono and multi-channel audio:
5793    /// - For mono: only `[0, sample_index]` is valid
5794    /// - For multi-channel: `[channel_index, sample_index]`
5795    ///
5796    /// # Panics
5797    /// - If channel or sample index is out of bounds
5798    #[inline]
5799    fn index(&self, index: [usize; 2]) -> &Self::Output {
5800        let channel = index[0];
5801        let sample = index[1];
5802        match &self.data {
5803            AudioData::Mono(arr) => {
5804                assert!(
5805                    channel == 0,
5806                    "Channel index {channel} out of bounds for mono audio (only channel 0 exists)"
5807                );
5808                &arr[sample]
5809            }
5810            AudioData::Multi(arr) => &arr[(channel, sample)],
5811        }
5812    }
5813}
5814
5815impl<T> IntoIterator for AudioSamples<'_, T>
5816where
5817    T: StandardSample,
5818{
5819    type Item = T;
5820    type IntoIter = std::vec::IntoIter<T>;
5821
5822    /// Consumes the AudioSamples and returns an iterator over the samples in interleaved order.
5823    ///
5824    /// For mono audio, this is simply the samples in order.
5825    /// For multi-channel audio, this interleaves samples from each channel.
5826    ///
5827    /// # Examples
5828    /// ```rust
5829    /// use audio_samples::{AudioSamples, sample_rate};
5830    /// use ndarray::array;
5831    ///
5832    /// // Mono audio
5833    /// let mono_data = array![1.0f32, 2.0, 3.0];
5834    /// let mono_audio = AudioSamples::new_mono(mono_data, sample_rate!(44100)).unwrap();
5835    /// let mono_samples: Vec<f32> = mono_audio.into_iter().collect();
5836    /// assert_eq!(mono_samples, vec![1.0, 2.0, 3.0]);
5837    ///
5838    /// // Multi-channel audio
5839    /// let stereo_data = array![[1.0f32, 2.0], [3.0, 4.0]];
5840    /// let stereo_audio = AudioSamples::new_multi_channel(stereo_data, sample_rate!(44100)).unwrap();
5841    /// let stereo_samples: Vec<f32> = stereo_audio.into_iter().collect();
5842    /// assert_eq!(stereo_samples, vec![1.0, 3.0, 2.0, 4.0]); // Interleaved
5843    /// ```
5844    #[inline]
5845    fn into_iter(self) -> Self::IntoIter {
5846        self.to_interleaved_vec().into_iter()
5847    }
5848}
5849
5850macro_rules! impl_audio_samples_ops {
5851    ($(
5852        $trait:ident, $method:ident,
5853        $assign_trait:ident, $assign_method:ident,
5854        $op:tt, $assign_op:tt
5855    );+ $(;)?) => {
5856        $(
5857            // Binary op with another AudioSamples<T>
5858            impl<T> std::ops::$trait<Self> for AudioSamples<'_, T>
5859                where
5860                    T: StandardSample,
5861
5862             {
5863                type Output = Self;
5864
5865                #[inline]
5866                fn $method(self, rhs: Self) -> Self::Output {
5867                    if self.sample_rate != rhs.sample_rate {
5868                        panic!(
5869                            concat!(
5870                                "Cannot ", stringify!($method),
5871                                " audio with different sample rates: {} vs {}"
5872                            ),
5873                            self.sample_rate, rhs.sample_rate
5874                        );
5875                    }
5876                    Self {
5877                        data: self.data $op rhs.data,
5878                        sample_rate: self.sample_rate,
5879                    }
5880                }
5881            }
5882
5883            // Binary op with scalar T
5884            impl<T> std::ops::$trait<T> for AudioSamples<'_, T>
5885                where
5886                    T: StandardSample,
5887
5888            {
5889                type Output = Self;
5890
5891                #[inline]
5892                fn $method(self, rhs: T) -> Self::Output {
5893                    Self {
5894                        data: self.data $op rhs,
5895                        sample_rate: self.sample_rate,
5896                    }
5897                }
5898            }
5899
5900            // Assign op with another AudioSamples<T>
5901            impl<T> std::ops::$assign_trait<Self> for AudioSamples<'_, T>
5902                where
5903                    T: StandardSample,
5904
5905            {
5906
5907                #[inline]
5908                fn $assign_method(&mut self, rhs: Self) {
5909                    if self.sample_rate != rhs.sample_rate {
5910                        panic!(
5911                            concat!(
5912                                "Cannot ", stringify!($assign_method),
5913                                " audio with different sample rates: {} vs {}"
5914                            ),
5915                            self.sample_rate, rhs.sample_rate
5916                        );
5917                    }
5918                    self.data $assign_op rhs.data;
5919                }
5920            }
5921
5922            // Assign op with scalar T
5923            impl<T> std::ops::$assign_trait<T> for AudioSamples<'_, T>
5924                where
5925                    T: StandardSample,
5926
5927            {
5928                #[inline]
5929                fn $assign_method(&mut self, rhs: T) {
5930                    self.data $assign_op rhs;
5931                }
5932            }
5933        )+
5934    };
5935}
5936
5937impl_audio_samples_ops!(
5938    Add, add, AddAssign, add_assign, +, +=;
5939    Sub, sub, SubAssign, sub_assign, -, -=;
5940    Mul, mul, MulAssign, mul_assign, *, *=;
5941    Div, div, DivAssign, div_assign, /, /=;
5942);
5943
5944// Negation
5945impl<T> Neg for AudioSamples<'_, T>
5946where
5947    T: StandardSample + Neg<Output = T> + ConvertTo<T> + ConvertFrom<T>,
5948{
5949    type Output = Self;
5950
5951    #[inline]
5952    fn neg(self) -> Self::Output {
5953        Self {
5954            data: -self.data,
5955            sample_rate: self.sample_rate,
5956        }
5957    }
5958}
5959
5960#[cfg(feature = "resampling")]
5961impl<'a, T> Adapter<'a, T> for AudioSamples<'a, T>
5962where
5963    T: StandardSample,
5964{
5965    #[inline]
5966    unsafe fn read_sample_unchecked(&self, channel: usize, frame: usize) -> T {
5967        self[(channel, frame)]
5968    }
5969
5970    #[inline]
5971    fn channels(&self) -> usize {
5972        self.num_channels().get() as usize
5973    }
5974
5975    #[inline]
5976    fn frames(&self) -> usize {
5977        self.total_frames().get() as usize
5978    }
5979}
5980
5981/// A newtype wrapper around [`AudioSamples`] that guarantees exactly two channels (stereo).
5982///
5983/// ## Purpose
5984///
5985/// `StereoAudioSamples` encodes the stereo invariant in the type system so that code
5986/// expecting a stereo signal can accept it without re-checking the channel count at
5987/// every call site.
5988///
5989/// ## Intended Usage
5990///
5991/// Construct via [`StereoAudioSamples::new`] or [`TryFrom<AudioSamples>`]. The inner
5992/// [`AudioSamples`] is accessible through `Deref` / `DerefMut` / `AsRef` / `AsMut`.
5993///
5994/// ## Invariants
5995///
5996/// - `num_channels()` always returns `2`.
5997/// - The inner data is always the `Multi` variant of [`AudioData`].
5998///
5999/// ## Assumptions
6000///
6001/// All constructors reject non-stereo input at runtime; there is no way to construct
6002/// a `StereoAudioSamples` with fewer or more than two channels through the public API.
6003#[non_exhaustive]
6004pub struct StereoAudioSamples<'a, T>(pub AudioSamples<'a, T>)
6005where
6006    T: StandardSample;
6007
6008impl<'a, T> StereoAudioSamples<'a, T>
6009where
6010    T: StandardSample,
6011{
6012    /// Creates a new `StereoAudioSamples` from audio data with exactly 2 channels.
6013    ///
6014    /// # Errors
6015    /// Returns an error if `stereo_data` is mono or has a channel count other than 2.
6016    #[inline]
6017    pub fn new(stereo_data: AudioData<'a, T>, sample_rate: SampleRate) -> AudioSampleResult<Self> {
6018        // Separated failure conditions which the following if statements check allow for more descriptive errors.
6019
6020        if stereo_data.is_mono() {
6021            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
6022                "channels",
6023                "Expected stereo data (2 channels), got mono (1 channel).",
6024            )));
6025        }
6026
6027        if stereo_data.num_channels() != channels!(2) {
6028            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
6029                "channels",
6030                format!(
6031                    "Expected stereo data (2 channels), got {} channels.",
6032                    stereo_data.num_channels()
6033                ),
6034            )));
6035        }
6036
6037        // From now on we have guaranteed that the stereo_data does in fact have 2 channels;
6038        Ok(Self(AudioSamples::new(stereo_data, sample_rate)))
6039    }
6040
6041    /// Safely access individual channels with borrowed references for efficient processing.
6042    ///
6043    /// This method provides zero-copy access to the left and right channels, allowing
6044    /// efficient operations like STFT on individual channels without data duplication.
6045    ///
6046    /// # Arguments
6047    /// * `f` - Closure that receives borrowed left and right channel data
6048    ///
6049    /// # Returns
6050    /// The result of the closure operation
6051    ///
6052    /// # Example
6053    /// ```rust
6054    /// use audio_samples::{AudioSamples, StereoAudioSamples, sample_rate, AudioSampleResult};
6055    /// use ndarray::array;
6056    ///
6057    /// let stereo_data = array![[0.1f32, 0.2, 0.3], [0.4, 0.5, 0.6]];
6058    /// let audio = AudioSamples::new_multi_channel(stereo_data, sample_rate!(44100)).unwrap();
6059    /// let stereo: StereoAudioSamples<'static, f32> = StereoAudioSamples::try_from(audio).unwrap();
6060    ///
6061    /// stereo.with_channels(|left, right| -> AudioSampleResult<()> {
6062    ///     // left and right are borrowed AudioSamples<'_, f32>
6063    ///     println!("Left channel samples: {}", left.len());
6064    ///     println!("Right channel samples: {}", right.len());
6065    ///     Ok(())
6066    /// }).unwrap();
6067    /// ```
6068    ///
6069    /// # Errors
6070    ///
6071    #[inline]
6072    pub fn with_channels<R, F>(&self, f: F) -> AudioSampleResult<R>
6073    where
6074        F: FnOnce(AudioSamples<'_, T>, AudioSamples<'_, T>) -> AudioSampleResult<R>,
6075    {
6076        match &self.0.data {
6077            AudioData::Multi(multi_data) => {
6078                // Extract left channel (row 0)
6079                let left_view = multi_data.index_axis(Axis(0), 0);
6080                // safety: self guarantees non-empty at construction
6081                let left_data = unsafe { MonoData::from_view_unchecked(left_view) };
6082                let left_audio =
6083                    AudioSamples::new(AudioData::Mono(left_data), self.0.sample_rate());
6084
6085                // Extract right channel (row 1)
6086                let right_view = multi_data.index_axis(Axis(0), 1);
6087
6088                // safety: self guarantees non-empty at construction
6089                let right_data = unsafe { MonoData::from_view_unchecked(right_view) };
6090                let right_audio =
6091                    AudioSamples::new(AudioData::Mono(right_data), self.0.sample_rate());
6092
6093                f(left_audio, right_audio)
6094            }
6095            AudioData::Mono(_) => {
6096                unreachable!("StereoAudioSamples guarantees exactly 2 channels")
6097            }
6098        }
6099    }
6100}
6101
6102/// Provides transparent read access to the inner [`AudioSamples`].
6103impl<'a, T> Deref for StereoAudioSamples<'a, T>
6104where
6105    T: StandardSample,
6106{
6107    type Target = AudioSamples<'a, T>;
6108
6109    #[inline]
6110    fn deref(&self) -> &Self::Target {
6111        &self.0
6112    }
6113}
6114
6115/// Provides transparent write access to the inner [`AudioSamples`].
6116///
6117/// Note: mutating the inner `AudioSamples` (e.g. via `replace_data`) can potentially
6118/// violate the stereo invariant. Prefer using [`StereoAudioSamples`] methods directly.
6119impl<T> DerefMut for StereoAudioSamples<'_, T>
6120where
6121    T: StandardSample,
6122{
6123    #[inline]
6124    fn deref_mut(&mut self) -> &mut Self::Target {
6125        &mut self.0
6126    }
6127}
6128
6129/// Provides a shared reference to the inner [`AudioSamples`].
6130impl<'a, T> AsRef<AudioSamples<'a, T>> for StereoAudioSamples<'a, T>
6131where
6132    T: StandardSample,
6133{
6134    #[inline]
6135    fn as_ref(&self) -> &AudioSamples<'a, T> {
6136        &self.0
6137    }
6138}
6139
6140/// Provides a mutable reference to the inner [`AudioSamples`].
6141impl<'a, T> AsMut<AudioSamples<'a, T>> for StereoAudioSamples<'a, T>
6142where
6143    T: StandardSample,
6144{
6145    #[inline]
6146    fn as_mut(&mut self) -> &mut AudioSamples<'a, T> {
6147        &mut self.0
6148    }
6149}
6150
6151/// Zero-copy conversion from an owned [`AudioSamples`] into a [`StereoAudioSamples`].
6152///
6153/// # Errors
6154///
6155/// Returns [crate::AudioSampleError::Parameter] if the audio does not have exactly 2 channels.
6156impl<T> TryFrom<AudioSamples<'static, T>> for StereoAudioSamples<'static, T>
6157where
6158    T: StandardSample,
6159{
6160    type Error = AudioSampleError;
6161
6162    #[inline]
6163    fn try_from(audio: AudioSamples<'static, T>) -> Result<Self, Self::Error> {
6164        if audio.num_channels() != channels!(2) {
6165            return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
6166                "channels",
6167                format!(
6168                    "Expected exactly 2 channels for stereo audio, but found {}",
6169                    audio.num_channels()
6170                ),
6171            )));
6172        }
6173
6174        match audio.data {
6175            AudioData::Multi(_) => Ok(StereoAudioSamples(audio)),
6176            AudioData::Mono(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
6177                "audio_format",
6178                "Cannot convert mono audio to stereo",
6179            ))),
6180        }
6181    }
6182}
6183
6184/// Unwraps a [`StereoAudioSamples`] back into the underlying [`AudioSamples`].
6185impl<T> From<StereoAudioSamples<'static, T>> for AudioSamples<'static, T>
6186where
6187    T: StandardSample,
6188{
6189    #[inline]
6190    fn from(stereo: StereoAudioSamples<'static, T>) -> Self {
6191        stereo.0
6192    }
6193}
6194
6195/// A mono [`AudioSamples`] buffer with a compile-time maximum capacity `N`.
6196///
6197/// ## Purpose
6198///
6199/// `FixedSizeAudioSamples` communicates a maximum buffer size through the type system,
6200/// enabling callers to reason about memory usage at compile time. The underlying storage
6201/// is heap-allocated `AudioSamples`; the const parameter `N` is advisory and enforces
6202/// no runtime constraint beyond what [`capacity`](FixedSizeAudioSamples::capacity) exposes.
6203///
6204/// ## Intended Usage
6205///
6206/// Use this type when you need to document and enforce a maximum sample count in an
6207/// audio processing pipeline, for example a fixed-size ring buffer or a DSP block
6208/// with a known maximum frame size.
6209///
6210/// ## Assumptions
6211///
6212/// The buffer is always mono. Operations that require stereo or multi-channel input
6213/// should not accept `FixedSizeAudioSamples` directly.
6214#[cfg(feature = "fixed-size-audio")]
6215#[non_exhaustive]
6216pub struct FixedSizeAudioSamples<T, const N: usize>
6217where
6218    T: StandardSample,
6219{
6220    /// The underlying audio samples.
6221    pub samples: AudioSamples<'static, T>,
6222}
6223
6224#[cfg(feature = "fixed-size-audio")]
6225impl<T, const N: usize> FixedSizeAudioSamples<T, N>
6226where
6227    T: StandardSample,
6228{
6229    /// Creates a fixed-size mono buffer from any value that dereferences to a [`NonEmptySlice`].
6230    ///
6231    /// # Arguments
6232    ///
6233    /// – `data` – the source slice; all elements are copied into owned storage.
6234    /// – `sample_rate` – the sample rate for the resulting audio.
6235    ///
6236    /// # Returns
6237    ///
6238    /// `Ok(Self)` wrapping the constructed [`AudioSamples`].
6239    ///
6240    /// # Errors
6241    ///
6242    /// Returns an error if the underlying [`AudioSamples`] constructor fails
6243    /// (this should not happen in practice as `NonEmptySlice` guarantees non-emptiness).
6244    #[inline]
6245    pub fn from_1d<D: AsRef<NonEmptySlice<T>>>(
6246        data: D,
6247        sample_rate: SampleRate,
6248    ) -> AudioSampleResult<Self> {
6249        let samples = AudioSamples::from_mono_vec(data.as_ref().to_non_empty_vec(), sample_rate);
6250        Ok(Self { samples })
6251    }
6252
6253    /// Returns the compile-time maximum capacity `N` of this buffer.
6254    ///
6255    /// # Returns
6256    ///
6257    /// The const generic `N`.
6258    #[inline]
6259    #[must_use]
6260    pub const fn capacity(&self) -> usize {
6261        N
6262    }
6263
6264    /// Swaps the underlying [`AudioSamples`] between two `FixedSizeAudioSamples` instances.
6265    ///
6266    /// # Safety
6267    ///
6268    /// The caller must ensure that both instances have the same sample rate and the same
6269    /// number of channels. These conditions are checked only via `debug_assert!` in debug
6270    /// builds; violating them in release builds produces silently incorrect audio.
6271    #[inline]
6272    pub unsafe fn swap_samples(&mut self, other: &mut Self) {
6273        debug_assert_eq!(
6274            self.samples.sample_rate(),
6275            other.samples.sample_rate(),
6276            "Sample rates must match for swap"
6277        );
6278        debug_assert_eq!(
6279            self.samples.num_channels(),
6280            other.samples.num_channels(),
6281            "Number of channels must match for swap"
6282        );
6283
6284        std::mem::swap(&mut self.samples, &mut other.samples);
6285    }
6286}
6287
6288#[cfg(test)]
6289mod tests {
6290    use super::*;
6291    use ndarray::{ArrayBase, array};
6292    use non_empty_slice::non_empty_vec;
6293
6294    #[test]
6295    fn test_new_mono_audio_samples() {
6296        let data: ArrayBase<ndarray::OwnedRepr<f32>, ndarray::Dim<[usize; 1]>> =
6297            array![1.0f32, 2.0, 3.0, 4.0, 5.0];
6298        let audio: AudioSamples<f32> = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
6299
6300        assert_eq!(audio.sample_rate(), sample_rate!(44100));
6301        assert_eq!(audio.num_channels(), channels!(1));
6302        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(5).unwrap());
6303        assert!(audio.is_mono());
6304        assert!(!audio.is_multi_channel());
6305    }
6306
6307    #[test]
6308    fn test_new_multi_channel_audio_samples() {
6309        let data = array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]]; // 2 channels, 3 samples each
6310        let audio = AudioSamples::new_multi_channel(data, sample_rate!(48000)).unwrap();
6311
6312        assert_eq!(audio.sample_rate(), sample_rate!(48000));
6313        assert_eq!(audio.num_channels(), channels!(2));
6314        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
6315        assert_eq!(audio.total_samples(), NonZeroUsize::new(6).unwrap());
6316        assert!(!audio.is_mono());
6317        assert!(audio.is_multi_channel());
6318    }
6319
6320    #[test]
6321    fn test_zeros_construction() {
6322        let mono_audio =
6323            AudioSamples::<f32>::zeros_mono(NonZeroUsize::new(100).unwrap(), sample_rate!(44100));
6324        assert_eq!(mono_audio.num_channels(), channels!(1));
6325        assert_eq!(
6326            mono_audio.samples_per_channel(),
6327            NonZeroUsize::new(100).unwrap()
6328        );
6329        assert_eq!(mono_audio.sample_rate(), sample_rate!(44100));
6330
6331        let multi_audio = AudioSamples::<f32>::zeros_multi(
6332            channels!(2),
6333            NonZeroUsize::new(50).unwrap(),
6334            sample_rate!(48000),
6335        );
6336        assert_eq!(multi_audio.num_channels(), channels!(2));
6337        assert_eq!(
6338            multi_audio.samples_per_channel(),
6339            NonZeroUsize::new(50).unwrap()
6340        );
6341        assert_eq!(multi_audio.total_samples(), NonZeroUsize::new(100).unwrap());
6342        assert_eq!(multi_audio.sample_rate(), sample_rate!(48000));
6343    }
6344
6345    #[test]
6346    fn test_duration_seconds() {
6347        let audio: AudioSamples<'_, f32> =
6348            AudioSamples::<f32>::zeros_mono(NonZeroUsize::new(44100).unwrap(), sample_rate!(44100));
6349        assert!((audio.duration_seconds() - 1.0).abs() < 1e-6);
6350
6351        let audio2: AudioSamples<'_, f32> = AudioSamples::<f32>::zeros_multi(
6352            channels!(2),
6353            NonZeroUsize::new(22050).unwrap(),
6354            sample_rate!(44100),
6355        );
6356        assert!((audio2.duration_seconds() - 0.5).abs() < 1e-6);
6357    }
6358
6359    #[test]
6360    fn test_apply_simple() {
6361        let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
6362        let mut audio: AudioSamples<f32> =
6363            AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
6364
6365        // Apply a simple scaling
6366        audio.apply(|sample| sample * 2.0);
6367
6368        let expected = array![2.0f32, 4.0, 6.0, 8.0, 10.0];
6369        let expected = AudioSamples::new_mono(expected, sample_rate!(44100)).unwrap();
6370        assert_eq!(
6371            audio, expected,
6372            "Applied audio samples do not match expected values"
6373        );
6374    }
6375
6376    #[test]
6377    fn test_apply_channels() {
6378        let data = array![[1.0f32, 2.0], [3.0, 4.0]];
6379        let mut audio: AudioSamples<f32> =
6380            AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
6381
6382        {
6383            // Mutable borrow lives only within this block
6384            audio.apply_to_channels(&[0, 1], |sample| sample * sample);
6385        } // Mutable borrow ends here
6386
6387        let expected = array![[1.0, 4.0], [9.0, 16.0]];
6388        let expected = AudioSamples::new_multi_channel(expected, sample_rate!(44100)).unwrap();
6389        assert_eq!(
6390            audio, expected,
6391            "Applied multi-channel audio samples do not match expected values"
6392        );
6393    }
6394
6395    #[test]
6396    fn test_map_functional() {
6397        let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
6398        let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
6399
6400        // Create a new audio instance with transformed samples
6401        let new_audio = audio.map(|sample| sample * 0.5);
6402
6403        // Original should be unchanged
6404        let original_expected = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
6405        let original_expected =
6406            AudioSamples::new_mono(original_expected, sample_rate!(44100)).unwrap();
6407        assert_eq!(
6408            audio, original_expected,
6409            "Original audio samples should remain unchanged"
6410        );
6411
6412        // New audio should contain transformed values
6413        let new_expected = array![0.5f32, 1.0, 1.5, 2.0, 2.5];
6414        let new_expected = AudioSamples::new_mono(new_expected, sample_rate!(44100)).unwrap();
6415        assert_eq!(
6416            new_audio, new_expected,
6417            "New audio should contain transformed samples"
6418        );
6419    }
6420
6421    #[test]
6422    fn test_apply_indexed() {
6423        let data = array![1.0f32, 1.0, 1.0, 1.0, 1.0];
6424        let mut audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
6425
6426        // Apply index-based transformation
6427        audio.apply_with_index(|index, sample| sample * (index + 1) as f32);
6428        let expected = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
6429        let expected = AudioSamples::new_mono(expected, sample_rate!(44100)).unwrap();
6430        assert_eq!(
6431            audio, expected,
6432            "Indexed applied audio samples do not match expected values"
6433        );
6434    }
6435
6436    // ========================
6437    // Indexing and Slicing Tests
6438    // ========================
6439
6440    #[test]
6441    fn test_index_mono_single() {
6442        let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
6443        let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
6444
6445        assert_eq!(audio[0], 1.0);
6446        assert_eq!(audio[2], 3.0);
6447        assert_eq!(audio[4], 5.0);
6448    }
6449
6450    #[test]
6451    #[should_panic(expected = "Cannot use single index on multi-channel audio")]
6452    fn test_index_mono_single_on_multi_panics() {
6453        let data = array![[1.0f32, 2.0], [3.0, 4.0]];
6454        let audio = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
6455
6456        let _ = audio[0]; // Should panic
6457    }
6458
6459    #[test]
6460    fn test_index_tuple() {
6461        // Test mono with tuple indexing
6462        let mono_data = array![1.0f32, 2.0, 3.0, 4.0];
6463        let mono_audio = AudioSamples::new_mono(mono_data, sample_rate!(44100)).unwrap();
6464
6465        assert_eq!(mono_audio[(0, 1)], 2.0);
6466        assert_eq!(mono_audio[(0, 3)], 4.0);
6467
6468        // Test multi-channel with tuple indexing
6469        let stereo_data = array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]];
6470        let stereo_audio =
6471            AudioSamples::new_multi_channel(stereo_data, sample_rate!(44100)).unwrap();
6472
6473        assert_eq!(stereo_audio[(0, 0)], 1.0);
6474        assert_eq!(stereo_audio[(0, 2)], 3.0);
6475        assert_eq!(stereo_audio[(1, 0)], 4.0);
6476        assert_eq!(stereo_audio[(1, 2)], 6.0);
6477    }
6478
6479    #[test]
6480    #[should_panic(expected = "Channel index 1 out of bounds for mono audio")]
6481    fn test_index_tuple_invalid_channel_mono() {
6482        let data = array![1.0f32, 2.0, 3.0];
6483        let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
6484
6485        let _ = audio[(1, 0)]; // Should panic - mono only has channel 0
6486    }
6487
6488    #[test]
6489    fn test_index_mut_mono() {
6490        let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
6491        let mut audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
6492
6493        audio[2] = 10.0;
6494        assert_eq!(audio[2], 10.0);
6495        assert_eq!(
6496            audio,
6497            AudioSamples::new_mono(array![1.0f32, 2.0, 10.0, 4.0, 5.0], sample_rate!(44100))
6498                .unwrap(),
6499            "Mutably indexed mono audio samples do not match expected values"
6500        );
6501    }
6502
6503    #[test]
6504    fn test_index_mut_tuple() {
6505        let data = array![[1.0f32, 2.0], [3.0, 4.0]];
6506        let mut audio = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
6507
6508        audio[(1, 0)] = 10.0;
6509        assert_eq!(audio[(1, 0)], 10.0);
6510
6511        let expected = array![[1.0f32, 2.0], [10.0, 4.0]];
6512        let expected = AudioSamples::new_multi_channel(expected, sample_rate!(44100)).unwrap();
6513        assert_eq!(
6514            audio, expected,
6515            "Mutably indexed audio samples do not match expected values"
6516        );
6517    }
6518
6519    // ========================
6520    // Audio Data Replacement Tests
6521    // ========================
6522
6523    #[test]
6524    fn test_replace_data_mono_success() {
6525        let initial_data = array![1.0f32, 2.0, 3.0];
6526        let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
6527
6528        // Replace with new mono data of different length
6529        let new_data = AudioData::try_from(array![4.0f32, 5.0, 6.0, 7.0, 8.0])
6530            .unwrap()
6531            .into_owned();
6532        assert!(audio.replace_data(new_data).is_ok());
6533
6534        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(5).unwrap());
6535        assert_eq!(audio.num_channels(), channels!(1));
6536        assert_eq!(audio.sample_rate(), sample_rate!(44100)); // Should be preserved
6537    }
6538
6539    #[test]
6540    fn test_replace_data_multi_success() {
6541        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6542        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6543
6544        // Replace with new stereo data of different length
6545        let new_data = AudioData::try_from(array![[5.0f32, 6.0, 7.0], [8.0, 9.0, 10.0]]).unwrap();
6546        assert!(audio.replace_data(new_data).is_ok());
6547
6548        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
6549        assert_eq!(audio.num_channels(), channels!(2));
6550        assert_eq!(audio.sample_rate(), sample_rate!(44100)); // Should be preserved
6551    }
6552
6553    #[test]
6554    fn test_replace_data_channel_count_mismatch() {
6555        let initial_data = array![1.0f32, 2.0, 3.0];
6556        let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
6557
6558        // Try to replace mono with stereo data
6559        let new_data = AudioData::try_from(array![[4.0f32, 5.0], [6.0, 7.0]]).unwrap();
6560        let result = audio.replace_data(new_data);
6561
6562        assert!(result.is_err());
6563        if let Err(e) = result {
6564            assert!(e.to_string().contains("Channel count mismatch"));
6565        }
6566    }
6567
6568    #[test]
6569    fn test_replace_data_layout_type_mismatch() {
6570        let initial_data = array![1.0f32, 2.0, 3.0];
6571        let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
6572
6573        // Try to replace mono with multi-channel data (even same channel count fails due to type)
6574        let new_data = AudioData::try_from(array![[4.0f32, 5.0, 6.0]]).unwrap();
6575        let result = audio.replace_data(new_data);
6576
6577        assert!(result.is_err());
6578        if let Err(e) = result {
6579            assert!(
6580                e.to_string()
6581                    .contains("Cannot replace mono audio data with multi-channel data")
6582            );
6583        }
6584    }
6585
6586    #[test]
6587    fn test_replace_with_mono_success() {
6588        let initial_data = array![1.0f32, 2.0];
6589        let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
6590
6591        // Replace with new mono data
6592        assert!(audio.replace_with_mono(array![3.0f32, 4.0, 5.0]).is_ok());
6593
6594        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
6595        assert_eq!(audio.num_channels(), channels!(1));
6596        assert_eq!(audio.sample_rate(), sample_rate!(44100));
6597        assert_eq!(audio[0], 3.0);
6598        assert_eq!(audio[1], 4.0);
6599        assert_eq!(audio[2], 5.0);
6600    }
6601
6602    #[test]
6603    fn test_replace_with_mono_on_multi_fails() {
6604        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6605        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6606
6607        let result = audio.replace_with_mono(array![5.0f32, 6.0, 7.0]);
6608        assert!(result.is_err());
6609        if let Err(e) = result {
6610            assert!(e.to_string().contains("Cannot replace multi-channel audio"));
6611        }
6612    }
6613
6614    #[test]
6615    fn test_replace_with_multi_success() {
6616        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6617        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6618
6619        // Replace with new stereo data of different length
6620        assert!(
6621            audio
6622                .replace_with_multi(array![[5.0f32, 6.0, 7.0], [8.0, 9.0, 10.0]])
6623                .is_ok()
6624        );
6625
6626        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
6627        assert_eq!(audio.num_channels(), channels!(2));
6628        assert_eq!(audio.sample_rate(), sample_rate!(44100));
6629        assert_eq!(audio[(0, 0)], 5.0);
6630        assert_eq!(audio[(0, 2)], 7.0);
6631        assert_eq!(audio[(1, 0)], 8.0);
6632        assert_eq!(audio[(1, 2)], 10.0);
6633    }
6634
6635    #[test]
6636    fn test_replace_with_multi_on_mono_fails() {
6637        let initial_data = array![1.0f32, 2.0, 3.0];
6638        let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
6639
6640        let result = audio.replace_with_multi(array![[4.0f32, 5.0], [6.0, 7.0]]);
6641        assert!(result.is_err());
6642        if let Err(e) = result {
6643            assert!(
6644                e.to_string()
6645                    .contains("Cannot replace mono audio with multi-channel data")
6646            );
6647        }
6648    }
6649
6650    #[test]
6651    fn test_replace_with_multi_channel_count_mismatch() {
6652        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6653        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6654
6655        // Try to replace stereo with tri-channel data
6656        let result = audio.replace_with_multi(array![[5.0f32, 6.0], [7.0, 8.0], [9.0, 10.0]]);
6657        assert!(result.is_err());
6658        if let Err(e) = result {
6659            assert!(e.to_string().contains("Channel count mismatch"));
6660        }
6661    }
6662
6663    #[test]
6664    fn test_replace_with_vec_mono_success() {
6665        let initial_data = array![1.0f32, 2.0];
6666        let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
6667
6668        // Replace with vector
6669        assert!(
6670            audio
6671                .replace_with_vec(&non_empty_vec![3.0f32, 4.0, 5.0, 6.0])
6672                .is_ok()
6673        );
6674
6675        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(4).unwrap());
6676        assert_eq!(audio.num_channels(), channels!(1));
6677        assert_eq!(audio.sample_rate(), sample_rate!(44100));
6678    }
6679
6680    #[test]
6681    fn test_replace_with_vec_multi_success() {
6682        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6683        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6684
6685        // Replace with vector (6 samples = 3 samples per channel × 2 channels)
6686        assert!(
6687            audio
6688                .replace_with_vec(&non_empty_vec![5.0f32, 6.0, 7.0, 8.0, 9.0, 10.0])
6689                .is_ok()
6690        );
6691
6692        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
6693        assert_eq!(audio.num_channels(), channels!(2));
6694        assert_eq!(audio.sample_rate(), sample_rate!(44100));
6695        // Data is arranged as: [ch0_sample0, ch0_sample1, ch0_sample2, ch1_sample0, ch1_sample1, ch1_sample2]
6696        assert_eq!(audio[(0, 0)], 5.0); // First channel, first sample
6697        assert_eq!(audio[(0, 1)], 6.0); // First channel, second sample
6698        assert_eq!(audio[(1, 0)], 8.0); // Second channel, first sample
6699    }
6700
6701    #[test]
6702    fn test_replace_with_vec_not_divisible_fails() {
6703        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6704        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6705
6706        // 5 samples is not divisible by 2 channels
6707        let result = audio.replace_with_vec(&non_empty_vec![5.0f32, 6.0, 7.0, 8.0, 9.0]);
6708        assert!(result.is_err());
6709        if let Err(e) = result {
6710            assert!(
6711                e.to_string()
6712                    .contains("Sample count 5 is not divisible by channel count 2")
6713            );
6714        }
6715    }
6716
6717    #[test]
6718    fn test_replace_with_vec_channels_success() {
6719        // Test with correct channel count
6720        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6721        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6722
6723        // Replace with vector, validating against expected 2 channels
6724        assert!(
6725            audio
6726                .replace_with_vec_channels(&non_empty_vec![5.0f32, 6.0, 7.0, 8.0], channels!(2))
6727                .is_ok()
6728        );
6729
6730        assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(2).unwrap());
6731        assert_eq!(audio.num_channels(), channels!(2));
6732        assert_eq!(audio.sample_rate(), sample_rate!(44100));
6733    }
6734
6735    #[test]
6736    fn test_replace_with_vec_channels_sample_count_mismatch() {
6737        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6738        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6739
6740        // Try with sample count not divisible by expected channels
6741        let result =
6742            audio.replace_with_vec_channels(&non_empty_vec![5.0f32, 6.0, 7.0], channels!(2));
6743        assert!(result.is_err());
6744        if let Err(e) = result {
6745            assert!(
6746                e.to_string()
6747                    .contains("Sample count 3 is not divisible by expected channel count 2")
6748            );
6749        }
6750    }
6751
6752    #[test]
6753    fn test_replace_with_vec_channels_audio_channel_mismatch() {
6754        let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
6755        let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
6756
6757        // Try with expected channel count different from current audio
6758        let result =
6759            audio.replace_with_vec_channels(&non_empty_vec![5.0f32, 6.0, 7.0], channels!(3));
6760        assert!(result.is_err());
6761        if let Err(e) = result {
6762            assert!(
6763                e.to_string()
6764                    .contains("Current audio has 2 channels, but expected 3 channels")
6765            );
6766        }
6767    }
6768
6769    #[test]
6770    fn test_user_workflow_with_channels_validation() {
6771        // Simulate the user's corrected use case
6772        let mut audio =
6773            AudioSamples::new_multi_channel(array![[0.0f32, 0.0], [0.0, 0.0]], sample_rate!(44100))
6774                .unwrap();
6775
6776        // Simulate converted data from their function
6777        let converted_samples: NonEmptyVec<f32> = non_empty_vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
6778        let num_channels = channels!(2); // This comes from the function parameter, not audio.num_channels()
6779
6780        // Validate against the parameter, not the existing audio configuration
6781        assert!(
6782            converted_samples
6783                .len()
6784                .get()
6785                .is_multiple_of(num_channels.get() as usize)
6786        );
6787        assert!(
6788            audio
6789                .replace_with_vec_channels(&converted_samples, num_channels)
6790                .is_err(),
6791            "audio has 2 channels with 2 samples per channel, but converted samples have 2 channels and 3 samples per channel (6/2)"
6792        );
6793    }
6794
6795    #[test]
6796    fn test_metadata_preservation_across_replacements() {
6797        let mut audio = AudioSamples::new_mono(array![1.0f32, 2.0], sample_rate!(48000)).unwrap();
6798
6799        let original_rate = audio.sample_rate();
6800
6801        // Replace data multiple times
6802        assert!(audio.replace_with_mono(array![3.0f32, 4.0, 5.0]).is_ok());
6803        assert_eq!(audio.sample_rate(), original_rate);
6804
6805        assert!(
6806            audio
6807                .replace_with_vec(&non_empty_vec![6.0f32, 7.0, 8.0, 9.0])
6808                .is_ok()
6809        );
6810        assert_eq!(audio.sample_rate(), original_rate);
6811
6812        let new_data = AudioData::try_from(array![10.0f32, 11.0])
6813            .unwrap()
6814            .into_owned();
6815        assert!(audio.replace_data(new_data).is_ok());
6816        assert_eq!(audio.sample_rate(), original_rate);
6817    }
6818}