#[cfg(feature = "editing")]
use non_empty_slice::{NonEmptyVec, non_empty_vec};
use crate::{
AudioData, AudioSampleError, AudioSampleResult, AudioSamples, LayoutError,
traits::StandardSample,
};
#[cfg(feature = "editing")]
use crate::AudioEditing;
use std::marker::PhantomData;
#[cfg(feature = "editing")]
use std::num::NonZeroUsize;
pub trait AudioSampleIterators<'a, T>
where
T: StandardSample,
{
fn frames<'iter>(&'iter self) -> FrameIterator<'iter, 'a, T>
where
'a: 'iter;
fn channels<'iter>(&'iter self) -> ChannelIterator<'iter, 'a, T>;
#[cfg(feature = "editing")]
fn windows<'iter>(
&'iter self,
window_size: usize,
hop_size: usize,
) -> WindowIterator<'iter, 'a, T>
where
'a: 'iter;
}
impl<'a, T> AudioSamples<'a, T>
where
T: StandardSample,
{
#[inline]
#[must_use]
pub fn frames<'iter>(&'iter self) -> FrameIterator<'iter, 'a, T>
where
'a: 'iter,
{
FrameIterator::new(self)
}
#[inline]
#[must_use]
pub fn channels<'iter>(&'iter self) -> ChannelIterator<'iter, 'a, T> {
ChannelIterator::new(self)
}
#[cfg(feature = "editing")]
#[inline]
#[must_use]
pub fn windows<'iter>(
&'iter self,
window_size: NonZeroUsize,
hop_size: NonZeroUsize,
) -> WindowIterator<'iter, 'a, T>
where
'a: 'iter,
{
WindowIterator::new(self, window_size, hop_size)
}
#[inline]
pub fn apply_to_frames<F>(&mut self, mut f: F)
where
F: FnMut(usize, &mut [T]), {
match &mut self.data {
AudioData::Mono(arr) => {
for (frame_idx, sample) in arr.iter_mut().enumerate() {
f(frame_idx, std::slice::from_mut(sample));
}
}
AudioData::Multi(arr) => {
let (channels, samples_per_channel) = arr.dim();
for frame_idx in 0..samples_per_channel.get() {
let mut frame = Vec::with_capacity(channels.get());
for ch in 0..channels.get() {
frame.push(arr[[ch, frame_idx]]);
}
f(frame_idx, &mut frame);
for ch in 0..channels.get() {
arr[[ch, frame_idx]] = frame[ch];
}
}
}
}
}
#[inline]
pub fn try_apply_to_channel_data<F>(&mut self, mut f: F) -> AudioSampleResult<()>
where
F: FnMut(usize, &mut [T]), {
match &mut self.data {
AudioData::Mono(arr) => {
let slice = arr.as_slice_mut();
f(0, slice);
}
AudioData::Multi(arr) => {
let (channels, samples_per_channel) = arr.dim();
let samples_per_channel = samples_per_channel.get();
let slice = arr.as_slice_mut().ok_or_else(|| {
AudioSampleError::Layout(LayoutError::NonContiguous {
operation: "multi-channel iterator access".to_string(),
layout_type: "non-contiguous multi-channel data".to_string(),
})
})?;
for ch in 0..channels.get() {
let start_idx = ch * samples_per_channel;
let channel_slice = &mut slice[start_idx..start_idx + samples_per_channel];
f(ch, channel_slice);
}
}
}
Ok(())
}
#[inline]
pub fn apply_to_channel_data<F>(&mut self, mut f: F)
where
F: FnMut(usize, &mut [T]), {
self.try_apply_to_channel_data(|ch, data| f(ch, data))
.expect("apply_to_channel_data requires contiguous storage; use try_apply_to_channel_data to handle non-contiguous inputs");
}
#[inline]
pub fn apply_to_windows<F>(&mut self, window_size: usize, hop_size: usize, mut f: F)
where
F: FnMut(usize, &mut [T]), {
let total_samples = self.samples_per_channel().get();
if total_samples == 0 || window_size == 0 {
return;
}
match &mut self.data {
AudioData::Mono(arr) => {
let mut window_idx = 0;
let mut pos = 0;
while pos + window_size <= total_samples {
let slice = arr.as_slice_mut();
let window_slice = &mut slice[pos..pos + window_size];
f(window_idx, window_slice);
pos += hop_size;
window_idx += 1;
}
}
AudioData::Multi(arr) => {
let (rows, cols) = arr.dim();
let rows = rows.get();
let samples_per_channel = cols;
let mut pos = 0;
let mut window_idx = 0;
while pos + window_size <= samples_per_channel.get() {
let mut window_data = vec![T::zero(); window_size * rows];
for ch in 0..rows {
for sample_idx in 0..window_size {
let dst_idx = sample_idx * rows + ch; window_data[dst_idx] = arr[[ch, pos + sample_idx]];
}
}
f(window_idx, &mut window_data);
for ch in 0..rows {
for sample_idx in 0..window_size {
let src_idx = sample_idx * rows + ch; arr[[ch, pos + sample_idx]] = window_data[src_idx];
}
}
pos += hop_size;
window_idx += 1;
}
}
}
}
}
pub struct FrameIterator<'iter, 'a, T>
where
T: StandardSample,
'a: 'iter,
{
audio: &'iter AudioSamples<'a, T>,
current_frame: usize,
total_frames: usize,
_phantom: PhantomData<T>,
}
impl<'iter, 'a, T> FrameIterator<'iter, 'a, T>
where
T: StandardSample,
'a: 'iter,
{
#[inline]
#[must_use]
pub fn new(audio: &'iter AudioSamples<'a, T>) -> Self {
let total_frames = audio.samples_per_channel().get();
Self {
audio,
current_frame: 0,
total_frames,
_phantom: PhantomData,
}
}
}
impl<'iter, 'a, T> Iterator for FrameIterator<'iter, 'a, T>
where
T: StandardSample,
'a: 'iter,
{
type Item = AudioSamples<'iter, T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.current_frame >= self.total_frames {
return None;
}
let frame_range = self.current_frame..self.current_frame + 1;
self.current_frame += 1;
let audio: &'iter AudioSamples<'a, T> = self.audio;
audio.slice_samples(frame_range).ok()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.total_frames - self.current_frame;
(remaining, Some(remaining))
}
}
impl<T> ExactSizeIterator for FrameIterator<'_, '_, T> where T: StandardSample {}
pub struct ChannelIterator<'iter, 'data, T>
where
T: StandardSample,
{
audio: &'iter AudioSamples<'data, T>,
current_channel: usize,
total_channels: usize,
}
impl<'iter, 'data, T> ChannelIterator<'iter, 'data, T>
where
T: StandardSample,
{
#[inline]
#[must_use]
pub fn new(audio: &'iter AudioSamples<'data, T>) -> Self {
let total_channels = audio.num_channels().get();
Self {
audio,
current_channel: 0,
total_channels: total_channels as usize,
}
}
}
impl<T> Iterator for ChannelIterator<'_, '_, T>
where
T: StandardSample,
{
type Item = AudioSamples<'static, T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.current_channel >= self.total_channels {
return None;
}
let channel = match self
.audio
.clone()
.into_owned()
.slice_channels(self.current_channel..=self.current_channel)
{
Ok(ch) => ch,
Err(e) => {
eprintln!("Error slicing channel {}: {}", self.current_channel, e);
return None;
}
};
self.current_channel += 1;
Some(channel)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.total_channels - self.current_channel;
(remaining, Some(remaining))
}
}
impl<T> ExactSizeIterator for ChannelIterator<'_, '_, T> where T: StandardSample {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum PaddingMode {
#[default]
Zero,
None,
Skip,
}
#[cfg(feature = "editing")]
pub struct WindowIterator<'iter, 'a, T>
where
T: StandardSample,
'a: 'iter,
{
audio: &'iter AudioSamples<'a, T>,
window_size: NonZeroUsize,
hop_size: NonZeroUsize,
current_position: usize,
total_samples: NonZeroUsize,
total_windows: NonZeroUsize,
current_window: usize,
padding_mode: PaddingMode,
_phantom: PhantomData<T>,
}
#[cfg(feature = "editing")]
impl<'iter, 'a, T> WindowIterator<'iter, 'a, T>
where
T: StandardSample,
'a: 'iter,
{
fn new(
audio: &'iter AudioSamples<'a, T>,
window_size: NonZeroUsize,
hop_size: NonZeroUsize,
) -> Self {
let total_samples = audio.samples_per_channel();
let total_windows =
Self::calculate_total_windows(total_samples, window_size, hop_size, PaddingMode::Zero);
Self {
audio,
window_size,
hop_size,
current_position: 0,
total_samples,
total_windows,
current_window: 0,
padding_mode: PaddingMode::Zero,
_phantom: PhantomData,
}
}
const fn calculate_total_windows(
total_samples: NonZeroUsize,
window_size: NonZeroUsize,
hop_size: NonZeroUsize,
padding_mode: PaddingMode,
) -> NonZeroUsize {
let max_windows = total_samples.get().div_ceil(hop_size.get());
let max_windows = unsafe {
NonZeroUsize::new_unchecked(max_windows)
};
match padding_mode {
PaddingMode::Zero => {
max_windows
}
PaddingMode::None => {
let mut count = 0;
let mut pos = 0;
while pos < total_samples.get() {
count += 1;
pos += hop_size.get();
}
unsafe { NonZeroUsize::new_unchecked(count) }
}
PaddingMode::Skip => {
unsafe {
NonZeroUsize::new_unchecked(
1 + (total_samples.get() - window_size.get()) / hop_size.get(),
)
}
}
}
}
#[inline]
#[must_use]
pub const fn with_padding_mode(mut self, mode: PaddingMode) -> Self {
self.padding_mode = mode;
self.total_windows = Self::calculate_total_windows(
self.total_samples,
self.window_size,
self.hop_size,
mode,
);
self
}
}
#[cfg(feature = "editing")]
impl<T> Iterator for WindowIterator<'_, '_, T>
where
T: StandardSample,
{
type Item = AudioSamples<'static, T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.current_window >= self.total_windows.get() {
return None;
}
let start_pos = self.current_position;
let end_pos = start_pos + self.window_size.get();
let audio = self.audio;
let window = if end_pos <= self.total_samples.get() {
audio
.slice_samples(start_pos..end_pos)
.ok()
.map(super::repr::AudioSamples::into_owned)
} else {
match self.padding_mode {
PaddingMode::Zero => {
let available_samples = self.total_samples.get().saturating_sub(start_pos);
match &audio.data {
AudioData::Mono(_) => {
let starting_slice = if available_samples > 0 {
let slice = audio
.slice_samples(start_pos..self.total_samples.get())
.ok()?
.into_owned();
Some(slice)
} else {
None
};
let silence_samples = self.window_size.get() - available_samples;
let length = NonZeroUsize::new(silence_samples)?;
let silence = if silence_samples > 0 {
let silence =
AudioSamples::<T>::zeros_mono(length, audio.sample_rate);
Some(silence)
} else {
return starting_slice;
};
match (starting_slice, silence) {
(None, None) => None,
(None, Some(silence)) => Some(silence),
(Some(starting_slice), None) => Some(starting_slice),
(Some(s), Some(z)) => {
let slices = vec![s, z];
let slices = NonEmptyVec::new(slices).ok()?;
Some(AudioSamples::concatenate_owned(slices).ok()?)
}
}
}
AudioData::Multi(_) => {
let interleaved_slice = if available_samples > 0 {
let slice = audio
.slice_samples(start_pos..self.total_samples.get())
.ok()?
.into_owned();
Some(slice)
} else {
None
};
let remaining_samples = self.window_size.get() - available_samples;
if remaining_samples == 0 {
return interleaved_slice;
}
let length = NonZeroUsize::new(remaining_samples)?;
let silence = AudioSamples::<T>::zeros_multi_channel(
audio.num_channels(),
length,
audio.sample_rate,
);
match interleaved_slice {
None => Some(silence),
Some(slice) => {
AudioSamples::concatenate_owned(non_empty_vec![slice, silence])
.ok()
}
}
}
}
}
PaddingMode::None => {
let available_samples = self.total_samples.get().saturating_sub(start_pos);
if available_samples == 0 {
return None;
}
audio
.slice_samples(start_pos..self.total_samples.get())
.ok()
.map(super::repr::AudioSamples::into_owned)
}
PaddingMode::Skip => {
return None;
}
}
};
self.current_position += self.hop_size.get();
self.current_window += 1;
window
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.total_windows.get() - self.current_window;
(remaining, Some(remaining))
}
}
#[cfg(feature = "editing")]
impl<T> ExactSizeIterator for WindowIterator<'_, '_, T> where T: StandardSample {}
#[cfg(test)]
mod tests {
use crate::AudioSamples;
#[cfg(feature = "editing")]
use crate::PaddingMode;
use crate::sample_rate;
use ndarray::{Array1, array};
use non_empty_slice::non_empty_vec;
#[test]
fn test_frame_iterator_mono() {
let audio = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0, 5.0], sample_rate!(44100))
.unwrap();
audio
.frames()
.zip([1.0f32, 2.0, 3.0, 4.0, 5.0])
.for_each(|(f, x)| {
assert_eq!(f.to_interleaved_vec(), non_empty_vec![x]);
});
}
#[test]
fn test_frame_iterator_stereo() {
let audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]],
sample_rate!(44100),
)
.unwrap();
let expected_frames = vec![vec![1.0, 4.0], vec![2.0, 5.0], vec![3.0, 6.0]];
for (i, frame) in audio.frames().enumerate() {
assert_eq!(frame.to_interleaved_vec().to_vec(), expected_frames[i]);
}
}
#[test]
fn test_channel_iterator_mono() {
let audio =
AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0], sample_rate!(44100)).unwrap();
let mut channel_count = 0;
for channel in audio.channels() {
channel_count += 1;
assert_eq!(
channel.to_interleaved_vec(),
non_empty_vec![1.0, 2.0, 3.0, 4.0]
);
}
assert_eq!(channel_count, 1);
}
#[test]
fn test_channel_iterator_stereo() {
let audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]],
sample_rate!(44100),
)
.unwrap();
let expected_channels = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
for (i, channel) in audio.channels().enumerate() {
assert_eq!(channel.as_mono().unwrap().to_vec(), expected_channels[i]);
}
}
#[cfg(feature = "editing")]
#[test]
fn test_window_iterator_no_overlap() {
let audio =
AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], sample_rate!(44100))
.unwrap();
let windows: Vec<AudioSamples<f32>> =
audio.windows(crate::nzu!(3), crate::nzu!(3)).collect();
assert_eq!(windows.len(), 2);
assert_eq!(windows[0].as_mono().unwrap().to_vec(), vec![1.0, 2.0, 3.0]);
assert_eq!(windows[1].as_mono().unwrap().to_vec(), vec![4.0, 5.0, 6.0]);
}
#[cfg(feature = "editing")]
#[test]
fn test_window_iterator_with_overlap() {
let audio =
AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], sample_rate!(44100))
.unwrap();
let windows: Vec<AudioSamples<f32>> =
audio.windows(crate::nzu!(4), crate::nzu!(2)).collect();
assert_eq!(windows.len(), 3);
assert_eq!(
windows[0].as_mono().unwrap().to_vec(),
vec![1.0, 2.0, 3.0, 4.0]
);
assert_eq!(
windows[1].as_mono().unwrap().to_vec(),
vec![3.0, 4.0, 5.0, 6.0]
);
assert_eq!(
windows[2].as_mono().unwrap().to_vec(),
vec![5.0, 6.0, 0.0, 0.0]
);
}
#[cfg(feature = "editing")]
#[test]
fn test_window_iterator_zero_padding() {
let audio = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0, 5.0], sample_rate!(44100))
.unwrap();
let windows: Vec<AudioSamples<f32>> = audio
.windows(crate::nzu!(4), crate::nzu!(3))
.with_padding_mode(PaddingMode::Zero)
.collect();
assert_eq!(windows.len(), 2);
assert_eq!(
windows[0].as_mono().unwrap().to_vec(),
vec![1.0, 2.0, 3.0, 4.0]
);
assert_eq!(
windows[1].as_mono().unwrap().to_vec(),
vec![4.0, 5.0, 0.0, 0.0]
); }
#[cfg(feature = "editing")]
#[test]
fn test_window_iterator_no_padding() {
let audio = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0, 5.0], sample_rate!(44100))
.unwrap();
let windows: Vec<AudioSamples<f32>> = audio
.windows(crate::nzu!(4), crate::nzu!(3))
.with_padding_mode(PaddingMode::None)
.collect();
assert_eq!(windows.len(), 2);
assert_eq!(
windows[0].as_mono().unwrap().to_vec(),
vec![1.0, 2.0, 3.0, 4.0]
);
assert_eq!(windows[1].as_mono().unwrap().to_vec(), vec![4.0, 5.0]); }
#[cfg(feature = "editing")]
#[test]
fn test_window_iterator_skip_padding() {
let audio = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0, 5.0], sample_rate!(44100))
.unwrap();
let windows: Vec<AudioSamples<f32>> = audio
.windows(crate::nzu!(4), crate::nzu!(3))
.with_padding_mode(PaddingMode::Skip)
.collect();
assert_eq!(windows.len(), 1);
assert_eq!(
windows[0].as_mono().unwrap().to_vec(),
vec![1.0, 2.0, 3.0, 4.0]
);
}
#[cfg(feature = "editing")]
#[test]
fn test_window_iterator_stereo_interleaved() {
let audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]],
sample_rate!(44100),
)
.unwrap();
let windows: Vec<AudioSamples<f32>> =
audio.windows(crate::nzu!(2), crate::nzu!(2)).collect();
assert_eq!(windows.len(), 2);
assert_eq!(
windows[0].to_interleaved_vec(),
non_empty_vec![1.0, 5.0, 2.0, 6.0]
);
assert_eq!(
windows[1].to_interleaved_vec(),
non_empty_vec![3.0, 7.0, 4.0, 8.0]
);
}
#[test]
fn test_exact_size_iterators() {
let audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]],
sample_rate!(44100),
)
.unwrap();
let frame_iter = audio.frames();
assert_eq!(frame_iter.len(), 3);
let channel_iter = audio.channels();
assert_eq!(channel_iter.len(), 2);
#[cfg(feature = "editing")]
{
let window_iter = audio.windows(crate::nzu!(2), crate::nzu!(1));
assert_eq!(window_iter.len(), 3); }
}
#[test]
fn test_multiple_iterators_from_same_audio() {
let audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]],
sample_rate!(44100),
)
.unwrap();
let frames = audio.frames();
let channels = audio.channels();
#[cfg(feature = "editing")]
let windows = audio.windows(crate::nzu!(2), crate::nzu!(1));
assert_eq!(frames.len(), 3);
assert_eq!(channels.len(), 2);
#[cfg(feature = "editing")]
assert_eq!(windows.len(), 3);
}
#[test]
fn test_frame_iterator_mut_stereo() {
let mut audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]],
sample_rate!(44100),
)
.unwrap();
let expected = array![[0.5f32, 1.0, 1.5], [6.0, 7.5, 9.0]];
audio.apply_to_channel_data(|ch, channel_data| {
let gain = if ch == 0 { 0.5 } else { 1.5 };
for sample in channel_data {
*sample *= gain;
}
});
assert_eq!(audio.as_multi_channel().unwrap(), &expected);
}
#[test]
fn test_frame_iterator_mut_individual_access() {
let mut audio =
AudioSamples::new_multi_channel(array![[1.0f32, 2.0], [3.0, 4.0]], sample_rate!(44100))
.unwrap();
let expected = array![[10.0f32, 20.0], [3.0, 4.0]];
audio.apply_to_channel_data(|ch, channel_data| {
if ch == 0 {
for sample in channel_data {
*sample *= 10.0;
}
}
});
assert_eq!(audio.as_multi_channel().unwrap(), &expected);
}
#[test]
fn test_channel_iterator_mut_mono() {
let mut audio =
AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0], sample_rate!(44100)).unwrap();
audio.apply_to_channel_data(|_ch, channel_data| {
for sample in channel_data {
*sample += 10.0;
}
});
assert_eq!(audio.as_mono().unwrap(), &array![11.0f32, 12.0, 13.0, 14.0]);
}
#[test]
fn test_channel_iterator_mut_stereo() {
let mut audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]],
sample_rate!(44100),
)
.unwrap();
let expected = array![[0.5f32, 1.0, 1.5], [8.0, 10.0, 12.0]];
audio.apply_to_channel_data(|ch, channel_data| {
let gain = if ch == 0 { 0.5 } else { 2.0 };
for sample in channel_data {
*sample *= gain;
}
});
assert_eq!(audio.as_multi_channel().unwrap(), &expected);
}
#[test]
fn test_window_iterator_mut_mono() {
let mut audio =
AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], sample_rate!(44100))
.unwrap();
audio.apply_to_windows(3, 3, |_window_idx, window_data| {
for sample in window_data {
*sample *= 0.5;
}
});
assert_eq!(
audio.as_mono().unwrap(),
&array![0.5f32, 1.0, 1.5, 2.0, 2.5, 3.0]
);
}
#[test]
fn test_window_iterator_mut_stereo() {
let mut audio = AudioSamples::new_multi_channel(
array![[1.0f32, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]],
sample_rate!(44100),
)
.unwrap();
let expected = array![[0.8f32, 1.6, 2.4, 3.2], [6.0, 7.2, 8.4, 9.6]];
audio.apply_to_windows(2, 2, |_window_idx, window_data| {
let samples_per_channel = window_data.len() / 2; for sample_idx in 0..samples_per_channel {
let left_idx = sample_idx * 2;
let right_idx = sample_idx * 2 + 1;
window_data[left_idx] *= 0.8; window_data[right_idx] *= 1.2; }
});
let result = audio.as_multi_channel().unwrap();
for (i, (&actual, &expected)) in result.iter().zip(expected.iter()).enumerate() {
assert!(
(actual - expected).abs() < 1e-6,
"Mismatch at index {}: {} != {} (diff: {})",
i,
actual,
expected,
(actual - expected).abs()
);
}
}
#[test]
fn test_window_function_application() {
let mut audio =
AudioSamples::new_mono(array![1.0f32, 1.0, 1.0, 1.0], sample_rate!(44100)).unwrap();
audio.apply_to_windows(4, 4, |_window_idx, window_data| {
let window_size = window_data.len();
for (i, sample) in window_data.iter_mut().enumerate() {
let hann_weight = 0.5
* (1.0
- (2.0 * std::f32::consts::PI * i as f32 / (window_size - 1) as f32).cos());
*sample *= hann_weight;
}
});
let result = audio.as_mono().unwrap();
assert!(result[0] < 1.0); assert!(result[1] > 0.5); assert!(result[2] > 0.5); assert!(result[3] < 1.0); }
#[test]
fn test_performance_comparison_apply_vs_iterator() {
let mut audio1 =
AudioSamples::new_mono(Array1::<f32>::ones(1000), sample_rate!(44100)).unwrap();
let mut audio2 = audio1.clone();
audio1.apply(|sample| sample * 0.5);
audio2.apply_to_frames(|_frame_idx, frame_data| {
for sample in frame_data {
*sample *= 0.5;
}
});
assert_eq!(audio1.as_mono().unwrap(), audio2.as_mono().unwrap());
}
}