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