rosa 0.1.0

Audio feature extraction library matching librosa output
Documentation
use crate::AudioError;
use rubato::{
    Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
};

/// Resample audio from `orig_sr` to `target_sr`.
///
/// Uses rubato's sinc interpolation for high quality resampling.
/// Output length matches `ceil(y.len() * target_sr / orig_sr)`.
pub fn resample(y: &[f64], orig_sr: u32, target_sr: u32) -> Result<Vec<f64>, AudioError> {
    if orig_sr == target_sr {
        return Ok(y.to_vec());
    }

    let ratio = target_sr as f64 / orig_sr as f64;
    let expected_len = (y.len() as f64 * ratio).ceil() as usize;

    let params = SincInterpolationParameters {
        sinc_len: 256,
        f_cutoff: 0.95,
        interpolation: SincInterpolationType::Linear,
        oversampling_factor: 256,
        window: WindowFunction::BlackmanHarris2,
    };

    // Use a reasonable chunk size for processing
    let chunk_size = 1024.min(y.len());

    let mut resampler = SincFixedIn::<f64>::new(
        ratio, 2.0, // max_resample_ratio_relative
        params, chunk_size, 1, // 1 channel
    )
    .map_err(|e| AudioError::Resample(e.to_string()))?;

    let mut output = Vec::with_capacity(expected_len);

    // Process in chunks
    let mut pos = 0;
    while pos + chunk_size <= y.len() {
        let chunk = vec![y[pos..pos + chunk_size].to_vec()];
        let result = resampler
            .process(&chunk, None)
            .map_err(|e| AudioError::Resample(e.to_string()))?;
        output.extend_from_slice(&result[0]);
        pos += chunk_size;
    }

    // Process remaining samples
    if pos < y.len() {
        let remaining = &y[pos..];
        let chunk = vec![remaining.to_vec()];
        let result = resampler
            .process_partial(Some(&chunk), None)
            .map_err(|e| AudioError::Resample(e.to_string()))?;
        output.extend_from_slice(&result[0]);
    } else {
        // Flush any remaining samples in the resampler
        let result = resampler
            .process_partial::<Vec<f64>>(None, None)
            .map_err(|e| AudioError::Resample(e.to_string()))?;
        output.extend_from_slice(&result[0]);
    }

    // Fix output length to match expected
    output.truncate(expected_len);
    output.resize(expected_len, 0.0);

    Ok(output)
}