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