use crate::convert::hz_to_octs;
use crate::matrix::Matrix;
use super::piptrack::{PiptrackParams, piptrack};
pub fn pitch_tuning(frequencies: &[f64], resolution: f64, bins_per_octave: usize) -> f64 {
let positive: Vec<f64> = frequencies.iter().copied().filter(|&f| f > 0.0).collect();
if positive.is_empty() {
return 0.0;
}
let residuals: Vec<f64> = positive
.iter()
.map(|&freq| {
let mut r = (bins_per_octave as f64 * hz_to_octs(freq)) % 1.0;
if r < 0.0 {
r += 1.0;
}
if r >= 0.5 {
r -= 1.0;
}
r
})
.collect();
let n_bins = (1.0 / resolution).ceil() as usize + 1;
let mut counts = vec![0usize; n_bins];
for &r in &residuals {
let bin = ((r + 0.5) / resolution).floor() as usize;
let bin = bin.min(n_bins - 1);
counts[bin] += 1;
}
let best_bin = counts
.iter()
.enumerate()
.max_by_key(|&(_, c)| *c)
.map(|(i, _)| i)
.unwrap_or(0);
-0.5 + (best_bin as f64) * resolution
}
pub fn estimate_tuning(
y: Option<&[f64]>,
sr: f64,
s: Option<&Matrix>,
n_fft: usize,
resolution: f64,
bins_per_octave: usize,
) -> f64 {
let params = PiptrackParams {
sr,
n_fft,
..Default::default()
};
let (pitches, mags) = piptrack(y, s, ¶ms);
let n_bins = pitches.rows();
let n_frames = pitches.cols();
let mut voiced_pitches = Vec::new();
let mut voiced_mags = Vec::new();
for t in 0..n_frames {
for k in 0..n_bins {
let p = pitches.get(k, t);
if p > 0.0 {
voiced_pitches.push(p);
voiced_mags.push(mags.get(k, t));
}
}
}
if voiced_pitches.is_empty() {
return 0.0;
}
let mut sorted_mags = voiced_mags.clone();
sorted_mags.sort_by(|a, b| a.total_cmp(b));
let median = sorted_mags[sorted_mags.len() / 2];
let high_conf: Vec<f64> = voiced_pitches
.iter()
.zip(voiced_mags.iter())
.filter(|(_, m)| **m >= median)
.map(|(p, _)| *p)
.collect();
pitch_tuning(&high_conf, resolution, bins_per_octave)
}