use common_dsp::{hz_to_midi, midi_to_hz, note_name, wav, Crossover, Saw, Yin}; fn cents(a: f32, b: f32) -> f32 { 1200. * (a / b).log2() } #[test] fn yin_tracks_bass_fundamentals_within_two_cents() { let rate = 48000.; let mut yin = Yin::new(2400); for hz in [43.65, 55., 110., 220.] { let mut saw = Saw::new(rate, hz); let x: Vec = (0..6000).map(|_| saw.sample()).collect(); let (got, clarity) = yin.estimate(&x, rate, 25., 1000.).unwrap(); assert!(cents(got, hz).abs() < 2., "{hz}: {got}"); assert!(clarity > 0.8); } } #[test] fn yin_rejects_silence_and_noise() { let mut yin = Yin::new(2400); assert!(yin.estimate(&[0.; 6000], 48000., 25., 1000.).is_none()); let mut seed = 1u32; let noise: Vec = (0..6000) .map(|_| { seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); (seed >> 8) as f32 / (1u32 << 24) as f32 - 0.5 }) .collect(); assert!(yin.estimate(&noise, 48000., 25., 1000.).is_none()); } #[test] fn crossover_bands_sum_to_flat_magnitude() { let rate = 48000.; for hz in [20., 45., 90., 180., 1000., 8000., 18000.] { let mut x = Crossover::new(rate, 90.); let (mut a, mut b) = (0f64, 0f64); let n = 96000; for i in 0..n { let s = (std::f32::consts::TAU * hz * i as f32 / rate).sin(); let (l, h) = x.process(s); if i > n / 2 { a += (s as f64).powi(2); b += ((l + h) as f64).powi(2); } } let db = 10. * (b / a).log10(); assert!(db.abs() < 0.1, "{hz} Hz: {db} dB"); } } #[test] fn notes_round_trip() { assert!((midi_to_hz(29.) - 43.65).abs() < 0.01); assert!((hz_to_midi(440.) - 69.).abs() < 1e-4); assert_eq!(note_name(29), "F1"); assert_eq!(note_name(61), "C#4"); } #[test] fn wave_round_trip_keeps_root_and_wavetable_chunks() { let w = wav::Wave { channels: 2, rate: 44100, samples: (0..999) .map(|i| (i as f32 * 0.01).sin()) .collect::>()[..998] .to_vec(), root: Some(29), frame: Some(2048), }; let bytes = wav::encode(&w); assert_eq!(&bytes[..4], b"RIFF"); assert_eq!( u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize, bytes.len() - 8 ); assert_eq!(wav::decode(&bytes).unwrap(), w); assert!(wav::decode(&bytes[..40]).is_err()); }