use crate::{BlissError, BlissResult};
use rustfft::{num_complex::Complex, FftPlanner};
use std::f32::consts::PI;
fn spectral_centroid(spec_norm: &[f32]) -> f32 {
let sum: f32 = spec_norm.iter().sum();
if sum == 0.0 {
return 0.0;
}
let mut sc = 0.0;
for (j, &norm_value) in spec_norm.iter().enumerate() {
sc += j as f32 * norm_value;
}
sc / sum
}
fn spectral_rolloff(spec_norm: &[f32]) -> f32 {
let mut cumsum = 0.0;
let mut rollsum = 0.0;
for &norm_value in spec_norm.iter() {
cumsum += norm_value * norm_value;
}
if cumsum == 0.0 {
return 0.0;
}
cumsum *= 0.95;
let mut j = 0;
while rollsum < cumsum && j < spec_norm.len() {
rollsum += spec_norm[j] * spec_norm[j];
j += 1;
}
j as f32
}
pub(crate) fn bin_to_freq(bin: f32, sample_rate: f32, fft_size: f32) -> f32 {
let freq = sample_rate / fft_size;
freq * bin.max(0.0)
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum SpecShape {
Centroid,
Rolloff,
}
pub(crate) struct SpecDesc {
shape: SpecShape,
_buf_size: usize,
}
impl SpecDesc {
pub(crate) fn new(shape: SpecShape, buf_size: usize) -> BlissResult<Self> {
Ok(SpecDesc {
shape,
_buf_size: buf_size,
})
}
pub(crate) fn do_result(&self, fftgrain: &[f32]) -> BlissResult<f32> {
let num_bins = fftgrain.len() / 2;
let norm = &fftgrain[..num_bins];
let result = match self.shape {
SpecShape::Centroid => spectral_centroid(norm),
SpecShape::Rolloff => spectral_rolloff(norm),
};
Ok(result)
}
}
pub(crate) struct PVoc {
win_s: usize,
hop_s: usize,
data: Vec<f32>,
dataold: Vec<f32>,
window: Vec<f32>,
fft: std::sync::Arc<dyn rustfft::Fft<f32>>,
buffer: Vec<Complex<f32>>, }
impl PVoc {
pub(crate) fn new(win_s: usize, hop_s: usize) -> BlissResult<Self> {
if hop_s < 1 {
return Err(BlissError::AnalysisError(format!(
"pvoc: hop_size ({}) must be >= 1",
hop_s
)));
}
if win_s < 2 {
return Err(BlissError::AnalysisError(format!(
"pvoc: win_size ({}) must be >= 2",
win_s
)));
}
if win_s < hop_s {
return Err(BlissError::AnalysisError(format!(
"pvoc: hop_size ({}) is larger than win_size ({})",
hop_s, win_s
)));
}
let mut window = vec![0.0; win_s];
for (i, item) in window.iter_mut().enumerate().take(win_s) {
*item = 0.5 * (1.0 - (2.0 * PI * i as f32 / win_s as f32).cos());
}
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(win_s);
let dataold_size = if win_s > hop_s { win_s - hop_s } else { 1 };
Ok(PVoc {
win_s,
hop_s,
data: vec![0.0; win_s],
dataold: vec![0.0; dataold_size],
window,
fft,
buffer: vec![Complex::new(0.0, 0.0); win_s],
})
}
pub(crate) fn do_(&mut self, input: &[f32], fftgrain: &mut [f32]) -> BlissResult<()> {
if input.len() < self.hop_s {
return Err(BlissError::AnalysisError(format!(
"pvoc input size mismatch: expected at least {}, got {}",
self.hop_s,
input.len()
)));
}
if fftgrain.len() != self.win_s {
return Err(BlissError::AnalysisError(format!(
"pvoc output size mismatch: expected {}, got {}",
self.win_s,
fftgrain.len()
)));
}
let end = self.win_s.saturating_sub(self.hop_s);
for i in 0..end {
self.data[i] = self.dataold[i];
}
self.data[end..(self.hop_s + end)].copy_from_slice(&input[..self.hop_s]);
for i in 0..end {
self.dataold[i] = self.data[i + self.hop_s];
}
for i in 0..self.win_s {
self.data[i] *= self.window[i];
}
let half = self.win_s / 2;
let start = if 2 * half < self.win_s {
half + 1 } else {
half
};
for j in 0..half {
self.data.swap(j, j + start);
}
for (i, &x) in self.data.iter().enumerate() {
self.buffer[i] = Complex::new(x, 0.0);
}
self.fft.process(&mut self.buffer);
let num_bins = self.win_s / 2;
fftgrain[0] = self.buffer[0].re.abs();
fftgrain[num_bins] = if self.buffer[0].re < 0.0 { PI } else { 0.0 };
for i in 1..num_bins - 1 {
let re = self.buffer[i].re;
let im = self.buffer[i].im;
fftgrain[i] = (re * re + im * im).sqrt(); fftgrain[num_bins + i] = im.atan2(re); }
let nyquist_idx = self.win_s / 2; fftgrain[num_bins - 1] = self.buffer[nyquist_idx].re.abs(); fftgrain[num_bins + num_bins - 1] = if self.buffer[nyquist_idx].re < 0.0 {
PI
} else {
0.0
};
Ok(())
}
}
struct PVocTempo {
win_s: usize,
hop_s: usize,
data: Vec<f32>,
dataold: Vec<f32>,
window: Vec<f32>,
fft: std::sync::Arc<dyn rustfft::Fft<f32>>,
buffer: Vec<Complex<f32>>, }
impl PVocTempo {
fn new(win_s: usize, hop_s: usize) -> BlissResult<Self> {
if hop_s < 1 {
return Err(BlissError::AnalysisError(format!(
"pvoc: hop_size ({}) must be >= 1",
hop_s
)));
}
if win_s < 2 {
return Err(BlissError::AnalysisError(format!(
"pvoc: win_size ({}) must be >= 2",
win_s
)));
}
if win_s < hop_s {
return Err(BlissError::AnalysisError(format!(
"pvoc: hop_size ({}) is larger than win_size ({})",
hop_s, win_s
)));
}
let mut window = vec![0.0; win_s];
for (i, item) in window.iter_mut().enumerate().take(win_s) {
*item = 0.5 * (1.0 - (2.0 * PI * i as f32 / win_s as f32).cos());
}
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(win_s);
let dataold_size = if win_s > hop_s { win_s - hop_s } else { 1 };
Ok(PVocTempo {
win_s,
hop_s,
data: vec![0.0; win_s],
dataold: vec![0.0; dataold_size],
window,
fft,
buffer: vec![Complex::new(0.0, 0.0); win_s],
})
}
fn do_(&mut self, input: &[f32], fftgrain: &mut [f32]) -> BlissResult<()> {
if input.len() < self.hop_s {
return Err(BlissError::AnalysisError(format!(
"pvoc input size mismatch: expected at least {}, got {}",
self.hop_s,
input.len()
)));
}
if fftgrain.len() != self.win_s + 2 {
return Err(BlissError::AnalysisError(format!(
"pvoc output size mismatch: expected {}, got {}",
self.win_s + 2,
fftgrain.len()
)));
}
let end = self.win_s.saturating_sub(self.hop_s);
for i in 0..end {
self.data[i] = self.dataold[i];
}
self.data[end..(self.hop_s + end)].copy_from_slice(&input[..self.hop_s]);
for i in 0..end {
self.dataold[i] = self.data[i + self.hop_s];
}
for i in 0..self.win_s {
self.data[i] *= self.window[i];
}
let half = self.win_s / 2;
let start = if 2 * half < self.win_s {
half + 1 } else {
half
};
for j in 0..half {
self.data.swap(j, j + start);
}
if start != half {
for j in 0..half {
self.data.swap(j + start - 1, j + start);
}
}
for (i, &x) in self.data.iter().enumerate() {
self.buffer[i] = Complex::new(x, 0.0);
}
self.fft.process(&mut self.buffer);
let num_bins = self.win_s / 2 + 1;
fftgrain[0] = self.buffer[0].re.abs();
fftgrain[num_bins] = if self.buffer[0].re < 0.0 { PI } else { 0.0 };
for i in 1..num_bins - 1 {
let re = self.buffer[i].re;
let im = self.buffer[i].im;
fftgrain[i] = (re * re + im * im).sqrt(); fftgrain[num_bins + i] = im.atan2(re); }
let nyquist_idx = self.win_s / 2; fftgrain[num_bins - 1] = self.buffer[nyquist_idx].re.abs();
fftgrain[num_bins + num_bins - 1] = if self.buffer[nyquist_idx].re < 0.0 {
PI
} else {
0.0
};
Ok(())
}
}
struct SpecFlux {
oldmag: Vec<f32>,
}
impl SpecFlux {
fn new(size: usize) -> Self {
let rsize = size / 2 + 1;
SpecFlux {
oldmag: vec![0.0; rsize],
}
}
fn do_(&mut self, fftgrain_norm: &[f32]) -> f32 {
let mut onset = 0.0;
for (j, &norm_val) in fftgrain_norm.iter().enumerate() {
if norm_val > self.oldmag[j] {
onset += norm_val - self.oldmag[j];
}
self.oldmag[j] = norm_val;
}
onset
}
}
fn vec_mean(data: &[f32]) -> f32 {
if data.is_empty() {
return 0.0;
}
let sum: f32 = data.iter().sum();
sum / data.len() as f32
}
fn vec_median(data: &mut [f32]) -> f32 {
let n = data.len();
if n == 0 {
return 0.0;
}
let mut low = 0;
let mut high = n - 1;
let median = (low + high) / 2;
loop {
if high <= low {
return data[median];
}
if high == low + 1 {
if data[low] > data[high] {
data.swap(low, high);
}
return data[median];
}
let middle = (low + high) / 2;
if data[middle] > data[high] {
data.swap(middle, high);
}
if data[low] > data[high] {
data.swap(low, high);
}
if data[middle] > data[low] {
data.swap(middle, low);
}
data.swap(middle, low + 1);
let mut ll = low + 1;
let mut hh = high;
loop {
ll += 1;
while data[low] > data[ll] {
ll += 1;
}
hh -= 1;
while data[hh] > data[low] {
hh -= 1;
}
if hh < ll {
break;
}
data.swap(ll, hh);
}
data.swap(low, hh);
if hh <= median {
low = ll;
}
if hh >= median {
high = hh - 1;
}
}
}
fn vec_push(data: &mut [f32], new_elem: f32) {
for i in 0..data.len() - 1 {
data[i] = data[i + 1];
}
data[data.len() - 1] = new_elem;
}
fn vec_peakpick(onset: &[f32], pos: usize) -> bool {
if pos == 0 || pos >= onset.len() - 1 {
return false;
}
onset[pos] > onset[pos - 1] && onset[pos] > onset[pos + 1] && onset[pos] > 0.0
}
fn vec_quadratic_peak_pos(x: &[f32], pos: usize) -> f32 {
if pos == 0 || pos >= x.len() - 1 {
return pos as f32;
}
let x0 = if pos < 1 { pos } else { pos - 1 };
let x2 = if pos + 1 < x.len() { pos + 1 } else { pos };
if x0 == pos {
return if x[pos] <= x[x2] {
pos as f32
} else {
x2 as f32
};
}
if x2 == pos {
return if x[pos] <= x[x0] {
pos as f32
} else {
x0 as f32
};
}
let s0 = x[x0];
let s1 = x[pos];
let s2 = x[x2];
pos as f32 + 0.5 * (s0 - s2) / (s0 - 2.0 * s1 + s2)
}
struct Biquad {
b0: f32,
b1: f32,
b2: f32,
a1: f32,
a2: f32,
x1: f32,
x2: f32,
y1: f32,
y2: f32,
}
impl Biquad {
fn new(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) -> Self {
Biquad {
b0,
b1,
b2,
a1,
a2,
x1: 0.0,
x2: 0.0,
y1: 0.0,
y2: 0.0,
}
}
fn process_sample(&mut self, x0: f32) -> f32 {
let y0 = self.b0 * x0 + self.b1 * self.x1 + self.b2 * self.x2
- self.a1 * self.y1
- self.a2 * self.y2;
self.x2 = self.x1;
self.x1 = x0;
self.y2 = self.y1;
self.y1 = y0;
y0
}
fn reset(&mut self) {
self.x1 = 0.0;
self.x2 = 0.0;
self.y1 = 0.0;
self.y2 = 0.0;
}
fn do_filtfilt(&mut self, data: &mut [f32], tmp: &mut [f32]) {
let length = data.len();
for item in data.iter_mut().take(length) {
*item = self.process_sample(*item);
}
self.reset();
for i in 0..length {
tmp[length - i - 1] = data[i];
}
for item in tmp.iter_mut().take(length) {
*item = self.process_sample(*item);
}
self.reset();
for i in 0..length {
data[i] = tmp[length - i - 1];
}
}
}
struct PeakPicker {
threshold: f32,
win_post: usize,
biquad: Biquad,
onset_keep: Vec<f32>,
onset_proc: Vec<f32>,
onset_peek: Vec<f32>,
thresholded: f32,
scratch: Vec<f32>,
}
impl PeakPicker {
fn new() -> Self {
let threshold = 0.1;
let win_post = 5;
let win_pre = 1;
let buf_size = win_post + win_pre + 1;
let biquad = Biquad::new(0.159_987_9, 0.31997577, 0.159_987_9, 0.23484048, 0.0);
PeakPicker {
threshold,
win_post,
biquad,
onset_keep: vec![0.0; buf_size],
onset_proc: vec![0.0; buf_size],
onset_peek: vec![0.0; 3],
thresholded: 0.0,
scratch: vec![0.0; buf_size],
}
}
fn do_(&mut self, onset: f32) -> f32 {
vec_push(&mut self.onset_keep, onset);
self.onset_proc.copy_from_slice(&self.onset_keep);
self.biquad
.do_filtfilt(&mut self.onset_proc, &mut self.scratch);
let mean = vec_mean(&self.onset_proc);
self.scratch.copy_from_slice(&self.onset_proc);
let median = vec_median(&mut self.scratch);
for j in 0..2 {
self.onset_peek[j] = self.onset_peek[j + 1];
}
self.thresholded = self.onset_proc[self.win_post] - median - mean * self.threshold;
self.onset_peek[2] = self.thresholded;
if vec_peakpick(&self.onset_peek, 1) {
return vec_quadratic_peak_pos(&self.onset_peek, 1);
}
0.0
}
fn get_thresholded(&self) -> f32 {
self.thresholded
}
fn set_threshold(&mut self, threshold: f32) {
self.threshold = threshold;
}
}
fn vec_max_elem(data: &[f32]) -> usize {
let mut pos = 0;
let mut tmp = 0.0;
for (j, &val) in data.iter().enumerate() {
if tmp <= val {
pos = j;
tmp = val;
}
}
pos
}
fn vec_rev(data: &mut [f32]) {
let len = data.len();
for j in 0..(len / 2) {
data.swap(j, len - 1 - j);
}
}
fn vec_weight(data: &mut [f32], weight: &[f32]) {
let length = data.len().min(weight.len());
for j in 0..length {
data[j] *= weight[j];
}
}
fn vec_autocorr(input: &[f32], output: &mut [f32]) {
let length = input.len();
for i in 0..length {
let mut tmp = 0.0;
for j in i..length {
tmp += input[j - i] * input[j];
}
output[i] = tmp / (length - i) as f32;
}
}
struct BeatTracking {
hop_size: usize,
samplerate: u32,
rwv: Vec<f32>, gwv: Vec<f32>, dfwv: Vec<f32>, dfrev: Vec<f32>, acf: Vec<f32>, acfout: Vec<f32>, phwv: Vec<f32>, phout: Vec<f32>,
timesig: u32,
step: usize,
rayparam: u32, lastbeat: f32,
counter: i32,
flagstep: u32,
g_var: f32,
gp: f32,
bp: f32, rp: f32,
rp1: f32,
rp2: f32,
}
impl BeatTracking {
fn get_timesig(acf: &[f32], gp: usize) -> u32 {
if gp < 2 {
return 4;
}
let mut three_energy = 0.0;
let mut four_energy = 0.0;
let acflen = acf.len();
if acflen > 6 * gp + 2 {
for k in -2..2 {
three_energy += acf[(3 * gp as i32 + k) as usize];
four_energy += acf[(4 * gp as i32 + k) as usize];
}
} else {
for k in -2..2 {
let idx3 = (3 * gp as i32 + k) as usize;
let idx6 = (6 * gp as i32 + k) as usize;
let idx4 = (4 * gp as i32 + k) as usize;
let idx2 = (2 * gp as i32 + k) as usize;
if idx3 < acflen && idx6 < acflen {
three_energy += acf[idx3] + acf[idx6];
} else if idx3 < acflen {
three_energy += acf[idx3];
}
if idx4 < acflen && idx2 < acflen {
four_energy += acf[idx4] + acf[idx2];
} else if idx4 < acflen {
four_energy += acf[idx4];
}
}
}
if three_energy > four_energy {
3
} else {
4
}
}
fn new(winlen: usize, hop_size: usize, samplerate: u32) -> Self {
let rayparam_float = 60.0 * samplerate as f32 / 120.0 / hop_size as f32;
let rayparam = rayparam_float as u32; let dfwvnorm = ((2.0_f32.ln() / rayparam_float) * (winlen + 2) as f32).exp();
let laglen = winlen / 4;
let step = winlen / 4;
let mut rwv = vec![0.0; laglen];
for (i, item) in rwv.iter_mut().enumerate().take(laglen) {
let i_f = (i + 1) as f32;
*item = (i_f / rayparam_float.powi(2))
* (-(i_f.powi(2)) / (2.0 * rayparam_float.powi(2))).exp();
}
let mut dfwv = vec![0.0; winlen];
for (i, item) in dfwv.iter_mut().enumerate().take(winlen) {
*item = ((2.0_f32.ln() / rayparam_float) * (i + 1) as f32).exp() / dfwvnorm;
}
BeatTracking {
hop_size,
samplerate,
rwv,
gwv: vec![0.0; laglen],
dfwv,
dfrev: vec![0.0; winlen],
acf: vec![0.0; winlen],
acfout: vec![0.0; laglen],
phwv: vec![1.0; 2 * laglen], phout: vec![0.0; winlen],
timesig: 0,
step,
rayparam,
lastbeat: 0.0,
counter: 0,
flagstep: 0,
g_var: 3.901,
gp: 0.0,
bp: 0.0,
rp: 1.0,
rp1: 0.0,
rp2: 0.0,
}
}
fn do_(&mut self, dfframe: &[f32], output: &mut [f32]) {
let step = self.step;
let laglen = self.rwv.len();
let winlen = self.dfwv.len();
let numelem = if self.timesig == 0 {
4
} else {
self.timesig as usize
};
self.dfrev.copy_from_slice(dfframe);
vec_weight(&mut self.dfrev, &self.dfwv);
vec_rev(&mut self.dfrev);
vec_autocorr(dfframe, &mut self.acf);
for val in self.acfout.iter_mut() {
*val = 0.0;
}
for i in 1..laglen - 1 {
for a in 1..=numelem {
for b in 1..2 * a {
if i * a + b - 1 < self.acf.len() {
self.acfout[i] += self.acf[i * a + b - 1] / (2.0 * a as f32 - 1.0);
}
}
}
}
vec_weight(&mut self.acfout, &self.rwv);
let maxindex = vec_max_elem(&self.acfout);
if maxindex > 0 && maxindex < self.acfout.len() - 1 {
self.rp = vec_quadratic_peak_pos(&self.acfout, maxindex);
} else {
self.rp = self.rayparam as f32; }
self.checkstate();
let bp = self.bp;
if bp == 0.0 {
for val in output.iter_mut() {
*val = 0.0;
}
return;
}
let kmax = (winlen as f32 / bp).floor() as usize;
for val in self.phout.iter_mut() {
*val = 0.0;
}
let mut i = 0;
while (i as f32) < bp && i < self.phout.len() {
for k in 0..kmax {
let idx = i + ((bp * k as f32) + 0.5).floor() as usize;
if idx < self.dfrev.len() {
self.phout[i] += self.dfrev[idx];
}
}
i += 1;
}
vec_weight(&mut self.phout, &self.phwv);
let maxindex = vec_max_elem(&self.phout);
let mut phase = if maxindex >= winlen - 1 {
step as f32 - self.lastbeat
} else {
vec_quadratic_peak_pos(&self.phout, maxindex)
};
phase += 1.0;
for val in output.iter_mut() {
*val = 0.0;
}
let mut i = 1;
let mut beat = bp - phase;
let skip_cond = (step as f32 - self.lastbeat - phase) < -0.40 * bp;
if skip_cond {
beat += bp;
}
while beat + bp < 0.0 {
beat += bp;
}
if beat >= 0.0 && i < output.len() {
output[i] = beat;
i += 1;
}
while beat + bp <= step as f32 && i < output.len() {
beat += bp;
output[i] = beat;
i += 1;
}
self.lastbeat = beat;
output[0] = i as f32;
}
fn checkstate(&mut self) {
let laglen = self.rwv.len();
let acflen = self.acf.len();
let step = self.step;
let mut counter = self.counter;
let mut flagstep = self.flagstep;
let mut gp = self.gp;
let rp = self.rp;
let mut rp1 = self.rp1;
let mut rp2 = self.rp2;
let mut flagconst = false;
let mut bp;
if gp > 0.0 {
for val in self.acfout.iter_mut() {
*val = 0.0;
}
for i in 1..(laglen - 1) {
for a in 1..=self.timesig {
for b in 1..(2 * a) {
let idx = i * a as usize + b as usize - 1;
if idx < acflen {
self.acfout[i] += self.acf[idx];
}
}
}
}
vec_weight(&mut self.acfout, &self.gwv);
let maxindex = vec_max_elem(&self.acfout);
gp = vec_quadratic_peak_pos(&self.acfout, maxindex);
} else {
gp = 0.0;
}
if counter == 0 {
if (gp - rp).abs() > 2.0 * self.g_var {
flagstep = 1; counter = 3; } else {
flagstep = 0;
}
}
if counter == 1 && flagstep == 1 {
if (2.0 * rp - rp1 - rp2).abs() < self.g_var {
flagconst = true;
counter = 0; } else {
flagconst = false;
counter = 2; }
} else if counter > 0 {
counter -= 1;
}
rp2 = rp1;
rp1 = rp;
if flagconst {
gp = rp;
self.timesig = Self::get_timesig(&self.acf, gp as usize);
for j in 0..laglen {
let diff = (j + 1) as f32 - gp;
self.gwv[j] = (-0.5 * diff * diff / (self.g_var * self.g_var)).exp();
}
bp = gp;
for val in self.phwv.iter_mut() {
*val = 1.0;
}
} else if self.timesig > 0 {
bp = gp;
if step as f32 > self.lastbeat {
for j in 0..(2 * laglen) {
let diff = 1.0 + j as f32 - step as f32 + self.lastbeat;
self.phwv[j] = (-0.5 * diff * diff / (bp / 8.0)).exp();
}
} else {
for val in self.phwv.iter_mut() {
*val = 1.0;
}
}
} else {
bp = rp;
for val in self.phwv.iter_mut() {
*val = 1.0;
}
}
while bp > 0.0 && bp < 25.0 {
bp *= 2.0;
}
self.counter = counter;
self.flagstep = flagstep;
self.gp = gp;
self.bp = bp;
self.rp1 = rp1;
self.rp2 = rp2;
}
fn get_bpm(&self) -> f32 {
if self.bp != 0.0 {
let period_samples = self.hop_size as f32 * self.bp;
let period_s = period_samples / self.samplerate as f32;
60.0 / period_s
} else {
0.0
}
}
}
fn next_power_of_two(a: usize) -> usize {
let mut i = 1;
while i < a {
i <<= 1;
}
i
}
fn level_lin(data: &[f32]) -> f32 {
let mut energy = 0.0;
for &x in data {
energy += x * x;
}
energy / data.len() as f32
}
fn db_spl(data: &[f32]) -> f32 {
10.0 * level_lin(data).log10()
}
fn is_silence(data: &[f32], threshold: f32) -> bool {
db_spl(data) < threshold
}
pub(crate) struct Tempo {
pv: PVocTempo,
od: SpecFlux,
pp: PeakPicker,
bt: BeatTracking,
fftgrain: Vec<f32>, of: f32, dfframe: Vec<f32>, out: Vec<f32>, onset: f32,
silence: f32, blockpos: isize, winlen: usize, step: usize, hop_size: usize, total_frames: usize, last_beat: usize, cycle_count: usize, }
impl Tempo {
pub fn new(buf_size: usize, hop_size: usize, samplerate: u32) -> BlissResult<Self> {
if hop_size < 1 {
return Err(BlissError::AnalysisError(
"error while loading aubio tempo object: creation error".to_string(),
));
}
if buf_size < 2 {
return Err(BlissError::AnalysisError(
"error while loading aubio tempo object: creation error".to_string(),
));
}
if buf_size < hop_size {
return Err(BlissError::AnalysisError(
"error while loading aubio tempo object: creation error".to_string(),
));
}
if samplerate < 1 {
return Err(BlissError::AnalysisError(
"error while loading aubio tempo object: creation error".to_string(),
));
}
let mut winlen = next_power_of_two(((5.8 * samplerate as f32) / hop_size as f32) as usize);
if winlen < 4 {
winlen = 4;
}
let step = winlen / 4;
let pv = PVocTempo::new(buf_size, hop_size)?;
let od = SpecFlux::new(buf_size);
let mut pp = PeakPicker::new();
pp.set_threshold(0.3); let bt = BeatTracking::new(winlen, hop_size, samplerate);
let fftgrain = vec![0.0; (buf_size / 2 + 1) * 2]; let dfframe = vec![0.0; winlen];
let out = vec![0.0; step];
Ok(Tempo {
pv,
od,
pp,
bt,
fftgrain,
of: 0.0,
dfframe,
out,
onset: 0.0,
silence: -90.0,
blockpos: 0,
winlen,
step,
hop_size,
total_frames: 0,
last_beat: 0,
cycle_count: 0,
})
}
pub fn do_(&mut self, input: &[f32]) -> BlissResult<f32> {
let winlen = self.winlen;
let step = self.step;
self.pv.do_(input, &mut self.fftgrain)?;
let fftgrain_norm = &self.fftgrain[0..(self.fftgrain.len() / 2)];
self.of = self.od.do_(fftgrain_norm);
if self.blockpos == (step as isize) - 1 {
self.bt.do_(&self.dfframe, &mut self.out);
self.cycle_count += 1;
for i in 0..(winlen - step) {
self.dfframe[i] = self.dfframe[i + step];
}
for i in (winlen - step)..winlen {
self.dfframe[i] = 0.0;
}
self.blockpos = -1;
}
self.blockpos += 1;
self.onset = self.pp.do_(self.of);
let thresholded = self.pp.get_thresholded();
self.dfframe[winlen - step + self.blockpos as usize] = thresholded;
let mut tempo_out = 0.0;
let num_beats = self.out[0] as usize;
for i in 1..num_beats {
let beat_pos = self.out[i];
if self.blockpos == beat_pos.floor() as isize {
tempo_out = beat_pos - beat_pos.floor();
if is_silence(input, self.silence) {
tempo_out = 0.0;
} else {
self.last_beat =
self.total_frames + (tempo_out * self.hop_size as f32).round() as usize;
}
}
}
self.total_frames += self.hop_size;
Ok(tempo_out)
}
pub fn get_bpm(&self) -> f32 {
self.bt.get_bpm()
}
}