use std::io::Read;
use std::path::Path;
use crate::error::{NidhiError, Result};
use crate::sample::Sample;
pub fn load_wav<P: AsRef<Path>>(path: P) -> Result<Sample> {
let reader = hound::WavReader::open(path.as_ref())
.map_err(|e| NidhiError::ImportError(format!("failed to open WAV: {e}")))?;
let spec = reader.spec();
let channels = spec.channels as u32;
let sample_rate = spec.sample_rate;
let data = read_wav_samples(reader)?;
let name = path
.as_ref()
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
let sample = if channels == 1 {
Sample::from_mono(data, sample_rate)
} else {
Sample::from_stereo(data, sample_rate)
};
Ok(sample.with_name(name))
}
pub fn load_wav_from_memory(data: &[u8]) -> Result<Sample> {
let cursor = std::io::Cursor::new(data);
let reader = hound::WavReader::new(cursor)
.map_err(|e| NidhiError::ImportError(format!("failed to parse WAV: {e}")))?;
let spec = reader.spec();
let channels = spec.channels as u32;
let sample_rate = spec.sample_rate;
let samples = read_wav_samples(reader)?;
let sample = if channels == 1 {
Sample::from_mono(samples, sample_rate)
} else {
Sample::from_stereo(samples, sample_rate)
};
Ok(sample)
}
fn read_wav_samples<R: Read>(reader: hound::WavReader<R>) -> Result<Vec<f32>> {
let spec = reader.spec();
match spec.sample_format {
hound::SampleFormat::Int => {
let bits = spec.bits_per_sample;
let scale = 1.0 / (1u32 << (bits - 1)) as f32;
reader
.into_samples::<i32>()
.map(|s| {
s.map(|v| v as f32 * scale)
.map_err(|e| NidhiError::ImportError(format!("WAV sample error: {e}")))
})
.collect()
}
hound::SampleFormat::Float => reader
.into_samples::<f32>()
.map(|s| s.map_err(|e| NidhiError::ImportError(format!("WAV sample error: {e}"))))
.collect(),
}
}
pub struct StreamingWavReader {
reader: hound::WavReader<std::io::BufReader<std::fs::File>>,
channels: u32,
sample_rate: u32,
total_frames: usize,
frames_read: usize,
}
impl StreamingWavReader {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let reader = hound::WavReader::open(path.as_ref()).map_err(|e| {
NidhiError::ImportError(format!("failed to open WAV for streaming: {e}"))
})?;
let spec = reader.spec();
let total_samples = reader.len() as usize;
let channels = spec.channels as u32;
Ok(Self {
reader,
channels,
sample_rate: spec.sample_rate,
total_frames: total_samples / channels as usize,
frames_read: 0,
})
}
#[must_use]
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
#[must_use]
pub fn channels(&self) -> u32 {
self.channels
}
#[must_use]
pub fn total_frames(&self) -> usize {
self.total_frames
}
#[must_use]
pub fn frames_read(&self) -> usize {
self.frames_read
}
pub fn read_chunk(&mut self, chunk_frames: usize) -> Result<Vec<f32>> {
let remaining = self.total_frames - self.frames_read;
let frames_to_read = chunk_frames.min(remaining);
if frames_to_read == 0 {
return Ok(Vec::new());
}
let samples_to_read = frames_to_read * self.channels as usize;
let spec = self.reader.spec();
let mut data = Vec::with_capacity(samples_to_read);
match spec.sample_format {
hound::SampleFormat::Int => {
let bits = spec.bits_per_sample;
let scale = 1.0 / (1u32 << (bits - 1)) as f32;
for s in self.reader.samples::<i32>().take(samples_to_read) {
let v =
s.map_err(|e| NidhiError::ImportError(format!("WAV stream error: {e}")))?;
data.push(v as f32 * scale);
}
}
hound::SampleFormat::Float => {
for s in self.reader.samples::<f32>().take(samples_to_read) {
let v =
s.map_err(|e| NidhiError::ImportError(format!("WAV stream error: {e}")))?;
data.push(v);
}
}
}
self.frames_read += data.len() / self.channels as usize;
Ok(data)
}
pub fn read_all(mut self) -> Result<Sample> {
let data = self.read_chunk(self.total_frames)?;
let sample = if self.channels == 1 {
Sample::from_mono(data, self.sample_rate)
} else {
Sample::from_stereo(data, self.sample_rate)
};
Ok(sample)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_wav_bytes(samples: &[f32], channels: u16, sample_rate: u32) -> Vec<u8> {
let mut cursor = std::io::Cursor::new(Vec::new());
let spec = hound::WavSpec {
channels,
sample_rate,
bits_per_sample: 32,
sample_format: hound::SampleFormat::Float,
};
let mut writer = hound::WavWriter::new(&mut cursor, spec).unwrap();
for &s in samples {
writer.write_sample(s).unwrap();
}
writer.finalize().unwrap();
cursor.into_inner()
}
#[test]
fn load_wav_from_memory_mono() {
let data = vec![0.1f32, 0.2, 0.3, 0.4];
let wav_bytes = make_wav_bytes(&data, 1, 44100);
let sample = load_wav_from_memory(&wav_bytes).unwrap();
assert_eq!(sample.frames(), 4);
assert_eq!(sample.channels(), 1);
assert_eq!(sample.sample_rate(), 44100);
}
#[test]
fn load_wav_from_memory_stereo() {
let data = vec![0.1f32, -0.1, 0.2, -0.2]; let wav_bytes = make_wav_bytes(&data, 2, 48000);
let sample = load_wav_from_memory(&wav_bytes).unwrap();
assert_eq!(sample.frames(), 2);
assert_eq!(sample.channels(), 2);
assert_eq!(sample.sample_rate(), 48000);
}
#[test]
fn load_wav_from_memory_invalid() {
assert!(load_wav_from_memory(&[0, 1, 2, 3]).is_err());
}
}