use std::sync::Arc;
use std::{cmp::min, f32};
use crate::{Spectrogram, WindowFn};
use rustfft::{num_complex::Complex, FftPlanner};
pub struct SpecCompute {
num_bins: usize, data: Vec<f32>, window_fn: WindowFn, step_size: usize, fft_fn: Arc<dyn rustfft::Fft<f32>>,
}
impl SpecCompute {
pub fn new(num_bins: usize, step_size: usize, data: Vec<f32>, window_fn: WindowFn) -> Self {
let mut planner = FftPlanner::<f32>::new();
let fft_fn = planner.plan_fft_forward(num_bins);
SpecCompute {
num_bins,
step_size,
data,
window_fn,
fft_fn,
}
}
pub fn set_data(&mut self, data: Vec<f32>) {
self.data = data;
}
pub fn compute(&mut self) -> Spectrogram {
let width = (self.data.len() - self.num_bins) / self.step_size;
let height = self.num_bins / 2;
let mut spec = vec![0.0; self.num_bins * width];
let mut p = 0;
let mut inplace_buf: Vec<Complex<f32>> = vec![Complex::new(0., 0.); self.num_bins];
let mut scratch_buf: Vec<Complex<f32>> =
vec![Complex::new(0., 0.); self.fft_fn.get_inplace_scratch_len()];
let inplace_slice = &mut inplace_buf[..];
let scratch_slice = &mut scratch_buf[..];
for w in 0..width {
self.data[p..]
.iter()
.take(self.num_bins)
.enumerate()
.map(|(i, val)| val * (self.window_fn)(i, self.num_bins)) .map(|val| Complex::new(val, 0.0))
.zip(inplace_slice.iter_mut())
.for_each(|(c, v)| *v = c);
let inplace = &mut inplace_slice[..min(self.num_bins, self.data.len() - p)];
self.fft_fn.process_with_scratch(inplace, scratch_slice);
inplace
.iter()
.take(height)
.rev()
.map(|c_val| c_val.norm())
.zip(spec[w..].iter_mut().step_by(width))
.for_each(|(a, b)| *b = a);
p += self.step_size;
}
Spectrogram {
spec,
width,
height,
}
}
}