use rayon::prelude::*;
use rustfft::{FftPlanner, num_complex::Complex};
use std::f32::consts::PI;
#[derive(Debug, Clone, Copy)]
pub enum SpectrogramType {
Magnitude,
Power,
}
fn create_hann_window(length: usize) -> Vec<f32> {
(0..length)
.map(|i| 0.5 * (1.0 - (2.0 * PI * i as f32 / (length - 1) as f32).cos()))
.collect()
}
pub fn compute_spectrogram(
audio: &[f32],
n_samples: usize,
hop_length: usize,
win_length: usize,
center: bool,
spectrogram_type: SpectrogramType,
) -> Vec<Vec<f32>> {
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(n_samples);
let transform_fn: fn(&Complex<f32>) -> f32 = match spectrogram_type {
SpectrogramType::Magnitude => |c| c.norm(),
SpectrogramType::Power => |c| c.norm_sqr(),
};
let window = create_hann_window(win_length);
let n_frames = (audio.len().saturating_sub(win_length)) / hop_length + 1;
let n_freq_bins = n_samples / 2 + 1;
let mut spectrogram = vec![vec![0.0f32; n_frames]; n_freq_bins];
for frame_idx in 0..n_frames {
let start = frame_idx * hop_length;
let end = (start + win_length).clamp(0, audio.len());
if start > audio.len() {
continue;
}
let mut frame = vec![Complex::<f32>::new(0.0, 0.0); n_samples];
let centering_offset = if center {
(n_samples - win_length) / 2_usize
} else {
0_usize
};
let src = &audio[start..end];
let win = &window[..src.len()];
for (dst, (&s, &w)) in frame
.iter_mut()
.skip(centering_offset)
.zip(src.iter().zip(win.iter()))
{
dst.re = s * w; dst.im = 0.0; }
fft.process(&mut frame);
for (freq_idx, c) in frame.iter().take(n_freq_bins).enumerate() {
spectrogram[freq_idx][frame_idx] = transform_fn(c);
}
}
spectrogram
}
pub fn par_compute_spectrogram(
audio: &[f32],
n_samples: usize,
hop_length: usize,
win_length: usize,
center: bool,
spectrogram_type: SpectrogramType,
) -> Vec<Vec<f32>> {
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(n_samples);
let transform_fn: fn(&Complex<f32>) -> f32 = match spectrogram_type {
SpectrogramType::Magnitude => |c| c.norm(),
SpectrogramType::Power => |c| c.norm_sqr(),
};
let window = create_hann_window(win_length);
let n_frames = (audio.len().saturating_sub(win_length)) / hop_length + 1;
let n_freq_bins = n_samples / 2 + 1;
let mut transposed_spectrogram = vec![vec![0.0f32; n_freq_bins]; n_frames];
transposed_spectrogram
.par_iter_mut() .enumerate() .for_each(|(frame_idx, out_row)| {
let start = frame_idx * hop_length;
let end = (start + win_length).clamp(0, audio.len());
if start > audio.len() {
return;
}
let mut frame = vec![Complex::<f32>::new(0.0, 0.0); n_samples];
let centering_offset = if center {
(n_samples - win_length) / 2_usize
} else {
0_usize
};
let src = &audio[start..end];
let win = &window[..src.len()];
for (dst, (&s, &w)) in frame
.iter_mut()
.skip(centering_offset)
.zip(src.iter().zip(win.iter()))
{
dst.re = s * w; dst.im = 0.0; }
fft.process(&mut frame);
for (k, c) in frame.iter().take(n_freq_bins).enumerate() {
out_row[k] = transform_fn(c);
}
});
let mut spectrogram = vec![vec![0.0f32; n_frames]; n_freq_bins];
for (t, row) in transposed_spectrogram.into_iter().enumerate() {
for (f, v) in row.into_iter().enumerate() {
spectrogram[f][t] = v;
}
}
spectrogram
}