rosa 0.1.0

Audio feature extraction library matching librosa output
Documentation
mod common;

#[test]
fn test_mel_spectrogram_matches_librosa() {
    let ref_data = common::load_audio_reference("mel_spectrogram.json");
    let sr = ref_data["sample_rate"].as_u64().unwrap() as f64;
    let n_fft = ref_data["n_fft"].as_u64().unwrap() as usize;
    let hop_length = ref_data["hop_length"].as_u64().unwrap() as usize;
    let n_mels = ref_data["n_mels"].as_u64().unwrap() as usize;
    let power = ref_data["power"].as_f64().unwrap();
    let expected_n_frames = ref_data["n_frames"].as_u64().unwrap() as usize;

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

    let mut params = rosa::stft::MelSpectrogramParams::new(sr);
    params.stft = rosa::stft::StftParams::new(n_fft);
    assert_eq!(params.stft.hop_length, hop_length);
    params.mel.n_mels = n_mels;
    params.power = power;

    let mel_spec = rosa::stft::mel_spectrogram(&samples, &params);

    assert_eq!(
        mel_spec.rows(),
        n_mels,
        "n_mels mismatch: got {}, expected {}",
        mel_spec.rows(),
        n_mels
    );
    assert_eq!(
        mel_spec.cols(),
        expected_n_frames,
        "n_frames mismatch: got {}, expected {}",
        mel_spec.cols(),
        expected_n_frames
    );

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

    for frame_ref in frames {
        let idx = frame_ref["index"].as_u64().unwrap() as usize;
        let expected_mel: Vec<f64> =
            serde_json::from_value(frame_ref["mel_spectrum"].clone()).unwrap();
        assert_eq!(expected_mel.len(), n_mels);

        let actual_mel: Vec<f64> = (0..n_mels).map(|bin| mel_spec[(bin, idx)]).collect();
        common::assert_approx_eq(
            &actual_mel,
            &expected_mel,
            1e-4,
            &format!("mel spectrogram frame {idx}"),
        );
    }
}