rosa 0.1.0

Audio feature extraction library matching librosa output
Documentation
mod common;

use rosa::dsp::util::{self, Norm};

#[test]
fn test_normalized_frames_match_librosa() {
    let ref_data = common::load_audio_reference("normalized_frames.json");
    let frame_length = ref_data["frame_length"].as_u64().unwrap() as usize;
    let hop_length = ref_data["hop_length"].as_u64().unwrap() as usize;

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

    let cases = ref_data["cases"].as_array().unwrap();
    assert!(!cases.is_empty(), "No test cases found");

    for case in cases {
        let frame_idx = case["frame_index"].as_u64().unwrap() as usize;
        let expected_l1: Vec<f64> = serde_json::from_value(case["norm_l1"].clone()).unwrap();
        let expected_l2: Vec<f64> = serde_json::from_value(case["norm_l2"].clone()).unwrap();
        let expected_linf: Vec<f64> = serde_json::from_value(case["norm_linf"].clone()).unwrap();

        let frame = common::extract_frame(&samples, frame_length, hop_length, frame_idx);

        // L1
        let mut l1 = frame.clone();
        util::normalize(&mut l1, Norm::L1);
        common::assert_approx_eq(&l1, &expected_l1, 1e-12, &format!("frame {frame_idx} L1"));

        // L2
        let mut l2 = frame.clone();
        util::normalize(&mut l2, Norm::L2);
        common::assert_approx_eq(&l2, &expected_l2, 1e-12, &format!("frame {frame_idx} L2"));

        // Linf
        let mut linf = frame.clone();
        util::normalize(&mut linf, Norm::Linf);
        common::assert_approx_eq(
            &linf,
            &expected_linf,
            1e-12,
            &format!("frame {frame_idx} Linf"),
        );
    }
}