use crate::util::hann_window;
use realfft::RealFftPlanner;
#[derive(Debug, Clone)]
pub struct Spectrogram {
pub sample_rate: u32,
pub fft_size: usize,
pub hop: usize,
pub bins: usize,
pub data: Vec<f32>,
}
impl Spectrogram {
pub fn frames(&self) -> usize {
self.data.len().checked_div(self.bins).unwrap_or(0)
}
pub fn get(&self, frame: usize, bin: usize) -> f32 {
self.data[frame * self.bins + bin]
}
pub fn bin_hz(&self, bin: usize) -> f32 {
bin as f32 * self.sample_rate as f32 / self.fft_size as f32
}
pub fn frame_time(&self, frame: usize) -> f32 {
(frame * self.hop) as f32 / self.sample_rate as f32
}
}
pub fn spectrogram(signal: &[f32], sample_rate: u32, fft_size: usize, hop: usize) -> Spectrogram {
let bins = fft_size / 2 + 1;
let hop = hop.max(1);
if signal.len() < fft_size {
return Spectrogram { sample_rate, fft_size, hop, bins, data: Vec::new() };
}
let mut planner = RealFftPlanner::<f32>::new();
let r2c = planner.plan_fft_forward(fft_size);
let win = hann_window(fft_size);
let win_sum: f32 = win.iter().sum::<f32>().max(1e-9);
let mut in_buf = r2c.make_input_vec();
let mut out_buf = r2c.make_output_vec();
let mut data = Vec::with_capacity((signal.len() / hop + 1) * bins);
let mut pos = 0;
while pos + fft_size <= signal.len() {
for (i, s) in in_buf.iter_mut().enumerate() {
*s = signal[pos + i] * win[i];
}
r2c.process(&mut in_buf, &mut out_buf).expect("realfft forward");
for c in &out_buf {
let mag = (c.re * c.re + c.im * c.im).sqrt() * 2.0 / win_sum;
data.push((20.0 * mag.max(1e-6).log10()).max(-120.0));
}
pos += hop;
}
Spectrogram { sample_rate, fft_size, hop, bins, data }
}