use crate::dsp::util::Norm;
use crate::matrix::Matrix;
use super::tempo::{TempoParams, tempo};
use super::tempogram::{TempogramParams, tempogram};
pub const DEFAULT_FACTORS: [f64; 13] = [
4.0,
8.0 / 3.0,
3.0,
2.0,
4.0 / 3.0,
3.0 / 2.0,
1.0,
2.0 / 3.0,
3.0 / 4.0,
1.0 / 2.0,
1.0 / 3.0,
3.0 / 8.0,
1.0 / 4.0,
];
pub struct TempogramRatioParams {
pub sr: f64,
pub hop_length: usize,
pub win_length: usize,
pub start_bpm: f64,
pub std_bpm: f64,
pub max_tempo: Option<f64>,
pub factors: Option<Vec<f64>>,
pub norm: Option<Norm>,
}
impl TempogramRatioParams {
pub fn new(sr: f64) -> Self {
Self {
sr,
hop_length: 512,
win_length: 384,
start_bpm: 120.0,
std_bpm: 1.0,
max_tempo: Some(320.0),
factors: None,
norm: Some(Norm::Linf),
}
}
}
pub fn tempogram_ratio(onset_envelope: &[f64], params: &TempogramRatioParams) -> Matrix {
let n_frames = onset_envelope.len();
if n_frames == 0 {
let n_factors = params
.factors
.as_ref()
.map(|f| f.len())
.unwrap_or(DEFAULT_FACTORS.len());
return Matrix::zeros(n_factors, 0);
}
let factors = params.factors.as_deref().unwrap_or(&DEFAULT_FACTORS);
let n_factors = factors.len();
let tg_params = TempogramParams {
sr: params.sr,
hop_length: params.hop_length,
win_length: params.win_length,
center: true,
norm: Some(Norm::Linf),
};
let tg = tempogram(onset_envelope, &tg_params);
let tg_rows = tg.rows(); let tg_cols = tg.cols();
let tempo_params = TempoParams {
sr: params.sr,
hop_length: params.hop_length,
start_bpm: params.start_bpm,
std_bpm: params.std_bpm,
ac_size: 8.0,
max_tempo: params.max_tempo,
};
let bpm = tempo(onset_envelope, &tempo_params);
let mut data = vec![0.0; n_factors * tg_cols];
for (fi, &factor) in factors.iter().enumerate() {
let target_bpm = factor * bpm;
if target_bpm <= 0.0 || !target_bpm.is_finite() {
continue;
}
let target_lag = 60.0 * params.sr / (params.hop_length as f64 * target_bpm);
if target_lag < 0.0 || target_lag >= (tg_rows - 1) as f64 {
continue;
}
let lag_floor = target_lag.floor() as usize;
let lag_ceil = (lag_floor + 1).min(tg_rows - 1);
let frac = target_lag - lag_floor as f64;
for t in 0..tg_cols {
let v0 = tg.get(lag_floor, t);
let v1 = tg.get(lag_ceil, t);
data[fi * tg_cols + t] = v0 + frac * (v1 - v0);
}
}
let mut result = Matrix::from_vec(data, n_factors, tg_cols);
if let Some(norm) = params.norm {
for t in 0..tg_cols {
let mut col: Vec<f64> = (0..n_factors).map(|f| result.get(f, t)).collect();
crate::dsp::util::normalize(&mut col, norm);
for (f, &val) in col.iter().enumerate() {
*result.get_mut(f, t) = val;
}
}
}
result
}