use crate::matrix::Matrix;
use num_complex::Complex;
use realfft::RealFftPlanner;
pub struct StftParams {
pub n_fft: usize,
pub hop_length: usize,
pub win_length: usize,
pub center: bool,
pub window: Option<Vec<f64>>,
}
impl StftParams {
pub fn new(n_fft: usize) -> Self {
Self {
n_fft,
hop_length: n_fft / 4,
win_length: n_fft,
center: true,
window: None,
}
}
}
impl Default for StftParams {
fn default() -> Self {
Self::new(2048)
}
}
fn stft_complex_inner(y: &[f64], params: &StftParams) -> Vec<Vec<Complex<f64>>> {
let n_fft = params.n_fft;
let hop_length = params.hop_length;
let win = match ¶ms.window {
Some(w) => w.clone(),
None => crate::windows::hann(params.win_length, true),
};
let win = crate::dsp::util::pad_center(&win, n_fft);
let padded: Vec<f64>;
let signal = if params.center {
let pad = n_fft / 2;
padded = std::iter::repeat_n(0.0, pad)
.chain(y.iter().copied())
.chain(std::iter::repeat_n(0.0, pad))
.collect();
&padded
} else {
y
};
if signal.len() < n_fft {
return Vec::new();
}
let n_frames = 1 + (signal.len() - n_fft) / hop_length;
let n_bins = 1 + n_fft / 2;
let mut planner = RealFftPlanner::<f64>::new();
let fft = planner.plan_fft_forward(n_fft);
let mut scratch = fft.make_scratch_vec();
let mut input_buf = fft.make_input_vec();
let mut output_buf = fft.make_output_vec();
let mut result = Vec::with_capacity(n_frames);
for i in 0..n_frames {
let start = i * hop_length;
let frame = &signal[start..start + n_fft];
for (j, sample) in frame.iter().enumerate() {
input_buf[j] = sample * win[j];
}
fft.process_with_scratch(&mut input_buf, &mut output_buf, &mut scratch)
.expect("FFT failed");
let frame_out = output_buf[..n_bins].to_vec();
result.push(frame_out);
}
result
}
pub fn spectrogram(y: &[f64], params: &StftParams, power: f64) -> Matrix {
let complex = stft_complex_inner(y, params);
let n_frames = complex.len();
if n_frames == 0 {
return Matrix::zeros(1 + params.n_fft / 2, 0);
}
let n_bins = complex[0].len();
let mut data = vec![0.0; n_bins * n_frames];
for (frame_idx, frame) in complex.iter().enumerate() {
for (bin_idx, &c) in frame.iter().enumerate() {
let mag = c.norm(); data[bin_idx * n_frames + frame_idx] = mag.powf(power);
}
}
Matrix::from_vec(data, n_bins, n_frames)
}
pub fn stft(y: &[f64], params: &StftParams) -> (Matrix, Matrix) {
let complex = stft_complex_inner(y, params);
let n_frames = complex.len();
if n_frames == 0 {
let rows = 1 + params.n_fft / 2;
return (Matrix::zeros(rows, 0), Matrix::zeros(rows, 0));
}
let n_bins = complex[0].len();
let mut mag_data = vec![0.0; n_bins * n_frames];
let mut phase_data = vec![0.0; n_bins * n_frames];
for (frame_idx, frame) in complex.iter().enumerate() {
for (bin_idx, &c) in frame.iter().enumerate() {
let idx = bin_idx * n_frames + frame_idx;
mag_data[idx] = c.norm();
phase_data[idx] = c.arg();
}
}
(
Matrix::from_vec(mag_data, n_bins, n_frames),
Matrix::from_vec(phase_data, n_bins, n_frames),
)
}
pub fn stft_as_real_imag(y: &[f64], params: &StftParams) -> (Matrix, Matrix) {
let complex = stft_complex_inner(y, params);
let n_frames = complex.len();
if n_frames == 0 {
let rows = 1 + params.n_fft / 2;
return (Matrix::zeros(rows, 0), Matrix::zeros(rows, 0));
}
let n_bins = complex[0].len();
let mut real_data = vec![0.0; n_bins * n_frames];
let mut imag_data = vec![0.0; n_bins * n_frames];
for (frame_idx, frame) in complex.iter().enumerate() {
for (bin_idx, &c) in frame.iter().enumerate() {
let idx = bin_idx * n_frames + frame_idx;
real_data[idx] = c.re;
imag_data[idx] = c.im;
}
}
(
Matrix::from_vec(real_data, n_bins, n_frames),
Matrix::from_vec(imag_data, n_bins, n_frames),
)
}
pub struct IstftParams {
pub n_fft: usize,
pub hop_length: usize,
pub win_length: usize,
pub center: bool,
pub length: Option<usize>,
}
impl IstftParams {
pub fn new(n_fft: usize) -> Self {
Self {
n_fft,
hop_length: n_fft / 4,
win_length: n_fft,
center: true,
length: None,
}
}
}
pub fn istft(stft_real: &Matrix, stft_imag: &Matrix, params: &IstftParams) -> Vec<f64> {
let n_bins = stft_real.rows();
let n_frames = stft_real.cols();
let n_fft = params.n_fft;
let hop_length = params.hop_length;
assert_eq!(n_bins, 1 + n_fft / 2, "istft: n_bins mismatch");
assert_eq!(stft_imag.rows(), n_bins);
assert_eq!(stft_imag.cols(), n_frames);
if n_frames == 0 {
return Vec::new();
}
let win = crate::windows::hann(params.win_length, true);
let win = crate::dsp::util::pad_center(&win, n_fft);
let expected_len = n_fft + hop_length * (n_frames - 1);
let mut output = vec![0.0; expected_len];
let mut window_sum = vec![0.0; expected_len];
let mut planner = RealFftPlanner::<f64>::new();
let ifft = planner.plan_fft_inverse(n_fft);
let mut scratch = ifft.make_scratch_vec();
let mut spectrum = ifft.make_input_vec();
let mut time_buf = ifft.make_output_vec();
let norm = 1.0 / n_fft as f64;
for frame_idx in 0..n_frames {
for (bin_idx, spec) in spectrum.iter_mut().enumerate().take(n_bins) {
*spec = Complex::new(
stft_real.get(bin_idx, frame_idx),
stft_imag.get(bin_idx, frame_idx),
);
}
ifft.process_with_scratch(&mut spectrum, &mut time_buf, &mut scratch)
.expect("Inverse FFT failed");
let start = frame_idx * hop_length;
for j in 0..n_fft {
output[start + j] += time_buf[j] * norm * win[j];
window_sum[start + j] += win[j] * win[j];
}
}
let tiny = crate::dsp::util::tiny();
for i in 0..expected_len {
if window_sum[i] > tiny {
output[i] /= window_sum[i];
}
}
if params.center {
let pad = n_fft / 2;
let end = expected_len.saturating_sub(pad);
output = output[pad.min(expected_len)..end].to_vec();
}
if let Some(length) = params.length {
output = crate::dsp::util::fix_length(&output, length);
}
output
}