aprender-core 0.31.2

Next-generation machine learning library in pure Rust
#![allow(clippy::disallowed_methods)]
#![cfg(feature = "audio")]

use aprender::audio::mel::{
    detect_clipping, stereo_to_mono, validate_audio, MelConfig, MelFilterbank,
};
use aprender::audio::resample::resample;
use aprender::audio::stream::{AudioChunker, ChunkConfig};

#[test]
fn verify_a1_mel_bins_80() {
    let config = MelConfig::whisper();
    let mel = MelFilterbank::new(&config);
    assert_eq!(mel.n_mels(), 80, "A1: Mel spectrogram must produce 80 bins");
}

#[test]
fn verify_a2_slaney_normalization() {
    let config = MelConfig::whisper();
    let mel = MelFilterbank::new(&config);
    let max_filter_val = mel.filters().iter().fold(0.0f32, |a, &b| f32::max(a, b));
    assert!(
        max_filter_val < 0.1,
        "A2: Filterbank must use Slaney normalization (max < 0.1), got {}",
        max_filter_val
    );
}

#[test]
fn verify_a3_silence_negative_mean() {
    let config = MelConfig::whisper();
    let mel = MelFilterbank::new(&config);
    let silence = vec![0.0; 16000];
    let spec = mel.compute(&silence).unwrap();
    let mean: f32 = spec.iter().sum::<f32>() / spec.len() as f32;
    assert!(
        mean < 0.0,
        "A3: Silence input must produce negative mel mean"
    );
}

#[test]
fn verify_a4_resample_preserves_duration() {
    let sr_in = 44100;
    let sr_out = 16000;
    let duration_sec = 1.0;
    let input_len = (duration_sec * sr_in as f32) as usize;
    let input = vec![0.0; input_len];
    let output = resample(&input, sr_in, sr_out).unwrap();
    let output_duration = output.len() as f32 / sr_out as f32;
    assert!(
        (output_duration - duration_sec).abs() < 0.001,
        "A4: Resample must preserve duration"
    );
}

#[test]
fn verify_a5_16khz_supported() {
    let config = MelConfig::whisper();
    assert_eq!(
        config.sample_rate, 16000,
        "A5: 16kHz must be supported sample rate"
    );
}

#[test]
fn verify_a6_streaming_chunker() {
    let chunk_config = ChunkConfig {
        chunk_size: 16000,
        overlap: 1600,
        sample_rate: 16000,
    };
    let mut chunker = AudioChunker::new(chunk_config);
    let audio = vec![0.0; 32000];
    chunker.push(&audio);
    assert!(chunker.has_chunk(), "A6: Streaming chunker should work");
}

#[test]
fn verify_a7_determinism() {
    let config = MelConfig::whisper();
    let mel = MelFilterbank::new(&config);
    let audio = vec![0.0; 16000];
    let spec1 = mel.compute(&audio).unwrap();
    let spec2 = mel.compute(&audio).unwrap();
    assert_eq!(spec1, spec2, "A7: Mel computation must be deterministic");
}

#[test]
fn verify_a8_fft_window_400() {
    let config = MelConfig::whisper();
    assert_eq!(config.n_fft, 400, "A8: FFT window size must be 400");
}

#[test]
fn verify_a9_hop_length_160() {
    let config = MelConfig::whisper();
    assert_eq!(config.hop_length, 160, "A9: Hop length must be 160");
}

#[test]
fn verify_a10_mel_range_0_8000() {
    let config = MelConfig::whisper();
    assert_eq!(config.fmin, 0.0, "A10: Mel min freq must be 0");
    assert_eq!(config.fmax, 8000.0, "A10: Mel max freq must be 8000");
}

#[test]
fn verify_a11_clipping_detection() {
    let samples = vec![1.1];
    let report = detect_clipping(&samples);
    assert!(report.has_clipping, "A11: Clipping detection failed");
}

#[test]
fn verify_a12_stereo_to_mono() {
    let stereo = vec![1.0, 0.0];
    let mono = stereo_to_mono(&stereo);
    assert_eq!(mono[0], 0.5, "A12: Stereo to mono failed");
}

#[test]
fn verify_a13_zero_length_error() {
    let result = validate_audio(&[]);
    assert!(result.is_err(), "A13: Zero-length audio must return error");
}

#[test]
fn verify_a14_nan_detection() {
    let result = validate_audio(&[f32::NAN]);
    assert!(result.is_err(), "A14: NaN in audio must be detected");
}

#[test]
fn verify_a15_inf_detection() {
    let result = validate_audio(&[f32::INFINITY]);
    assert!(result.is_err(), "A15: Inf in audio must be detected");
}