1use std::{collections::HashMap, ops::Range};
2
3use crate::{
4 core::YinCore,
5 hmm::{PitchCandidate, PitchHmm},
6 PitchDetector,
7};
8
9const PYIN_N_THRESHOLDS: usize = 100;
10const PYIN_MIN_THRESHOLD: f64 = 0.025;
11
12const BETA_DISTRIBUTION_0: [f64; 100] = [
14 0.000000, 0.029069, 0.048836, 0.061422, 0.068542, 0.071571, 0.071607, 0.069516, 0.065976,
15 0.061512, 0.056523, 0.051309, 0.046089, 0.041021, 0.036211, 0.031727, 0.027608, 0.023871,
16 0.020517, 0.017534, 0.014903, 0.012601, 0.010601, 0.008875, 0.007393, 0.006130, 0.005059,
17 0.004155, 0.003397, 0.002765, 0.002239, 0.001806, 0.001449, 0.001157, 0.000920, 0.000727,
18 0.000572, 0.000448, 0.000349, 0.000271, 0.000209, 0.000160, 0.000122, 0.000092, 0.000070,
19 0.000052, 0.000039, 0.000029, 0.000021, 0.000015, 0.000011, 0.000008, 0.000006, 0.000004,
20 0.000003, 0.000002, 0.000001, 0.000001, 0.000001, 0.000000, 0.000000, 0.000000, 0.000000,
21 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
22 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
23 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
24 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
25 0.000000,
26];
27
28pub struct Pyin {
30 core: YinCore,
31 hmm: PitchHmm,
32}
33
34impl Pyin {
35 pub fn new(input_size: usize, sample_rate: usize) -> Self {
36 let hmm = PitchHmm::new(0.5, None);
37 let core = YinCore::new(input_size, sample_rate);
38
39 Self { core, hmm }
40 }
41
42 fn probabilistic_threshold(&self, frequency_range: Option<Range<f64>>) -> Vec<PitchCandidate> {
43 let tau_range = self.core.calculate_tau_range(frequency_range);
44
45 let mut tau_prob_dist = HashMap::new();
47
48 for n in 0..PYIN_N_THRESHOLDS {
49 let threshold = (n + 1) as f64 * PYIN_MIN_THRESHOLD;
50 let tau = self.core.threshold(threshold, &tau_range);
51
52 if tau >= 0 {
53 *tau_prob_dist.entry(tau as usize).or_insert(0.0) += BETA_DISTRIBUTION_0[n];
54 }
55 }
56
57 let mut pitch_candidates = Vec::new();
58 for (tau, probability) in tau_prob_dist {
59 let f0 = self.core.sample_rate as f64 / self.core.parabolic_interpolation(tau);
60
61 if f0 != -0.0 {
62 pitch_candidates.push(PitchCandidate::new(f0, probability));
63 }
64 }
65
66 pitch_candidates
67 }
68}
69
70impl PitchDetector for Pyin {
71 fn pitch(&mut self, audio_buffer: &[f64], frequency_range: Option<Range<f64>>) -> f64 {
72 self.core.preprocess(audio_buffer);
73 let f0_estimates = self.probabilistic_threshold(frequency_range);
74
75 self.core.fft.clear();
76 self.hmm.inference(&f0_estimates)
77 }
78}