rosa 0.1.0

Audio feature extraction library matching librosa output
Documentation
mod common;

use rosa::effects::{deemphasis, preemphasis, split, trim};

#[test]
fn test_preemphasis_matches_librosa() {
    let ref_data = common::load_audio_reference("effects.json");
    let expected: Vec<f64> = serde_json::from_value(ref_data["preemphasis"].clone()).unwrap();
    let original: Vec<f64> = serde_json::from_value(ref_data["original"].clone()).unwrap();

    let result = preemphasis(&original, 0.97);

    // Compare first 1000 samples
    let result_slice = &result[..result.len().min(expected.len())];
    assert_eq!(
        result_slice.len(),
        expected.len(),
        "preemphasis length mismatch"
    );
    common::assert_approx_eq(result_slice, &expected, 1e-10, "preemphasis");
}

#[test]
fn test_deemphasis_roundtrip_matches_librosa() {
    let ref_data = common::load_audio_reference("effects.json");
    let expected: Vec<f64> =
        serde_json::from_value(ref_data["deemphasis_roundtrip"].clone()).unwrap();
    let original: Vec<f64> = serde_json::from_value(ref_data["original"].clone()).unwrap();

    // preemphasis then deemphasis should recover original
    let pre = preemphasis(&original, 0.97);
    let result = deemphasis(&pre, 0.97);

    let result_slice = &result[..result.len().min(expected.len())];
    assert_eq!(
        result_slice.len(),
        expected.len(),
        "deemphasis roundtrip length mismatch"
    );
    common::assert_approx_eq(result_slice, &expected, 1e-6, "deemphasis_roundtrip");
}

#[test]
fn test_trim_matches_librosa() {
    let ref_data = common::load_audio_reference("effects.json");
    let expected_start = ref_data["trim_start"].as_u64().unwrap() as usize;
    let expected_end = ref_data["trim_end"].as_u64().unwrap() as usize;

    let wav = common::wav_path("20_Pink Noise mono 500Hz_2kHz -20dBFS 48k.wav");
    let (samples, _) = common::read_wav_mono(&wav);

    let (start, end) = trim(&samples, 60.0, 2048, 512);
    assert_eq!(
        start, expected_start,
        "trim start mismatch: got {start}, expected {expected_start}"
    );
    assert_eq!(
        end, expected_end,
        "trim end mismatch: got {end}, expected {expected_end}"
    );
}

#[test]
fn test_split_matches_librosa() {
    let ref_data = common::load_audio_reference("effects.json");
    let expected_intervals: Vec<Vec<usize>> =
        serde_json::from_value(ref_data["split_intervals"].clone()).unwrap();

    let wav = common::wav_path("20_Pink Noise mono 500Hz_2kHz -20dBFS 48k.wav");
    let (samples, _) = common::read_wav_mono(&wav);

    let result = split(&samples, 60.0, 2048, 512);

    assert_eq!(
        result.len(),
        expected_intervals.len(),
        "split interval count mismatch"
    );

    for (i, (actual, expected)) in result.iter().zip(expected_intervals.iter()).enumerate() {
        assert_eq!(
            actual.0, expected[0],
            "split interval {i} start mismatch: got {}, expected {}",
            actual.0, expected[0]
        );
        assert_eq!(
            actual.1, expected[1],
            "split interval {i} end mismatch: got {}, expected {}",
            actual.1, expected[1]
        );
    }
}