use crate::matrix::Matrix;
use crate::stft::{StftParams, fft_frequencies};
pub struct PiptrackParams {
pub sr: f64,
pub n_fft: usize,
pub hop_length: Option<usize>,
pub fmin: f64,
pub fmax: f64,
pub threshold: f64,
}
impl Default for PiptrackParams {
fn default() -> Self {
Self {
sr: 22050.0,
n_fft: 2048,
hop_length: None,
fmin: 150.0,
fmax: 4000.0,
threshold: 0.1,
}
}
}
pub fn piptrack(
y: Option<&[f64]>,
s: Option<&Matrix>,
params: &PiptrackParams,
) -> (Matrix, Matrix) {
let n_fft = params.n_fft;
let sr = params.sr;
let owned_s;
let mag = match s {
Some(s) => s,
None => {
let y = y.expect("piptrack: either y or S must be provided");
let stft_params = StftParams {
n_fft,
hop_length: params.hop_length.unwrap_or(n_fft / 4),
win_length: n_fft,
center: true,
window: None,
};
owned_s = crate::stft::spectrogram(y, &stft_params, 1.0);
&owned_s
}
};
let n_bins = mag.rows();
let n_frames = mag.cols();
if n_frames == 0 || n_bins < 3 {
return (
Matrix::zeros(n_bins, n_frames),
Matrix::zeros(n_bins, n_frames),
);
}
let fft_freqs = fft_frequencies(sr, n_fft);
let fmin = params.fmin.max(0.0);
let fmax = params.fmax.min(sr / 2.0);
let mut pitches = Matrix::zeros(n_bins, n_frames);
let mut magnitudes = Matrix::zeros(n_bins, n_frames);
for t in 0..n_frames {
let mut frame_max = f64::NEG_INFINITY;
for k in 0..n_bins {
let v = mag.get(k, t);
if v > frame_max {
frame_max = v;
}
}
let ref_value = params.threshold * frame_max;
for k in 1..n_bins - 1 {
let freq = fft_freqs[k];
if freq < fmin || freq >= fmax {
continue;
}
let v = mag.get(k, t);
let v_prev = mag.get(k - 1, t);
let v_next = mag.get(k + 1, t);
if v < ref_value {
continue;
}
if !(v > v_prev && v >= v_next) {
continue;
}
let a = v_prev + v_next - 2.0 * v;
let b = (v_next - v_prev) / 2.0;
let shift = if b.abs() < a.abs() { -b / a } else { 0.0 };
let avg = (v_next - v_prev) / 2.0;
let dskew = 0.5 * avg * shift;
*pitches.get_mut(k, t) = (k as f64 + shift) * sr / n_fft as f64;
*magnitudes.get_mut(k, t) = v + dskew;
}
}
(pitches, magnitudes)
}