| |
|
|
| 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() |
| } |
|
|