| |
|
|
| use crate::{Error, Result}; |
| use rubato::{ |
| FastFixedIn, PolynomialDegree, Resampler, |
| }; |
|
|
| use super::AudioData; |
|
|
| |
| |
| |
| pub fn resample(audio: &AudioData, target_sr: u32) -> Result<AudioData> { |
| if audio.sample_rate == target_sr { |
| return Ok(audio.clone()); |
| } |
|
|
| let resample_ratio = target_sr as f64 / audio.sample_rate as f64; |
|
|
| |
| let mut resampler = FastFixedIn::<f32>::new( |
| resample_ratio, |
| 1.0, |
| PolynomialDegree::Cubic, |
| 1024, |
| 1, |
| ).map_err(|e| Error::Audio(format!("Failed to create resampler: {}", e)))?; |
|
|
| |
| let input_frames_needed = resampler.input_frames_next(); |
| let mut input_buffer = vec![vec![0.0f32; input_frames_needed]]; |
| let mut output_samples = Vec::new(); |
|
|
| let mut pos = 0; |
| while pos < audio.samples.len() { |
| |
| let end = (pos + input_frames_needed).min(audio.samples.len()); |
| let chunk_size = end - pos; |
|
|
| input_buffer[0][..chunk_size].copy_from_slice(&audio.samples[pos..end]); |
|
|
| |
| if chunk_size < input_frames_needed { |
| input_buffer[0][chunk_size..].fill(0.0); |
| } |
|
|
| |
| let output = resampler |
| .process(&input_buffer, None) |
| .map_err(|e| Error::Audio(format!("Resampling failed: {}", e)))?; |
|
|
| output_samples.extend_from_slice(&output[0]); |
| pos += chunk_size; |
|
|
| if chunk_size < input_frames_needed { |
| break; |
| } |
| } |
|
|
| |
| let expected_len = (audio.samples.len() as f64 * resample_ratio).ceil() as usize; |
| output_samples.truncate(expected_len); |
|
|
| Ok(AudioData::new(output_samples, target_sr)) |
| } |
|
|
| |
| pub fn resample_to_22k(audio: &AudioData) -> Result<AudioData> { |
| resample(audio, 22050) |
| } |
|
|
| |
| pub fn resample_to_16k(audio: &AudioData) -> Result<AudioData> { |
| resample(audio, 16000) |
| } |
|
|