use crate::autocorrelate::autocorrelate;
use crate::dsp::util::{Norm, pad_linear_ramp};
use crate::matrix::{FrameIter, Matrix};
pub struct TempogramParams {
pub sr: f64,
pub hop_length: usize,
pub win_length: usize,
pub center: bool,
pub norm: Option<Norm>,
}
impl TempogramParams {
pub fn new(sr: f64) -> Self {
Self {
sr,
hop_length: 512,
win_length: 384,
center: true,
norm: Some(Norm::Linf),
}
}
}
pub fn tempogram(onset_envelope: &[f64], params: &TempogramParams) -> Matrix {
let win_length = params.win_length;
let original_len = onset_envelope.len();
if original_len == 0 || win_length == 0 {
return Matrix::zeros(win_length, 0);
}
let padded;
let env = if params.center {
let pad = win_length / 2;
padded = pad_linear_ramp(onset_envelope, pad, pad);
&padded[..]
} else {
onset_envelope
};
let frame_iter = FrameIter::new(env, win_length, 1);
let total_frames = frame_iter.n_frames();
let n_frames = if params.center {
total_frames.min(original_len)
} else {
total_frames
};
if n_frames == 0 {
return Matrix::zeros(win_length, 0);
}
let window = crate::windows::hann(win_length, true);
let mut data = vec![0.0; win_length * n_frames];
for t in 0..n_frames {
let frame = frame_iter.frame(t);
let windowed: Vec<f64> = frame
.iter()
.zip(window.iter())
.map(|(s, w)| s * w)
.collect();
let ac = autocorrelate(&windowed, None);
for (lag, &val) in ac.iter().enumerate().take(win_length) {
data[lag * n_frames + t] = val;
}
}
let mut result = Matrix::from_vec(data, win_length, n_frames);
if let Some(norm) = params.norm {
for t in 0..n_frames {
let mut col: Vec<f64> = (0..win_length).map(|lag| result.get(lag, t)).collect();
crate::dsp::util::normalize(&mut col, norm);
for (lag, &val) in col.iter().enumerate() {
*result.get_mut(lag, t) = val;
}
}
}
result
}
pub struct FourierTempogramParams {
pub sr: f64,
pub hop_length: usize,
pub win_length: usize,
pub center: bool,
}
impl FourierTempogramParams {
pub fn new(sr: f64) -> Self {
Self {
sr,
hop_length: 512,
win_length: 384,
center: true,
}
}
}
pub fn fourier_tempogram(
onset_envelope: &[f64],
params: &FourierTempogramParams,
) -> (Matrix, Matrix) {
use crate::stft::{StftParams, stft_as_real_imag};
let stft_params = StftParams {
n_fft: params.win_length,
hop_length: 1,
win_length: params.win_length,
center: params.center,
window: None,
};
stft_as_real_imag(onset_envelope, &stft_params)
}