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