use crate::strategies::window_stats::WindowStats;
use crate::strategies::AnalysisState;
use crate::{BeatInfo, Strategy, StrategyKind};
use lowpass_filter as lpf;
#[derive(Debug)]
pub struct LpfBeatDetector {
state: AnalysisState,
}
impl LpfBeatDetector {
#[inline(always)]
pub fn new(sampling_rate: u32) -> Self {
Self {
state: AnalysisState::new(sampling_rate),
}
}
}
impl Strategy for LpfBeatDetector {
#[inline(always)]
fn is_beat(&self, samples: &[i16]) -> Option<BeatInfo> {
self.state.update_time(samples.len());
if !self.last_beat_beyond_threshold(&self.state) {
return None;
};
let w_stats = WindowStats::from(samples);
if !self.amplitude_high_enough(&w_stats) {
return None;
};
const CUTOFF_FR: u16 = 120;
let mut samples = samples.to_vec();
lpf::simple::sp::apply_lpf_i16_sp(
&mut samples,
self.state.sampling_rate() as u16,
CUTOFF_FR,
);
let threshold = (0.77 * w_stats.max() as f32) as i16;
let is_beat = samples.iter().any(|s| s.abs() >= threshold);
is_beat.then(|| {
self.state.update_last_discovered_beat_timestamp();
BeatInfo::new(self.state.beat_time_ms())
})
}
#[inline(always)]
fn kind(&self) -> StrategyKind {
StrategyKind::LPF
}
fn name() -> &'static str
where
Self: Sized,
{
"Simple Lowpass Filter"
}
fn description() -> &'static str
where
Self: Sized,
{
"A simple beat detection using a lowpass filter. It's not smart enough \
to cope with 'complex' music, like most of today's pop. But it will give \
pretty good results in 'easy' music, like most of 90s pop hits."
}
#[inline(always)]
fn min_duration_between_beats_ms() -> u32
where
Self: Sized,
{
400
}
}