use crate::dsp::util::{Norm, tiny};
use crate::matrix::Matrix;
use crate::stft::{IstftParams, istft};
use super::tempo::fourier_tempo_frequencies;
use super::tempogram::{FourierTempogramParams, fourier_tempogram};
pub struct PlpParams {
pub sr: f64,
pub hop_length: usize,
pub win_length: usize,
pub tempo_min: Option<f64>,
pub tempo_max: Option<f64>,
}
impl PlpParams {
pub fn new(sr: f64) -> Self {
Self {
sr,
hop_length: 512,
win_length: 384,
tempo_min: Some(30.0),
tempo_max: Some(300.0),
}
}
}
pub fn plp(onset_envelope: &[f64], params: &PlpParams) -> Vec<f64> {
let n_frames = onset_envelope.len();
if n_frames == 0 {
return Vec::new();
}
let ft_params = FourierTempogramParams {
sr: params.sr,
hop_length: params.hop_length,
win_length: params.win_length,
center: true,
};
let (ft_real, ft_imag) = fourier_tempogram(onset_envelope, &ft_params);
let n_bins = ft_real.rows();
let n_cols = ft_real.cols();
let tempo_freqs = fourier_tempo_frequencies(params.sr, params.win_length, params.hop_length);
let mut valid = vec![true; n_bins];
if let Some(tempo_min) = params.tempo_min {
for (i, &freq) in tempo_freqs.iter().enumerate() {
if freq < tempo_min {
valid[i] = false;
}
}
}
if let Some(tempo_max) = params.tempo_max {
for (i, &freq) in tempo_freqs.iter().enumerate() {
if freq > tempo_max {
valid[i] = false;
}
}
}
let mut out_real = vec![0.0; n_bins * n_cols];
let mut out_imag = vec![0.0; n_bins * n_cols];
for t in 0..n_cols {
let mut peak_val = f64::NEG_INFINITY;
let mut peak_bin = 0;
for bin in 0..n_bins {
if !valid[bin] {
continue;
}
let re = ft_real.get(bin, t);
let im = ft_imag.get(bin, t);
let mag = (re * re + im * im).sqrt();
let log_mag = (1e6 * mag).ln_1p();
if log_mag > peak_val {
peak_val = log_mag;
peak_bin = bin;
}
}
let re = ft_real.get(peak_bin, t);
let im = ft_imag.get(peak_bin, t);
let mag = (re * re + im * im).sqrt();
let tiny_sqrt = tiny().sqrt();
let denom = mag.max(tiny_sqrt);
let idx = peak_bin * n_cols + t;
out_real[idx] = re / denom;
out_imag[idx] = im / denom;
}
let stft_real = Matrix::from_vec(out_real, n_bins, n_cols);
let stft_imag = Matrix::from_vec(out_imag, n_bins, n_cols);
let istft_params = IstftParams {
n_fft: params.win_length,
hop_length: 1,
win_length: params.win_length,
center: true,
length: Some(n_frames),
};
let mut pulse = istft(&stft_real, &stft_imag, &istft_params);
for val in pulse.iter_mut() {
*val = val.max(0.0);
}
crate::dsp::util::normalize(&mut pulse, Norm::Linf);
pulse
}