use rand::distributions::Uniform;
use rand::rngs::ThreadRng;
use rand::{thread_rng, Rng};
use itertools_num::linspace;
use std::f32;
#[derive(Debug)]
pub struct SimpleEstimator {
pub lower: f32,
pub upper: f32,
pub interval: u64,
pub rate: f32,
pub steps: u32,
pub samples: u32,
rng: ThreadRng,
}
impl SimpleEstimator {
pub fn default() -> SimpleEstimator {
SimpleEstimator {
lower: 50.0,
upper: 450.0,
interval: 64,
rate: 44100.0,
steps: 800,
samples: 1024,
rng: thread_rng(),
}
}
pub fn with_accuracy(level: u32) -> SimpleEstimator {
match level {
0 => SimpleEstimator::with_settings(2048, 800, 2048),
1 => SimpleEstimator::with_settings(128, 1600, 2048),
2 => SimpleEstimator::with_settings(256, 800, 8192),
_ => SimpleEstimator::default(),
}
}
pub fn with_settings(interval: u64, steps: u32, samples: u32) -> SimpleEstimator {
SimpleEstimator {
lower: 50.0,
upper: 450.0,
interval: interval,
rate: 44100.0,
steps: steps,
samples: samples,
rng: thread_rng(),
}
}
pub fn analyse<T>(self: &mut SimpleEstimator, samples: T) -> f32
where
T: Iterator<Item = f32>,
{
let nrg: Vec<f32> = samples
.scan(0.0, |v, s| {
let z: f32 = s.abs();
let cnd = (z > *v) as i32 as f32;
Some(*v + (cnd * (z - *v) / 8.0) - (cnd * (*v - z) / 512.0))
})
.step_by(self.interval as usize)
.collect();
self.scan_for_bpm(&nrg)
}
#[inline(always)]
fn scan_for_bpm(self: &mut SimpleEstimator, nrg: &Vec<f32>) -> f32 {
let slowest = self.bpm_to_interval(self.lower);
let fastest = self.bpm_to_interval(self.upper);
let flen = nrg.len() as f32;
let udistr = Uniform::new(0.0, flen);
let intervals: Vec<f32> = linspace::<f32>(slowest, fastest, self.steps as usize).collect();
let randsamples: Vec<Vec<f32>> = (0..self.steps)
.map(|_| {
self.rng
.sample_iter(&udistr)
.take(self.samples as usize)
.collect()
})
.collect();
let (_, trough) = intervals.into_iter().zip(randsamples).fold(
(f32::INFINITY, f32::NAN),
|(height, trough), (interval, rsamples)| {
let t = rsamples.into_iter().fold(0.0, |acc, mid| {
acc + utils::autodifference(&nrg, flen, interval, mid)
});
if t < height {
(t, interval)
} else {
(height, trough)
}
},
);
self.interval_to_bpm(trough)
}
#[inline(always)]
fn bpm_to_interval(self: &SimpleEstimator, bpm: f32) -> f32 {
let beats_per_second: f32 = bpm / 60.0;
let samples_per_beat: f32 = self.rate / beats_per_second;
samples_per_beat / self.interval as f32
}
#[inline(always)]
fn interval_to_bpm(self: &SimpleEstimator, interval: f32) -> f32 {
let samples_per_beat: f32 = interval * self.interval as f32;
let beats_per_second: f32 = self.rate / samples_per_beat;
beats_per_second * 60.0
}
}
mod utils {
#[inline]
pub fn autodifference(nrg: &Vec<f32>, flen: f32, interval: f32, mid: f32) -> f32 {
const BEATS: [f32; 12] = [
-32.0, -16.0, -8.0, -4.0, -2.0, -1.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0,
];
const NOBEATS: [f32; 4] = [-0.5, -0.25, 0.25, 0.5];
let v: f32 = sample(&nrg, flen, mid);
let (bd, bt) = BEATS.iter().fold((0.0, 0.0), |(d, t), b| {
let y: f32 = sample(&nrg, flen, mid + b * interval);
let w = 1.0 / b.abs();
(d + w * (y - v).abs(), t + w)
});
let (nd, nt) = NOBEATS.iter().fold((0.0, 0.0), |(d, t), b| {
let y = sample(&nrg, flen, mid + b * interval);
let w = b.abs();
(d - w * (y - v).abs(), t + w)
});
(bd + nd) / (bt + nt)
}
#[inline]
fn sample(nrg: &Vec<f32>, flen: f32, offset: f32) -> f32 {
let n: f32 = offset.floor();
let i: usize = n as usize;
if n >= 0.0 && n < flen {
nrg[i]
} else {
0.0
}
}
}