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}