|
|
|
|
|
|
|
|
use crate::{Error, Result}; |
|
|
use hound::{SampleFormat, WavReader, WavSpec, WavWriter}; |
|
|
use std::path::Path; |
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
pub struct AudioData { |
|
|
|
|
|
pub samples: Vec<f32>, |
|
|
|
|
|
pub sample_rate: u32, |
|
|
} |
|
|
|
|
|
impl AudioData { |
|
|
|
|
|
pub fn new(samples: Vec<f32>, sample_rate: u32) -> Self { |
|
|
Self { |
|
|
samples, |
|
|
sample_rate, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn duration(&self) -> f32 { |
|
|
self.samples.len() as f32 / self.sample_rate as f32 |
|
|
} |
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize { |
|
|
self.samples.len() |
|
|
} |
|
|
|
|
|
|
|
|
pub fn is_empty(&self) -> bool { |
|
|
self.samples.is_empty() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn load_audio<P: AsRef<Path>>(path: P, target_sr: Option<u32>) -> Result<AudioData> { |
|
|
let path = path.as_ref(); |
|
|
if !path.exists() { |
|
|
return Err(Error::FileNotFound(path.display().to_string())); |
|
|
} |
|
|
|
|
|
let reader = WavReader::open(path).map_err(|e| Error::Audio(format!("Failed to open WAV: {}", e)))?; |
|
|
let spec = reader.spec(); |
|
|
let sample_rate = spec.sample_rate; |
|
|
let channels = spec.channels as usize; |
|
|
|
|
|
|
|
|
let samples: Vec<f32> = match spec.sample_format { |
|
|
SampleFormat::Float => { |
|
|
let samples: Vec<f32> = reader |
|
|
.into_samples::<f32>() |
|
|
.collect::<std::result::Result<Vec<_>, _>>() |
|
|
.map_err(|e| Error::Audio(format!("Failed to read samples: {}", e)))?; |
|
|
samples |
|
|
} |
|
|
SampleFormat::Int => { |
|
|
let bits = spec.bits_per_sample; |
|
|
let samples: Vec<i32> = reader |
|
|
.into_samples::<i32>() |
|
|
.collect::<std::result::Result<Vec<_>, _>>() |
|
|
.map_err(|e| Error::Audio(format!("Failed to read samples: {}", e)))?; |
|
|
|
|
|
|
|
|
let max_val = (1 << (bits - 1)) as f32; |
|
|
samples.iter().map(|&s| s as f32 / max_val).collect() |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
let mono_samples = if channels > 1 { |
|
|
samples |
|
|
.chunks(channels) |
|
|
.map(|chunk| chunk.iter().sum::<f32>() / channels as f32) |
|
|
.collect() |
|
|
} else { |
|
|
samples |
|
|
}; |
|
|
|
|
|
let mut audio = AudioData::new(mono_samples, sample_rate); |
|
|
|
|
|
|
|
|
if let Some(target) = target_sr { |
|
|
if target != sample_rate { |
|
|
audio = super::resample::resample(&audio, target)?; |
|
|
} |
|
|
} |
|
|
|
|
|
Ok(audio) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn save_audio<P: AsRef<Path>>(path: P, audio: &AudioData) -> Result<()> { |
|
|
let spec = WavSpec { |
|
|
channels: 1, |
|
|
sample_rate: audio.sample_rate, |
|
|
bits_per_sample: 32, |
|
|
sample_format: SampleFormat::Float, |
|
|
}; |
|
|
|
|
|
let mut writer = WavWriter::create(path, spec) |
|
|
.map_err(|e| Error::Audio(format!("Failed to create WAV writer: {}", e)))?; |
|
|
|
|
|
for &sample in &audio.samples { |
|
|
writer |
|
|
.write_sample(sample) |
|
|
.map_err(|e| Error::Audio(format!("Failed to write sample: {}", e)))?; |
|
|
} |
|
|
|
|
|
writer |
|
|
.finalize() |
|
|
.map_err(|e| Error::Audio(format!("Failed to finalize WAV: {}", e)))?; |
|
|
|
|
|
Ok(()) |
|
|
} |
|
|
|
|
|
|
|
|
pub fn save_samples<P: AsRef<Path>>(path: P, samples: &[f32], sample_rate: u32) -> Result<()> { |
|
|
let audio = AudioData::new(samples.to_vec(), sample_rate); |
|
|
save_audio(path, &audio) |
|
|
} |
|
|
|
|
|
|
|
|
pub fn load_audio_batch<P: AsRef<Path> + Sync>( |
|
|
paths: &[P], |
|
|
target_sr: Option<u32>, |
|
|
) -> Result<Vec<AudioData>> { |
|
|
use rayon::prelude::*; |
|
|
|
|
|
paths |
|
|
.par_iter() |
|
|
.map(|p| load_audio(p, target_sr)) |
|
|
.collect() |
|
|
} |
|
|
|