use crate::strategies::window_stats::WindowStats;
use crate::strategies::AnalysisState;
use crate::{BeatInfo, Strategy, StrategyKind};
use spectrum_analyzer::FrequencyLimit;
use ringbuffer::{ConstGenericRingBuffer, RingBufferWrite, RingBufferExt};
use std::cell::RefCell;
use spectrum_analyzer::scaling::divide_by_N;
#[derive(Debug)]
pub struct SABeatDetector {
state: AnalysisState,
audio_data_buf: RefCell<ConstGenericRingBuffer<f32, 1024>>,
}
impl SABeatDetector {
#[inline(always)]
pub fn new(sampling_rate: u32) -> Self {
const LEN: usize = 1024;
let mut initial_buf = ConstGenericRingBuffer::<f32, LEN>::new();
(0..LEN).for_each(|_| initial_buf.push(0.0));
Self {
state: AnalysisState::new(sampling_rate),
audio_data_buf: RefCell::from(initial_buf),
}
}
}
impl Strategy for SABeatDetector {
#[inline(always)]
fn is_beat(&self, callback_samples: &[i16]) -> Option<BeatInfo> {
let mut audio_data_buf = self.audio_data_buf.borrow_mut();
for sample in callback_samples {
audio_data_buf.push(*sample as f32);
}
self.state.update_time(callback_samples.len());
if !self.last_beat_beyond_threshold(&self.state) {
return None;
};
let w_stats = WindowStats::from(callback_samples);
if !self.amplitude_high_enough(&w_stats) {
return None;
};
let spectrum = spectrum_analyzer::samples_fft_to_spectrum(
&audio_data_buf.to_vec(),
self.state.sampling_rate(),
FrequencyLimit::Max(90.0),
Some(÷_by_N),
).unwrap();
if spectrum.max().1.val() > 2_100_000.0 {
self.state.update_last_discovered_beat_timestamp();
Some(BeatInfo::new(self.state.beat_time_ms()))
} else {
None
}
}
#[inline(always)]
fn kind(&self) -> StrategyKind {
StrategyKind::Spectrum
}
fn name() -> &'static str
where
Self: Sized,
{
"Simple Spectrum Analysis"
}
fn description() -> &'static str
where
Self: Sized,
{
"A simple beat detection using a spectrum analysis. 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
}
}
#[cfg(test)]
mod tests {
}