use crate::{
AudioChannelOps, AudioData, AudioSample, AudioSampleError, AudioSampleResult, AudioSamples,
CastFrom, LayoutError, ParameterError, StandardSample,
operations::types::{MonoConversionMethod, StereoConversionMethod},
repr::MonoData,
};
use ndarray::{Array1, Array2, Axis};
use non_empty_slice::NonEmptySlice;
use num_traits::FloatConst;
impl<T> AudioChannelOps for AudioSamples<'_, T>
where
T: StandardSample,
{
fn to_mono(
&self,
method: MonoConversionMethod,
) -> AudioSampleResult<AudioSamples<'static, Self::Sample>> {
if self.num_channels().get() == 1 {
return Ok(AudioSamples::from_borrowed(
self.data().clone().into_owned(),
self.sample_rate(),
));
}
match method {
MonoConversionMethod::Average => match self.data() {
AudioData::Mono(_) => {
unreachable!("We check for mono at the start of the function")
}
AudioData::Multi(data) => {
if data.dim().0.get() == 2 {
let left = data.index_axis(Axis(0), 0);
let right = data.index_axis(Axis(0), 1);
let n = left.len();
let mut mono_vec: Vec<T> = Vec::with_capacity(n);
let dst = mono_vec.as_mut_ptr();
if let (Some(ls), Some(rs)) = (left.as_slice(), right.as_slice()) {
for i in 0..n {
let lf: f32 = ls[i].cast_into();
let rf: f32 = rs[i].cast_into();
let avg: f64 = ((lf + rf) * 0.5f32) as f64;
unsafe {
dst.add(i).write(T::cast_from(avg));
}
}
} else {
for (i, (&l, &r)) in left.iter().zip(right.iter()).enumerate() {
let lf: f32 = l.cast_into();
let rf: f32 = r.cast_into();
let avg: f64 = ((lf + rf) * 0.5f32) as f64;
unsafe {
dst.add(i).write(T::cast_from(avg));
}
}
}
unsafe {
mono_vec.set_len(n);
}
let mono = Array1::from(mono_vec);
Ok(AudioSamples::new_mono(mono, self.sample_rate())?)
} else {
let mono_data = data.mean_axis(Axis(0));
Ok(AudioSamples::new_mono(mono_data, self.sample_rate())?)
}
}
},
MonoConversionMethod::Left => match self.data() {
AudioData::Mono(_) => {
unreachable!("We check for mono at the start of the function")
}
AudioData::Multi(data) => {
let left_channel = data.index_axis(Axis(0), 0).to_owned();
Ok(AudioSamples::new_mono(left_channel, self.sample_rate())?)
}
},
MonoConversionMethod::Right => match self.data() {
AudioData::Mono(_) => {
unreachable!("We check for mono at the start of the function")
}
AudioData::Multi(data) => {
let right_channel = data.index_axis(Axis(0), 1).to_owned();
Ok(AudioSamples::new_mono(right_channel, self.sample_rate())?)
}
},
MonoConversionMethod::Weighted(weights) => {
match self.data() {
AudioData::Mono(_) => Ok(self.clone().into_owned()),
AudioData::Multi(multi) => {
if weights.len() != multi.nrows().get() {
return Err(AudioSampleError::Parameter(
ParameterError::invalid_value(
"weights",
format!(
"Weight count ({}) doesn't match channel count ({})",
weights.len(),
multi.nrows()
),
),
));
}
let samples_per_channel = multi.ncols().get();
let mut mono_samples = vec![T::default(); samples_per_channel];
for sample_idx in 0..samples_per_channel {
let mut weighted_sum = 0.0;
for (channel_idx, &weight) in weights.iter().enumerate() {
let sample_value: f64 =
multi[(channel_idx, sample_idx)].convert_to();
weighted_sum += sample_value * weight;
}
mono_samples[sample_idx] =
<T as CastFrom<f64>>::cast_from(weighted_sum);
}
Ok(AudioSamples::new_mono(
Array1::from(mono_samples),
self.sample_rate(),
)?)
}
}
}
MonoConversionMethod::Center => {
match self.data() {
AudioData::Mono(_) => Ok(self.clone().into_owned()),
AudioData::Multi(multi) => {
let num_channels = multi.nrows().get();
let samples_per_channel = multi.ncols().get();
let mono_samples = if num_channels >= 6 {
(0..samples_per_channel).map(|i| multi[(2, i)]).collect()
} else if num_channels == 2 {
let two: f64 = 2.0;
let mut mono_samples = vec![T::default(); samples_per_channel];
for sample_idx in 0..samples_per_channel {
let left: f64 = multi[(0, sample_idx)].convert_to();
let right: f64 = multi[(1, sample_idx)].convert_to();
let avg = (left + right) / two;
mono_samples[sample_idx] = <T as CastFrom<f64>>::cast_from(avg);
}
mono_samples
} else {
let mut mono_samples = vec![T::default(); samples_per_channel];
for sample_idx in 0..samples_per_channel {
let mut sum = 0.0;
for channel_idx in 0..num_channels {
let sample_value: f64 =
multi[(channel_idx, sample_idx)].convert_to();
sum += sample_value;
}
let avg = sum / num_channels as f64;
mono_samples[sample_idx] = <T as CastFrom<f64>>::cast_from(avg);
}
mono_samples
};
Ok(AudioSamples::new_mono(
Array1::from(mono_samples),
self.sample_rate(),
)?)
}
}
}
}
}
fn to_mono_in_place(&mut self, method: MonoConversionMethod) -> AudioSampleResult<()> {
*self = self.to_mono(method)?.into_owned();
Ok(())
}
fn to_stereo(
&self,
method: StereoConversionMethod,
) -> AudioSampleResult<AudioSamples<'static, T>> {
match method {
StereoConversionMethod::Duplicate => match self.data() {
AudioData::Mono(mono_data) => {
let stereo_data =
ndarray::stack(Axis(0), &[mono_data.view(), mono_data.view()]).map_err(
|e| AudioSampleError::Layout(LayoutError::shape_error("to_stereo", e)),
)?;
let stereo_data = AudioData::try_from(stereo_data)?;
Ok(AudioSamples::new(stereo_data, self.sample_rate()))
}
AudioData::Multi(_) => Ok(self.clone().into_owned()),
},
StereoConversionMethod::Pan(pan) => {
let pan = pan.clamp(-1.0, 1.0);
let pan_radians = (pan + 1.0) * f64::PI() / 4.0;
let left_gain = num_traits::Float::cos(pan_radians);
let right_gain = num_traits::Float::sin(pan_radians);
match self.data() {
AudioData::Mono(mono) => {
let samples_per_channel = mono.len().get();
let mut left_channel = vec![T::default(); samples_per_channel];
let mut right_channel = vec![T::default(); samples_per_channel];
for (i, &sample) in mono.iter().enumerate() {
let sample_f: f64 = sample.cast_into();
let left = sample_f * left_gain;
let right = sample_f * right_gain;
let left = <T as CastFrom<f64>>::cast_from(left);
let right = <T as CastFrom<f64>>::cast_from(right);
left_channel[i] = left;
right_channel[i] = right;
}
let mut stereo_matrix = Array2::<T>::zeros((2, samples_per_channel));
for (i, &sample) in left_channel.iter().enumerate() {
stereo_matrix[(0, i)] = sample;
}
for (i, &sample) in right_channel.iter().enumerate() {
stereo_matrix[(1, i)] = sample;
}
Ok(AudioSamples::new(
AudioData::Multi(stereo_matrix.try_into()?),
self.sample_rate(),
))
}
AudioData::Multi(_) => {
Ok(self.clone().into_owned())
}
}
}
StereoConversionMethod::Left => self.extract_channel(0),
StereoConversionMethod::Right => self.extract_channel(1),
}
}
fn to_stereo_in_place(&mut self, method: StereoConversionMethod) -> AudioSampleResult<()> {
*self = self.to_stereo(method)?.into_owned();
Ok(())
}
fn duplicate_to_channels(
&self,
n_channels: usize,
) -> AudioSampleResult<AudioSamples<'static, T>> {
if n_channels == 0 {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"n_channels",
"must be at least 1",
)));
}
let mono_data = match self.data() {
AudioData::Mono(data) => data.as_view().to_owned(),
AudioData::Multi(data) => {
data.index_axis(Axis(0), 0).to_owned()
}
};
if n_channels == 1 {
return AudioSamples::new_mono(mono_data, self.sample_rate());
}
let views: Vec<_> = (0..n_channels).map(|_| mono_data.view()).collect();
let multi_data: AudioData<T> = ndarray::stack(Axis(0), &views)
.map_err(|e| {
AudioSampleError::Layout(LayoutError::shape_error("duplicate_to_channels", e))
})?
.try_into()?;
Ok(AudioSamples::new(multi_data, self.sample_rate()))
}
fn duplicate_to_channels_in_place(&mut self, n_channels: usize) -> AudioSampleResult<()> {
*self = self.duplicate_to_channels(n_channels)?.into_owned();
Ok(())
}
fn extract_channel(&self, channel_index: usize) -> AudioSampleResult<AudioSamples<'static, T>> {
if channel_index >= self.num_channels().get() as usize {
return Err(AudioSampleError::Parameter(ParameterError::out_of_range(
"channel_index",
channel_index.to_string(),
"0",
(self.num_channels().get() - 1).to_string(),
"Channel index must be within available channels",
)));
}
match self.data() {
AudioData::Mono(_) => Ok(self.clone().into_owned()),
AudioData::Multi(data) => {
let channel: Array1<T> = data.index_axis(Axis(0), channel_index).to_owned();
Ok(AudioSamples::new(
AudioData::Mono(channel.try_into()?),
self.sample_rate(),
))
}
}
}
fn borrow_channel(&self, channel_index: usize) -> AudioSampleResult<AudioSamples<'_, T>> {
if channel_index >= self.num_channels().get() as usize {
return Err(AudioSampleError::Parameter(ParameterError::out_of_range(
"channel_index",
channel_index.to_string(),
"0",
(self.num_channels().get() - 1).to_string(),
"Channel index must be within available channels",
)));
}
match self.data() {
AudioData::Mono(data) => {
let channel = data.view();
Ok(AudioSamples::new(
AudioData::Mono(MonoData::from_view(channel)?),
self.sample_rate(),
))
}
AudioData::Multi(data) => {
let channel = data.index_axis(Axis(0), channel_index);
Ok(AudioSamples::new(
AudioData::Mono(MonoData::from_view(channel)?),
self.sample_rate(),
))
}
}
}
fn swap_channels_in_place(
&mut self,
channel1: usize,
channel2: usize,
) -> AudioSampleResult<()> {
let n = self.num_channels().get() as usize;
if channel1 >= n || channel2 >= n {
return Err(AudioSampleError::Parameter(ParameterError::out_of_range(
"channel_indices",
format!("{channel1}, {channel2}"),
"0",
(self.num_channels().get() - 1).to_string(),
"Channel indices must be within available channels",
)));
}
match self.data_mut() {
AudioData::Mono(_) => Ok(()), AudioData::Multi(data) => {
if channel1 != channel2 {
let mut view = data.view_mut();
let cols = view.ncols();
for c in 0..cols {
view.swap((channel1, c), (channel2, c));
}
}
Ok(())
}
}
}
fn pan_in_place(&mut self, pan_value: f64) -> AudioSampleResult<()> {
match self.data_mut() {
AudioData::Mono(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"audio_data",
"Cannot pan mono audio",
))),
AudioData::Multi(data) => {
if data.shape()[0] != 2 {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
"Panning requires stereo audio",
)));
}
{
let left = &mut data.index_axis_mut(Axis(0), 0);
let left_gain = 1.0 - pan_value.clamp(-1.0, 1.0);
left.mapv_inplace(|x| {
let x: f64 = x.cast_into();
let diff = x * left_gain;
T::cast_from(diff)
});
}
{
let right = &mut data.index_axis_mut(Axis(0), 1);
let right_gain = 1.0 + pan_value.clamp(-1.0, 1.0);
right.mapv_inplace(|x| {
let x: f64 = x.cast_into();
let diff = x * right_gain;
T::cast_from(diff)
});
}
Ok(())
}
}
}
fn balance_in_place(&mut self, balance: f64) -> AudioSampleResult<()> {
match self.data_mut() {
AudioData::Mono(_) => Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"audio_data",
"Cannot balance mono audio",
))),
AudioData::Multi(data) => {
if data.shape()[0] != 2 {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
"Balancing requires stereo audio",
)));
}
{
let left = &mut data.index_axis_mut(Axis(0), 0);
let left_gain = 1.0 - balance.clamp(-1.0, 1.0);
left.mapv_inplace(|x| {
let x: f64 = x.cast_into();
let diff = x * left_gain;
T::cast_from(diff)
});
}
{
let right = &mut data.index_axis_mut(Axis(0), 1);
let right_gain = 1.0 + balance.clamp(-1.0, 1.0);
right.mapv_inplace(|x| {
let x: f64 = x.cast_into();
let diff = x * right_gain;
T::cast_from(diff)
});
}
Ok(())
}
}
}
fn apply_to_channel_in_place<F>(
&mut self,
channel_index: usize,
func: F,
) -> AudioSampleResult<()>
where
F: FnMut(T) -> T,
{
match self.data_mut() {
AudioData::Mono(array_base) => {
array_base.mapv_inplace(func);
}
AudioData::Multi(array_base) => {
if channel_index >= array_base.shape()[0] {
return Err(AudioSampleError::Parameter(ParameterError::out_of_range(
"channel_index",
channel_index.to_string(),
"0",
(array_base.shape()[0] - 1).to_string(),
"Channel index must be within available channels",
)));
}
let mut channel = array_base.index_axis_mut(Axis(0), channel_index);
channel.mapv_inplace(func);
}
}
Ok(())
}
fn interleave_channels(
channels: &NonEmptySlice<AudioSamples<'_, T>>,
) -> AudioSampleResult<AudioSamples<'static, T>> {
#[cfg(feature = "simd")]
{
interleave_channels_simd(channels)
}
#[cfg(not(feature = "simd"))]
{
interleave_channels_base(channels)
}
}
fn deinterleave_channels(&self) -> AudioSampleResult<Vec<AudioSamples<'static, T>>> {
let num_channels = self.num_channels().get();
let mut result = Vec::with_capacity(num_channels as usize);
for ch in 0..num_channels {
let channel = self.extract_channel(ch as usize)?;
result.push(channel);
}
Ok(result)
}
}
fn interleave_channels_base<'b, T>(
channels: &NonEmptySlice<AudioSamples<'_, T>>,
) -> AudioSampleResult<AudioSamples<'b, T>>
where
T: StandardSample,
{
let num_channels = channels.len();
let samples_per_channel = channels[0].samples_per_channel().get();
for ch in channels {
if ch.samples_per_channel().get() != samples_per_channel {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"channels",
"All channels must have the same length",
)));
}
}
let mut interleaved = Vec::with_capacity(num_channels.get() * samples_per_channel);
for i in 0..samples_per_channel {
for ch in channels {
let slice = ch.data().as_slice().ok_or_else(|| {
AudioSampleError::Layout(LayoutError::NonContiguous {
operation: "channel data access".to_string(),
layout_type: "non-contiguous".to_string(),
})
})?;
interleaved.push(slice[i]);
}
}
let array =
ndarray::Array2::from_shape_vec((num_channels.get(), samples_per_channel), interleaved)?;
AudioSamples::new_multi_channel(array, channels[0].sample_rate())
}
#[cfg(feature = "simd")]
#[inline]
fn interleave_channels_simd<'b, T>(
channels: &NonEmptySlice<AudioSamples<'_, T>>,
) -> AudioSampleResult<AudioSamples<'b, T>>
where
T: StandardSample,
{
interleave_channels_base(channels)
}
#[inline]
pub fn deinterleave<T: AudioSample + 'static>(
samples: &mut [T],
num_channels: usize,
) -> AudioSampleResult<()> {
if samples.is_empty() {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"samples",
"Cannot deinterleave an empty sample buffer",
)));
}
if num_channels == 0 {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"num_channels",
"Number of channels must be > 0",
)));
}
if !samples.len().is_multiple_of(num_channels) {
return Err(AudioSampleError::Parameter(ParameterError::invalid_value(
"samples",
"Sample buffer length must be a multiple of number of channels",
)));
}
let num_frames = samples.len() / num_channels;
if num_channels == 1 {
return Ok(());
}
let mut tmp = vec![T::zero(); samples.len()];
for frame in 0..num_frames {
let frame_base = frame * num_channels;
for ch in 0..num_channels {
tmp[ch * num_frames + frame] = samples[frame_base + ch];
}
}
samples.copy_from_slice(&tmp);
Ok(())
}
#[cfg(test)]
mod tests {
use std::num::NonZeroUsize;
use super::*;
use crate::sample_rate;
use ndarray::Array1;
#[test]
fn test_duplicate_to_channels_mono_to_stereo() {
let mono = AudioSamples::new_mono(
Array1::from(vec![0.1f32, 0.2, 0.3, 0.4]),
sample_rate!(44100),
)
.unwrap();
let stereo = mono.duplicate_to_channels(2).unwrap();
assert_eq!(stereo.num_channels().get(), 2);
assert_eq!(stereo.samples_per_channel(), NonZeroUsize::new(4).unwrap());
let interleaved = stereo.to_interleaved_vec();
assert_eq!(interleaved.len().get(), 8);
for i in 0..4 {
assert_eq!(interleaved[i * 2], interleaved[i * 2 + 1]);
}
}
#[test]
fn test_duplicate_to_channels_mono_to_surround() {
let mono =
AudioSamples::new_mono(Array1::from(vec![1.0f32, 2.0, 3.0]), sample_rate!(48000))
.unwrap();
let surround = mono.duplicate_to_channels(6).unwrap();
assert_eq!(surround.num_channels().get(), 6);
assert_eq!(
surround.samples_per_channel(),
NonZeroUsize::new(3).unwrap()
);
let multi = surround.as_multi_channel().unwrap();
let expected = [1.0f32, 2.0, 3.0];
for ch_idx in 0..6 {
for (s_idx, &exp) in expected.iter().enumerate() {
assert_eq!(multi[(ch_idx, s_idx)], exp);
}
}
}
#[test]
fn test_duplicate_to_channels_single_channel() {
let mono =
AudioSamples::new_mono(Array1::from(vec![0.5f32, 0.6]), sample_rate!(44100)).unwrap();
let result = mono.duplicate_to_channels(1).unwrap();
assert_eq!(result.num_channels().get(), 1);
assert!(result.is_mono());
}
#[test]
fn test_duplicate_to_channels_zero_channels_error() {
let mono =
AudioSamples::new_mono(Array1::from(vec![0.1f32, 0.2]), sample_rate!(44100)).unwrap();
let result = mono.duplicate_to_channels(0);
assert!(result.is_err());
}
#[test]
fn test_swap_channels_dual_variant_equivalence() {
let mut data = ndarray::Array2::zeros((2, 3));
data[[0, 0]] = 1.0f32;
data[[0, 1]] = 2.0;
data[[0, 2]] = 3.0;
data[[1, 0]] = 10.0;
data[[1, 1]] = 20.0;
data[[1, 2]] = 30.0;
let original = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
let borrowed = original.swap_channels(0, 1).unwrap();
let orig_multi = original.as_multi_channel().unwrap();
assert_eq!(orig_multi[(0, 0)], 1.0);
assert_eq!(orig_multi[(1, 0)], 10.0);
let mut in_place = original.clone();
in_place.swap_channels_in_place(0, 1).unwrap();
let b = borrowed.as_multi_channel().unwrap();
let p = in_place.as_multi_channel().unwrap();
assert_eq!(b.shape(), p.shape());
for ch in 0..b.shape()[0] {
for s in 0..b.shape()[1] {
assert_eq!(b[(ch, s)], p[(ch, s)]);
}
}
}
#[test]
fn test_swap_channels_actually_swaps() {
let mut data = ndarray::Array2::zeros((2, 3));
data[[0, 0]] = 1.0f32;
data[[0, 1]] = 2.0;
data[[0, 2]] = 3.0;
data[[1, 0]] = 10.0;
data[[1, 1]] = 20.0;
data[[1, 2]] = 30.0;
let mut audio = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
audio.swap_channels_in_place(0, 1).unwrap();
let m = audio.as_multi_channel().unwrap();
assert_eq!(m.shape(), &[2, 3]);
assert_eq!([m[(0, 0)], m[(0, 1)], m[(0, 2)]], [10.0f32, 20.0, 30.0]);
assert_eq!([m[(1, 0)], m[(1, 1)], m[(1, 2)]], [1.0f32, 2.0, 3.0]);
}
#[test]
fn test_to_mono_dual_variant_equivalence() {
let mut data = ndarray::Array2::zeros((2, 3));
data[[0, 0]] = 1.0f32;
data[[0, 1]] = 3.0;
data[[0, 2]] = 5.0;
data[[1, 0]] = 3.0;
data[[1, 1]] = 5.0;
data[[1, 2]] = 7.0;
let original = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
let borrowed = original.to_mono(MonoConversionMethod::Average).unwrap();
assert!(borrowed.is_mono());
assert_eq!(original.num_channels().get(), 2);
let mut in_place = original.clone();
in_place
.to_mono_in_place(MonoConversionMethod::Average)
.unwrap();
assert!(in_place.is_mono());
let b = borrowed.as_slice().unwrap();
let p = in_place.as_slice().unwrap();
assert_eq!(b, p);
assert_eq!(b[0], 2.0);
assert_eq!(b[1], 4.0);
assert_eq!(b[2], 6.0);
}
#[test]
fn test_duplicate_to_channels_from_multi() {
let mut data = ndarray::Array2::zeros((2, 3));
data[[0, 0]] = 1.0f32;
data[[0, 1]] = 2.0;
data[[0, 2]] = 3.0;
data[[1, 0]] = 10.0; data[[1, 1]] = 20.0;
data[[1, 2]] = 30.0;
let stereo = AudioSamples::new_multi_channel(data, sample_rate!(44100)).unwrap();
let quad = stereo.duplicate_to_channels(4).unwrap();
assert_eq!(quad.num_channels().get(), 4);
let multi = quad.as_multi_channel().unwrap();
let expected = [1.0f32, 2.0, 3.0];
for ch_idx in 0..4 {
for (s_idx, &exp) in expected.iter().enumerate() {
assert_eq!(multi[(ch_idx, s_idx)], exp);
}
}
}
}