pub mod error;
pub mod traits;
pub mod types;
#[cfg(feature = "wav")]
pub mod wav;
#[cfg(feature = "wav")]
pub use crate::wav::{
StreamedWavFile, StreamedWavWriter, build_wav_header, wav_data_len, wav_file::WavFile, wav_file_len, wav_header_len,
};
#[cfg(feature = "flac")]
pub mod flac;
#[cfg(feature = "flac")]
pub use crate::flac::{CompressionLevel, StreamedFlacFile, StreamedFlacWriter};
#[cfg(any(feature = "wav", feature = "flac"))]
pub mod streaming;
#[cfg(any(feature = "wav", feature = "flac"))]
pub use crate::streaming::StreamedAudioWriter;
#[cfg(feature = "numpy")]
pub mod python;
#[cfg(feature = "resampling")]
use std::num::NonZeroU32;
use std::{
any::TypeId,
fs::File,
io::{BufReader, BufWriter, Read, Seek, Write},
path::Path,
};
#[cfg(feature = "resampling")]
pub use audio_samples::operations::ResamplingQuality;
#[cfg(feature = "resampling")]
pub use audio_samples::operations::resample;
use audio_samples::{AudioSamples, traits::StandardSample};
#[cfg(feature = "numpy")]
pub use crate::python::read_pyarray;
#[cfg(all(feature = "numpy", target_endian = "little"))]
pub use crate::python::{NativeAudioArray, read_pyarray_native};
pub use crate::{
error::{AudioIOError, AudioIOResult},
traits::{AudioFile, AudioFileMetadata, AudioFileRead, AudioStreamReader},
types::{BaseAudioInfo, FileType, OpenOptions, ValidatedSampleType, WriteOptions},
};
pub(crate) const MAX_WAV_SIZE: u64 = 2 * 1024 * 1024 * 1024; pub(crate) const MAX_MMAP_SIZE: u64 = 512 * 1024 * 1024;
pub trait ReadSeek: Read + Seek {}
impl<RS> ReadSeek for RS where RS: Read + Seek {}
pub trait WriteSeek: Write + Seek {}
impl<WS> WriteSeek for WS where WS: Write + Seek {}
pub fn peek_native_type<P: AsRef<Path>>(fp: P) -> AudioIOResult<ValidatedSampleType> {
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
return Err(crate::error::AudioIOError::missing_feature(
"'wav' feature must be enabled to peek WAV files",
));
#[cfg(feature = "wav")]
{
use crate::wav::wav_file::parse_wav_header_streaming;
let file = File::open(path)?;
let mut reader = BufReader::with_capacity(65536, file);
let (info, _) = parse_wav_header_streaming(&mut reader)?;
ValidatedSampleType::try_from(info.sample_type).map_err(|_| {
AudioIOError::unsupported_format(format!("Unsupported native sample type: {:?}", info.sample_type))
})
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
return Err(crate::error::AudioIOError::missing_feature(
"'flac' feature must be enabled to peek FLAC files",
));
#[cfg(feature = "flac")]
{
use crate::flac::FlacFile;
use crate::traits::{AudioFile, AudioFileMetadata};
let flac_file = FlacFile::open_with_options(path, OpenOptions::default())?;
let info = flac_file.base_info()?;
ValidatedSampleType::try_from(info.sample_type).map_err(|_| {
AudioIOError::unsupported_format(format!("Unsupported native sample type: {:?}", info.sample_type))
})
}
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"peek_native_type does not support: {other:?}"
))),
}
}
pub fn info<P: AsRef<Path>>(fp: P) -> AudioIOResult<BaseAudioInfo> {
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
return Err(crate::error::AudioIOError::missing_feature(
"'wav' feature must be enabled to read WAV files",
));
#[cfg(feature = "wav")]
{
let wav_file = WavFile::open_metadata(path)?;
wav_file.base_info()
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
return Err(crate::error::AudioIOError::missing_feature(
"'flac' feature must be enabled to read FLAC files",
));
#[cfg(feature = "flac")]
{
use crate::flac::FlacFile;
use crate::traits::AudioFileMetadata;
let flac_file = FlacFile::open_metadata(path)?;
flac_file.base_info()
}
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported file format: {other:?}"
))),
}
}
pub fn read<P, T>(fp: P) -> AudioIOResult<AudioSamples<'static, T>>
where
P: AsRef<Path>,
T: StandardSample + 'static,
{
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
return Err(crate::error::AudioIOError::missing_feature(
"'wav' feature must be enabled to read WAV files",
));
#[cfg(feature = "wav")]
{
let wav_file = WavFile::open_with_options(path, OpenOptions::default())?;
let samples = wav_file.read::<T>()?;
Ok(samples.into_owned())
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
return Err(crate::error::AudioIOError::missing_feature(
"'flac' feature must be enabled to read FLAC files",
));
#[cfg(feature = "flac")]
{
use crate::flac::FlacFile;
use crate::traits::{AudioFile, AudioFileRead};
let flac_file = FlacFile::open_with_options(path, OpenOptions::default())?;
let samples = flac_file.read::<T>()?;
Ok(samples.into_owned())
}
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported file format: {other:?}"
))),
}
}
#[cfg(feature = "resampling")]
pub fn read_and_resample<P, T>(
fp: P,
target_sr: NonZeroU32,
quality: Option<ResamplingQuality>,
) -> AudioIOResult<AudioSamples<'static, T>>
where
P: AsRef<Path>,
T: StandardSample,
{
let signal = read(fp)?;
resample::<T>(&signal, target_sr, quality.unwrap_or(ResamplingQuality::Fast)).map_err(AudioIOError::AudioSamples)
}
#[cfg(feature = "wav")]
pub fn open_streamed<P>(fp: P) -> AudioIOResult<StreamedWavFile<BufReader<File>>>
where
P: AsRef<Path>,
{
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::WAV => {
let file = File::open(path)?;
let reader = BufReader::new(file);
StreamedWavFile::new_with_path(reader, path.to_path_buf())
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported file format for streaming: {other:?}"
))),
}
}
#[cfg(feature = "wav")]
pub fn open_streamed_reader<R>(reader: R) -> AudioIOResult<wav::StreamedWavFile<R>>
where
R: ReadSeek,
{
wav::StreamedWavFile::new(reader)
}
#[cfg(feature = "flac")]
pub fn open_streamed_flac<P>(fp: P) -> AudioIOResult<StreamedFlacFile<BufReader<File>>>
where
P: AsRef<Path>,
{
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::FLAC => {
let file = File::open(path)?;
let reader = BufReader::new(file);
StreamedFlacFile::new_with_path(reader, path.to_path_buf())
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported file format for FLAC streaming: {other:?}"
))),
}
}
#[cfg(feature = "flac")]
pub fn open_streamed_flac_reader<R>(reader: R) -> AudioIOResult<flac::StreamedFlacFile<R>>
where
R: ReadSeek,
{
flac::StreamedFlacFile::new(reader)
}
pub fn open_streamed_dyn<P>(fp: P) -> AudioIOResult<Box<dyn AudioStreamReader>>
where
P: AsRef<Path>,
{
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
return Err(crate::error::AudioIOError::missing_feature(
"'wav' feature must be enabled for WAV streaming",
));
#[cfg(feature = "wav")]
{
let file = File::open(path)?;
let reader = BufReader::new(file);
let streamed = StreamedWavFile::new_with_path(reader, path.to_path_buf())?;
Ok(Box::new(streamed))
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
return Err(crate::error::AudioIOError::missing_feature(
"'flac' feature must be enabled for FLAC streaming",
));
#[cfg(feature = "flac")]
{
let file = File::open(path)?;
let reader = BufReader::new(file);
let streamed = StreamedFlacFile::new_with_path(reader, path.to_path_buf())?;
Ok(Box::new(streamed))
}
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported file format for streaming: {other:?}"
))),
}
}
#[cfg(any(feature = "wav", feature = "flac"))]
pub fn create_streamed<P, T>(
fp: P,
channels: u16,
sample_rate: u32,
) -> AudioIOResult<StreamedAudioWriter<BufWriter<File>>>
where
P: AsRef<Path>,
T: StandardSample + 'static,
{
let path = fp.as_ref();
let format = FileType::from_path(path);
let file = File::create(path)?;
let writer = BufWriter::with_capacity(256 * 1024, file);
create_streamed_with::<_, T>(writer, channels, sample_rate, format)
}
#[cfg(any(feature = "wav", feature = "flac"))]
pub fn create_streamed_with_options<P, T>(
fp: P,
channels: u16,
sample_rate: u32,
opts: WriteOptions,
) -> AudioIOResult<StreamedAudioWriter<BufWriter<File>>>
where
P: AsRef<Path>,
T: StandardSample + 'static,
{
let path = fp.as_ref();
let format = FileType::from_path(path);
let file = File::create(path)?;
let writer = BufWriter::with_capacity(opts.write_buf_capacity, file);
create_streamed_with::<_, T>(writer, channels, sample_rate, format)
}
#[cfg(any(feature = "wav", feature = "flac"))]
pub fn create_streamed_with<W, T>(
writer: W,
channels: u16,
sample_rate: u32,
format: FileType,
) -> AudioIOResult<StreamedAudioWriter<W>>
where
W: WriteSeek,
T: StandardSample + 'static,
{
match format {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
{
let _ = (writer, channels, sample_rate);
Err(AudioIOError::missing_feature(
"'wav' feature must be enabled for WAV streaming writes",
))
}
#[cfg(feature = "wav")]
{
Ok(StreamedAudioWriter::Wav(wav_writer_for_type::<T, W>(
writer,
channels,
sample_rate,
)?))
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
{
let _ = (writer, channels, sample_rate);
Err(AudioIOError::missing_feature(
"'flac' feature must be enabled for FLAC streaming writes",
))
}
#[cfg(feature = "flac")]
{
Ok(StreamedAudioWriter::Flac(flac_writer_for_type::<T, W>(
writer,
channels,
sample_rate,
)?))
}
},
other => Err(AudioIOError::unsupported_format(format!(
"Unsupported output format for streaming write: {other:?}"
))),
}
}
#[cfg(feature = "flac")]
pub fn create_streamed_flac<P, T>(
fp: P,
channels: u16,
sample_rate: u32,
) -> AudioIOResult<StreamedFlacWriter<BufWriter<File>>>
where
P: AsRef<Path>,
T: StandardSample + 'static,
{
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::FLAC => {
let file = File::create(path)?;
let writer = BufWriter::with_capacity(256 * 1024, file);
flac_writer_for_type::<T, _>(writer, channels, sample_rate)
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported output format for streaming FLAC write: {other:?}"
))),
}
}
#[cfg(feature = "flac")]
pub fn create_streamed_flac_writer<W, T>(
writer: W,
channels: u16,
sample_rate: u32,
) -> AudioIOResult<StreamedFlacWriter<W>>
where
W: WriteSeek,
T: StandardSample + 'static,
{
flac_writer_for_type::<T, W>(writer, channels, sample_rate)
}
#[cfg(feature = "flac")]
fn flac_writer_for_type<T, W>(writer: W, channels: u16, sample_rate: u32) -> AudioIOResult<StreamedFlacWriter<W>>
where
T: StandardSample + 'static,
W: WriteSeek,
{
let sample_type = validated_sample_type_of::<T>()?;
StreamedFlacWriter::new(writer, channels, sample_rate, sample_type, CompressionLevel::default())
}
#[cfg(feature = "wav")]
pub fn create_streamed_writer<W, T>(writer: W, channels: u16, sample_rate: u32) -> AudioIOResult<StreamedWavWriter<W>>
where
W: WriteSeek,
T: StandardSample + 'static,
{
wav_writer_for_type::<T, W>(writer, channels, sample_rate)
}
#[cfg(feature = "wav")]
fn wav_writer_for_type<T, W>(writer: W, channels: u16, sample_rate: u32) -> AudioIOResult<StreamedWavWriter<W>>
where
T: StandardSample + 'static,
W: WriteSeek,
{
use audio_samples::I24;
let type_id = TypeId::of::<T>();
match type_id {
id if id == TypeId::of::<u8>() || id == TypeId::of::<i16>() => {
StreamedWavWriter::new_i16(writer, channels, sample_rate)
},
id if id == TypeId::of::<I24>() => StreamedWavWriter::new_i24(writer, channels, sample_rate),
id if id == TypeId::of::<i32>() => StreamedWavWriter::new_i32(writer, channels, sample_rate),
id if id == TypeId::of::<f32>() => StreamedWavWriter::new_f32(writer, channels, sample_rate),
id if id == TypeId::of::<f64>() => StreamedWavWriter::new_f64(writer, channels, sample_rate),
_ => Err(AudioIOError::unsupported_format(format!(
"No WAV encoding for sample type (TypeId: {type_id:?})"
))),
}
}
#[cfg(any(feature = "wav", feature = "flac"))]
fn validated_sample_type_of<T>() -> AudioIOResult<ValidatedSampleType>
where
T: StandardSample + 'static,
{
use audio_samples::I24;
let id = TypeId::of::<T>();
if id == TypeId::of::<u8>() {
Ok(ValidatedSampleType::U8)
} else if id == TypeId::of::<i16>() {
Ok(ValidatedSampleType::I16)
} else if id == TypeId::of::<I24>() {
Ok(ValidatedSampleType::I24)
} else if id == TypeId::of::<i32>() {
Ok(ValidatedSampleType::I32)
} else if id == TypeId::of::<f32>() {
Ok(ValidatedSampleType::F32)
} else if id == TypeId::of::<f64>() {
Ok(ValidatedSampleType::F64)
} else {
Err(AudioIOError::unsupported_format(format!(
"No WAV encoding for sample type (TypeId: {id:?})"
)))
}
}
#[cfg(feature = "wav")]
pub fn create_streamed_sink<W, T>(
writer: W,
channels: u16,
sample_rate: u32,
total_frames: Option<usize>,
) -> AudioIOResult<wav::WavSink<W>>
where
W: Write,
T: StandardSample + 'static,
{
let sample_type = validated_sample_type_of::<T>()?;
wav::WavSink::new(writer, channels, sample_rate, sample_type, total_frames)
}
pub fn open<P>(fp: P) -> AudioIOResult<Box<dyn AudioFile>>
where
P: AsRef<Path>,
{
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
return Err(crate::error::AudioIOError::missing_feature(
"'wav' feature must be enabled to open WAV files",
));
#[cfg(feature = "wav")]
{
let wav_file = WavFile::open_with_options(path, OpenOptions::default())?;
Ok(Box::new(wav_file))
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
return Err(crate::error::AudioIOError::missing_feature(
"'flac' feature must be enabled to open FLAC files",
));
#[cfg(feature = "flac")]
{
use crate::flac::FlacFile;
use crate::traits::AudioFile;
let flac_file = FlacFile::open_with_options(path, OpenOptions::default())?;
Ok(Box::new(flac_file))
}
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported file format: {other:?}"
))),
}
}
pub fn write<P, T>(fp: P, audio: &AudioSamples<T>) -> AudioIOResult<()>
where
P: AsRef<Path>,
T: StandardSample + 'static,
{
write_with_options(fp, audio, WriteOptions::default())
}
pub fn write_with_options<P, T>(fp: P, audio: &AudioSamples<T>, opts: WriteOptions) -> AudioIOResult<()>
where
P: AsRef<Path>,
T: StandardSample + 'static,
{
let path = fp.as_ref();
match FileType::from_path(path) {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
return Err(crate::error::AudioIOError::missing_feature(
"'wav' feature must be enabled to write WAV files",
));
#[cfg(feature = "wav")]
{
let file = std::fs::File::create(path)?;
crate::wav::wav_file::write_wav(file, audio, opts)
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
return Err(crate::error::AudioIOError::missing_feature(
"'flac' feature must be enabled to write FLAC files",
));
#[cfg(feature = "flac")]
{
let file = std::fs::File::create(path)?;
let buf_writer = std::io::BufWriter::with_capacity(opts.write_buf_capacity, file);
crate::flac::write_flac(buf_writer, audio, CompressionLevel::default())
}
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported format: {other:?}"
))),
}
}
#[cfg(feature = "wav")]
pub fn write_with_metadata<P, T>(
fp: P,
audio: &AudioSamples<T>,
metadata: &crate::wav::WavMetadata,
) -> AudioIOResult<()>
where
P: AsRef<Path>,
T: StandardSample + 'static,
{
let file = std::fs::File::create(fp)?;
crate::wav::wav_file::write_wav_with_metadata(file, audio, WriteOptions::default(), metadata)
}
#[cfg(feature = "wav")]
pub fn write_with_metadata_to<T, W>(
writer: W,
audio: &AudioSamples<T>,
metadata: &crate::wav::WavMetadata,
) -> AudioIOResult<()>
where
T: StandardSample + 'static,
W: Write,
{
crate::wav::wav_file::write_wav_with_metadata(writer, audio, WriteOptions::default(), metadata)
}
pub fn write_with<T, W>(writer: W, audio: &AudioSamples<T>, format: FileType) -> AudioIOResult<()>
where
T: StandardSample + 'static,
W: Write,
{
write_with_writer_options(writer, audio, format, WriteOptions::default())
}
pub fn write_with_writer_options<T, W>(
writer: W,
audio: &AudioSamples<T>,
format: FileType,
opts: WriteOptions,
) -> AudioIOResult<()>
where
T: StandardSample + 'static,
W: Write,
{
match format {
FileType::WAV => {
#[cfg(not(feature = "wav"))]
return Err(crate::error::AudioIOError::missing_feature(
"'wav' feature must be enabled to write WAV files",
));
#[cfg(feature = "wav")]
{
crate::wav::wav_file::write_wav(writer, audio, opts)
}
},
FileType::FLAC => {
#[cfg(not(feature = "flac"))]
return Err(crate::error::AudioIOError::missing_feature(
"'flac' feature must be enabled to write FLAC files",
));
#[cfg(feature = "flac")]
{
crate::flac::write_flac(writer, audio, CompressionLevel::default())
}
},
other => Err(crate::error::AudioIOError::unsupported_format(format!(
"Unsupported format for write_with: {other:?}"
))),
}
}
#[cfg(all(test, feature = "wav"))]
mod lib_tests {
use std::time::Duration;
use audio_samples::sample_rate;
use super::*;
#[test]
fn test_info_function() {
let info_result = info("resources/test.wav");
assert!(info_result.is_ok(), "Failed to get info from test WAV file");
let audio_info = info_result.expect("Expected successful info retrieval");
assert_eq!(audio_info.file_type, FileType::WAV);
assert!(audio_info.sample_rate.get() > 0, "Sample rate should be positive");
assert!(audio_info.channels > 0, "Channel count should be positive");
println!("Audio info: {audio_info:#}");
}
#[test]
fn test_read_function() {
let audio_result = read::<_, f32>("resources/test.wav");
assert!(audio_result.is_ok(), "Failed to read test WAV file");
let audio_samples = audio_result.expect("Expected successful audio read");
println!(
"Read {} samples at {} Hz",
audio_samples.len(),
audio_samples.sample_rate()
);
}
#[test]
fn test_open_function() {
let file_result = open("resources/test.wav");
assert!(file_result.is_ok(), "Failed to open test WAV file");
}
#[test]
fn test_write_function() {
use std::fs;
use audio_samples::sine_wave;
let sample_rate = sample_rate!(44100);
let sine_samples = sine_wave::<f32>(440.0, Duration::from_secs_f64(0.1), sample_rate, 0.5);
let output_path = std::env::temp_dir().join("test_lib_write.wav");
write(&output_path, &sine_samples).expect("Failed to write WAV file");
assert!(fs::metadata(&output_path).is_ok(), "Output file should exist");
let read_back = read::<_, f32>(&output_path).expect("Failed to read back WAV file");
assert_eq!(read_back.sample_rate(), sample_rate);
assert_eq!(read_back.total_samples(), sine_samples.total_samples());
assert_eq!(read_back.num_channels(), sine_samples.num_channels());
let original_bytes = sine_samples.bytes().expect("bytes should be available");
let read_bytes = read_back.bytes().expect("bytes should be available");
assert_eq!(
original_bytes.as_slice().len(),
read_bytes.as_slice().len(),
"Audio data size should match"
);
let original_samples: &[f32] = bytemuck::cast_slice(original_bytes.as_slice());
let read_samples: &[f32] = bytemuck::cast_slice(read_bytes.as_slice());
for (i, (orig, read)) in original_samples.iter().zip(read_samples.iter()).enumerate() {
let diff = (orig - read).abs();
assert!(
diff < 1e-6,
"Sample {i} differs too much: {orig} vs {read} (diff: {diff})"
);
}
fs::remove_file(&output_path).ok();
}
#[test]
fn test_write_with_function() {
use std::io::Cursor;
use audio_samples::sine_wave;
let sample_rate = sample_rate!(22050);
let sine_samples = sine_wave::<i16>(880.0, Duration::from_secs_f64(0.05), sample_rate, 0.8);
let mut buffer = Vec::new();
let cursor = Cursor::new(&mut buffer);
write_with(cursor, &sine_samples, FileType::WAV).expect("Failed to write with cursor");
assert!(buffer.len() > 44, "Buffer should contain WAV header and data");
assert_eq!(&buffer[0..4], b"RIFF", "Should start with RIFF header");
assert_eq!(&buffer[8..12], b"WAVE", "Should contain WAVE identifier");
let temp_file = std::env::temp_dir().join("test_write_with_buffer.wav");
std::fs::write(&temp_file, &buffer).expect("Failed to write buffer to temp file");
let read_back = read::<_, i16>(&temp_file).expect("Failed to read back WAV from buffer");
assert_eq!(read_back.sample_rate(), sample_rate);
assert_eq!(read_back.total_samples(), sine_samples.total_samples());
assert_eq!(read_back.num_channels(), sine_samples.num_channels());
let original_bytes = sine_samples.bytes().expect("bytes should be available");
let read_bytes = read_back.bytes().expect("bytes should be available");
assert_eq!(
original_bytes.as_slice(),
read_bytes.as_slice(),
"Audio data should match exactly for i16"
);
std::fs::remove_file(&temp_file).ok();
}
#[test]
fn test_write_with_format_parameter() {
use std::io::Cursor;
use audio_samples::sine_wave;
let sample_rate = sample_rate!(44100);
let sine_samples = sine_wave::<f32>(440.0, Duration::from_secs_f64(0.01), sample_rate, 0.5);
let mut wav_buffer = Vec::new();
let wav_cursor = Cursor::new(&mut wav_buffer);
write_with(wav_cursor, &sine_samples, FileType::WAV).expect("Failed to write WAV format");
assert!(wav_buffer.len() > 44, "WAV buffer should contain header and data");
assert_eq!(&wav_buffer[0..4], b"RIFF", "Should start with RIFF header");
assert_eq!(&wav_buffer[8..12], b"WAVE", "Should contain WAVE identifier");
let mut buffer = Vec::new();
let cursor = Cursor::new(&mut buffer);
let result = write_with(cursor, &sine_samples, FileType::MP3);
assert!(result.is_err(), "Should return error for unsupported format");
let error_msg = format!("{}", result.expect_err("Expected error"));
assert!(
error_msg.contains("Unsupported format"),
"Error should mention unsupported format"
);
}
#[test]
fn test_write_different_formats() {
use std::fs;
use audio_samples::{AudioTypeConversion, sine_wave};
let sample_rate = sample_rate!(48000);
let sine_base = sine_wave::<f32>(1000.0, Duration::from_secs_f64(0.02), sample_rate, 0.3);
let test_cases = vec![
("i16", std::env::temp_dir().join("test_format_i16.wav")),
("f32", std::env::temp_dir().join("test_format_f32.wav")),
];
for (format_name, output_path) in test_cases {
match format_name {
"i16" => {
let samples_i16 = sine_base.to_format::<i16>();
write(&output_path, &samples_i16).expect("Failed to write i16 WAV");
let read_back = read::<_, i16>(&output_path).expect("Failed to read back i16 WAV");
assert_eq!(read_back.sample_rate(), sample_rate, "Sample rate mismatch for i16");
assert_eq!(
read_back.total_samples(),
samples_i16.total_samples(),
"Sample count mismatch for i16"
);
let wav_info = info(&output_path).expect("Failed to get WAV info for i16");
assert_eq!(wav_info.bits_per_sample, 16, "Bits per sample should be 16 for i16");
assert_eq!(
wav_info.sample_type,
audio_samples::SampleType::I16,
"Sample type should be I16"
);
},
"f32" => {
write(&output_path, &sine_base).expect("Failed to write f32 WAV");
let read_back = read::<_, f32>(&output_path).expect("Failed to read back f32 WAV");
assert_eq!(read_back.sample_rate(), sample_rate, "Sample rate mismatch for f32");
assert_eq!(
read_back.total_samples(),
sine_base.total_samples(),
"Sample count mismatch for f32"
);
let wav_info = info(&output_path).expect("Failed to get WAV info for f32");
assert_eq!(wav_info.bits_per_sample, 32, "Bits per sample should be 32 for f32");
assert_eq!(
wav_info.sample_type,
audio_samples::SampleType::F32,
"Sample type should be F32"
);
},
_ => unreachable!("Unknown format"),
}
assert!(
fs::metadata(&output_path).is_ok(),
"File should exist for {format_name}"
);
fs::remove_file(&output_path).ok();
}
}
#[test]
fn test_unsupported_format_error() {
use audio_samples::sine_wave;
let sample_rate = sample_rate!(44100);
let sine_samples = sine_wave::<f32>(440.0, Duration::from_secs_f64(0.01), sample_rate, 0.1);
let result = write(std::env::temp_dir().join("test.mp3"), &sine_samples);
assert!(result.is_err(), "Should fail for unsupported format");
let error_msg = format!("{}", result.expect_err("Expected error"));
assert!(
error_msg.contains("Unsupported"),
"Error should mention unsupported format"
);
}
}