use core::fmt::{Display, Formatter, Result as FmtResult};
use core::num::{NonZeroU32, NonZeroUsize};
use ndarray::iter::AxisIterMut;
use ndarray::{
Array1, Array2, ArrayView1, ArrayView2, ArrayViewMut1, ArrayViewMut2, Axis, Ix1, Ix2, SliceArg,
s,
};
use non_empty_iter::{IntoNonEmptyIterator, NonEmptyIterator};
use non_empty_slice::{NonEmptyByteVec, NonEmptyBytes, NonEmptySlice, NonEmptyVec};
#[cfg(feature = "resampling")]
use rubato::audioadapter::Adapter;
use std::any::TypeId;
use std::num::NonZeroU8;
use std::ops::{Bound, Deref, DerefMut, Index, IndexMut, Mul, Neg, RangeBounds};
#[macro_export]
macro_rules! sample_rate {
($rate:expr) => {{
const RATE: u32 = $rate;
const { assert!(RATE > 0, "sample rate must be greater than 0") };
unsafe { ::core::num::NonZeroU32::new_unchecked(RATE) }
}};
}
#[macro_export]
macro_rules! channels {
($count:expr) => {{
const COUNT: u32 = $count;
const { assert!(COUNT > 0, "channel count must be greater than 0") };
unsafe { ::core::num::NonZeroU32::new_unchecked(COUNT) }
}};
}
pub type SampleRate = NonZeroU32;
pub type ChannelCount = NonZeroU32;
use crate::traits::{ConvertFrom, StandardSample};
use crate::{
AudioSampleError, AudioSampleResult, CastInto, ConvertTo, I24, LayoutError, ParameterError,
};
#[derive(Debug, Clone)]
pub enum AudioBytes<'a> {
Borrowed(&'a NonEmptyBytes),
Owned(NonEmptyByteVec),
}
impl AudioBytes<'_> {
#[inline]
#[must_use]
pub const fn as_slice(&self) -> &[u8] {
match self {
AudioBytes::Borrowed(b) => b.as_slice(),
AudioBytes::Owned(v) => v.as_slice(),
}
}
#[inline]
#[must_use]
pub fn into_owned(self) -> NonEmptyByteVec {
match self {
AudioBytes::Borrowed(b) => b.to_owned(),
AudioBytes::Owned(v) => v,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SampleType {
U8,
I16,
I24,
I32,
F32,
F64,
}
impl SampleType {
#[inline]
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::U8 => "u8",
Self::I16 => "i16",
Self::I24 => "i24",
Self::I32 => "i32",
Self::F32 => "f32",
Self::F64 => "f64",
}
}
#[inline]
#[must_use]
pub const fn description(self) -> &'static str {
match self {
Self::U8 => "8-bit unsigned integer",
Self::I16 => "16-bit signed integer",
Self::I24 => "24-bit signed integer",
Self::I32 => "32-bit signed integer",
Self::F32 => "32-bit floating point",
Self::F64 => "64-bit floating point",
}
}
#[inline]
#[must_use]
pub const fn bits(self) -> Option<u32> {
match self {
Self::U8 => Some(8),
Self::I16 => Some(16),
Self::I24 => Some(24),
Self::I32 | Self::F32 => Some(32),
Self::F64 => Some(64),
}
}
#[inline]
#[must_use]
pub const fn bytes(self) -> Option<usize> {
match self {
Self::U8 => Some(1),
Self::I16 => Some(2),
Self::I24 => Some(3),
Self::I32 | Self::F32 => Some(4),
Self::F64 => Some(8),
}
}
#[inline]
#[must_use]
pub const fn is_integer(self) -> bool {
matches!(self, Self::U8 | Self::I16 | Self::I24 | Self::I32)
}
#[inline]
#[must_use]
pub const fn is_signed(self) -> bool {
matches!(self, Self::I16 | Self::I24 | Self::I32)
}
#[inline]
#[must_use]
pub const fn is_unsigned(self) -> bool {
matches!(self, Self::U8)
}
#[inline]
#[must_use]
pub const fn is_float(self) -> bool {
matches!(self, Self::F32 | Self::F64)
}
#[inline]
#[must_use]
pub const fn is_dsp_native(self) -> bool {
matches!(self, Self::F32 | Self::F64)
}
}
impl SampleType {
#[inline]
#[must_use]
pub fn from_type_id(type_id: std::any::TypeId) -> Option<Self> {
use std::any::TypeId;
if type_id == TypeId::of::<u8>() {
Some(Self::U8)
} else if type_id == TypeId::of::<i16>() {
Some(Self::I16)
} else if type_id == TypeId::of::<I24>() {
Some(Self::I24)
} else if type_id == TypeId::of::<i32>() {
Some(Self::I32)
} else if type_id == TypeId::of::<f32>() {
Some(Self::F32)
} else if type_id == TypeId::of::<f64>() {
Some(Self::F64)
} else {
None
}
}
#[inline]
#[must_use]
pub const fn from_bits(bits: u16) -> Self {
match bits {
8 => Self::U8,
16 => Self::I16,
24 => Self::I24,
32 => Self::I32,
64 => Self::F64,
_ => panic!("Unsupported bits per sample"),
}
}
}
impl Display for SampleType {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if f.alternate() {
write!(f, "{}", self.description())
} else {
write!(f, "{}", self.as_str())
}
}
}
impl TryFrom<&str> for SampleType {
type Error = ();
#[inline]
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"u8" => Ok(Self::U8),
"i16" => Ok(Self::I16),
"i24" => Ok(Self::I24),
"i32" => Ok(Self::I32),
"f32" => Ok(Self::F32),
"f64" => Ok(Self::F64),
_ => Err(()),
}
}
}
#[derive(Debug, PartialEq)]
pub enum MonoRepr<'a, T>
where
T: StandardSample,
{
Borrowed(ArrayView1<'a, T>),
BorrowedMut(ArrayViewMut1<'a, T>),
Owned(Array1<T>),
}
#[derive(Debug, PartialEq)]
pub struct MonoData<'a, T>(MonoRepr<'a, T>)
where
T: StandardSample;
impl<'a, T> MonoData<'a, T>
where
T: StandardSample,
{
#[inline]
pub const unsafe fn from_array1(array: Array1<T>) -> Self {
MonoData(MonoRepr::Owned(array))
}
pub const unsafe fn from_array_view<'b>(view: ArrayView1<'b, T>) -> Self
where
'b: 'a,
{
MonoData(MonoRepr::Borrowed(view))
}
#[inline]
pub fn borrow(&'a self) -> Self {
MonoData(MonoRepr::Borrowed(self.as_view()))
}
#[inline]
pub fn as_view(&self) -> ArrayView1<'_, T> {
match &self.0 {
MonoRepr::Borrowed(v) => *v,
MonoRepr::BorrowedMut(v) => v.view(),
MonoRepr::Owned(a) => a.view(),
}
}
#[inline]
pub fn promote(&mut self) {
if let MonoRepr::Borrowed(v) = &self.0 {
self.0 = MonoRepr::Owned(v.to_owned());
}
}
#[inline]
pub fn from_view<'b>(view: ArrayView1<'b, T>) -> AudioSampleResult<Self>
where
'b: 'a,
{
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MonoData(MonoRepr::Borrowed(view)))
}
#[inline]
pub const unsafe fn from_view_unchecked<'b>(view: ArrayView1<'b, T>) -> Self
where
'b: 'a,
{
MonoData(MonoRepr::Borrowed(view))
}
#[inline]
pub fn from_view_mut<'b>(view: ArrayViewMut1<'b, T>) -> AudioSampleResult<Self>
where
'b: 'a,
{
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MonoData(MonoRepr::BorrowedMut(view)))
}
#[inline]
pub const unsafe fn from_view_mut_unchecked<'b>(view: ArrayViewMut1<'b, T>) -> Self
where
'b: 'a,
{
MonoData(MonoRepr::BorrowedMut(view))
}
#[inline]
pub fn from_owned(array: Array1<T>) -> AudioSampleResult<Self> {
if array.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MonoData(MonoRepr::Owned(array)))
}
pub const unsafe fn from_owned_unchecked(array: Array1<T>) -> Self {
MonoData(MonoRepr::Owned(array))
}
fn to_mut(&mut self) -> ArrayViewMut1<'_, T> {
if let MonoRepr::Borrowed(v) = self.0 {
self.0 = MonoRepr::Owned(v.to_owned());
}
match &mut self.0 {
MonoRepr::BorrowedMut(view) => view.view_mut(), MonoRepr::Owned(a) => a.view_mut(),
MonoRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
fn into_owned<'b>(self) -> MonoData<'b, T> {
match self.0 {
MonoRepr::Borrowed(v) => MonoData(MonoRepr::Owned(v.to_owned())),
MonoRepr::BorrowedMut(v) => MonoData(MonoRepr::Owned(v.to_owned())),
MonoRepr::Owned(a) => MonoData(MonoRepr::Owned(a)),
}
}
#[inline]
pub fn len(&self) -> NonZeroUsize {
NonZeroUsize::new(self.as_view().len()).expect("Array is guaranteed to be non-empty")
}
#[inline]
pub fn view(&self) -> ArrayView1<'_, T> {
self.as_view()
}
#[inline]
pub fn mean(&self) -> T {
self.as_view()
.mean()
.expect("Array is guaranteed to be non-empty")
}
#[inline]
pub fn variance(&self) -> f64 {
self.variance_with_ddof(0)
}
#[inline]
pub fn variance_with_ddof(&self, ddof: usize) -> f64 {
let degrees_of_freedom = (self.len().get() - ddof) as f64;
let mean: f64 = self.mean().cast_into();
self.iter()
.map(|&x| {
let diff: f64 = <T as CastInto<f64>>::cast_into(x) - mean;
diff * diff
})
.sum::<f64>()
/ degrees_of_freedom
}
#[inline]
pub fn stddev(&self) -> f64 {
self.stddev_with_ddof(0)
}
#[inline]
pub fn stddev_with_ddof(&self, ddof: usize) -> f64 {
self.variance_with_ddof(ddof).sqrt()
}
#[inline]
pub fn sum(&self) -> T {
self.as_view().sum()
}
#[inline]
pub fn fold<F>(&self, init: T, f: F) -> T
where
F: FnMut(T, &T) -> T,
{
self.iter().fold(init, f)
}
#[inline]
pub fn slice<I>(&self, info: I) -> ArrayView1<'_, T>
where
I: SliceArg<Ix1, OutDim = Ix1>,
{
match &self.0 {
MonoRepr::Borrowed(v) => v.slice(info),
MonoRepr::BorrowedMut(v) => v.slice(info),
MonoRepr::Owned(a) => a.slice(info),
}
}
#[inline]
pub fn slice_mut<I>(&mut self, info: I) -> ArrayViewMut1<'_, T>
where
I: ndarray::SliceArg<Ix1, OutDim = Ix1>,
{
self.promote();
match &mut self.0 {
MonoRepr::BorrowedMut(a) => a.slice_mut(info),
MonoRepr::Owned(a) => a.slice_mut(info),
MonoRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn iter(&self) -> ndarray::iter::Iter<'_, T, Ix1> {
match &self.0 {
MonoRepr::Borrowed(v) => v.iter(),
MonoRepr::BorrowedMut(v) => v.iter(),
MonoRepr::Owned(a) => a.iter(),
}
}
#[inline]
pub fn iter_mut(&mut self) -> ndarray::iter::IterMut<'_, T, Ix1> {
if let MonoRepr::Borrowed(a) = &mut self.0 {
self.0 = MonoRepr::Owned(a.to_owned());
}
match &mut self.0 {
MonoRepr::BorrowedMut(b) => b.iter_mut(),
MonoRepr::Owned(a) => a.iter_mut(),
MonoRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn mapv_inplace<F>(&mut self, f: F)
where
F: FnMut(T) -> T,
{
self.to_mut().mapv_inplace(f);
}
#[inline]
pub fn as_slice_mut(&mut self) -> &mut [T] {
self.promote();
match &mut self.0 {
MonoRepr::BorrowedMut(a) => a
.as_slice_mut()
.expect("Structures backing audio samples should be contiguous in memory"),
MonoRepr::Owned(a) => a
.as_slice_mut()
.expect("Structures backing audio samples should be contiguous in memory"),
MonoRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn as_slice(&self) -> Option<&[T]> {
match &self.0 {
MonoRepr::Borrowed(v) => v.as_slice(),
MonoRepr::BorrowedMut(v) => v.as_slice(),
MonoRepr::Owned(a) => a.as_slice(),
}
}
#[inline]
pub fn shape(&self) -> &[usize] {
match &self.0 {
MonoRepr::Borrowed(v) => v.shape(),
MonoRepr::BorrowedMut(v) => v.shape(),
MonoRepr::Owned(a) => a.shape(),
}
}
#[inline]
pub fn mapv<U, F>(&self, f: F) -> Array1<U>
where
F: Fn(T) -> U,
U: Clone,
{
self.as_view().mapv(f)
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.to_mut().as_mut_ptr()
}
#[inline]
pub fn to_vec(&self) -> Vec<T> {
self.as_view().to_vec()
}
#[inline]
pub fn fill(&mut self, value: T) {
self.to_mut().fill(value);
}
#[inline]
pub fn into_raw_vec_and_offset(self) -> (Vec<T>, usize) {
match self.0 {
MonoRepr::Borrowed(v) => {
let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
(vec, offset.unwrap_or(0))
}
MonoRepr::BorrowedMut(v) => {
let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
(vec, offset.unwrap_or(0))
}
MonoRepr::Owned(a) => {
let (vec, offset) = a.into_raw_vec_and_offset();
(vec, offset.unwrap_or(0))
}
}
}
#[inline]
pub fn take(self) -> Array1<T> {
match self.0 {
MonoRepr::Borrowed(v) => v.to_owned(),
MonoRepr::BorrowedMut(v) => v.to_owned(),
MonoRepr::Owned(a) => a,
}
}
}
impl<T> PartialEq<Array1<T>> for MonoData<'_, T>
where
T: StandardSample,
{
#[inline]
fn eq(&self, other: &Array1<T>) -> bool {
self.as_view() == other.view()
}
}
impl<'a, T> PartialEq<MonoData<'a, T>> for Array1<T>
where
T: StandardSample,
{
#[inline]
fn eq(&self, other: &MonoData<'a, T>) -> bool {
self.view() == other.as_view()
}
}
impl<'a, T> IntoIterator for &'a MonoData<'_, T>
where
T: StandardSample,
{
type Item = &'a T;
type IntoIter = ndarray::iter::Iter<'a, T, ndarray::Ix1>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.as_view().into_iter()
}
}
impl<T> Index<usize> for MonoData<'_, T>
where
T: StandardSample,
{
type Output = T;
#[inline]
fn index(&self, idx: usize) -> &Self::Output
where
T: StandardSample,
{
match &self.0 {
MonoRepr::Borrowed(arr) => &arr[idx],
MonoRepr::BorrowedMut(arr) => &arr[idx],
MonoRepr::Owned(arr) => &arr[idx],
}
}
}
impl<T> IndexMut<usize> for MonoData<'_, T>
where
T: StandardSample,
{
#[inline]
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
self.promote();
match &mut self.0 {
MonoRepr::BorrowedMut(arr) => &mut arr[idx],
MonoRepr::Owned(arr) => &mut arr[idx],
MonoRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
}
#[derive(Debug, PartialEq)]
pub enum MultiRepr<'a, T>
where
T: StandardSample,
{
Borrowed(ArrayView2<'a, T>),
BorrowedMut(ArrayViewMut2<'a, T>),
Owned(Array2<T>),
}
#[derive(Debug, PartialEq)]
pub struct MultiData<'a, T>(MultiRepr<'a, T>)
where
T: StandardSample;
impl<T> Index<(usize, usize)> for MultiData<'_, T>
where
T: StandardSample,
{
type Output = T;
#[inline]
fn index(&self, (ch, s): (usize, usize)) -> &Self::Output {
match &self.0 {
MultiRepr::Borrowed(arr) => &arr[[ch, s]],
MultiRepr::BorrowedMut(arr) => &arr[[ch, s]],
MultiRepr::Owned(arr) => &arr[[ch, s]],
}
}
}
impl<T> IndexMut<(usize, usize)> for MultiData<'_, T>
where
T: StandardSample,
{
#[inline]
fn index_mut(&mut self, (ch, s): (usize, usize)) -> &mut Self::Output {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(arr) => &mut arr[[ch, s]],
MultiRepr::Owned(arr) => &mut arr[[ch, s]],
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
}
impl<T> Index<[usize; 2]> for MultiData<'_, T>
where
T: StandardSample,
{
type Output = T;
#[inline]
fn index(&self, index: [usize; 2]) -> &Self::Output {
match &self.0 {
MultiRepr::Borrowed(arr) => &arr[index],
MultiRepr::BorrowedMut(arr) => &arr[index],
MultiRepr::Owned(arr) => &arr[index],
}
}
}
impl<T> IndexMut<[usize; 2]> for MultiData<'_, T>
where
T: StandardSample,
{
#[inline]
fn index_mut(&mut self, index: [usize; 2]) -> &mut Self::Output {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(arr) => &mut arr[index],
MultiRepr::Owned(arr) => &mut arr[index],
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
}
impl<'a, T> MultiData<'a, T>
where
T: StandardSample,
{
#[inline]
pub const unsafe fn from_array2(array: Array2<T>) -> Self {
MultiData(MultiRepr::Owned(array))
}
#[inline]
pub const unsafe fn from_array_view<'b>(view: ArrayView2<'b, T>) -> Self
where
'b: 'a,
{
MultiData(MultiRepr::Borrowed(view))
}
#[inline]
pub fn borrow(&'a self) -> Self {
MultiData(MultiRepr::Borrowed(self.as_view()))
}
#[inline]
pub fn as_view(&self) -> ArrayView2<'_, T> {
match &self.0 {
MultiRepr::Borrowed(a) => *a,
MultiRepr::BorrowedMut(a) => a.view(),
MultiRepr::Owned(a) => a.view(),
}
}
#[inline]
pub fn promote(&mut self) {
if let MultiRepr::Borrowed(v) = &self.0 {
self.0 = MultiRepr::Owned(v.to_owned());
}
}
#[inline]
pub fn from_view<'b>(view: ArrayView2<'b, T>) -> AudioSampleResult<Self>
where
'b: 'a,
{
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MultiData(MultiRepr::Borrowed(view)))
}
#[inline]
pub fn from_view_mut<'b>(view: ArrayViewMut2<'b, T>) -> AudioSampleResult<Self>
where
'b: 'a,
{
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MultiData(MultiRepr::BorrowedMut(view)))
}
#[inline]
pub fn from_owned(array: Array2<T>) -> AudioSampleResult<Self> {
if array.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MultiData(MultiRepr::Owned(array)))
}
#[inline]
pub const unsafe fn from_owned_unchecked(array: Array2<T>) -> Self {
MultiData(MultiRepr::Owned(array))
}
fn to_mut(&mut self) -> ArrayViewMut2<'_, T> {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.view_mut(),
MultiRepr::Owned(a) => a.view_mut(),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
fn into_owned<'b>(self) -> MultiData<'b, T> {
match self.0 {
MultiRepr::Borrowed(v) => MultiData(MultiRepr::Owned(v.to_owned())),
MultiRepr::BorrowedMut(v) => MultiData(MultiRepr::Owned(v.to_owned())),
MultiRepr::Owned(a) => MultiData(MultiRepr::Owned(a)),
}
}
#[inline]
pub fn nrows(&self) -> NonZeroUsize {
unsafe { NonZeroUsize::new_unchecked(self.as_view().nrows()) }
}
#[inline]
pub fn ncols(&self) -> NonZeroUsize {
unsafe { NonZeroUsize::new_unchecked(self.as_view().ncols()) }
}
#[inline]
pub fn dim(&self) -> (NonZeroUsize, NonZeroUsize) {
let (r, c) = self.as_view().dim();
unsafe {
(
NonZeroUsize::new_unchecked(r),
NonZeroUsize::new_unchecked(c),
)
}
}
#[inline]
pub fn mean_axis(&self, axis: Axis) -> Array1<T> {
self.as_view()
.mean_axis(axis)
.expect("self is guaranteed non-empty")
}
#[inline]
pub fn mean(&self) -> T {
self.as_view().mean().expect("self is guaranteed non-empty")
}
#[inline]
pub fn variance_axis(&self, axis: Axis) -> Array1<f64> {
self.variance_axis_ddof(axis, 0)
}
#[inline]
pub fn variance_axis_ddof(&self, axis: Axis, ddof: usize) -> Array1<f64> {
let view = self.as_view();
let degrees_of_freedom = (view.len() - ddof) as f64;
let means = self.mean_axis(axis);
view.outer_iter()
.map(|lane| {
lane.iter()
.zip(means.iter())
.map(|(&x, &mean)| {
let mean: f64 = mean.cast_into();
let diff: f64 = <T as CastInto<f64>>::cast_into(x) - mean;
diff * diff
})
.sum::<f64>()
/ degrees_of_freedom
})
.collect::<Array1<f64>>()
}
#[inline]
pub fn variance(&self) -> Array1<f64> {
self.variance_axis(Axis(0))
}
#[inline]
pub fn stddev_axis(&self, axis: Axis) -> Array1<f64> {
self.stddev_axis_ddof(axis, 0)
}
#[inline]
pub fn stddev_axis_ddof(&self, axis: Axis, ddof: usize) -> Array1<f64> {
self.variance_axis_ddof(axis, ddof).mapv(f64::sqrt)
}
#[inline]
pub fn stddev(&self) -> Array1<f64> {
self.stddev_axis(Axis(0))
}
#[inline]
pub fn sum(&self) -> T {
self.as_view().sum()
}
#[inline]
pub fn index_axis(&self, axis: Axis, index: usize) -> ArrayView1<'_, T> {
match &self.0 {
MultiRepr::Borrowed(a) => a.index_axis(axis, index),
MultiRepr::BorrowedMut(a) => a.index_axis(axis, index),
MultiRepr::Owned(a) => a.index_axis(axis, index),
}
}
#[inline]
pub fn column(&self, index: usize) -> ArrayView1<'_, T> {
match &self.0 {
MultiRepr::Borrowed(v) => v.column(index),
MultiRepr::BorrowedMut(v) => v.column(index),
MultiRepr::Owned(a) => a.column(index),
}
}
#[inline]
pub fn slice<I>(&self, info: I) -> ArrayView2<'_, T>
where
I: ndarray::SliceArg<ndarray::Ix2, OutDim = ndarray::Ix2>,
{
match &self.0 {
MultiRepr::Borrowed(v) => v.slice(info),
MultiRepr::BorrowedMut(v) => v.slice(info),
MultiRepr::Owned(a) => a.slice(info),
}
}
#[inline]
pub fn slice_mut<I>(&mut self, info: I) -> ArrayViewMut2<'_, T>
where
I: ndarray::SliceArg<ndarray::Ix2, OutDim = ndarray::Ix2>,
{
self.promote();
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.slice_mut(info),
MultiRepr::Owned(a) => a.slice_mut(info),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn view(&self) -> ArrayView2<'_, T> {
self.as_view()
}
#[inline]
pub fn view_mut(&mut self) -> ArrayViewMut2<'_, T> {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.view_mut(),
MultiRepr::Owned(a) => a.view_mut(),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn swap_axes(&mut self, a: usize, b: usize) {
self.to_mut().swap_axes(a, b);
}
#[inline]
pub fn index_axis_mut(&mut self, axis: Axis, index: usize) -> ArrayViewMut1<'_, T> {
self.promote();
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.index_axis_mut(axis, index),
MultiRepr::Owned(a) => a.index_axis_mut(axis, index),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn shape(&self) -> &[usize] {
match &self.0 {
MultiRepr::Borrowed(v) => v.shape(),
MultiRepr::BorrowedMut(v) => v.shape(),
MultiRepr::Owned(a) => a.shape(),
}
}
#[inline]
pub fn mapv_inplace<F>(&mut self, f: F)
where
F: FnMut(T) -> T,
{
self.to_mut().mapv_inplace(f);
}
#[inline]
pub fn axis_iter_mut(&mut self, axis: Axis) -> AxisIterMut<'_, T, Ix1> {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.axis_iter_mut(axis),
MultiRepr::Owned(a) => a.axis_iter_mut(axis),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn row(&self, index: usize) -> ArrayView1<'_, T> {
match &self.0 {
MultiRepr::Borrowed(v) => v.row(index),
MultiRepr::BorrowedMut(v) => v.row(index),
MultiRepr::Owned(a) => a.row(index),
}
}
#[inline]
pub fn iter(&self) -> ndarray::iter::Iter<'_, T, Ix2> {
match &self.0 {
MultiRepr::Borrowed(v) => v.iter(),
MultiRepr::BorrowedMut(v) => v.iter(),
MultiRepr::Owned(a) => a.iter(),
}
}
#[inline]
pub fn iter_mut(&mut self) -> ndarray::iter::IterMut<'_, T, Ix2> {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.iter_mut(),
MultiRepr::Owned(a) => a.iter_mut(),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn len(&self) -> NonZeroUsize {
NonZeroUsize::new(self.as_view().len()).expect("Array is guaranteed to be non-empty")
}
#[inline]
pub fn mapv<U, F>(&self, f: F) -> Array2<U>
where
F: Fn(T) -> U,
U: Clone,
{
self.as_view().mapv(f)
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.to_mut().as_mut_ptr()
}
#[inline]
pub fn as_slice(&self) -> Option<&[T]> {
match &self.0 {
MultiRepr::Borrowed(v) => v.as_slice(),
MultiRepr::BorrowedMut(v) => v.as_slice(),
MultiRepr::Owned(a) => a.as_slice(),
}
}
#[inline]
pub fn outer_iter(&mut self) -> ndarray::iter::AxisIterMut<'_, T, ndarray::Ix1> {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.outer_iter_mut(),
MultiRepr::Owned(a) => a.outer_iter_mut(),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn as_slice_mut(&mut self) -> Option<&mut [T]> {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.as_slice_mut(),
MultiRepr::Owned(a) => a.as_slice_mut(),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn axis_iter(&self, axis: ndarray::Axis) -> ndarray::iter::AxisIter<'_, T, ndarray::Ix1> {
match &self.0 {
MultiRepr::Borrowed(v) => v.axis_iter(axis),
MultiRepr::BorrowedMut(v) => v.axis_iter(axis),
MultiRepr::Owned(a) => a.axis_iter(axis),
}
}
#[inline]
pub fn raw_dim(&self) -> ndarray::Dim<[usize; 2]> {
self.as_view().raw_dim()
}
#[inline]
pub fn row_mut(&mut self, index: usize) -> ndarray::ArrayViewMut1<'_, T> {
self.promote();
match &mut self.0 {
MultiRepr::BorrowedMut(a) => a.row_mut(index),
MultiRepr::Owned(a) => a.row_mut(index),
MultiRepr::Borrowed(_) => {
unreachable!("Self should have been converted to owned by now")
}
}
}
#[inline]
pub fn fill(&mut self, value: T) {
self.to_mut().fill(value);
}
#[inline]
pub fn into_raw_vec_and_offset(self) -> (Vec<T>, usize) {
match self.0 {
MultiRepr::Borrowed(v) => {
let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
(vec, offset.unwrap_or(0))
}
MultiRepr::BorrowedMut(v) => {
let (vec, offset) = v.to_owned().into_raw_vec_and_offset();
(vec, offset.unwrap_or(0))
}
MultiRepr::Owned(a) => {
let (vec, offset) = a.into_raw_vec_and_offset();
(vec, offset.unwrap_or(0))
}
}
}
#[inline]
pub fn fold<B, F>(&self, init: B, f: F) -> B
where
F: FnMut(B, &T) -> B,
{
self.as_view().iter().fold(init, f)
}
#[inline]
pub fn take(self) -> Array2<T> {
match self.0 {
MultiRepr::Borrowed(v) => v.to_owned(),
MultiRepr::BorrowedMut(v) => v.to_owned(),
MultiRepr::Owned(a) => a,
}
}
}
impl<T> PartialEq<Array2<T>> for MultiData<'_, T>
where
T: StandardSample,
{
#[inline]
fn eq(&self, other: &Array2<T>) -> bool {
self.as_view() == other.view()
}
}
impl<'a, T> PartialEq<MultiData<'a, T>> for Array2<T>
where
T: StandardSample,
{
#[inline]
fn eq(&self, other: &MultiData<'a, T>) -> bool {
self.view() == other.as_view()
}
}
impl<'a, T> IntoIterator for &'a MultiData<'_, T>
where
T: StandardSample,
{
type Item = &'a T;
type IntoIter = ndarray::iter::Iter<'a, T, ndarray::Ix2>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.as_view().into_iter()
}
}
#[derive(Debug, PartialEq)]
#[allow(clippy::exhaustive_enums)]
pub enum AudioData<'a, T>
where
T: StandardSample,
{
Mono(MonoData<'a, T>),
Multi(MultiData<'a, T>),
}
impl<T> AudioData<'static, T>
where
T: StandardSample,
{
#[inline]
#[must_use]
pub fn from_owned(data: AudioData<'_, T>) -> Self {
data.into_owned()
}
#[inline]
#[must_use]
pub fn mean(&self) -> T {
match self {
AudioData::Mono(m) => m.mean(),
AudioData::Multi(m) => m.mean(),
}
}
#[inline]
#[must_use]
pub fn into_mono_data(self) -> Option<Array1<T>> {
if let AudioData::Mono(m) = self {
Some(m.take())
} else {
None
}
}
#[inline]
#[must_use]
pub fn into_multi_data(self) -> Option<Array2<T>> {
if let AudioData::Multi(m) = self {
Some(m.take())
} else {
None
}
}
}
impl<T> Clone for AudioData<'_, T>
where
T: StandardSample,
{
#[inline]
fn clone(&self) -> Self {
match self {
AudioData::Mono(m) => AudioData::Mono(
MonoData::from_owned(m.as_view().to_owned())
.expect("self has already guaranteed non-emptiness"),
),
AudioData::Multi(m) => AudioData::Multi(
MultiData::from_owned(m.as_view().to_owned())
.expect("self has already guaranteed non-emptiness"),
),
}
}
}
impl<T> AudioData<'_, T>
where
T: StandardSample,
{
#[inline]
pub fn new_mono(data: Array1<T>) -> AudioSampleResult<AudioData<'static, T>> {
if data.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Mono(MonoData(MonoRepr::Owned(data))))
}
#[inline]
pub fn new_multi(data: Array2<T>) -> AudioSampleResult<AudioData<'static, T>> {
if data.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Multi(MultiData(MultiRepr::Owned(data))))
}
#[inline]
#[must_use]
pub fn borrow(&self) -> AudioData<'_, T> {
match self {
AudioData::Mono(mono_data) => {
AudioData::Mono(MonoData(MonoRepr::Borrowed(mono_data.as_view())))
}
AudioData::Multi(multi_data) => {
AudioData::Multi(MultiData(MultiRepr::Borrowed(multi_data.as_view())))
}
}
}
#[inline]
#[must_use]
pub fn as_vec(&self) -> Vec<T> {
match &self {
AudioData::Mono(mono_data) => mono_data.as_view().to_vec(),
AudioData::Multi(multi_data) => multi_data.as_view().iter().copied().collect(),
}
}
#[inline]
#[must_use]
pub fn total_frames(&self) -> NonZeroUsize {
match self {
AudioData::Mono(_) => self.len(),
AudioData::Multi(multi_data) => multi_data.ncols(),
}
}
#[inline]
#[must_use]
pub fn is_standard_layout(&self) -> bool {
match &self {
AudioData::Mono(m) => m.as_view().is_standard_layout(),
AudioData::Multi(m) => m.as_view().is_standard_layout(),
}
}
#[inline]
#[must_use]
pub fn from_slice(slice: &NonEmptySlice<T>) -> AudioData<'_, T> {
let view = ArrayView1::from(slice);
AudioData::Mono(MonoData(MonoRepr::Borrowed(view)))
}
#[inline]
#[must_use]
pub fn from_slice_mut(slice: &mut NonEmptySlice<T>) -> AudioData<'_, T> {
let view = ArrayViewMut1::from(slice);
AudioData::Mono(MonoData(MonoRepr::BorrowedMut(view)))
}
#[inline]
pub fn from_slice_multi(
slice: &NonEmptySlice<T>,
channels: ChannelCount,
) -> AudioSampleResult<AudioData<'_, T>> {
let total_samples = slice.len().get();
let samples_per_channel = total_samples / channels.get() as usize;
let view = ArrayView2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
Ok(AudioData::Multi(MultiData(MultiRepr::Borrowed(view))))
}
#[inline]
pub fn from_slice_multi_mut(
slice: &mut NonEmptySlice<T>,
channels: ChannelCount,
) -> AudioSampleResult<AudioData<'_, T>> {
let total_samples = slice.len().get();
let samples_per_channel = total_samples / channels.get() as usize;
let view =
ArrayViewMut2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
Ok(AudioData::Multi(MultiData(MultiRepr::BorrowedMut(view))))
}
#[inline]
#[must_use]
pub fn from_vec(vec: NonEmptyVec<T>) -> AudioData<'static, T> {
AudioData::Mono(MonoData(MonoRepr::Owned(Array1::from(vec.to_vec()))))
}
#[inline]
pub fn from_vec_multi(
vec: NonEmptyVec<T>,
channels: ChannelCount,
) -> AudioSampleResult<AudioData<'static, T>> {
let total_samples = vec.len().get();
let samples_per_channel = total_samples / channels.get() as usize;
let arr =
Array2::from_shape_vec((channels.get() as usize, samples_per_channel), vec.to_vec())?;
Ok(AudioData::Multi(MultiData(MultiRepr::Owned(arr))))
}
}
impl<'a, T> AudioData<'a, T>
where
T: StandardSample,
{
#[inline]
#[must_use]
pub const unsafe fn from_array1(arr: Array1<T>) -> Self {
AudioData::Mono(MonoData(MonoRepr::Owned(arr)))
}
#[inline]
#[must_use]
pub const unsafe fn from_array2(arr: Array2<T>) -> Self {
AudioData::Multi(MultiData(MultiRepr::Owned(arr)))
}
#[inline]
#[must_use]
pub const unsafe fn from_mono_data(data: MonoData<'a, T>) -> Self {
AudioData::Mono(data)
}
#[inline]
#[must_use]
pub const unsafe fn from_multi_data(data: MultiData<'a, T>) -> Self {
AudioData::Multi(data)
}
#[inline]
#[must_use]
pub const unsafe fn from_array1_view(view: ArrayView1<'a, T>) -> Self {
AudioData::Mono(MonoData(MonoRepr::Borrowed(view)))
}
#[inline]
#[must_use]
pub const unsafe fn from_array2_view(view: ArrayView2<'a, T>) -> Self {
AudioData::Multi(MultiData(MultiRepr::Borrowed(view)))
}
#[inline]
#[must_use]
pub fn into_owned<'b>(self) -> AudioData<'b, T> {
match self {
AudioData::Mono(m) => AudioData::Mono(m.into_owned()),
AudioData::Multi(m) => AudioData::Multi(m.into_owned()),
}
}
#[inline]
#[must_use]
pub fn from_borrowed(&self) -> AudioData<'_, T> {
match self {
AudioData::Mono(m) => AudioData::Mono(MonoData(MonoRepr::Borrowed(m.as_view()))),
AudioData::Multi(m) => AudioData::Multi(MultiData(MultiRepr::Borrowed(m.as_view()))),
}
}
#[inline]
pub fn from_borrowed_array1(view: ArrayView1<'_, T>) -> AudioSampleResult<AudioData<'_, T>> {
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Mono(MonoData(MonoRepr::Borrowed(view))))
}
#[inline]
#[must_use]
pub const unsafe fn from_borrowed_array1_unchecked(
view: ArrayView1<'_, T>,
) -> AudioData<'_, T> {
AudioData::Mono(MonoData(MonoRepr::Borrowed(view)))
}
#[inline]
pub fn from_borrowed_array1_mut(
view: ArrayViewMut1<'_, T>,
) -> AudioSampleResult<AudioData<'_, T>> {
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Mono(MonoData(MonoRepr::BorrowedMut(view))))
}
#[inline]
#[must_use]
pub const unsafe fn from_borrowed_array1_mut_unchecked(
view: ArrayViewMut1<'_, T>,
) -> AudioData<'_, T> {
AudioData::Mono(MonoData(MonoRepr::BorrowedMut(view)))
}
#[inline]
pub fn from_borrowed_array2(view: ArrayView2<'_, T>) -> AudioSampleResult<AudioData<'_, T>> {
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Multi(MultiData(MultiRepr::Borrowed(view))))
}
#[inline]
#[must_use]
pub const unsafe fn from_borrowed_array2_unchecked(
view: ArrayView2<'_, T>,
) -> AudioData<'_, T> {
AudioData::Multi(MultiData(MultiRepr::Borrowed(view)))
}
#[inline]
pub fn from_borrowed_array2_mut(
view: ArrayViewMut2<'_, T>,
) -> AudioSampleResult<AudioData<'_, T>> {
if view.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Multi(MultiData(MultiRepr::BorrowedMut(view))))
}
#[inline]
#[must_use]
pub const unsafe fn from_borrowed_array2_mut_unchecked(
view: ArrayViewMut2<'_, T>,
) -> AudioData<'_, T> {
AudioData::Multi(MultiData(MultiRepr::BorrowedMut(view)))
}
#[inline]
#[must_use]
pub fn len(&self) -> NonZeroUsize {
match self {
AudioData::Mono(m) => m.len(),
AudioData::Multi(m) => m.len(),
}
}
#[inline]
#[must_use]
pub fn num_channels(&self) -> ChannelCount {
match self {
AudioData::Mono(_) => channels!(1),
AudioData::Multi(m) => unsafe { ChannelCount::new_unchecked(m.shape()[0] as u32) },
}
}
#[inline]
#[must_use]
pub const fn is_mono(&self) -> bool {
matches!(self, AudioData::Mono(_))
}
#[inline]
#[must_use]
pub const fn is_multi_channel(&self) -> bool {
matches!(self, AudioData::Multi(_))
}
#[inline]
#[must_use]
pub fn shape(&self) -> &[usize] {
match &self {
AudioData::Mono(m) => m.shape(),
AudioData::Multi(m) => m.shape(),
}
}
#[inline]
#[must_use]
pub fn samples_per_channel(&self) -> NonZeroUsize {
match self {
AudioData::Mono(m) => {
unsafe { NonZeroUsize::new_unchecked(m.as_view().len()) }
}
AudioData::Multi(m) => unsafe { NonZeroUsize::new_unchecked(m.as_view().shape()[1]) },
}
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> Option<&[T]> {
match &self {
AudioData::Mono(m) => m.as_slice(),
AudioData::Multi(m) => m.as_slice(),
}
}
#[inline]
pub fn bytes(&self) -> AudioSampleResult<AudioBytes<'_>> {
let slice = self.as_slice().ok_or_else(|| {
AudioSampleError::Layout(LayoutError::NonContiguous {
operation: "bytes view".to_string(),
layout_type: "non-contiguous audio data".to_string(),
})
})?;
if TypeId::of::<T>() == TypeId::of::<I24>() {
let i24_slice: &[I24] =
unsafe { core::slice::from_raw_parts(slice.as_ptr().cast::<I24>(), slice.len()) };
let packed = I24::write_i24s_ne(i24_slice);
let packed = unsafe { NonEmptyByteVec::new_unchecked(packed) };
return Ok(AudioBytes::Owned(packed));
}
let byte_len = std::mem::size_of_val(slice);
let byte_ptr = slice.as_ptr().cast::<u8>();
let bytes = unsafe { core::slice::from_raw_parts(byte_ptr, byte_len) };
let bytes = unsafe { NonEmptySlice::new_unchecked(bytes) };
Ok(AudioBytes::Borrowed(bytes))
}
#[inline]
#[must_use]
pub const fn bytes_per_sample(&self) -> NonZeroU32 {
unsafe { NonZeroU32::new_unchecked(T::BYTES) }
}
#[inline]
#[must_use]
pub const fn bits_per_sample(&self) -> NonZeroU8 {
unsafe { NonZeroU8::new_unchecked(T::BITS) }
}
#[inline]
pub fn into_bytes(&self) -> AudioSampleResult<NonEmptyByteVec> {
self.bytes().map(AudioBytes::into_owned)
}
#[inline]
pub fn mapv<U, F>(&self, f: F) -> AudioData<'static, U>
where
F: Fn(T) -> U,
U: StandardSample,
{
match self {
AudioData::Mono(m) => {
let out = m.as_slice().map_or_else(
|| m.as_view().mapv(&f),
|slice| {
let vec: Vec<U> = slice.iter().map(|&x| f(x)).collect();
Array1::from(vec)
},
);
AudioData::Mono(MonoData(MonoRepr::Owned(out)))
}
AudioData::Multi(m) => {
let out = m.as_slice().map_or_else(|| m.as_view().mapv(&f), |slice| {
let (rows, cols) = m.as_view().dim();
let vec: Vec<U> = slice.iter().map(|&x| f(x)).collect();
Array2::from_shape_vec((rows, cols), vec).unwrap_or_else(|_| unreachable!("Output shape is guaranteed to match input shape since we're just mapping elementwise"))
});
AudioData::Multi(MultiData(MultiRepr::Owned(out)))
}
}
}
#[inline]
pub fn mapv_inplace<F>(&mut self, f: F)
where
F: Fn(T) -> T,
{
match self {
AudioData::Mono(m) => m.to_mut().iter_mut().for_each(|x| *x = f(*x)),
AudioData::Multi(m) => m.to_mut().iter_mut().for_each(|x| *x = f(*x)),
}
}
#[inline]
pub fn apply<F>(&mut self, func: F)
where
F: Fn(T) -> T,
{
self.mapv_inplace(func);
}
#[inline]
pub fn apply_with_index<F>(&mut self, func: F)
where
F: Fn(usize, T) -> T,
{
match self {
AudioData::Mono(m) => {
for (i, x) in m.to_mut().iter_mut().enumerate() {
*x = func(i, *x);
}
}
AudioData::Multi(m) => {
for mut row in m.to_mut().rows_mut() {
for (i, x) in row.iter_mut().enumerate() {
*x = func(i, *x);
}
}
}
}
}
#[inline]
pub fn apply_windowed<F>(
&mut self,
window_size: NonZeroUsize,
hop_size: NonZeroUsize,
func: F,
) -> AudioSampleResult<()>
where
F: Fn(&[T], &[T]) -> Vec<T>, {
let window_size = window_size.get();
let hop_size = hop_size.get();
match self {
AudioData::Mono(m) => {
let data = m.as_view();
let x = data.as_slice().ok_or_else(|| {
AudioSampleError::Layout(LayoutError::NonContiguous {
operation: "mono processing".to_string(),
layout_type: "non-contiguous mono data".to_string(),
})
})?;
let n = x.len();
if n < window_size {
return Ok(());
}
let num_windows = (n - window_size) / hop_size + 1;
let out_len = (num_windows - 1) * hop_size + window_size;
let mut result = vec![T::default(); out_len];
let mut overlap = vec![0usize; out_len];
let mut prev = vec![T::default(); window_size];
for w in 0..num_windows {
let pos = w * hop_size;
let win = &x[pos..pos + window_size];
let processed = func(win, &prev);
for (i, &s) in processed.iter().enumerate() {
let idx = pos + i;
result[idx] += s;
overlap[idx] += 1;
}
prev.copy_from_slice(win);
}
for (y, &c) in result.iter_mut().zip(&overlap) {
if c > 1 {
*y /= T::cast_from(c);
}
}
m.0 = MonoRepr::Owned(ndarray::Array1::from(result));
Ok(())
}
AudioData::Multi(m) => {
let view = m.as_view();
let (ch, spc) = view.dim();
if spc < window_size {
return Ok(());
}
let num_windows = (spc - window_size) / hop_size + 1;
let out_len = (num_windows - 1) * hop_size + window_size;
let mut out = ndarray::Array2::from_elem((ch, out_len), T::default());
let mut cnt = vec![0usize; out_len];
let mut prev = vec![T::default(); window_size];
for c in 0..ch {
let row = view.row(c);
let x = row.as_slice().ok_or_else(|| {
AudioSampleError::Layout(LayoutError::NonContiguous {
operation: "multi-channel row processing".to_string(),
layout_type: "non-contiguous row data".to_string(),
})
})?;
cnt.fill(0);
prev.fill(T::default());
for w in 0..num_windows {
let pos = w * hop_size;
let win = &x[pos..pos + window_size];
let processed = func(win, &prev);
for (i, &s) in processed.iter().enumerate() {
let idx = pos + i;
out[[c, idx]] += s;
cnt[idx] += 1;
}
prev.copy_from_slice(win);
}
for i in 0..out_len {
if cnt[i] > 1 {
out[[c, i]] /= T::cast_from(cnt[i]);
}
}
}
m.0 = MultiRepr::Owned(out);
Ok(())
}
}
}
#[inline]
pub fn apply_to_all_channels<F>(&mut self, f: F)
where
F: Fn(T) -> T,
{
self.mapv_inplace(f);
}
#[inline]
pub fn apply_to_channels<F>(&mut self, channels: &[u32], f: F)
where
F: Fn(T) -> T,
{
match self {
AudioData::Mono(m) => m.to_mut().iter_mut().for_each(|x| *x = f(*x)),
AudioData::Multi(m) => {
let mut a = m.to_mut();
for (ch_idx, mut row) in a.axis_iter_mut(Axis(0)).enumerate() {
if channels.contains(&(ch_idx as u32)) {
for x in &mut row {
*x = f(*x);
}
}
}
}
}
}
#[inline]
#[must_use]
pub fn convert_to<O>(&self) -> AudioData<'static, O>
where
O: StandardSample + ConvertTo<T> + ConvertFrom<T>,
{
match self {
AudioData::Mono(m) => {
let out = m.as_view().mapv(super::traits::ConvertTo::convert_to);
AudioData::Mono(MonoData(MonoRepr::Owned(out)))
}
AudioData::Multi(m) => {
let out = m.as_view().mapv(super::traits::ConvertTo::convert_to);
AudioData::Multi(MultiData(MultiRepr::Owned(out)))
}
}
}
#[inline]
#[must_use]
pub fn to_interleaved_vec(self) -> NonEmptyVec<T> {
match self {
AudioData::Mono(m) => match m.0 {
MonoRepr::Borrowed(v) => unsafe { NonEmptyVec::new_unchecked(v.to_vec()) },
MonoRepr::BorrowedMut(v) => unsafe { NonEmptyVec::new_unchecked(v.to_vec()) },
MonoRepr::Owned(a) => unsafe { NonEmptyVec::new_unchecked(a.to_vec()) },
},
AudioData::Multi(m) => {
let (ch, _spc) = m.as_view().dim();
let planar: Vec<T> = m
.as_view()
.as_slice()
.map_or_else(|| m.as_view().iter().copied().collect(), <[T]>::to_vec);
let planar = unsafe { NonEmptyVec::new_unchecked(planar) };
let ch = unsafe { NonZeroU32::new_unchecked(ch as u32) };
crate::simd_conversions::interleave_multi_vec(&planar, ch)
.expect("Interleave failed - this should not happen with valid input")
}
}
}
#[inline]
#[must_use]
pub fn as_interleaved_vec(&self) -> NonEmptyVec<T> {
match self {
AudioData::Mono(m) => unsafe { NonEmptyVec::new_unchecked(m.as_view().to_vec()) },
AudioData::Multi(m) => {
let v = m.as_view();
let (ch, _spc) = v.dim();
let planar: Vec<T> = v
.as_slice()
.map_or_else(|| v.iter().copied().collect(), <[T]>::to_vec);
let planar = unsafe { NonEmptyVec::new_unchecked(planar) };
let ch = unsafe { NonZeroU32::new_unchecked(ch as u32) };
crate::simd_conversions::interleave_multi_vec(&planar, ch)
.expect("Interleave failed - this should not happen with valid input")
}
}
}
}
impl<'a, T> TryFrom<ArrayView1<'a, T>> for AudioData<'a, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(arr: ArrayView1<'a, T>) -> Result<Self, Self::Error> {
if arr.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Mono(MonoData(MonoRepr::Borrowed(arr))))
}
}
impl<'a, T> TryFrom<ArrayViewMut1<'a, T>> for AudioData<'a, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(arr: ArrayViewMut1<'a, T>) -> Result<Self, Self::Error> {
if arr.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Mono(MonoData(MonoRepr::BorrowedMut(arr))))
}
}
impl<'a, T> TryFrom<ArrayView2<'a, T>> for AudioData<'a, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(arr: ArrayView2<'a, T>) -> Result<Self, Self::Error> {
if arr.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Multi(MultiData(MultiRepr::Borrowed(arr))))
}
}
impl<'a, T> TryFrom<ArrayViewMut2<'a, T>> for AudioData<'a, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(arr: ArrayViewMut2<'a, T>) -> Result<Self, Self::Error> {
if arr.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Multi(MultiData(MultiRepr::BorrowedMut(arr))))
}
}
impl<T> Index<usize> for AudioData<'_, T>
where
T: StandardSample,
{
type Output = T;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
match self {
AudioData::Mono(arr) => &arr[index],
AudioData::Multi(arr) => {
let (channels, samples_per_channel) = arr.dim();
let total_samples = channels.get() * samples_per_channel.get();
assert!(
index < total_samples,
"Index {index} out of bounds for total samples {total_samples}"
);
let channel = index / samples_per_channel;
let sample_idx = index % samples_per_channel;
&arr[(channel, sample_idx)]
}
}
}
}
impl<T> TryFrom<Array1<T>> for MonoData<'_, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(a: Array1<T>) -> Result<Self, Self::Error> {
if a.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MonoData(MonoRepr::Owned(a)))
}
}
impl<T> TryFrom<Array2<T>> for MultiData<'_, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(a: Array2<T>) -> Result<Self, Self::Error> {
if a.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(MultiData(MultiRepr::Owned(a)))
}
}
impl<T> TryFrom<Array1<T>> for AudioData<'_, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(a: Array1<T>) -> Result<Self, Self::Error> {
if a.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Mono(a.try_into()?))
}
}
impl<T> TryFrom<Array2<T>> for AudioData<'_, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(a: Array2<T>) -> Result<Self, Self::Error> {
if a.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioData::Multi(a.try_into()?))
}
}
macro_rules! impl_audio_data_ops {
($(
$trait:ident, $method:ident,
$assign_trait:ident, $assign_method:ident,
$op:tt,
$mono_err:literal,
$multi_err:literal,
$mismatch_err:literal
);+ $(;)?) => {
$(
impl<'a, T> std::ops::$trait<Self> for AudioData<'a, T>
where
T: StandardSample,
{
type Output = Self;
#[inline]
fn $method(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(AudioData::Mono(lhs), AudioData::Mono(rhs)) => {
if lhs.len() != rhs.len() {
panic!($mono_err);
}
let arr: Array1<T> = &lhs.as_view() $op &rhs.as_view();
unsafe {
AudioData::Mono(MonoData::from_array1(arr))
}
}
(AudioData::Multi(lhs), AudioData::Multi(rhs)) => {
if lhs.as_view().dim() != rhs.as_view().dim() {
panic!($multi_err);
}
AudioData::Multi(unsafe { MultiData::from_array2(&lhs.as_view() $op &rhs.as_view()) })
}
_ => panic!($mismatch_err),
}
}
}
impl<'a, T> std::ops::$trait<T> for AudioData<'a, T>
where
T: StandardSample,
{
type Output = Self;
#[inline]
fn $method(self, rhs: T) -> Self::Output {
match self {
AudioData::Mono(a) => AudioData::Mono(unsafe { MonoData::from_array1(a.as_view().mapv(|x| x $op rhs)) }),
AudioData::Multi(a) => AudioData::Multi(unsafe { MultiData::from_array2(a.as_view().mapv(|x| x $op rhs)) }),
}
}
}
impl<'a, T> std::ops::$assign_trait<Self> for AudioData<'a, T>
where
T: StandardSample,
{
#[inline]
fn $assign_method(&mut self, rhs: Self) {
match (self, rhs) {
(AudioData::Mono(lhs), AudioData::Mono(rhs)) => {
if lhs.len() != rhs.len() {
panic!($mono_err);
}
let mut lhs_mut = lhs.to_mut();
let rhs_view = rhs.as_view();
lhs_mut.zip_mut_with(&rhs_view, |a, &b| *a = *a $op b);
}
(AudioData::Multi(lhs), AudioData::Multi(rhs)) => {
if lhs.as_view().dim() != rhs.as_view().dim() {
panic!($multi_err);
}
let mut lhs_mut = lhs.to_mut();
let rhs_view = rhs.as_view();
lhs_mut.zip_mut_with(&rhs_view, |a, &b| *a = *a $op b);
}
_ => panic!($mismatch_err),
}
}
}
impl<'a, T> std::ops::$assign_trait<T> for AudioData<'a, T>
where
T: StandardSample,
{
#[inline]
fn $assign_method(&mut self, rhs: T) {
match self {
AudioData::Mono(lhs) => {
let mut lhs_mut = lhs.to_mut();
lhs_mut.iter_mut().for_each(|x| *x = *x $op rhs);
}
AudioData::Multi(lhs) => {
let mut lhs_mut = lhs.to_mut();
lhs_mut.iter_mut().for_each(|x| *x = *x $op rhs);
}
}
}
}
)+
};
}
impl_audio_data_ops!(
Add, add, AddAssign, add_assign, +,
"Cannot add mono audio with different lengths",
"Cannot add multi-channel audio with different shapes",
"Cannot add mono and multi-channel audio";
Sub, sub, SubAssign, sub_assign, -,
"Cannot subtract mono audio with different lengths",
"Cannot subtract multi-channel audio with different shapes",
"Cannot subtract mono and multi-channel audio";
Mul, mul, MulAssign, mul_assign, *,
"Cannot multiply mono audio with different lengths",
"Cannot multiply multi-channel audio with different shapes",
"Cannot multiply mono and multi-channel audio";
Div, div, DivAssign, div_assign, /,
"Cannot divide mono audio with different lengths",
"Cannot divide multi-channel audio with different shapes",
"Cannot divide mono and multi-channel audio";
);
impl<T> Neg for AudioData<'_, T>
where
T: StandardSample + Neg<Output = T> + ConvertTo<T> + ConvertFrom<T>,
{
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
match self {
AudioData::Mono(arr) => {
AudioData::Mono(unsafe { MonoData::from_array1(arr.as_view().mapv(|x| -x)) })
}
AudioData::Multi(arr) => {
AudioData::Multi(unsafe { MultiData::from_array2(arr.as_view().mapv(|x| -x)) })
}
}
}
}
impl<T, S> Mul<&[S]> for AudioSamples<'_, T>
where
T: StandardSample + ConvertTo<S> + ConvertFrom<S>,
S: StandardSample,
{
type Output = Option<Self>;
#[inline]
fn mul(self, rhs: &[S]) -> Self::Output {
if self.is_multi_channel() || self.len().get() != rhs.len() {
return None;
}
let mut out = self.into_owned();
out.apply_with_index(|idx, x| {
let x: S = T::convert_to(x);
let res = x * rhs[idx];
let res: T = S::convert_to(res);
res
});
Some(out)
}
}
#[derive(Debug, PartialEq)]
#[allow(clippy::exhaustive_structs)] pub struct AudioSamples<'a, T>
where
T: StandardSample,
{
pub data: AudioData<'a, T>,
pub sample_rate: SampleRate,
}
impl<T> Display for AudioSamples<'_, T>
where
T: StandardSample,
{
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let type_name = std::any::type_name::<T>();
let n_channels = self.num_channels();
let n_samples = self.samples_per_channel();
let rate = self.sample_rate;
writeln!(
f,
"AudioSamples<{type_name}>: {n_channels} ch x {n_samples} samples @ {rate} Hz"
)?;
if f.alternate() {
match &self.data {
AudioData::Mono(arr) => {
let len = arr.len();
let display_len = 5.min(len.get());
write!(f, "Mono Channel\n First {display_len} samples: [")?;
for (i, val) in arr.iter().take(display_len).enumerate() {
write!(f, "{val:.4}")?;
if i < display_len - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
if len.get() > display_len {
write!(f, "\n Last {display_len} samples: [")?;
for (i, val) in arr.iter().rev().take(display_len).rev().enumerate() {
write!(f, "{val:.4}")?;
if i < display_len - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
}
}
AudioData::Multi(arr) => {
let (channels, samples) = arr.dim();
for ch in 0..channels.get() {
let ch_data = arr.index_axis(ndarray::Axis(0), ch);
let len = samples.get();
let display_len = 5.min(len);
write!(f, "\nChannel {ch}:")?;
write!(f, "\n First {display_len} samples: [")?;
for (i, val) in ch_data.iter().take(display_len).enumerate() {
write!(f, "{val:.4}")?;
if i < display_len - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
if len > display_len {
write!(f, "\n Last {display_len} samples: [")?;
for (i, val) in ch_data.iter().rev().take(display_len).rev().enumerate()
{
write!(f, "{val:.4}")?;
if i < display_len - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
}
}
}
}
} else {
match &self.data {
AudioData::Mono(arr) => {
let len = arr.len();
let preview = 5.min(len.get());
write!(f, "[")?;
for (i, val) in arr.as_view().iter().take(preview).enumerate() {
write!(f, "{val:.4}")?;
if i < preview - 1 {
write!(f, ", ")?;
}
}
if len.get() > preview {
write!(f, ", ...")?;
}
write!(f, "]")?;
}
AudioData::Multi(arr) => {
let channels = arr.ncols().get();
for ch in 0..channels {
let ch_data = arr.index_axis(ndarray::Axis(0), ch);
let len = ch_data.len();
let preview = 3.min(len);
write!(f, "\nCh {ch}: [")?;
for (i, val) in ch_data.iter().take(preview).enumerate() {
write!(f, "{val:.4}")?;
if i < preview - 1 {
write!(f, ", ")?;
}
}
if len > preview {
write!(f, ", ...")?;
}
write!(f, "]")?;
}
}
}
}
Ok(())
}
}
impl<T> AudioSamples<'static, T>
where
T: StandardSample,
{
#[inline]
#[must_use]
pub fn from_owned(data: AudioData<'_, T>, sample_rate: SampleRate) -> Self {
let owned = data.into_owned();
Self {
data: owned,
sample_rate,
}
}
#[inline]
#[must_use]
pub fn into_data(self) -> AudioData<'static, T> {
self.data.into_owned()
}
#[inline]
#[must_use]
pub fn into_array1(self) -> Option<Array1<T>> {
match self.data {
AudioData::Mono(m) => Some(m.take()),
AudioData::Multi(_) => None,
}
}
#[inline]
#[must_use]
pub fn into_array2(self) -> Option<Array2<T>> {
match self.data {
AudioData::Multi(m) => Some(m.take()),
AudioData::Mono(_) => None,
}
}
}
impl<'a, T> AudioSamples<'a, T>
where
T: StandardSample,
{
#[inline]
#[must_use]
pub const fn new(data: AudioData<'a, T>, sample_rate: SampleRate) -> Self {
Self { data, sample_rate }
}
#[inline]
#[must_use]
pub fn borrow(&self) -> AudioSamples<'_, T> {
AudioSamples {
data: self.data.borrow(),
sample_rate: self.sample_rate,
}
}
#[inline]
#[must_use]
pub fn as_vec(&self) -> Vec<T> {
self.data.as_vec()
}
#[inline]
#[must_use]
pub fn is_standard_layout(&self) -> bool {
self.data.is_standard_layout()
}
#[inline]
#[must_use]
pub const fn from_borrowed(data: AudioData<'a, T>, sample_rate: SampleRate) -> Self {
Self { data, sample_rate }
}
#[inline]
#[must_use]
pub const fn from_borrowed_with_layout(
data: AudioData<'a, T>,
sample_rate: SampleRate,
) -> Self {
Self { data, sample_rate }
}
#[inline]
pub fn convert_to<O>(&self) -> AudioSamples<'static, O>
where
T: ConvertTo<O>,
O: StandardSample + ConvertFrom<T> + ConvertTo<O> + ConvertFrom<O>,
{
self.map_into(O::convert_from)
}
#[inline]
#[must_use]
pub fn to_interleaved_vec(&self) -> NonEmptyVec<T> {
self.data.as_interleaved_vec()
}
#[inline]
#[must_use]
pub fn total_frames(&self) -> NonZeroUsize {
self.data.total_frames()
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> Option<&[T]> {
match &self.data {
AudioData::Mono(m) => m.as_slice(),
AudioData::Multi(m) => m.as_slice(),
}
}
#[inline]
pub fn as_slice_mut(&mut self) -> Option<&mut [T]> {
match &mut self.data {
AudioData::Mono(mono_data) => Some(mono_data.as_slice_mut()),
AudioData::Multi(multi_data) => multi_data.as_slice_mut(),
}
}
#[inline]
pub fn new_mono<'b>(
data: Array1<T>,
sample_rate: SampleRate,
) -> AudioSampleResult<AudioSamples<'b, T>> {
if data.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioSamples {
data: AudioData::Mono(MonoData(MonoRepr::Owned(data))),
sample_rate,
})
}
#[inline]
#[must_use]
pub const unsafe fn new_mono_unchecked<'b>(
data: Array1<T>,
sample_rate: SampleRate,
) -> AudioSamples<'b, T> {
AudioSamples {
data: AudioData::Mono(MonoData(MonoRepr::Owned(data))),
sample_rate,
}
}
#[inline]
pub fn new_multi_channel<'b>(
data: Array2<T>,
sample_rate: SampleRate,
) -> AudioSampleResult<AudioSamples<'b, T>> {
if data.is_empty() {
return Err(AudioSampleError::EmptyData);
}
Ok(AudioSamples {
data: AudioData::Multi(MultiData(MultiRepr::Owned(data))),
sample_rate,
})
}
#[inline]
#[must_use]
pub fn zeros_mono(length: NonZeroUsize, sample_rate: SampleRate) -> Self {
Self {
data: AudioData::Mono(MonoData(MonoRepr::Owned(Array1::zeros(length.get())))),
sample_rate,
}
}
#[inline]
#[must_use]
pub fn zeros_multi(
channels: ChannelCount,
length: NonZeroUsize,
sample_rate: SampleRate,
) -> Self {
Self {
data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::zeros((
channels.get() as usize,
length.get(),
))))),
sample_rate,
}
}
#[inline]
#[must_use]
pub fn zeros_multi_channel(
channels: ChannelCount,
length: NonZeroUsize,
sample_rate: SampleRate,
) -> AudioSamples<'static, T> {
AudioSamples {
data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::zeros((
channels.get() as usize,
length.get(),
))))),
sample_rate,
}
}
#[inline]
#[must_use]
pub fn ones_mono(length: NonZeroUsize, sample_rate: SampleRate) -> Self {
Self {
data: AudioData::Mono(MonoData(MonoRepr::Owned(Array1::ones(length.get())))),
sample_rate,
}
}
#[inline]
#[must_use]
pub fn ones_multi(
channels: ChannelCount,
length: NonZeroUsize,
sample_rate: SampleRate,
) -> Self {
Self {
data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::ones((
channels.get() as usize,
length.get(),
))))),
sample_rate,
}
}
#[inline]
pub fn uniform_mono(length: NonZeroUsize, sample_rate: SampleRate, value: T) -> Self {
Self {
data: AudioData::Mono(MonoData(MonoRepr::Owned(Array1::from_elem(
length.get(),
value,
)))),
sample_rate,
}
}
#[inline]
pub fn uniform_multi(
channels: ChannelCount,
length: NonZeroUsize,
sample_rate: SampleRate,
value: T,
) -> Self {
Self {
data: AudioData::Multi(MultiData(MultiRepr::Owned(Array2::from_elem(
(channels.get() as usize, length.get()),
value,
)))),
sample_rate,
}
}
#[inline]
#[must_use]
pub fn info(&self) -> (ChannelCount, NonZeroUsize, f64, NonZeroU32) {
(
self.num_channels(),
self.samples_per_channel(),
self.duration_seconds(),
self.sample_rate,
)
}
#[inline]
#[must_use]
pub const fn sample_rate(&self) -> SampleRate {
self.sample_rate
}
#[inline]
#[must_use]
pub const fn sample_rate_hz(&self) -> f64 {
self.sample_rate.get() as f64
}
#[inline]
#[must_use]
pub fn num_channels(&self) -> ChannelCount {
self.data.num_channels()
}
#[inline]
#[must_use]
pub fn samples_per_channel(&self) -> NonZeroUsize {
self.data.samples_per_channel()
}
#[inline]
#[must_use]
pub fn duration_seconds(&self) -> f64 {
self.samples_per_channel().get() as f64 / self.sample_rate_hz()
}
#[inline]
#[must_use]
pub fn total_samples(&self) -> NonZeroUsize {
unsafe {
NonZeroUsize::new_unchecked(
self.num_channels().get() as usize * self.samples_per_channel().get(),
)
}
}
#[inline]
#[must_use]
pub const fn bytes_per_sample(&self) -> NonZeroU32 {
self.data.bytes_per_sample()
}
#[inline]
#[must_use]
pub const fn sample_type() -> SampleType {
T::SAMPLE_TYPE
}
#[inline]
#[must_use]
pub const fn is_mono(&self) -> bool {
self.data.is_mono()
}
#[inline]
#[must_use]
pub const fn is_multi_channel(&self) -> bool {
self.data.is_multi_channel()
}
#[inline]
#[must_use]
pub fn len(&self) -> NonZeroUsize {
self.data.len()
}
#[inline]
#[must_use]
pub fn shape(&self) -> &[usize] {
self.data.shape()
}
#[inline]
pub fn apply<F>(&mut self, f: F)
where
F: Fn(T) -> T,
{
self.data.apply(f);
}
#[inline]
pub fn apply_to_channels<F>(&mut self, channels: &[u32], f: F)
where
F: Fn(T) -> T + Copy,
{
self.data.apply_to_channels(channels, f);
}
#[inline]
pub fn map<F>(&self, f: F) -> AudioSamples<'static, T>
where
F: Fn(T) -> T,
{
let new_data = self.data.mapv(f);
AudioSamples::from_owned(new_data, self.sample_rate)
}
#[inline]
pub fn map_into<O, F>(&self, f: F) -> AudioSamples<'static, O>
where
F: Fn(T) -> O,
T: ConvertTo<O>,
O: StandardSample,
{
let new_data = AudioData::from_owned(self.data.mapv(f));
AudioSamples::from_owned(new_data, self.sample_rate)
}
#[inline]
pub fn apply_with_index<F>(&mut self, f: F)
where
F: Fn(usize, T) -> T,
{
self.data.apply_with_index(f);
}
#[inline]
pub fn slice_samples<R>(&self, sample_range: R) -> AudioSampleResult<AudioSamples<'_, T>>
where
R: RangeBounds<usize> + Clone,
{
let samples_per_channel = self.samples_per_channel();
let start = match sample_range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n + 1,
Bound::Unbounded => 0,
};
let end = match sample_range.end_bound() {
Bound::Included(&n) => n + 1,
Bound::Excluded(&n) => n,
Bound::Unbounded => samples_per_channel.get(),
};
if start >= samples_per_channel.get() || end > samples_per_channel.get() || start >= end {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"sample_range",
format!(
"Sample range {start}..{end} out of bounds for {samples_per_channel} samples"
),
)));
}
if start == end {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"sample_range",
"Start and end of sample range cannot be equal".to_string(),
)));
}
match &self.data {
AudioData::Mono(arr) => {
let sliced =
unsafe { AudioData::from_array1_view(arr.slice(ndarray::s![start..end])) };
Ok(AudioSamples::from_borrowed_with_layout(
sliced,
self.sample_rate(),
))
}
AudioData::Multi(arr) => {
let sliced =
unsafe { AudioData::from_array2_view(arr.slice(ndarray::s![.., start..end])) };
Ok(AudioSamples::from_borrowed_with_layout(
sliced,
self.sample_rate(),
))
}
}
}
#[inline]
pub fn slice_channels<'iter, R>(
&'iter self,
channel_range: R,
) -> AudioSampleResult<AudioSamples<'static, T>>
where
'iter: 'a,
R: RangeBounds<usize> + Clone,
{
let num_channels = self.num_channels();
let start = match channel_range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n + 1,
Bound::Unbounded => 0,
};
let end = match channel_range.end_bound() {
Bound::Included(&n) => n + 1,
Bound::Excluded(&n) => n,
Bound::Unbounded => num_channels.get() as usize,
};
if start >= num_channels.get() as usize || end > num_channels.get() as usize || start >= end
{
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channel_range",
format!(
"Channel range {}..{} out of bounds for {} channels",
start,
end,
num_channels.get()
),
)));
}
match &self.data {
AudioData::Mono(arr) => {
if start != 0 || end != 1 {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channel_range",
format!(
"Channel range {start}..{end} invalid for mono audio (only 0..1 allowed)"
),
)));
}
let audio_data = AudioData::try_from(arr.as_view())?;
let audio = AudioSamples::from_owned(audio_data.into_owned(), self.sample_rate);
Ok(audio)
}
AudioData::Multi(arr) => {
let sliced = arr.slice(ndarray::s![start..end, ..]);
if end - start == 1 {
let mono_data = sliced
.index_axis(ndarray::Axis(0), 0)
.to_owned()
.try_into()?;
let audio = AudioSamples::from_owned(mono_data, self.sample_rate);
Ok(audio)
} else {
let audio =
AudioSamples::from_owned(sliced.to_owned().try_into()?, self.sample_rate);
Ok(audio)
}
}
}
}
#[inline]
pub fn slice_both<CR, SR>(
&self,
channel_range: CR,
sample_range: SR,
) -> AudioSampleResult<AudioSamples<'static, T>>
where
CR: RangeBounds<isize> + Clone,
SR: RangeBounds<isize> + Clone,
{
let num_channels = self.num_channels().get() as isize;
let samples_per_channel = self.samples_per_channel().get().cast_signed();
let norm = |idx: isize, len: isize| -> usize {
if idx < 0 {
(len + idx).max(0) as usize
} else {
idx.min(len) as usize
}
};
let ch_start = match channel_range.start_bound() {
Bound::Included(&n) => norm(n, num_channels),
Bound::Excluded(&n) => norm(n + 1, num_channels),
Bound::Unbounded => 0,
};
let ch_end = match channel_range.end_bound() {
Bound::Included(&n) => norm(n + 1, num_channels),
Bound::Excluded(&n) => norm(n, num_channels),
Bound::Unbounded => num_channels as usize,
};
let s_start = match sample_range.start_bound() {
Bound::Included(&n) => norm(n, samples_per_channel),
Bound::Excluded(&n) => norm(n + 1, samples_per_channel),
Bound::Unbounded => 0,
};
let s_end = match sample_range.end_bound() {
Bound::Included(&n) => norm(n + 1, samples_per_channel),
Bound::Excluded(&n) => norm(n, samples_per_channel),
Bound::Unbounded => samples_per_channel as usize,
};
if ch_start >= num_channels as usize || ch_end > num_channels as usize || ch_start >= ch_end
{
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channel_range",
format!(
"Channel range {ch_start}..{ch_end} out of bounds for {num_channels} channels"
),
)));
}
if s_start >= samples_per_channel as usize
|| s_end > samples_per_channel as usize
|| s_start >= s_end
{
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"sample_range",
format!(
"Sample range {s_start}..{s_end} out of bounds for {samples_per_channel} samples"
),
)));
}
match &self.data {
AudioData::Mono(arr) => {
if ch_start != 0 || ch_end != 1 {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channel_range",
format!(
"Channel range {ch_start}..{ch_end} invalid for mono audio (only 0..1 allowed)"
),
)));
}
let sliced = arr
.slice(ndarray::s![s_start..s_end])
.to_owned()
.try_into()?;
Ok(AudioSamples::from_owned(sliced, self.sample_rate))
}
AudioData::Multi(arr) => {
let sliced = arr.slice(ndarray::s![ch_start..ch_end, s_start..s_end]);
if ch_end - ch_start == 1 {
let mono_data: AudioData<_> = sliced
.index_axis(ndarray::Axis(0), 0)
.to_owned()
.try_into()?;
Ok(AudioSamples::from_owned(mono_data, self.sample_rate))
} else {
Ok(AudioSamples::from_owned(
sliced.to_owned().try_into()?,
self.sample_rate,
))
}
}
}
}
#[inline]
pub fn bytes(&self) -> AudioSampleResult<AudioBytes<'_>> {
self.data.bytes()
}
#[inline]
pub fn into_bytes(&self) -> AudioSampleResult<NonEmptyByteVec> {
self.data.into_bytes()
}
#[inline]
#[must_use]
pub fn total_byte_size(&self) -> NonZeroUsize {
unsafe {
NonZeroUsize::new_unchecked(
self.bytes_per_sample().get() as usize * self.data.len().get(),
)
}
}
#[inline]
pub fn apply_windowed<F>(
&mut self,
window_size: NonZeroUsize,
hop_size: NonZeroUsize,
func: F,
) -> AudioSampleResult<()>
where
F: Fn(&[T], &[T]) -> Vec<T>,
{
self.data.apply_windowed(window_size, hop_size, func)
}
#[inline]
#[must_use]
pub fn into_owned<'b>(self) -> AudioSamples<'b, T> {
AudioSamples {
data: self.data.into_owned(),
sample_rate: self.sample_rate,
}
}
#[inline]
pub fn replace_data(&mut self, new_data: AudioData<'a, T>) -> AudioSampleResult<()> {
let current_channels = self.data.num_channels();
let new_channels = new_data.num_channels();
if current_channels != new_channels {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"new_data",
format!(
"Channel count mismatch: existing audio has {current_channels} channels, new data has {new_channels} channels"
),
)));
}
match (&self.data, &new_data) {
(AudioData::Mono(_), AudioData::Multi(_)) => {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"new_data",
"Cannot replace mono audio data with multi-channel data",
)));
}
(AudioData::Multi(_), AudioData::Mono(_)) => {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"new_data",
"Cannot replace multi-channel audio data with mono data",
)));
}
_ => {} }
self.data = new_data;
Ok(())
}
#[inline]
pub fn replace_with_mono(&mut self, data: Array1<T>) -> AudioSampleResult<()> {
if data.is_empty() {
return Err(AudioSampleError::EmptyData);
}
if !self.is_mono() {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"audio_type",
format!(
"Cannot replace multi-channel audio ({} channels) with mono data",
self.num_channels()
),
)));
}
let new_data = AudioData::try_from(data)?.into_owned();
self.replace_data(new_data)
}
#[inline]
pub fn replace_with_multi(&mut self, data: Array2<T>) -> AudioSampleResult<()> {
if data.is_empty() {
return Err(AudioSampleError::EmptyData);
}
let new_channels = data.nrows();
let new_channels = unsafe { ChannelCount::new_unchecked(new_channels as u32) };
if self.is_mono() {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"audio_type",
"Cannot replace mono audio with multi-channel data",
)));
}
if self.num_channels() != new_channels {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"new_data",
format!(
"Channel count mismatch: existing audio has {} channels, new data has {} channels",
self.num_channels(),
new_channels.get()
),
)));
}
let new_data = AudioData::try_from(data)?.into_owned();
self.replace_data(new_data)
}
#[inline]
pub fn replace_with_vec(&mut self, samples: &NonEmptyVec<T>) -> AudioSampleResult<()> {
let num_channels = self.num_channels().get() as usize;
if !samples.len().get().is_multiple_of(num_channels) {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"samples",
format!(
"Sample count {} is not divisible by channel count {}",
samples.len(),
num_channels
),
)));
}
if num_channels == 1 {
let array = Array1::from(samples.to_vec());
self.replace_with_mono(array)
} else {
let samples_per_channel = samples.len().get() / num_channels;
let array =
Array2::from_shape_vec((num_channels, samples_per_channel), samples.to_vec())
.map_err(|e| {
AudioSampleError::Parameter(ParameterError::invalid_value(
"samples",
format!(
"Failed to reshape samples into {num_channels}x{samples_per_channel} array: {e}"
),
))
})?;
self.replace_with_multi(array)
}
}
#[inline]
pub fn replace_with_vec_channels(
&mut self,
samples: &NonEmptyVec<T>,
expected_channels: ChannelCount,
) -> AudioSampleResult<()> {
if !samples
.len()
.get()
.is_multiple_of(expected_channels.get() as usize)
{
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"samples",
format!(
"Sample count {} is not divisible by expected channel count {}",
samples.len(),
expected_channels.get()
),
)));
}
if self.num_channels() != expected_channels {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"expected_channels",
format!(
"Current audio has {} channels, but expected {} channels",
self.num_channels(),
expected_channels
),
)));
}
if expected_channels == channels!(1) {
let array: Array1<T> = Array1::from_vec(samples.to_vec());
self.replace_with_mono(array)
} else {
let samples_per_channel = self.samples_per_channel();
let array = Array2::from_shape_vec(
(expected_channels.get() as usize, samples_per_channel.get()),
samples.to_vec(),
)?;
self.replace_with_multi(array)
}
}
#[inline]
#[must_use]
pub const fn as_mono(&self) -> Option<&MonoData<'a, T>> {
match &self.data {
AudioData::Mono(arr) => Some(arr),
AudioData::Multi(_) => None,
}
}
#[inline]
#[must_use]
pub const fn as_multi_channel(&self) -> Option<&MultiData<'a, T>> {
match &self.data {
AudioData::Mono(_) => None,
AudioData::Multi(arr) => Some(arr),
}
}
#[inline]
pub const fn as_mono_mut(&mut self) -> Option<&mut MonoData<'a, T>> {
match &mut self.data {
AudioData::Mono(arr) => Some(arr),
AudioData::Multi(_) => None,
}
}
#[inline]
pub const fn as_multi_channel_mut(&mut self) -> Option<&mut MultiData<'a, T>> {
match &mut self.data {
AudioData::Mono(_) => None,
AudioData::Multi(arr) => Some(arr),
}
}
#[inline]
pub fn new_mono_from_slice(slice: &'a NonEmptySlice<T>, sample_rate: SampleRate) -> Self {
let arr = ArrayView1::from(slice);
let mono_data = unsafe { MonoData::from_view_unchecked(arr) };
let audio_data = AudioData::Mono(mono_data);
AudioSamples {
data: audio_data,
sample_rate,
}
}
#[inline]
pub fn new_mono_from_mut_slice(
slice: &'a mut NonEmptySlice<T>,
sample_rate: SampleRate,
) -> Self {
let arr = ArrayViewMut1::from(slice);
let mono_data = unsafe { MonoData::from_view_mut_unchecked(arr) };
let audio_data = AudioData::Mono(mono_data);
AudioSamples {
data: audio_data,
sample_rate,
}
}
#[inline]
pub fn new_multi_channel_from_slice(
slice: &'a NonEmptySlice<T>,
channels: ChannelCount,
sample_rate: SampleRate,
) -> AudioSampleResult<Self> {
let total_samples = slice.len().get();
let samples_per_channel = total_samples / channels.get() as usize;
let arr = ArrayView2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
let multi_data = MultiData::from_view(arr)?;
let audio_data = AudioData::Multi(multi_data);
Ok(AudioSamples {
data: audio_data,
sample_rate,
})
}
#[inline]
pub fn new_multi_channel_from_mut_slice(
slice: &'a mut NonEmptySlice<T>,
channels: ChannelCount,
sample_rate: SampleRate,
) -> AudioSampleResult<Self> {
let total_samples = slice.len().get();
let samples_per_channel = total_samples / channels.get() as usize;
let arr = ArrayViewMut2::from_shape((channels.get() as usize, samples_per_channel), slice)?;
let multi_data = MultiData::from_view_mut(arr)?;
let audio_data = AudioData::Multi(multi_data);
Ok(AudioSamples {
data: audio_data,
sample_rate,
})
}
#[inline]
pub fn new_multi_channel_from_vec<O>(
vec: NonEmptyVec<O>,
channels: ChannelCount,
sample_rate: SampleRate,
) -> AudioSampleResult<Self>
where
T: ConvertFrom<O>,
O: StandardSample,
{
let total_samples = vec.len().get();
if !total_samples.is_multiple_of(channels.get() as usize) {
return Err(AudioSampleError::invalid_number_of_samples(
total_samples,
channels.get(),
));
}
let samples_per_channel = total_samples / channels.get() as usize;
let vec: NonEmptyVec<T> = vec
.into_non_empty_iter()
.map(T::convert_from)
.collect_non_empty();
let arr =
Array2::from_shape_vec((channels.get() as usize, samples_per_channel), vec.to_vec())?;
let multi_data = MultiData::from_owned(arr)?;
let audio_data = AudioData::Multi(multi_data);
Ok(AudioSamples {
data: audio_data,
sample_rate,
})
}
#[inline]
pub fn from_mono_vec<O>(
vec: NonEmptyVec<O>,
sample_rate: SampleRate,
) -> AudioSamples<'static, T>
where
O: StandardSample,
T: ConvertFrom<O>,
{
let vec: NonEmptyVec<T> = vec
.into_non_empty_iter()
.map(ConvertFrom::convert_from)
.collect_non_empty();
AudioSamples {
data: AudioData::from_vec(vec),
sample_rate,
}
}
#[inline]
pub fn from_vec_with_channels(
vec: NonEmptyVec<T>,
channels: ChannelCount,
sample_rate: SampleRate,
) -> AudioSampleResult<AudioSamples<'static, T>> {
let total_samples = vec.len().get();
if !total_samples.is_multiple_of(channels.get() as usize) {
return Err(AudioSampleError::invalid_number_of_samples(
total_samples,
channels.get(),
));
}
Ok(AudioSamples {
data: AudioData::from_vec_multi(vec, channels)?,
sample_rate,
})
}
#[inline]
pub fn from_interleaved_vec<O>(
samples: NonEmptyVec<O>,
channels: ChannelCount,
sample_rate: SampleRate,
) -> AudioSampleResult<AudioSamples<'static, T>>
where
T: ConvertFrom<O>,
O: StandardSample,
{
if channels.get() == 1 {
return Ok(AudioSamples::from_mono_vec(samples, sample_rate));
}
let deinterleaved = crate::simd_conversions::deinterleave_multi_vec(&samples, channels)?;
AudioSamples::new_multi_channel_from_vec::<O>(deinterleaved, channels, sample_rate)
}
#[inline]
pub fn from_interleaved_slice(
samples: &'a NonEmptySlice<T>,
channels: ChannelCount,
sample_rate: SampleRate,
) -> AudioSampleResult<Self> {
if channels == channels!(1) {
return Ok(AudioSamples::new_mono_from_slice(samples, sample_rate));
}
AudioSamples::new_multi_channel_from_slice(samples, channels, sample_rate)
}
#[inline]
pub fn from_channels<O>(
channels: NonEmptyVec<NonEmptyVec<O>>,
sample_rate: SampleRate,
) -> AudioSampleResult<AudioSamples<'static, T>>
where
T: ConvertFrom<O>,
O: StandardSample,
{
let num_channels = channels.len().get();
let samples_per_channel = channels[0].len().get();
for (idx, ch) in channels.iter().enumerate() {
if ch.len().get() != samples_per_channel {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
format!(
"Channel {} has {} samples, but channel 0 has {} samples",
idx,
ch.len(),
samples_per_channel
),
)));
}
}
if num_channels == 1 {
return Ok(AudioSamples::from_mono_vec(
channels[0].clone(),
sample_rate,
));
}
let flat: NonEmptyVec<T> = channels
.into_non_empty_iter()
.flatten()
.map(ConvertTo::convert_to)
.collect_non_empty();
let arr = Array2::from_shape_vec((num_channels, samples_per_channel), flat.to_vec())
.map_err(|e| {
AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
format!("Failed to create multi-channel array: {e}"),
))
})?;
Ok(AudioSamples {
data: AudioData::Multi(MultiData::from_owned(arr)?),
sample_rate,
})
}
#[inline]
pub fn from_mono_channels<O>(
channels: NonEmptyVec<AudioSamples<'_, O>>,
) -> AudioSampleResult<AudioSamples<'static, T>>
where
O: StandardSample + ConvertFrom<T> + ConvertTo<O> + ConvertFrom<O>,
T: ConvertFrom<O>,
{
let sample_rate = channels[0].sample_rate();
let samples_per_channel = channels[0].samples_per_channel();
for (idx, ch) in channels.iter().enumerate() {
if !ch.is_mono() {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
format!(
"Channel {} is not mono (has {} channels)",
idx,
ch.num_channels()
),
)));
}
if ch.sample_rate() != sample_rate {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
format!(
"Channel {} has sample rate {}, but channel 0 has sample rate {}",
idx,
ch.sample_rate(),
sample_rate
),
)));
}
if ch.samples_per_channel() != samples_per_channel {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
format!(
"Channel {} has {} samples, but channel 0 has {} samples",
idx,
ch.samples_per_channel(),
samples_per_channel
),
)));
}
}
let channel_data: NonEmptyVec<NonEmptyVec<O>> = channels
.into_non_empty_iter()
.map(|ch| match ch.data {
AudioData::Mono(m) => unsafe { NonEmptyVec::new_unchecked(m.to_vec()) },
AudioData::Multi(_) => unreachable!("Already validated as mono"),
})
.collect_non_empty();
AudioSamples::from_channels(channel_data, sample_rate)
}
#[inline]
pub fn from_iter<I>(iter: I, sample_rate: SampleRate) -> AudioSamples<'static, T>
where
I: IntoNonEmptyIterator<Item = T>,
NonEmptyVec<T>: non_empty_iter::FromNonEmptyIterator<T>,
{
let ne_iter = iter.into_non_empty_iter();
let ne_vec: NonEmptyVec<T> = ne_iter.collect_non_empty();
AudioSamples::from_mono_vec::<T>(ne_vec, sample_rate)
}
#[inline]
pub fn from_iter_with_channels<I>(
iter: I,
channels: ChannelCount,
sample_rate: SampleRate,
) -> AudioSamples<'static, T>
where
I: IntoNonEmptyIterator<Item = T>,
NonEmptyVec<T>: non_empty_iter::FromNonEmptyIterator<T>,
{
let ne_iter = iter.into_non_empty_iter();
let ne_vec: NonEmptyVec<T> = ne_iter.collect_non_empty();
AudioSamples::from_vec_with_channels(ne_vec, channels, sample_rate)
.expect("Collected samples should be valid for the given channel count")
}
#[inline]
#[must_use]
pub fn nyquist(&self) -> f64 {
f64::from(self.sample_rate.get()) / 2.0
}
#[inline]
#[must_use]
pub fn powf(&self, exponent: f64, modulo: Option<T>) -> Self {
let new_data = match &self.data {
AudioData::Mono(mono) => {
let powered = mono.mapv(|sample: T| {
let base: f64 = sample.cast_into();
let result = base.powf(exponent);
let powered_sample: T = T::cast_from(result);
modulo.map_or(powered_sample, |mod_val| powered_sample % mod_val)
});
unsafe { AudioData::Mono(MonoData::from_owned_unchecked(powered)) }
}
AudioData::Multi(multi) => {
let powered = multi.mapv(|sample| {
let base: f64 = sample.cast_into();
let result = base.powf(exponent);
let powered_sample: T = T::cast_from(result);
modulo.map_or(powered_sample, |mod_val| powered_sample % mod_val)
});
unsafe { AudioData::Multi(MultiData::from_owned_unchecked(powered)) }
}
};
AudioSamples {
data: new_data,
sample_rate: self.sample_rate,
}
}
#[inline]
pub fn with_window_mut<R>(
&mut self,
start: usize,
len: NonZeroUsize,
f: impl FnOnce(WindowMut<'_, T>) -> R,
) -> Option<R> {
let len = len.get();
let total = self.samples_per_channel().get();
if start >= total {
return None;
}
let end = (start + len).min(total);
let out = match &mut self.data {
AudioData::Mono(mono_data) => {
let view = mono_data.slice_mut(s![start..end]);
f(WindowMut::Mono(view))
}
AudioData::Multi(multi_data) => {
let view = multi_data.slice_mut(s![.., start..end]);
f(WindowMut::Multi(view))
}
};
Some(out)
}
}
#[derive(Debug)]
pub enum WindowMut<'a, T> {
Mono(ArrayViewMut1<'a, T>),
Multi(ArrayViewMut2<'a, T>),
}
#[derive(Clone, Copy, Debug)]
#[allow(unused)]
pub enum TimeAxis {
SamplesAreAxis1,
SamplesAreAxis0,
}
impl<T> Clone for AudioSamples<'_, T>
where
T: StandardSample,
{
#[inline]
fn clone(&self) -> Self {
AudioSamples {
data: self.data.clone(),
sample_rate: self.sample_rate,
}
}
}
impl<T> TryInto<Array1<T>> for AudioSamples<'_, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_into(self) -> Result<Array1<T>, Self::Error> {
match self.data {
AudioData::Mono(mono) => Ok(mono.as_view().to_owned()),
AudioData::Multi(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"audio_type",
"Cannot convert multi-channel AudioSamples into Array1",
))),
}
}
}
impl<T> TryInto<Array2<T>> for AudioSamples<'_, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_into(self) -> Result<Array2<T>, Self::Error> {
match self.data {
AudioData::Mono(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"audio_type",
"Cannot convert mono AudioSamples into Array2",
))),
AudioData::Multi(multi) => Ok(multi.as_view().to_owned()),
}
}
}
impl<T> TryFrom<(ChannelCount, SampleRate, NonEmptyVec<T>)> for AudioSamples<'static, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(
(channels, sample_rate, samples): (ChannelCount, SampleRate, NonEmptyVec<T>),
) -> Result<Self, Self::Error> {
if channels == channels!(1) {
Ok(AudioSamples::from_mono_vec(samples, sample_rate))
} else {
AudioSamples::from_vec_with_channels(samples, channels, sample_rate)
}
}
}
impl<T> From<(NonEmptyVec<T>, SampleRate)> for AudioSamples<'static, T>
where
T: StandardSample,
{
#[inline]
fn from((samples, sample_rate): (NonEmptyVec<T>, SampleRate)) -> Self {
AudioSamples::from_mono_vec(samples, sample_rate)
}
}
impl<T> From<(SampleRate, NonEmptyVec<T>)> for AudioSamples<'static, T>
where
T: StandardSample,
{
#[inline]
fn from((sample_rate, samples): (SampleRate, NonEmptyVec<T>)) -> Self {
AudioSamples::from_mono_vec(samples, sample_rate)
}
}
impl<'a, T> From<(&'a NonEmptySlice<T>, SampleRate)> for AudioSamples<'a, T>
where
T: StandardSample,
{
#[inline]
fn from((samples, sample_rate): (&'a NonEmptySlice<T>, SampleRate)) -> Self {
AudioSamples::new_mono_from_slice(samples, sample_rate)
}
}
impl<T> Index<usize> for AudioSamples<'_, T>
where
T: StandardSample,
{
type Output = T;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
match &self.data {
AudioData::Mono(arr) => &arr[index],
AudioData::Multi(_) => {
panic!(
"Cannot use single index on multi-channel audio. Use (channel, sample) indexing instead."
);
}
}
}
}
impl<T> IndexMut<usize> for AudioSamples<'_, T>
where
T: StandardSample,
{
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
match &mut self.data {
AudioData::Mono(arr) => &mut arr[index],
AudioData::Multi(_) => {
panic!(
"Cannot use single index on multi-channel audio. Use (channel, sample) indexing instead."
);
}
}
}
}
impl<T> Index<(usize, usize)> for AudioSamples<'_, T>
where
T: StandardSample,
{
type Output = T;
#[inline]
fn index(&self, index: (usize, usize)) -> &Self::Output {
let (channel, sample) = index;
match &self.data {
AudioData::Mono(arr) => {
assert!(
channel == 0,
"Channel index {channel} out of bounds for mono audio (only channel 0 exists)"
);
&arr[sample]
}
AudioData::Multi(arr) => &arr[(channel, sample)],
}
}
}
impl<T> IndexMut<(usize, usize)> for AudioSamples<'_, T>
where
T: StandardSample,
{
#[inline]
fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
let (channel, sample) = index;
match &mut self.data {
AudioData::Mono(arr) => {
assert!(
channel == 0,
"Channel index {channel} out of bounds for mono audio (only channel 0 exists)"
);
&mut arr[sample]
}
AudioData::Multi(arr) => &mut arr[(channel, sample)],
}
}
}
impl<T> Index<[usize; 2]> for AudioSamples<'_, T>
where
T: StandardSample,
{
type Output = T;
#[inline]
fn index(&self, index: [usize; 2]) -> &Self::Output {
let channel = index[0];
let sample = index[1];
match &self.data {
AudioData::Mono(arr) => {
assert!(
channel == 0,
"Channel index {channel} out of bounds for mono audio (only channel 0 exists)"
);
&arr[sample]
}
AudioData::Multi(arr) => &arr[(channel, sample)],
}
}
}
impl<T> IntoIterator for AudioSamples<'_, T>
where
T: StandardSample,
{
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.to_interleaved_vec().into_iter()
}
}
macro_rules! impl_audio_samples_ops {
($(
$trait:ident, $method:ident,
$assign_trait:ident, $assign_method:ident,
$op:tt, $assign_op:tt
);+ $(;)?) => {
$(
impl<T> std::ops::$trait<Self> for AudioSamples<'_, T>
where
T: StandardSample,
{
type Output = Self;
#[inline]
fn $method(self, rhs: Self) -> Self::Output {
if self.sample_rate != rhs.sample_rate {
panic!(
concat!(
"Cannot ", stringify!($method),
" audio with different sample rates: {} vs {}"
),
self.sample_rate, rhs.sample_rate
);
}
Self {
data: self.data $op rhs.data,
sample_rate: self.sample_rate,
}
}
}
impl<T> std::ops::$trait<T> for AudioSamples<'_, T>
where
T: StandardSample,
{
type Output = Self;
#[inline]
fn $method(self, rhs: T) -> Self::Output {
Self {
data: self.data $op rhs,
sample_rate: self.sample_rate,
}
}
}
impl<T> std::ops::$assign_trait<Self> for AudioSamples<'_, T>
where
T: StandardSample,
{
#[inline]
fn $assign_method(&mut self, rhs: Self) {
if self.sample_rate != rhs.sample_rate {
panic!(
concat!(
"Cannot ", stringify!($assign_method),
" audio with different sample rates: {} vs {}"
),
self.sample_rate, rhs.sample_rate
);
}
self.data $assign_op rhs.data;
}
}
impl<T> std::ops::$assign_trait<T> for AudioSamples<'_, T>
where
T: StandardSample,
{
#[inline]
fn $assign_method(&mut self, rhs: T) {
self.data $assign_op rhs;
}
}
)+
};
}
impl_audio_samples_ops!(
Add, add, AddAssign, add_assign, +, +=;
Sub, sub, SubAssign, sub_assign, -, -=;
Mul, mul, MulAssign, mul_assign, *, *=;
Div, div, DivAssign, div_assign, /, /=;
);
impl<T> Neg for AudioSamples<'_, T>
where
T: StandardSample + Neg<Output = T> + ConvertTo<T> + ConvertFrom<T>,
{
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Self {
data: -self.data,
sample_rate: self.sample_rate,
}
}
}
#[cfg(feature = "resampling")]
unsafe impl<'a, T> Adapter<'a, T> for AudioSamples<'a, T>
where
T: StandardSample,
{
#[inline]
unsafe fn read_sample_unchecked(&self, channel: usize, frame: usize) -> T {
self[(channel, frame)]
}
#[inline]
fn channels(&self) -> usize {
self.num_channels().get() as usize
}
#[inline]
fn frames(&self) -> usize {
self.total_frames().get() as usize
}
}
#[non_exhaustive]
pub struct StereoAudioSamples<'a, T>(pub AudioSamples<'a, T>)
where
T: StandardSample;
impl<'a, T> StereoAudioSamples<'a, T>
where
T: StandardSample,
{
#[inline]
pub fn new(stereo_data: AudioData<'a, T>, sample_rate: SampleRate) -> AudioSampleResult<Self> {
if stereo_data.is_mono() {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
"Expected stereo data (2 channels), got mono (1 channel).",
)));
}
if stereo_data.num_channels() != channels!(2) {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
format!(
"Expected stereo data (2 channels), got {} channels.",
stereo_data.num_channels()
),
)));
}
Ok(Self(AudioSamples::new(stereo_data, sample_rate)))
}
#[inline]
pub fn with_channels<R, F>(&self, f: F) -> AudioSampleResult<R>
where
F: FnOnce(AudioSamples<'_, T>, AudioSamples<'_, T>) -> AudioSampleResult<R>,
{
match &self.0.data {
AudioData::Multi(multi_data) => {
let left_view = multi_data.index_axis(Axis(0), 0);
let left_data = unsafe { MonoData::from_view_unchecked(left_view) };
let left_audio =
AudioSamples::new(AudioData::Mono(left_data), self.0.sample_rate());
let right_view = multi_data.index_axis(Axis(0), 1);
let right_data = unsafe { MonoData::from_view_unchecked(right_view) };
let right_audio =
AudioSamples::new(AudioData::Mono(right_data), self.0.sample_rate());
f(left_audio, right_audio)
}
AudioData::Mono(_) => {
unreachable!("StereoAudioSamples guarantees exactly 2 channels")
}
}
}
}
impl<'a, T> Deref for StereoAudioSamples<'a, T>
where
T: StandardSample,
{
type Target = AudioSamples<'a, T>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for StereoAudioSamples<'_, T>
where
T: StandardSample,
{
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a, T> AsRef<AudioSamples<'a, T>> for StereoAudioSamples<'a, T>
where
T: StandardSample,
{
#[inline]
fn as_ref(&self) -> &AudioSamples<'a, T> {
&self.0
}
}
impl<'a, T> AsMut<AudioSamples<'a, T>> for StereoAudioSamples<'a, T>
where
T: StandardSample,
{
#[inline]
fn as_mut(&mut self) -> &mut AudioSamples<'a, T> {
&mut self.0
}
}
impl<T> TryFrom<AudioSamples<'static, T>> for StereoAudioSamples<'static, T>
where
T: StandardSample,
{
type Error = AudioSampleError;
#[inline]
fn try_from(audio: AudioSamples<'static, T>) -> Result<Self, Self::Error> {
if audio.num_channels() != channels!(2) {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
format!(
"Expected exactly 2 channels for stereo audio, but found {}",
audio.num_channels()
),
)));
}
match audio.data {
AudioData::Multi(_) => Ok(StereoAudioSamples(audio)),
AudioData::Mono(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"audio_format",
"Cannot convert mono audio to stereo",
))),
}
}
}
impl<T> From<StereoAudioSamples<'static, T>> for AudioSamples<'static, T>
where
T: StandardSample,
{
#[inline]
fn from(stereo: StereoAudioSamples<'static, T>) -> Self {
stereo.0
}
}
#[cfg(feature = "fixed-size-audio")]
#[non_exhaustive]
pub struct FixedSizeAudioSamples<T, const N: usize>
where
T: StandardSample,
{
pub samples: AudioSamples<'static, T>,
}
#[cfg(feature = "fixed-size-audio")]
impl<T, const N: usize> FixedSizeAudioSamples<T, N>
where
T: StandardSample,
{
#[inline]
pub fn from_1d<D: AsRef<NonEmptySlice<T>>>(
data: D,
sample_rate: SampleRate,
) -> AudioSampleResult<Self> {
let samples = AudioSamples::from_mono_vec(data.as_ref().to_non_empty_vec(), sample_rate);
Ok(Self { samples })
}
#[inline]
#[must_use]
pub const fn capacity(&self) -> usize {
N
}
#[inline]
pub unsafe fn swap_samples(&mut self, other: &mut Self) {
debug_assert_eq!(
self.samples.sample_rate(),
other.samples.sample_rate(),
"Sample rates must match for swap"
);
debug_assert_eq!(
self.samples.num_channels(),
other.samples.num_channels(),
"Number of channels must match for swap"
);
std::mem::swap(&mut self.samples, &mut other.samples);
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{ArrayBase, array};
use non_empty_slice::non_empty_vec;
#[test]
fn test_new_mono_audio_samples() {
let data: ArrayBase<ndarray::OwnedRepr<f32>, ndarray::Dim<[usize; 1]>> =
array![1.0f32, 2.0, 3.0, 4.0, 5.0];
let audio: AudioSamples<f32> = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
assert_eq!(audio.sample_rate(), sample_rate!(44100));
assert_eq!(audio.num_channels(), channels!(1));
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(5).unwrap());
assert!(audio.is_mono());
assert!(!audio.is_multi_channel());
}
#[test]
fn test_new_multi_channel_audio_samples() {
let data = array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]]; let audio = AudioSamples::new_multi_channel(data, sample_rate!(48000)).unwrap();
assert_eq!(audio.sample_rate(), sample_rate!(48000));
assert_eq!(audio.num_channels(), channels!(2));
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
assert_eq!(audio.total_samples(), NonZeroUsize::new(6).unwrap());
assert!(!audio.is_mono());
assert!(audio.is_multi_channel());
}
#[test]
fn test_zeros_construction() {
let mono_audio =
AudioSamples::<f32>::zeros_mono(NonZeroUsize::new(100).unwrap(), sample_rate!(44100));
assert_eq!(mono_audio.num_channels(), channels!(1));
assert_eq!(
mono_audio.samples_per_channel(),
NonZeroUsize::new(100).unwrap()
);
assert_eq!(mono_audio.sample_rate(), sample_rate!(44100));
let multi_audio = AudioSamples::<f32>::zeros_multi(
channels!(2),
NonZeroUsize::new(50).unwrap(),
sample_rate!(48000),
);
assert_eq!(multi_audio.num_channels(), channels!(2));
assert_eq!(
multi_audio.samples_per_channel(),
NonZeroUsize::new(50).unwrap()
);
assert_eq!(multi_audio.total_samples(), NonZeroUsize::new(100).unwrap());
assert_eq!(multi_audio.sample_rate(), sample_rate!(48000));
}
#[test]
fn test_duration_seconds() {
let audio: AudioSamples<'_, f32> =
AudioSamples::<f32>::zeros_mono(NonZeroUsize::new(44100).unwrap(), sample_rate!(44100));
assert!((audio.duration_seconds() - 1.0).abs() < 1e-6);
let audio2: AudioSamples<'_, f32> = AudioSamples::<f32>::zeros_multi(
channels!(2),
NonZeroUsize::new(22050).unwrap(),
sample_rate!(44100),
);
assert!((audio2.duration_seconds() - 0.5).abs() < 1e-6);
}
#[test]
fn test_apply_simple() {
let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
let mut audio: AudioSamples<f32> =
AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
audio.apply(|sample| sample * 2.0);
let expected = array![2.0f32, 4.0, 6.0, 8.0, 10.0];
let expected = AudioSamples::new_mono(expected, sample_rate!(44100)).unwrap();
assert_eq!(
audio, expected,
"Applied audio samples do not match expected values"
);
}
#[test]
fn test_apply_channels() {
let data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio: AudioSamples<f32> =
AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
{
audio.apply_to_channels(&[0, 1], |sample| sample * sample);
}
let expected = array![[1.0, 4.0], [9.0, 16.0]];
let expected = AudioSamples::new_multi_channel(expected, sample_rate!(44100)).unwrap();
assert_eq!(
audio, expected,
"Applied multi-channel audio samples do not match expected values"
);
}
#[test]
fn test_map_functional() {
let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
let new_audio = audio.map(|sample| sample * 0.5);
let original_expected = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
let original_expected =
AudioSamples::new_mono(original_expected, sample_rate!(44100)).unwrap();
assert_eq!(
audio, original_expected,
"Original audio samples should remain unchanged"
);
let new_expected = array![0.5f32, 1.0, 1.5, 2.0, 2.5];
let new_expected = AudioSamples::new_mono(new_expected, sample_rate!(44100)).unwrap();
assert_eq!(
new_audio, new_expected,
"New audio should contain transformed samples"
);
}
#[test]
fn test_apply_indexed() {
let data = array![1.0f32, 1.0, 1.0, 1.0, 1.0];
let mut audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
audio.apply_with_index(|index, sample| sample * (index + 1) as f32);
let expected = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
let expected = AudioSamples::new_mono(expected, sample_rate!(44100)).unwrap();
assert_eq!(
audio, expected,
"Indexed applied audio samples do not match expected values"
);
}
#[test]
fn test_index_mono_single() {
let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
assert_eq!(audio[0], 1.0);
assert_eq!(audio[2], 3.0);
assert_eq!(audio[4], 5.0);
}
#[test]
#[should_panic(expected = "Cannot use single index on multi-channel audio")]
fn test_index_mono_single_on_multi_panics() {
let data = array![[1.0f32, 2.0], [3.0, 4.0]];
let audio = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
let _ = audio[0]; }
#[test]
fn test_index_tuple() {
let mono_data = array![1.0f32, 2.0, 3.0, 4.0];
let mono_audio = AudioSamples::new_mono(mono_data, sample_rate!(44100)).unwrap();
assert_eq!(mono_audio[(0, 1)], 2.0);
assert_eq!(mono_audio[(0, 3)], 4.0);
let stereo_data = array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]];
let stereo_audio =
AudioSamples::new_multi_channel(stereo_data, sample_rate!(44100)).unwrap();
assert_eq!(stereo_audio[(0, 0)], 1.0);
assert_eq!(stereo_audio[(0, 2)], 3.0);
assert_eq!(stereo_audio[(1, 0)], 4.0);
assert_eq!(stereo_audio[(1, 2)], 6.0);
}
#[test]
#[should_panic(expected = "Channel index 1 out of bounds for mono audio")]
fn test_index_tuple_invalid_channel_mono() {
let data = array![1.0f32, 2.0, 3.0];
let audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
let _ = audio[(1, 0)]; }
#[test]
fn test_index_mut_mono() {
let data = array![1.0f32, 2.0, 3.0, 4.0, 5.0];
let mut audio = AudioSamples::new_mono(data, sample_rate!(44100)).unwrap();
audio[2] = 10.0;
assert_eq!(audio[2], 10.0);
assert_eq!(
audio,
AudioSamples::new_mono(array![1.0f32, 2.0, 10.0, 4.0, 5.0], sample_rate!(44100))
.unwrap(),
"Mutably indexed mono audio samples do not match expected values"
);
}
#[test]
fn test_index_mut_tuple() {
let data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
audio[(1, 0)] = 10.0;
assert_eq!(audio[(1, 0)], 10.0);
let expected = array![[1.0f32, 2.0], [10.0, 4.0]];
let expected = AudioSamples::new_multi_channel(expected, sample_rate!(44100)).unwrap();
assert_eq!(
audio, expected,
"Mutably indexed audio samples do not match expected values"
);
}
#[test]
fn test_replace_data_mono_success() {
let initial_data = array![1.0f32, 2.0, 3.0];
let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
let new_data = AudioData::try_from(array![4.0f32, 5.0, 6.0, 7.0, 8.0])
.unwrap()
.into_owned();
assert!(audio.replace_data(new_data).is_ok());
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(5).unwrap());
assert_eq!(audio.num_channels(), channels!(1));
assert_eq!(audio.sample_rate(), sample_rate!(44100)); }
#[test]
fn test_replace_data_multi_success() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
let new_data = AudioData::try_from(array![[5.0f32, 6.0, 7.0], [8.0, 9.0, 10.0]]).unwrap();
assert!(audio.replace_data(new_data).is_ok());
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
assert_eq!(audio.num_channels(), channels!(2));
assert_eq!(audio.sample_rate(), sample_rate!(44100)); }
#[test]
fn test_replace_data_channel_count_mismatch() {
let initial_data = array![1.0f32, 2.0, 3.0];
let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
let new_data = AudioData::try_from(array![[4.0f32, 5.0], [6.0, 7.0]]).unwrap();
let result = audio.replace_data(new_data);
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("Channel count mismatch"));
}
}
#[test]
fn test_replace_data_layout_type_mismatch() {
let initial_data = array![1.0f32, 2.0, 3.0];
let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
let new_data = AudioData::try_from(array![[4.0f32, 5.0, 6.0]]).unwrap();
let result = audio.replace_data(new_data);
assert!(result.is_err());
if let Err(e) = result {
assert!(
e.to_string()
.contains("Cannot replace mono audio data with multi-channel data")
);
}
}
#[test]
fn test_replace_with_mono_success() {
let initial_data = array![1.0f32, 2.0];
let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
assert!(audio.replace_with_mono(array![3.0f32, 4.0, 5.0]).is_ok());
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
assert_eq!(audio.num_channels(), channels!(1));
assert_eq!(audio.sample_rate(), sample_rate!(44100));
assert_eq!(audio[0], 3.0);
assert_eq!(audio[1], 4.0);
assert_eq!(audio[2], 5.0);
}
#[test]
fn test_replace_with_mono_on_multi_fails() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
let result = audio.replace_with_mono(array![5.0f32, 6.0, 7.0]);
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("Cannot replace multi-channel audio"));
}
}
#[test]
fn test_replace_with_multi_success() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
assert!(
audio
.replace_with_multi(array![[5.0f32, 6.0, 7.0], [8.0, 9.0, 10.0]])
.is_ok()
);
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
assert_eq!(audio.num_channels(), channels!(2));
assert_eq!(audio.sample_rate(), sample_rate!(44100));
assert_eq!(audio[(0, 0)], 5.0);
assert_eq!(audio[(0, 2)], 7.0);
assert_eq!(audio[(1, 0)], 8.0);
assert_eq!(audio[(1, 2)], 10.0);
}
#[test]
fn test_replace_with_multi_on_mono_fails() {
let initial_data = array![1.0f32, 2.0, 3.0];
let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
let result = audio.replace_with_multi(array![[4.0f32, 5.0], [6.0, 7.0]]);
assert!(result.is_err());
if let Err(e) = result {
assert!(
e.to_string()
.contains("Cannot replace mono audio with multi-channel data")
);
}
}
#[test]
fn test_replace_with_multi_channel_count_mismatch() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
let result = audio.replace_with_multi(array![[5.0f32, 6.0], [7.0, 8.0], [9.0, 10.0]]);
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("Channel count mismatch"));
}
}
#[test]
fn test_replace_with_vec_mono_success() {
let initial_data = array![1.0f32, 2.0];
let mut audio = AudioSamples::new_mono(initial_data, sample_rate!(44100)).unwrap();
assert!(
audio
.replace_with_vec(&non_empty_vec![3.0f32, 4.0, 5.0, 6.0])
.is_ok()
);
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(4).unwrap());
assert_eq!(audio.num_channels(), channels!(1));
assert_eq!(audio.sample_rate(), sample_rate!(44100));
}
#[test]
fn test_replace_with_vec_multi_success() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
assert!(
audio
.replace_with_vec(&non_empty_vec![5.0f32, 6.0, 7.0, 8.0, 9.0, 10.0])
.is_ok()
);
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(3).unwrap());
assert_eq!(audio.num_channels(), channels!(2));
assert_eq!(audio.sample_rate(), sample_rate!(44100));
assert_eq!(audio[(0, 0)], 5.0); assert_eq!(audio[(0, 1)], 6.0); assert_eq!(audio[(1, 0)], 8.0); }
#[test]
fn test_replace_with_vec_not_divisible_fails() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
let result = audio.replace_with_vec(&non_empty_vec![5.0f32, 6.0, 7.0, 8.0, 9.0]);
assert!(result.is_err());
if let Err(e) = result {
assert!(
e.to_string()
.contains("Sample count 5 is not divisible by channel count 2")
);
}
}
#[test]
fn test_replace_with_vec_channels_success() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
assert!(
audio
.replace_with_vec_channels(&non_empty_vec![5.0f32, 6.0, 7.0, 8.0], channels!(2))
.is_ok()
);
assert_eq!(audio.samples_per_channel(), NonZeroUsize::new(2).unwrap());
assert_eq!(audio.num_channels(), channels!(2));
assert_eq!(audio.sample_rate(), sample_rate!(44100));
}
#[test]
fn test_replace_with_vec_channels_sample_count_mismatch() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
let result =
audio.replace_with_vec_channels(&non_empty_vec![5.0f32, 6.0, 7.0], channels!(2));
assert!(result.is_err());
if let Err(e) = result {
assert!(
e.to_string()
.contains("Sample count 3 is not divisible by expected channel count 2")
);
}
}
#[test]
fn test_replace_with_vec_channels_audio_channel_mismatch() {
let initial_data = array![[1.0f32, 2.0], [3.0, 4.0]];
let mut audio = AudioSamples::new_multi_channel(initial_data, sample_rate!(44100)).unwrap();
let result =
audio.replace_with_vec_channels(&non_empty_vec![5.0f32, 6.0, 7.0], channels!(3));
assert!(result.is_err());
if let Err(e) = result {
assert!(
e.to_string()
.contains("Current audio has 2 channels, but expected 3 channels")
);
}
}
#[test]
fn test_user_workflow_with_channels_validation() {
let mut audio =
AudioSamples::new_multi_channel(array![[0.0f32, 0.0], [0.0, 0.0]], sample_rate!(44100))
.unwrap();
let converted_samples: NonEmptyVec<f32> = non_empty_vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let num_channels = channels!(2);
assert!(
converted_samples
.len()
.get()
.is_multiple_of(num_channels.get() as usize)
);
assert!(
audio
.replace_with_vec_channels(&converted_samples, num_channels)
.is_err(),
"audio has 2 channels with 2 samples per channel, but converted samples have 2 channels and 3 samples per channel (6/2)"
);
}
#[test]
fn test_metadata_preservation_across_replacements() {
let mut audio = AudioSamples::new_mono(array![1.0f32, 2.0], sample_rate!(48000)).unwrap();
let original_rate = audio.sample_rate();
assert!(audio.replace_with_mono(array![3.0f32, 4.0, 5.0]).is_ok());
assert_eq!(audio.sample_rate(), original_rate);
assert!(
audio
.replace_with_vec(&non_empty_vec![6.0f32, 7.0, 8.0, 9.0])
.is_ok()
);
assert_eq!(audio.sample_rate(), original_rate);
let new_data = AudioData::try_from(array![10.0f32, 11.0])
.unwrap()
.into_owned();
assert!(audio.replace_data(new_data).is_ok());
assert_eq!(audio.sample_rate(), original_rate);
}
}