use crate::signal_processing::{resample, to_mono};
use hound::{SampleFormat, WavReader, WavSpec, WavWriter};
use ndarray::ShapeError;
use rayon::prelude::*;
use std::fs::File;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{Receiver, channel};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AudioError {
#[error("WAV open failed: {0}")]
OpenError(#[from] hound::Error),
#[error("Unsupported WAV sample format")]
UnsupportedFormat,
#[error("Offset or duration out of bounds")]
InvalidRange,
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Hound processing error: {0}")]
HoundError(hound::Error),
#[error("Resampling error: {0}")]
ResampleError(#[from] crate::signal_processing::resampling::ResampleError),
#[error("Stream processing error")]
StreamError,
#[error("Shape mismatch: {0}")]
ShapeError(#[from] ShapeError),
#[error("Insufficient sample count: {0}")]
InsufficientData(String),
#[error("Invalid parameter: {0}")]
InvalidInput(String),
#[error("Computation error: {0}")]
ComputationFailed(String),
#[error("File not found: {0}")]
FileNotFound(String),
}
#[derive(Debug, Clone)]
pub struct AudioData {
pub samples: Vec<f32>,
pub sample_rate: u32,
pub channels: u16,
}
impl AudioData {
pub fn new(samples: Vec<f32>, sample_rate: u32, channels: u16) -> Result<Self, AudioError> {
if sample_rate == 0 {
return Err(AudioError::InvalidInput(
"Sample rate must be positive".into(),
));
}
if channels == 0 {
return Err(AudioError::InvalidInput(
"Channel count must be positive".into(),
));
}
Ok(Self {
samples,
sample_rate,
channels,
})
}
pub fn to_mono(&self) -> Self {
let samples = if self.channels > 1 {
to_mono(&self.samples, self.channels as usize)
} else {
self.samples.clone()
};
Self {
samples,
sample_rate: self.sample_rate,
channels: 1,
}
}
pub fn split_channels(&self) -> Result<Vec<Vec<f32>>, AudioError> {
if self.samples.len() % self.channels as usize != 0 {
return Err(AudioError::InvalidInput(
"Sample length must be a multiple of channels".into(),
));
}
let frame_count = self.samples.len() / self.channels as usize;
let mut channels = vec![Vec::with_capacity(frame_count); self.channels as usize];
for (i, &sample) in self.samples.iter().enumerate() {
let channel_idx = i % self.channels as usize;
channels[channel_idx].push(sample);
}
Ok(channels)
}
pub fn duration(&self) -> f32 {
self.samples.len() as f32 / (self.channels as f32 * self.sample_rate as f32)
}
pub fn frame_count(&self) -> usize {
self.samples.len() / self.channels as usize
}
pub fn to_raw(&self) -> (&[f32], u32, u16) {
(&self.samples, self.sample_rate, self.channels)
}
}
pub fn load<P: AsRef<Path>>(
path: P,
sr: Option<u32>,
mono: Option<bool>,
offset: Option<f32>,
duration: Option<f32>,
) -> Result<AudioData, AudioError> {
let path = path.as_ref();
if !path.exists() {
return Err(AudioError::FileNotFound(
path.to_string_lossy().into_owned(),
));
}
if let Some(off) = offset {
if off < 0.0 {
return Err(AudioError::InvalidInput("Offset cannot be negative".into()));
}
}
if let Some(dur) = duration {
if dur <= 0.0 {
return Err(AudioError::InvalidInput("Duration must be positive".into()));
}
}
if let Some(rate) = sr {
if rate == 0 {
return Err(AudioError::InvalidInput(
"Sample rate must be positive".into(),
));
}
}
let file = File::open(path)?;
let mut reader = WavReader::new(file)?;
let spec = reader.spec();
let sample_rate = spec.sample_rate;
let start = (offset.unwrap_or(0.0) * sample_rate as f32) as usize;
let len = duration.map(|d| (d * sample_rate as f32) as usize);
let samples: Vec<f32> = match spec.sample_format {
SampleFormat::Float => reader
.samples::<f32>()
.skip(start)
.take(len.unwrap_or(usize::MAX))
.collect::<Result<Vec<_>, _>>()
.map_err(AudioError::HoundError)?,
SampleFormat::Int => match spec.bits_per_sample {
8 => reader
.samples::<i8>()
.skip(start)
.take(len.unwrap_or(usize::MAX))
.map(|s| s.map(|v| v as f32 / i8::MAX as f32))
.collect::<Result<Vec<_>, _>>()
.map_err(AudioError::HoundError)?,
16 => reader
.samples::<i16>()
.skip(start)
.take(len.unwrap_or(usize::MAX))
.map(|s| s.map(|v| v as f32 / i16::MAX as f32))
.collect::<Result<Vec<_>, _>>()
.map_err(AudioError::HoundError)?,
24 => reader
.samples::<i32>()
.skip(start)
.take(len.unwrap_or(usize::MAX))
.map(|s| s.map(|v| v as f32 / 8_388_608.0))
.collect::<Result<Vec<_>, _>>()
.map_err(AudioError::HoundError)?,
32 => reader
.samples::<i32>()
.skip(start)
.take(len.unwrap_or(usize::MAX))
.map(|s| s.map(|v| v as f32 / 2_147_483_648.0))
.collect::<Result<Vec<_>, _>>()
.map_err(AudioError::HoundError)?,
_ => return Err(AudioError::UnsupportedFormat),
},
};
if samples.is_empty() && len != Some(0) {
return Err(AudioError::InsufficientData("No samples available".into()));
}
if start > samples.len() && !samples.is_empty() {
return Err(AudioError::InvalidRange);
}
let mut samples = samples;
let channels = spec.channels as usize;
if channels > 1 && mono.unwrap_or(false) {
samples = to_mono(&samples, channels);
}
let final_samples = if let Some(target_samplerate) = sr {
if target_samplerate != sample_rate {
resample(&samples, sample_rate, target_samplerate)?
} else {
samples
}
} else {
samples
};
AudioData::new(
final_samples,
sr.unwrap_or(sample_rate),
if mono.unwrap_or(false) {
1
} else {
spec.channels
},
)
}
#[derive(Debug, Clone)]
pub struct Decoder {
path: PathBuf,
sample_rate: Option<u32>,
mono: bool,
offset: Option<f32>,
duration: Option<f32>,
}
impl Decoder {
pub fn from<P: AsRef<Path>>(path: P) -> Self {
Self {
path: path.as_ref().to_path_buf(),
sample_rate: None,
mono: false,
offset: None,
duration: None,
}
}
pub fn sample_rate(mut self, rate: u32) -> Self {
self.sample_rate = Some(rate);
self
}
pub fn mono(mut self) -> Self {
self.mono = true;
self
}
pub fn offset(mut self, seconds: f32) -> Self {
self.offset = Some(seconds);
self
}
pub fn duration(mut self, seconds: f32) -> Self {
self.duration = Some(seconds);
self
}
pub fn load(self) -> Result<AudioData, AudioError> {
load(
&self.path,
self.sample_rate,
Some(self.mono),
self.offset,
self.duration,
)
}
}
pub fn export<P: AsRef<Path>>(path: P, audio_data: &AudioData) -> Result<(), AudioError> {
if audio_data.channels == 0 {
return Err(AudioError::InvalidInput(
"Channel count must be positive".into(),
));
}
if audio_data.sample_rate == 0 {
return Err(AudioError::InvalidInput(
"Sample rate must be positive".into(),
));
}
let spec = WavSpec {
channels: audio_data.channels,
sample_rate: audio_data.sample_rate,
bits_per_sample: 32,
sample_format: SampleFormat::Float,
};
let mut buffer = Vec::new();
let mut writer = WavWriter::new(Cursor::new(&mut buffer), spec)?;
for &sample in &audio_data.samples {
writer.write_sample(sample.clamp(-1.0, 1.0))?;
}
writer.finalize()?;
std::fs::write(path, buffer)?;
Ok(())
}
pub fn stream<P: AsRef<Path>>(
path: P,
block_length: usize,
frame_length: usize,
hop_length: Option<usize>,
) -> Result<Vec<Vec<f32>>, AudioError> {
let path = path.as_ref();
if !path.exists() {
return Err(AudioError::FileNotFound(
path.to_string_lossy().into_owned(),
));
}
if frame_length == 0 {
return Err(AudioError::InvalidInput(
"Frame length must be positive".into(),
));
}
let hop = hop_length.unwrap_or(frame_length);
if hop == 0 {
return Err(AudioError::InvalidInput(
"Hop length must be positive".into(),
));
}
let file = File::open(path)?;
let mut reader = WavReader::new(file)?;
let spec = reader.spec();
let samples_iter: Box<dyn Iterator<Item = Result<f32, hound::Error>>> = match spec.sample_format
{
SampleFormat::Float => Box::new(reader.samples::<f32>()),
SampleFormat::Int => match spec.bits_per_sample {
8 => Box::new(
reader
.samples::<i8>()
.map(|s| s.map(|v| v as f32 / i8::MAX as f32)),
),
16 => Box::new(
reader
.samples::<i16>()
.map(|s| s.map(|v| v as f32 / i16::MAX as f32)),
),
24 => Box::new(
reader
.samples::<i32>()
.map(|s| s.map(|v| v as f32 / 8_388_608.0)),
),
32 => Box::new(
reader
.samples::<i32>()
.map(|s| s.map(|v| v as f32 / 2_147_483_648.0)),
),
_ => return Err(AudioError::UnsupportedFormat),
},
};
let mut blocks = Vec::new();
let mut buffer = Vec::with_capacity(frame_length);
let mut index = 0;
let mut block_count = 0;
for sample in samples_iter {
let sample = sample.map_err(AudioError::HoundError)?;
buffer.push(sample);
if buffer.len() >= frame_length && (index % hop == 0 || buffer.len() >= frame_length) {
let mut block = Vec::with_capacity(frame_length);
block.extend_from_slice(&buffer[..frame_length]);
block.resize(frame_length, 0.0);
blocks.push(block);
buffer.drain(..hop.min(buffer.len()));
block_count += 1;
if block_count >= block_length {
break;
}
}
index += 1;
}
if blocks.is_empty() {
return Err(AudioError::InsufficientData("No blocks generated".into()));
}
let blocks = if frame_length * block_length > 1_000_000 {
blocks.into_par_iter().collect()
} else {
blocks
};
Ok(blocks)
}
pub fn stream_lazy<P: AsRef<Path>>(
path: P,
block_length: usize,
frame_length: usize,
hop_length: Option<usize>,
) -> Result<Receiver<Vec<f32>>, AudioError> {
let path = path.as_ref();
if !path.exists() {
return Err(AudioError::FileNotFound(
path.to_string_lossy().into_owned(),
));
}
if frame_length == 0 {
return Err(AudioError::InvalidInput(
"Frame length must be positive".into(),
));
}
let hop = hop_length.unwrap_or(frame_length);
if hop == 0 {
return Err(AudioError::InvalidInput(
"Hop length must be positive".into(),
));
}
let file = File::open(path)?;
let reader = WavReader::new(file)?;
let spec = reader.spec();
let (tx, rx) = channel();
std::thread::spawn(move || {
let mut reader = reader;
let samples_iter: Box<dyn Iterator<Item = Result<f32, _>>> = match spec.sample_format {
SampleFormat::Float => Box::new(reader.samples::<f32>()),
SampleFormat::Int => match spec.bits_per_sample {
8 => Box::new(
reader
.samples::<i8>()
.map(|s| s.map(|v| v as f32 / i8::MAX as f32)),
),
16 => Box::new(
reader
.samples::<i16>()
.map(|s| s.map(|v| v as f32 / i16::MAX as f32)),
),
24 => Box::new(
reader
.samples::<i32>()
.map(|s| s.map(|v| v as f32 / 8_388_608.0)),
),
32 => Box::new(
reader
.samples::<i32>()
.map(|s| s.map(|v| v as f32 / 2_147_483_648.0)),
),
_ => {
let _ = tx.send(Vec::new());
return;
}
},
};
let mut chunk = Vec::with_capacity(frame_length * block_length);
let mut block_count = 0;
for sample in samples_iter {
let sample = match sample {
Ok(s) => s,
Err(_) => {
let _ = tx.send(Vec::new());
return;
}
};
chunk.push(sample);
if chunk.len() >= frame_length
&& (chunk.len() % hop == 0 || chunk.len() >= frame_length * block_length)
{
let indices: Vec<usize> = (0..chunk.len())
.step_by(hop)
.take(block_length - block_count)
.collect();
let drain_to = indices.last().map_or(0, |&i| (i + hop).min(chunk.len()));
let use_parallel = indices.len() * frame_length > 1_000_000;
let blocks: Vec<Vec<f32>> = if use_parallel {
indices
.into_par_iter()
.map(|i| {
let end = (i + frame_length).min(chunk.len());
let mut block = Vec::with_capacity(frame_length);
block.extend_from_slice(&chunk[i..end]);
block.resize(frame_length, 0.0);
block
})
.collect()
} else {
indices
.into_iter()
.map(|i| {
let end = (i + frame_length).min(chunk.len());
let mut block = Vec::with_capacity(frame_length);
block.extend_from_slice(&chunk[i..end]);
block.resize(frame_length, 0.0);
block
})
.collect()
};
for block in blocks {
if tx.send(block).is_err() {
return;
}
block_count += 1;
if block_count >= block_length {
return;
}
}
chunk.drain(..drain_to);
}
}
if !chunk.is_empty() && block_count < block_length {
chunk.resize(frame_length, 0.0);
let _ = tx.send(chunk);
}
});
Ok(rx)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::NamedTempFile;
fn create_test_wav() -> AudioData {
AudioData::new(vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.5], 44100, 1).unwrap()
}
#[test]
fn test_audio_data_new_valid() {
let audio = AudioData::new(vec![0.1, 0.2], 44100, 1).unwrap();
assert_eq!(audio.samples, vec![0.1, 0.2]);
assert_eq!(audio.sample_rate, 44100);
assert_eq!(audio.channels, 1);
}
#[test]
fn test_audio_data_new_invalid_sample_rate() {
let result = AudioData::new(vec![0.1], 0, 1);
assert!(matches!(result, Err(AudioError::InvalidInput(_))));
}
#[test]
fn test_audio_data_new_invalid_channels() {
let result = AudioData::new(vec![0.1], 44100, 0);
assert!(matches!(result, Err(AudioError::InvalidInput(_))));
}
#[test]
fn test_audio_data_to_mono() {
let stereo = AudioData::new(vec![0.1, 0.2, 0.3, 0.4], 44100, 2).unwrap();
let mono = stereo.to_mono();
assert_eq!(mono.channels, 1);
for (actual, expected) in mono.samples.iter().zip(vec![0.15, 0.35]) {
assert!((actual - expected).abs() < 1e-6, "Expected {}, got {}", expected, actual);
}
}
#[test]
fn test_audio_data_split_channels() {
let stereo = AudioData::new(vec![0.1, 0.2, 0.3, 0.4], 44100, 2).unwrap();
let channels = stereo.split_channels().unwrap();
assert_eq!(channels, vec![vec![0.1, 0.3], vec![0.2, 0.4]]);
}
#[test]
fn test_audio_data_split_channels_invalid() {
let invalid = AudioData::new(vec![0.1, 0.2, 0.3], 44100, 2).unwrap();
let result = invalid.split_channels();
assert!(matches!(result, Err(AudioError::InvalidInput(_))));
}
#[test]
fn test_audio_data_duration() {
let audio = AudioData::new(vec![0.1, 0.2], 44100, 1).unwrap();
assert_eq!(audio.duration(), 2.0 / 44100.0);
}
#[test]
fn test_audio_data_frame_count() {
let stereo = AudioData::new(vec![0.1, 0.2, 0.3, 0.4], 44100, 2).unwrap();
assert_eq!(stereo.frame_count(), 2);
}
#[test]
fn test_audio_data_to_raw() {
let audio = AudioData::new(vec![0.1, 0.2], 44100, 1).unwrap();
let (samples, sr, ch) = audio.to_raw();
assert_eq!(samples, &[0.1, 0.2]);
assert_eq!(sr, 44100);
assert_eq!(ch, 1);
}
#[test]
fn test_load() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let loaded = load(path, None, None, None, None).unwrap();
assert_eq!(loaded.samples, audio.samples);
assert_eq!(loaded.channels, audio.channels);
}
#[test]
fn test_load_segment() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let loaded = load(path, None, None, Some(0.00004535147), Some(0.00004535148)).unwrap();
assert_eq!(loaded.samples, vec![0.1, 0.2]);
}
#[test]
fn test_export() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let loaded = load(path, None, None, None, None).unwrap();
assert_eq!(loaded.samples, audio.samples);
}
#[test]
fn test_stream() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let blocks = stream(path, 3, 2, Some(2)).unwrap();
assert_eq!(blocks, vec![vec![0.0, 0.1], vec![0.2, 0.3], vec![0.4, 0.5]]);
}
#[test]
fn test_stream_lazy() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let rx = stream_lazy(path, 3, 2, Some(2)).unwrap();
let blocks: Vec<_> = rx.into_iter().collect();
assert_eq!(blocks, vec![vec![0.0, 0.1], vec![0.2, 0.3], vec![0.4, 0.5]]);
}
#[test]
fn test_load_file_not_found() {
let path = Path::new("test.wav");
if path.exists() {
fs::remove_file(path).unwrap();
}
let result = load(path, None, None, None, None);
assert!(matches!(result.unwrap_err(), AudioError::FileNotFound(_)));
}
#[test]
fn test_stream_file_not_found() {
let path = Path::new("test.wav");
if path.exists() {
fs::remove_file(path).unwrap();
}
let result = stream(path, 3, 2, Some(2));
assert!(matches!(result.unwrap_err(), AudioError::FileNotFound(_)));
}
#[test]
fn test_stream_lazy_file_not_found() {
let path = Path::new("test.wav");
if path.exists() {
fs::remove_file(path).unwrap();
}
let result = stream_lazy(path, 3, 2, Some(2));
assert!(matches!(result.unwrap_err(), AudioError::FileNotFound(_)));
}
#[test]
fn test_load_empty_file() {
let spec = WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 32,
sample_format: SampleFormat::Float,
};
let mut buffer = Vec::new();
let writer = WavWriter::new(Cursor::new(&mut buffer), spec).unwrap();
writer.finalize().unwrap();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
fs::write(path, buffer).unwrap();
let result = load(path, None, None, None, None);
assert!(matches!(
result.unwrap_err(),
AudioError::InsufficientData(_)
));
}
#[test]
fn test_load_negative_offset() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let result = load(path, None, None, Some(-1.0), None);
assert!(matches!(result.unwrap_err(), AudioError::InvalidInput(_)));
}
#[test]
fn test_load_zero_duration() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let result = load(path, None, None, None, Some(0.0));
assert!(matches!(result.unwrap_err(), AudioError::InvalidInput(_)));
}
#[test]
fn test_stream_zero_frame_length() {
let audio = create_test_wav();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
export(path, &audio).unwrap();
assert!(
fs::metadata(path).is_ok(),
"File should exist after export: {:?}",
path
);
let result = stream(path, 3, 0, Some(2));
assert!(matches!(result.unwrap_err(), AudioError::InvalidInput(_)));
}
#[test]
fn test_export_invalid_channels() {
let audio = AudioData::new(vec![0.1, 0.2], 44100, 0);
assert!(
matches!(audio, Err(AudioError::InvalidInput(_))),
"AudioData::new should fail with zero channels"
);
}
fn write_int_wav<S: hound::Sample + Copy>(path: &std::path::Path, bits: u16, samples: &[S]) {
let spec = WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: bits,
sample_format: SampleFormat::Int,
};
let mut writer = WavWriter::create(path, spec).unwrap();
for &s in samples {
writer.write_sample(s).unwrap();
}
writer.finalize().unwrap();
}
#[test]
fn test_load_8bit_int() {
let temp = NamedTempFile::new().unwrap();
write_int_wav(temp.path(), 8, &[0i8, 64, -64]);
let loaded = load(temp.path(), None, None, None, None).unwrap();
let expected = [0.0, 64.0 / i8::MAX as f32, -64.0 / i8::MAX as f32];
assert_eq!(loaded.samples.len(), 3);
for (a, e) in loaded.samples.iter().zip(expected) {
assert!((a - e).abs() < 1e-4, "8-bit: expected {e}, got {a}");
}
}
#[test]
fn test_load_16bit_int() {
let temp = NamedTempFile::new().unwrap();
write_int_wav(temp.path(), 16, &[0i16, 16384, -16384]);
let loaded = load(temp.path(), None, None, None, None).unwrap();
let expected = [0.0, 16384.0 / i16::MAX as f32, -16384.0 / i16::MAX as f32];
for (a, e) in loaded.samples.iter().zip(expected) {
assert!((a - e).abs() < 1e-4, "16-bit: expected {e}, got {a}");
}
}
#[test]
fn test_load_24bit_int() {
let temp = NamedTempFile::new().unwrap();
write_int_wav(temp.path(), 24, &[0i32, 4_194_304, -4_194_304]);
let loaded = load(temp.path(), None, None, None, None).unwrap();
let expected = [0.0, 0.5, -0.5];
for (a, e) in loaded.samples.iter().zip(expected) {
assert!((a - e).abs() < 1e-6, "24-bit: expected {e}, got {a}");
}
}
#[test]
fn test_load_32bit_int() {
let temp = NamedTempFile::new().unwrap();
write_int_wav(temp.path(), 32, &[0i32, 1_073_741_824, -1_073_741_824]);
let loaded = load(temp.path(), None, None, None, None).unwrap();
let expected = [0.0, 0.5, -0.5];
for (a, e) in loaded.samples.iter().zip(expected) {
assert!((a - e).abs() < 1e-6, "32-bit int: expected {e}, got {a}");
}
}
#[test]
fn test_load_32bit_float() {
let spec = WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 32,
sample_format: SampleFormat::Float,
};
let temp = NamedTempFile::new().unwrap();
{
let mut writer = WavWriter::create(temp.path(), spec).unwrap();
for &s in &[0.0f32, 0.5, -0.5] {
writer.write_sample(s).unwrap();
}
writer.finalize().unwrap();
}
let loaded = load(temp.path(), None, None, None, None).unwrap();
for (a, e) in loaded.samples.iter().zip([0.0, 0.5, -0.5]) {
assert!((a - e).abs() < 1e-6, "32-bit float: expected {e}, got {a}");
}
}
#[test]
fn test_stream_24bit_int() {
let temp = NamedTempFile::new().unwrap();
write_int_wav(temp.path(), 24, &[0i32, 4_194_304, -4_194_304, 4_194_304, -4_194_304, 0]);
let blocks = stream(temp.path(), 3, 2, Some(2)).unwrap();
assert_eq!(blocks, vec![vec![0.0, 0.5], vec![-0.5, 0.5], vec![-0.5, 0.0]]);
}
#[test]
fn test_stream_lazy_24bit_int() {
let temp = NamedTempFile::new().unwrap();
write_int_wav(temp.path(), 24, &[0i32, 4_194_304, -4_194_304, 4_194_304, -4_194_304, 0]);
let rx = stream_lazy(temp.path(), 3, 2, Some(2)).unwrap();
let blocks: Vec<_> = rx.into_iter().collect();
assert_eq!(blocks, vec![vec![0.0, 0.5], vec![-0.5, 0.5], vec![-0.5, 0.0]]);
}
}