use crate::matrix::Matrix;
use crate::stft::fft_frequencies;
pub fn tempo_frequencies(n_bins: usize, hop_length: usize, sr: f64) -> Vec<f64> {
let mut freqs = vec![0.0; n_bins];
if n_bins > 0 {
freqs[0] = f64::INFINITY;
}
for i in 1..n_bins {
freqs[i] = 60.0 * sr / (hop_length as f64 * i as f64);
}
freqs
}
pub fn fourier_tempo_frequencies(sr: f64, win_length: usize, hop_length: usize) -> Vec<f64> {
let sr_bpm = sr * 60.0 / hop_length as f64;
fft_frequencies(sr_bpm, win_length)
}
pub struct TempoParams {
pub sr: f64,
pub hop_length: usize,
pub start_bpm: f64,
pub std_bpm: f64,
pub ac_size: f64,
pub max_tempo: Option<f64>,
}
impl TempoParams {
pub fn new(sr: f64) -> Self {
Self {
sr,
hop_length: 512,
start_bpm: 120.0,
std_bpm: 1.0,
ac_size: 8.0,
max_tempo: Some(320.0),
}
}
}
pub fn tempo(onset_envelope: &[f64], params: &TempoParams) -> f64 {
let win_length = (params.ac_size * params.sr / params.hop_length as f64).round() as usize;
if win_length < 2 {
return params.start_bpm;
}
let tg_params = super::tempogram::TempogramParams {
sr: params.sr,
hop_length: params.hop_length,
win_length,
center: true,
norm: Some(crate::dsp::util::Norm::Linf),
};
let tg = super::tempogram::tempogram(onset_envelope, &tg_params);
tempo_from_tempogram(&tg, win_length, params)
}
pub(crate) fn tempo_from_tempogram(tg: &Matrix, win_length: usize, params: &TempoParams) -> f64 {
let n_frames = tg.cols();
let mut tg_mean = vec![0.0; win_length];
if n_frames > 0 {
for lag in 0..win_length {
let mut sum = 0.0;
for t in 0..n_frames {
sum += tg.get(lag, t);
}
tg_mean[lag] = sum / n_frames as f64;
}
}
let bpms = tempo_frequencies(win_length, params.hop_length, params.sr);
let mut logprior = vec![f64::NEG_INFINITY; win_length];
let log2_start = params.start_bpm.log2();
for i in 1..win_length {
if bpms[i].is_finite() && bpms[i] > 0.0 {
let log2_bpm = bpms[i].log2();
logprior[i] = -0.5 * ((log2_bpm - log2_start) / params.std_bpm).powi(2);
}
}
if let Some(max_tempo) = params.max_tempo {
for i in 0..win_length {
if bpms[i] < max_tempo {
break;
}
logprior[i] = f64::NEG_INFINITY;
}
}
let mut best_idx = 1; let mut best_score = f64::NEG_INFINITY;
for i in 1..win_length {
let score = (1e6_f64 * tg_mean[i]).ln_1p() + logprior[i];
if score > best_score {
best_score = score;
best_idx = i;
}
}
bpms[best_idx]
}