#![cfg(feature = "std")]
use std::collections::HashSet;
use audiofp::classical::{Panako, Wang};
use audiofp::io::decode_to_mono_at;
use audiofp::{AudioBuffer, Fingerprinter, SampleRate};
fn wang_hashes(path: &str) -> HashSet<u32> {
let samples =
decode_to_mono_at(path, 8_000).unwrap_or_else(|e| panic!("failed to decode {path}: {e}"));
let mut wang = Wang::default();
wang.extract(AudioBuffer::new(&samples, SampleRate::HZ_8000))
.unwrap()
.hashes
.into_iter()
.map(|h| h.hash)
.collect()
}
fn jaccard(a: &HashSet<u32>, b: &HashSet<u32>) -> f32 {
let union = a.union(b).count();
if union == 0 {
return 0.0;
}
a.intersection(b).count() as f32 / union as f32
}
#[test]
fn aiff_decodes_and_matches_flac() {
let ref_h = wang_hashes("tests/assets/galway.flac");
let aiff_h = wang_hashes("tests/assets/galway.aiff");
let j = jaccard(&ref_h, &aiff_h);
eprintln!("Wang AIFF vs FLAC: Jaccard = {j:.3}");
assert!(j >= 0.95, "AIFF vs FLAC Jaccard = {j:.3} (expected ≥0.95)");
}
#[test]
fn same_track_different_codecs_high_overlap() {
let freak_flac = wang_hashes("tests/assets/freak.flac");
let freak_mp3 = wang_hashes("tests/assets/freak.mp3");
let j = jaccard(&freak_flac, &freak_mp3);
eprintln!("Freak MP3 vs FLAC: Jaccard = {j:.3}");
assert!(
j >= 0.20,
"Same track cross-codec Jaccard = {j:.3} (expected ≥0.20)"
);
}
#[test]
fn different_tracks_near_zero_overlap() {
let galway = wang_hashes("tests/assets/galway.flac");
let freak = wang_hashes("tests/assets/freak.flac");
let j = jaccard(&galway, &freak);
eprintln!("Galway vs Freak: Jaccard = {j:.3} (should be near 0)");
assert!(
j < 0.05,
"Different tracks overlap = {j:.3} (expected <0.05 — fingerprints not discriminating!)"
);
}
#[test]
fn identification_scenario_cross_codec() {
let query = wang_hashes("tests/assets/galway.mp3");
let db_galway = wang_hashes("tests/assets/galway.flac");
let db_freak = wang_hashes("tests/assets/freak.flac");
let overlap_galway = jaccard(&query, &db_galway);
let overlap_freak = jaccard(&query, &db_freak);
eprintln!("Query=galway.mp3 vs DB galway.flac: {overlap_galway:.3}");
eprintln!("Query=galway.mp3 vs DB freak.flac: {overlap_freak:.3}");
assert!(
overlap_galway > overlap_freak * 5.0,
"Identification failed: galway overlap ({overlap_galway:.3}) not >> freak overlap ({overlap_freak:.3})"
);
}
#[test]
fn mono_vs_stereo_same_track_high_overlap() {
let mono_h = wang_hashes("tests/assets/galway.mp3");
let stereo_h = wang_hashes("tests/assets/galway_stereo.mp3");
let j = jaccard(&mono_h, &stereo_h);
eprintln!("Wang mono vs stereo MP3: Jaccard = {j:.3}");
assert!(
j >= 0.25,
"Mono vs stereo Jaccard = {j:.3} (expected ≥0.25)"
);
}
#[test]
fn mono_vs_stereo_flac() {
let mono_h = wang_hashes("tests/assets/galway.flac");
let stereo_h = wang_hashes("tests/assets/galway_stereo.flac");
let j = jaccard(&mono_h, &stereo_h);
eprintln!("Wang mono vs stereo FLAC: Jaccard = {j:.3}");
assert!(
j >= 0.60,
"Mono vs stereo FLAC Jaccard = {j:.3} (expected ≥0.60)"
);
}
#[test]
fn sample_rate_ladder_all_produce_hashes() {
let rates = [
("tests/assets/freak_8000hz.mp3", 8000),
("tests/assets/freak_11025hz.mp3", 11025),
("tests/assets/freak_16000hz.mp3", 16000),
("tests/assets/freak_22050hz.mp3", 22050),
("tests/assets/freak_32000hz.mp3", 32000),
("tests/assets/freak_44100hz.mp3", 44100),
];
for (path, native_sr) in rates {
let samples = decode_to_mono_at(path, 8_000)
.unwrap_or_else(|e| panic!("{path} (native {native_sr} Hz) decode failed: {e}"));
let mut wang = Wang::default();
let fp = wang
.extract(AudioBuffer::new(&samples, SampleRate::HZ_8000))
.unwrap_or_else(|e| panic!("{path} extract failed: {e}"));
assert!(
fp.hashes.len() > 20,
"{path} (native {native_sr} Hz): only {} hashes (expected >20)",
fp.hashes.len()
);
eprintln!("{path} ({native_sr} Hz → 8kHz): {} hashes", fp.hashes.len());
}
}
#[test]
fn sample_rate_ladder_overlap_with_reference() {
let ref_h = wang_hashes("tests/assets/freak_44100hz.mp3");
let variants = [
"tests/assets/freak_8000hz.mp3",
"tests/assets/freak_11025hz.mp3",
"tests/assets/freak_16000hz.mp3",
"tests/assets/freak_22050hz.mp3",
"tests/assets/freak_32000hz.mp3",
];
for path in variants {
let h = wang_hashes(path);
let j = jaccard(&ref_h, &h);
eprintln!("{path} vs 44.1kHz ref: Jaccard = {j:.3}");
assert!(
j >= 0.05,
"{path}: Jaccard vs 44.1kHz = {j:.3} (expected ≥0.05)"
);
}
}
#[test]
fn panako_sample_rate_ladder() {
let ref_samples = decode_to_mono_at("tests/assets/freak_44100hz.mp3", 8_000).unwrap();
let mut panako = Panako::default();
let ref_fp = panako
.extract(AudioBuffer::new(&ref_samples, SampleRate::HZ_8000))
.unwrap();
let ref_set: HashSet<u32> = ref_fp.hashes.iter().map(|h| h.hash).collect();
let variants = [
"tests/assets/freak_8000hz.mp3",
"tests/assets/freak_22050hz.mp3",
"tests/assets/freak_32000hz.mp3",
];
for path in variants {
let samples = decode_to_mono_at(path, 8_000).unwrap();
let fp = panako
.extract(AudioBuffer::new(&samples, SampleRate::HZ_8000))
.unwrap();
let h: HashSet<u32> = fp.hashes.iter().map(|h| h.hash).collect();
let j = jaccard(&ref_set, &h);
eprintln!("Panako {path} vs 44.1kHz: Jaccard = {j:.3}");
assert!(
fp.hashes.len() > 20,
"Panako {path}: only {} hashes",
fp.hashes.len()
);
}
}