use ndarray::Array2;
use rustfft::{num_complex::Complex, FftPlanner};
use std::sync::Arc;
const SAMPLE_RATE: f32 = 16000.0;
const FFT: usize = 512;
const WIN: usize = 400;
const HOP: usize = 160;
const N_MELS: usize = 128;
const PREEMPH: f32 = 0.97;
const LOG_GUARD: f32 = 1e-10;
const DITHER: f32 = 1e-5;
pub const MEL_BINS: usize = N_MELS;
pub const HOP_SIZE: usize = HOP;
pub struct MelSpectrogram {
fft: Arc<dyn rustfft::Fft<f32>>,
window: Vec<f32>, filterbank: Vec<Vec<f32>>, }
impl Default for MelSpectrogram {
fn default() -> Self {
Self::new()
}
}
impl MelSpectrogram {
pub fn new() -> Self {
let mut planner = FftPlanner::<f32>::new();
Self {
fft: planner.plan_fft_forward(FFT),
window: centered_hann(WIN, FFT),
filterbank: build_filterbank(N_MELS, FFT, SAMPLE_RATE),
}
}
pub fn compute(&self, samples: &[f32]) -> Array2<f32> {
let dithered = dither(samples);
let emphasized = preemphasis(&dithered);
let padded = reflect_pad(&emphasized, FFT / 2); let bins = FFT / 2 + 1;
let num_frames = if padded.len() >= FFT {
(padded.len() - FFT) / HOP + 1
} else {
0
};
let mut out = Array2::<f32>::zeros((num_frames, N_MELS));
let mut buf = vec![Complex::new(0.0f32, 0.0); FFT];
for t in 0..num_frames {
let s = t * HOP;
for (i, b) in buf.iter_mut().enumerate() {
*b = Complex::new(padded[s + i] * self.window[i], 0.0);
}
self.fft.process(&mut buf);
for (m, filter) in self.filterbank.iter().enumerate() {
let acc: f32 = filter
.iter()
.zip(buf[..bins].iter())
.map(|(w, c)| w * c.norm_sqr())
.sum();
out[[t, m]] = (acc + LOG_GUARD).ln();
}
}
out
}
}
fn dither(samples: &[f32]) -> Vec<f32> {
let mut s: u32 = 0x9E37_79B9;
samples
.iter()
.map(|&x| {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
let u = (s as f32 / u32::MAX as f32) * 2.0 - 1.0; x + DITHER * u
})
.collect()
}
fn preemphasis(samples: &[f32]) -> Vec<f32> {
let mut e = Vec::with_capacity(samples.len());
if let Some(&first) = samples.first() {
e.push(first);
for i in 1..samples.len() {
e.push(samples[i] - PREEMPH * samples[i - 1]);
}
}
e
}
fn reflect_pad(x: &[f32], p: usize) -> Vec<f32> {
let n = x.len();
if n <= p {
let mut out = vec![0.0; p];
out.extend_from_slice(x);
out.extend(std::iter::repeat_n(0.0, p));
return out;
}
let mut out = Vec::with_capacity(n + 2 * p);
for k in 0..p {
out.push(x[p - k]); }
out.extend_from_slice(x);
for k in 0..p {
out.push(x[n - 2 - k]); }
out
}
fn centered_hann(win: usize, fft: usize) -> Vec<f32> {
let mut w = vec![0.0f32; fft];
let off = (fft - win) / 2;
for n in 0..win {
w[off + n] = 0.5 * (1.0 - (2.0 * std::f32::consts::PI * n as f32 / win as f32).cos());
}
w
}
fn hz_to_mel(hz: f32) -> f32 {
if hz < 1000.0 {
hz / 200.0 * 3.0
} else {
15.0 + (hz / 1000.0).ln() * (27.0 / (6400.0f32 / 1000.0).ln())
}
}
fn mel_to_hz(mel: f32) -> f32 {
if mel < 15.0 {
mel * 200.0 / 3.0
} else {
1000.0 * ((mel - 15.0) * (6400.0f32 / 1000.0).ln() / 27.0).exp()
}
}
fn build_filterbank(n_mels: usize, fft: usize, sr: f32) -> Vec<Vec<f32>> {
let bins = fft / 2 + 1;
let (mmin, mmax) = (hz_to_mel(0.0), hz_to_mel(sr / 2.0));
let mel_pts: Vec<f32> = (0..n_mels + 2)
.map(|i| mmin + (mmax - mmin) * i as f32 / (n_mels + 1) as f32)
.collect();
let bin_pts: Vec<f32> = mel_pts
.iter()
.map(|&m| mel_to_hz(m) * fft as f32 / sr)
.collect();
let mut fb = vec![vec![0.0f32; bins]; n_mels];
for m in 0..n_mels {
let (l, ce, r) = (bin_pts[m], bin_pts[m + 1], bin_pts[m + 2]);
for (k, w) in fb[m].iter_mut().enumerate() {
let kf = k as f32;
if kf >= l && kf <= ce && ce > l {
*w = (kf - l) / (ce - l);
} else if kf > ce && kf <= r && r > ce {
*w = (r - kf) / (r - ce);
}
}
let enorm = 2.0 / (mel_to_hz(mel_pts[m + 2]) - mel_to_hz(mel_pts[m]));
for w in fb[m].iter_mut() {
*w *= enorm;
}
}
fb
}