use std::f64::consts::PI;
use std::ops::Range;
use crate::fft::Fft;
pub const MELS: usize = 128;
pub const N_FFT: usize = 512;
pub const WINDOW: usize = 400;
pub const HOP: usize = 160;
pub const PRE_EMPHASIS: f32 = 0.97;
pub const F_MAX: f32 = 8000.0;
const BINS: usize = N_FFT / 2 + 1;
const LOG_EPS: f32 = 1.0 / (1 << 24) as f32;
pub struct Frontend {
window: Vec<f64>,
filters: Vec<f32>,
spans: Vec<Range<usize>>,
fft: Fft<f64>,
}
impl Frontend {
pub fn new() -> Self {
let pad = (N_FFT - WINDOW) / 2;
let window = (0..N_FFT)
.map(|i| match i.wrapping_sub(pad) {
k if k < WINDOW => 0.5 - 0.5 * (2.0 * PI * k as f64 / (WINDOW - 1) as f64).cos(),
_ => 0.0,
})
.collect();
let filters = slaney_filterbank();
let spans = (filters.as_chunks::<BINS>().0.iter())
.map(|f| {
let lo = f.iter().position(|&w| w != 0.0).unwrap_or(0);
lo..f.iter().rposition(|&w| w != 0.0).map_or(lo, |i| i + 1)
})
.collect();
Self { window, filters, spans, fft: Fft::new(N_FFT) }
}
pub fn compute(&self, pcm: &[f32]) -> Vec<f32> {
let frames = pcm.len() / HOP + 1;
if frames < 3 {
return Vec::new();
}
let mut padded = vec![0f64; pcm.len() + N_FFT];
padded[N_FFT / 2] = pcm[0] as f64;
for (y, x) in padded[N_FFT / 2 + 1..].iter_mut().zip(pcm.windows(2)) {
*y = x[1] as f64 - PRE_EMPHASIS as f64 * x[0] as f64;
}
let mut mel = Vec::with_capacity(frames * MELS);
let (mut frame, mut buf) = ([0f64; N_FFT], [0f64; 4 * N_FFT]);
let mut power = [0f32; BINS];
for t in 0..frames {
for ((x, s), w) in frame.iter_mut().zip(&padded[t * HOP..]).zip(&self.window) {
*x = s * w;
}
let (re, im) = self.fft.forward(&frame, &mut buf);
for ((p, re), im) in power.iter_mut().zip(re).zip(im) {
*p = (re * re + im * im) as f32;
}
for (filter, span) in self.filters.as_chunks::<BINS>().0.iter().zip(&self.spans) {
let e = (filter[span.clone()].iter().zip(&power[span.clone()]))
.fold(0f32, |s, (w, p)| w.mul_add(*p, s));
mel.push((e + LOG_EPS).ln());
}
}
let n = frames - 1;
for m in 0..MELS {
let column = || (0..n).map(|t| mel[t * MELS + m] as f64);
let mean = column().sum::<f64>() / n as f64;
let var = column().map(|x| (x - mean) * (x - mean)).sum::<f64>() / (n - 1) as f64;
let inv = 1.0 / (var.sqrt() + 1e-5f32 as f64);
for t in 0..n {
mel[t * MELS + m] = ((mel[t * MELS + m] as f64 - mean) * inv) as f32;
}
mel[n * MELS + m] = 0.0;
}
mel
}
}
fn slaney_filterbank() -> Vec<f32> {
const FSP: f64 = 200.0 / 3.0;
const MIN_LOG_HZ: f64 = 1000.0;
let min_log_mel = MIN_LOG_HZ / FSP;
let log_step = 6.4f64.ln() / 27.0;
let hz_to_mel = |hz: f64| {
if hz < MIN_LOG_HZ { hz / FSP } else { min_log_mel + (hz / MIN_LOG_HZ).ln() / log_step }
};
let mel_to_hz = |mel: f64| {
if mel < min_log_mel { mel * FSP } else { MIN_LOG_HZ * (log_step * (mel - min_log_mel)).exp() }
};
let mel_max = hz_to_mel(F_MAX as f64);
let hz: Vec<f64> =
(0..MELS + 2).map(|m| mel_to_hz(mel_max * m as f64 / (MELS + 1) as f64)).collect();
let mut out = vec![0f32; MELS * BINS];
for (m, row) in out.as_chunks_mut::<BINS>().0.iter_mut().enumerate() {
let enorm = 2.0 / (hz[m + 2] - hz[m]);
for (k, w) in row.iter_mut().enumerate() {
let f = 16000.0 * k as f64 / N_FFT as f64;
let lower = (f - hz[m]) / (hz[m + 1] - hz[m]);
let upper = (hz[m + 2] - f) / (hz[m + 2] - hz[m + 1]);
*w = (lower.min(upper).max(0.0) * enorm) as f32;
}
}
out
}
#[cfg(test)]
mod tests {
#[test]
fn short_input() {
let frontend = super::Frontend::new();
assert!(frontend.compute(&[0.1; 319]).is_empty());
let mel = frontend.compute(&[0.1; 320]);
assert_eq!(mel.len(), 3 * super::MELS);
assert!(mel.iter().all(|x| x.is_finite()));
}
}