pub const FBANK_WINDOW_SAMPLES: usize = 160_000;
pub(crate) const FBANK_FRAME_LENGTH: usize = 400;
pub(crate) const FBANK_FRAME_SHIFT: usize = 160;
pub const FBANK_FRAMES: usize = (FBANK_WINDOW_SAMPLES - FBANK_FRAME_LENGTH) / FBANK_FRAME_SHIFT + 1;
pub const FBANK_MEL_BINS: usize = 80;
pub(crate) const FFT_SIZE: usize = 512;
pub(crate) const SPECTRUM_BINS: usize = FFT_SIZE / 2 + 1;
pub(crate) const WAVEFORM_SCALE: f32 = 32_768.0;
pub(crate) const PREEMPHASIS: f32 = 0.97;
pub(crate) const ENERGY_FLOOR: f32 = f32::EPSILON;
const SAMPLE_RATE: f64 = 16_000.0;
const LOW_FREQUENCY: f64 = 20.0;
#[derive(Debug, Clone)]
pub(crate) struct MelTable {
pub(crate) first: Vec<u32>,
pub(crate) count: Vec<u32>,
pub(crate) width: usize,
pub(crate) weights: Vec<f32>,
}
impl MelTable {
pub(crate) fn new(mel: &[f32]) -> Self {
let weight = |bin: usize, filter: usize| mel[bin * FBANK_MEL_BINS + filter];
let runs: Vec<(usize, usize)> = (0..FBANK_MEL_BINS)
.map(|filter| {
let mut bins = (0..SPECTRUM_BINS).filter(|&bin| weight(bin, filter) != 0.0);
let first = bins.next().unwrap_or(0);
let last = bins.next_back().unwrap_or(first);
(first, last + 1 - first)
})
.collect();
let width = runs.iter().map(|&(_, count)| count).max().unwrap_or(0);
let mut weights = vec![0.0; FBANK_MEL_BINS * width];
for (filter, &(first, count)) in runs.iter().enumerate() {
for offset in 0..count {
weights[filter * width + offset] = weight(first + offset, filter);
}
}
Self {
first: runs.iter().map(|&(first, _)| first as u32).collect(),
count: runs.iter().map(|&(_, count)| count as u32).collect(),
width,
weights,
}
}
}
pub(crate) fn hamming_window() -> Vec<f32> {
let denominator = (FBANK_FRAME_LENGTH - 1) as f64;
(0..FBANK_FRAME_LENGTH)
.map(|n| (0.54 - 0.46 * (std::f64::consts::TAU * n as f64 / denominator).cos()) as f32)
.collect()
}
pub(crate) fn mel_filters() -> Vec<f32> {
let mel_scalar = |frequency: f64| 1127.0 * (1.0 + frequency / 700.0).ln();
let mel_low = mel_scalar(LOW_FREQUENCY);
let mel_high = mel_scalar(SAMPLE_RATE / 2.0);
let delta = ((mel_high - mel_low) / (FBANK_MEL_BINS + 1) as f64) as f32;
let mel_low = mel_low as f32;
let bin_width = (SAMPLE_RATE / FFT_SIZE as f64) as f32;
let mut mel = vec![0.0_f32; SPECTRUM_BINS * FBANK_MEL_BINS];
for filter in 0..FBANK_MEL_BINS {
let index = filter as f32;
let left = mel_low + index * delta;
let center = mel_low + (index + 1.0) * delta;
let right = mel_low + (index + 2.0) * delta;
for bin in 0..SPECTRUM_BINS - 1 {
let frequency = bin_width * bin as f32;
let bin_mel = 1127.0_f32 * (f64::from(1.0_f32 + frequency / 700.0).ln() as f32);
let up = (bin_mel - left) / (center - left);
let down = (right - bin_mel) / (right - center);
mel[bin * FBANK_MEL_BINS + filter] = up.min(down).max(0.0);
}
}
mel
}
#[cfg(test)]
mod tests {
use super::{FBANK_MEL_BINS, MelTable, SPECTRUM_BINS, mel_filters};
#[test]
fn sparse_mel_runs_reproduce_dense_filter_energy() {
let mel = mel_filters();
let table = MelTable::new(&mel);
let spectrum: Vec<f32> = (0..SPECTRUM_BINS)
.map(|bin| ((bin * 17 + 3) % 101) as f32)
.collect();
for filter in 0..FBANK_MEL_BINS {
let dense: f32 = spectrum
.iter()
.enumerate()
.map(|(bin, value)| value * mel[bin * FBANK_MEL_BINS + filter])
.sum();
let first = table.first[filter] as usize;
let count = table.count[filter] as usize;
let sparse: f32 = (0..count)
.map(|offset| {
spectrum[first + offset] * table.weights[filter * table.width + offset]
})
.sum();
assert_eq!(dense.to_bits(), sparse.to_bits(), "filter={filter}");
assert!(
table.weights[filter * table.width + count..(filter + 1) * table.width]
.iter()
.all(|value| *value == 0.0)
);
}
}
}