use crate::synthesis::simd::{SIMD, SimdLanes, SimdWidth};
use rustfft::num_complex::Complex;
use rustfft::{Fft, FftPlanner};
use std::collections::VecDeque;
use std::f32::consts::PI;
use dashmap::DashMap;
use std::sync::Arc;
use wide::{f32x4, f32x8};
use lazy_static::lazy_static;
mod blur;
mod compressor;
mod delay;
mod dynamics;
mod exciter;
mod filter;
mod formant_shifter;
mod freeze;
mod gate;
mod invert;
mod morph;
mod phase_vocoder;
mod robotize;
mod scramble;
mod shift;
mod spectral_harmonizer;
mod spectral_panner;
mod spectral_resonator;
mod widen;
pub use blur::SpectralBlur;
pub use compressor::SpectralCompressor;
pub use delay::SpectralDelay;
pub use dynamics::SpectralDynamics;
pub use exciter::SpectralExciter;
pub use filter::SpectralFilter;
pub use formant_shifter::FormantShifter;
pub use freeze::SpectralFreeze;
pub use gate::SpectralGate;
pub use invert::SpectralInvert;
pub use morph::{MorphTarget, SpectralMorph};
pub use phase_vocoder::PhaseVocoder;
pub use robotize::SpectralRobotize;
pub use scramble::SpectralScramble;
pub use shift::SpectralShift;
pub use spectral_harmonizer::{HarmonyVoice, SpectralHarmonizer};
pub use spectral_panner::{PanPoint, SpectralPanner};
pub use spectral_resonator::{Resonance, SpectralResonator};
pub use widen::SpectralWiden;
type WindowCache = DashMap<(WindowType, usize), Arc<Vec<f32>>>;
lazy_static! {
static ref WINDOW_CACHE: WindowCache = {
let cache = DashMap::new();
let common_sizes = [256, 512, 1024, 2048, 4096, 8192];
let window_types = [
WindowType::Rectangular,
WindowType::Hann,
WindowType::Hamming,
WindowType::Blackman,
WindowType::BlackmanHarris,
];
for &size in &common_sizes {
for &window_type in &window_types {
let coefficients = Window::generate_coefficients(window_type, size);
cache.insert((window_type, size), Arc::new(coefficients));
}
}
cache
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WindowType {
Rectangular,
Hann,
Hamming,
Blackman,
BlackmanHarris,
}
#[derive(Clone)]
pub struct Window {
pub window_type: WindowType,
pub size: usize,
coefficients: Arc<Vec<f32>>,
}
impl Window {
pub fn new(window_type: WindowType, size: usize) -> Self {
let coefficients = if let Some(cached) = WINDOW_CACHE.get(&(window_type, size)) {
Arc::clone(&cached) } else {
let coeff = Arc::new(Self::generate_coefficients(window_type, size));
if size <= 16384 {
WINDOW_CACHE.insert((window_type, size), Arc::clone(&coeff));
}
coeff
};
Self {
window_type,
size,
coefficients,
}
}
fn generate_coefficients(window_type: WindowType, size: usize) -> Vec<f32> {
match window_type {
WindowType::Rectangular => vec![1.0; size],
WindowType::Hann => Self::generate_hann(size),
WindowType::Hamming => Self::generate_hamming(size),
WindowType::Blackman => Self::generate_blackman(size),
WindowType::BlackmanHarris => Self::generate_blackman_harris(size),
}
}
fn generate_hann(size: usize) -> Vec<f32> {
(0..size)
.map(|n| {
let angle = 2.0 * PI * n as f32 / (size - 1) as f32;
0.5 * (1.0 - angle.cos())
})
.collect()
}
fn generate_hamming(size: usize) -> Vec<f32> {
(0..size)
.map(|n| {
let angle = 2.0 * PI * n as f32 / (size - 1) as f32;
0.54 - 0.46 * angle.cos()
})
.collect()
}
fn generate_blackman(size: usize) -> Vec<f32> {
(0..size)
.map(|n| {
let t = n as f32 / (size - 1) as f32;
let angle1 = 2.0 * PI * t;
let angle2 = 4.0 * PI * t;
0.42 - 0.5 * angle1.cos() + 0.08 * angle2.cos()
})
.collect()
}
fn generate_blackman_harris(size: usize) -> Vec<f32> {
const A0: f32 = 0.35875;
const A1: f32 = 0.48829;
const A2: f32 = 0.14128;
const A3: f32 = 0.01168;
(0..size)
.map(|n| {
let t = n as f32 / size as f32;
let angle1 = 2.0 * PI * t;
let angle2 = 4.0 * PI * t;
let angle3 = 6.0 * PI * t;
A0 - A1 * angle1.cos() + A2 * angle2.cos() - A3 * angle3.cos()
})
.collect()
}
#[inline]
pub fn apply(&self, buffer: &mut [f32]) {
assert_eq!(
buffer.len(),
self.size,
"Buffer length {} doesn't match window size {}",
buffer.len(),
self.size
);
self.apply_simd(buffer);
}
#[inline(always)]
fn apply_simd(&self, buffer: &mut [f32]) {
match SIMD.simd_width() {
SimdWidth::X8 => self.apply_simd_impl::<f32x8>(buffer),
SimdWidth::X4 => self.apply_simd_impl::<f32x4>(buffer),
SimdWidth::Scalar => self.apply_simd_impl::<f32>(buffer),
}
}
#[inline(always)]
fn apply_simd_impl<V: SimdLanes>(&self, buffer: &mut [f32]) {
let len = buffer.len();
let (buf_chunks, buf_rem) = buffer.split_at_mut(len - (len % V::LANES));
let (coef_chunks, coef_rem) = self.coefficients.split_at(len - (len % V::LANES));
for (buf_chunk, coef_chunk) in buf_chunks
.chunks_exact_mut(V::LANES)
.zip(coef_chunks.chunks_exact(V::LANES))
{
let signal = V::from_array(buf_chunk);
let window = V::from_array(coef_chunk);
let result = signal.mul(window);
result.write_to_slice(buf_chunk);
}
for (sample, &coef) in buf_rem.iter_mut().zip(coef_rem.iter()) {
*sample *= coef;
}
}
pub fn gain(&self) -> f32 {
self.coefficients.iter().sum::<f32>() / self.size as f32
}
pub fn coherent_gain(&self) -> f32 {
self.coefficients.iter().sum::<f32>() / self.size as f32
}
}
pub struct ComplexOps;
impl ComplexOps {
#[inline]
pub fn multiply(output: &mut [Complex<f32>], a: &[Complex<f32>], b: &[Complex<f32>]) {
let len = output.len().min(a.len()).min(b.len());
match SIMD.simd_width() {
SimdWidth::X8 => Self::multiply_impl::<8>(&mut output[..len], &a[..len], &b[..len]),
SimdWidth::X4 => Self::multiply_impl::<4>(&mut output[..len], &a[..len], &b[..len]),
SimdWidth::Scalar => Self::multiply_scalar(&mut output[..len], &a[..len], &b[..len]),
}
}
#[inline(always)]
fn multiply_impl<const N: usize>(
output: &mut [Complex<f32>],
a: &[Complex<f32>],
b: &[Complex<f32>],
) {
if N == 8 {
Self::multiply_simd::<f32x8>(output, a, b);
} else if N == 4 {
Self::multiply_simd::<f32x4>(output, a, b);
}
}
#[inline(always)]
fn multiply_simd<V: SimdLanes>(
output: &mut [Complex<f32>],
a: &[Complex<f32>],
b: &[Complex<f32>],
) {
const MAX_LANES: usize = 8;
let lanes = V::LANES;
let num_chunks = output.len() / lanes;
for i in 0..num_chunks {
let idx = i * lanes;
let out_chunk = &mut output[idx..idx + lanes];
let a_chunk = &a[idx..idx + lanes];
let b_chunk = &b[idx..idx + lanes];
let mut a_re = [0.0f32; MAX_LANES];
let mut a_im = [0.0f32; MAX_LANES];
let mut b_re = [0.0f32; MAX_LANES];
let mut b_im = [0.0f32; MAX_LANES];
for j in 0..lanes {
a_re[j] = a_chunk[j].re;
a_im[j] = a_chunk[j].im;
b_re[j] = b_chunk[j].re;
b_im[j] = b_chunk[j].im;
}
let a_re_vec = V::from_array(&a_re[..lanes]);
let a_im_vec = V::from_array(&a_im[..lanes]);
let b_re_vec = V::from_array(&b_re[..lanes]);
let b_im_vec = V::from_array(&b_im[..lanes]);
let out_re_vec = a_re_vec.mul(b_re_vec).sub(a_im_vec.mul(b_im_vec));
let out_im_vec = a_re_vec.mul(b_im_vec).add(a_im_vec.mul(b_re_vec));
let mut out_re = [0.0f32; MAX_LANES];
let mut out_im = [0.0f32; MAX_LANES];
out_re_vec.write_to_slice(&mut out_re[..lanes]);
out_im_vec.write_to_slice(&mut out_im[..lanes]);
for j in 0..lanes {
out_chunk[j] = Complex::new(out_re[j], out_im[j]);
}
}
Self::multiply_scalar(
&mut output[num_chunks * lanes..],
&a[num_chunks * lanes..],
&b[num_chunks * lanes..],
);
}
#[inline(always)]
fn multiply_scalar(output: &mut [Complex<f32>], a: &[Complex<f32>], b: &[Complex<f32>]) {
for i in 0..output.len() {
output[i] = a[i] * b[i];
}
}
#[inline]
pub fn magnitude(output: &mut [f32], input: &[Complex<f32>]) {
let len = output.len().min(input.len());
let mut re_buf = vec![0.0f32; len];
let mut im_buf = vec![0.0f32; len];
for (i, &c) in input[..len].iter().enumerate() {
re_buf[i] = c.re;
im_buf[i] = c.im;
}
for i in 0..len {
re_buf[i] *= re_buf[i]; im_buf[i] *= im_buf[i]; }
for i in 0..len {
output[i] = re_buf[i] + im_buf[i];
}
for sample in &mut output[..len] {
*sample = sample.sqrt();
}
}
#[inline]
pub fn scale(buffer: &mut [Complex<f32>], scalar: f32) {
let len = buffer.len();
let mut re_buf = vec![0.0f32; len];
let mut im_buf = vec![0.0f32; len];
for (i, &c) in buffer.iter().enumerate() {
re_buf[i] = c.re;
im_buf[i] = c.im;
}
SIMD.multiply_const(&mut re_buf, scalar);
SIMD.multiply_const(&mut im_buf, scalar);
for (i, c) in buffer.iter_mut().enumerate() {
c.re = re_buf[i];
c.im = im_buf[i];
}
}
}
#[derive(Clone)]
#[allow(clippy::upper_case_acronyms)]
pub struct STFT {
fft_size: usize,
hop_size: usize,
analysis_window: Window,
synthesis_window: Window,
fft: Arc<dyn Fft<f32>>,
ifft: Arc<dyn Fft<f32>>,
input_buffer: VecDeque<f32>,
output_buffer: Vec<f32>,
output_position: usize,
fft_buffer: Vec<Complex<f32>>,
time_buffer: Vec<f32>,
windowed_buffer: Vec<f32>,
}
impl STFT {
pub fn new(fft_size: usize, hop_size: usize, window_type: WindowType) -> Self {
assert!(fft_size.is_power_of_two(), "FFT size must be power of 2");
assert!(hop_size <= fft_size, "Hop size must be <= FFT size");
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(fft_size);
let ifft = planner.plan_fft_inverse(fft_size);
let analysis_window = Window::new(window_type, fft_size);
let synthesis_window = Window::new(window_type, fft_size);
Self {
fft_size,
hop_size,
analysis_window,
synthesis_window,
fft,
ifft,
input_buffer: VecDeque::with_capacity(fft_size * 2),
output_buffer: vec![0.0; fft_size * 2],
output_position: 0,
fft_buffer: vec![Complex::new(0.0, 0.0); fft_size],
time_buffer: vec![0.0; fft_size],
windowed_buffer: vec![0.0; fft_size],
}
}
pub fn process<F>(&mut self, output: &mut [f32], mut processor: F)
where
F: FnMut(&mut [Complex<f32>]),
{
output.fill(0.0);
let mut write_pos = 0;
while write_pos < output.len() {
let available = self.output_buffer.len() - self.output_position;
let needed = output.len() - write_pos;
if available >= self.hop_size || available >= needed {
let to_copy = available.min(needed).min(self.hop_size);
output[write_pos..write_pos + to_copy].copy_from_slice(
&self.output_buffer[self.output_position..self.output_position + to_copy],
);
write_pos += to_copy;
self.output_position += to_copy;
if self.output_position >= self.hop_size {
self.shift_output_buffer();
self.process_frame(&mut processor);
}
} else {
self.process_frame(&mut processor);
}
}
}
fn process_frame<F>(&mut self, processor: &mut F)
where
F: FnMut(&mut [Complex<f32>]),
{
if self.input_buffer.len() >= self.fft_size {
let input_slice = self.input_buffer.make_contiguous();
self.time_buffer[..].copy_from_slice(&input_slice[..self.fft_size]);
self.input_buffer.drain(..self.hop_size);
} else {
self.time_buffer.fill(0.0);
}
self.windowed_buffer.copy_from_slice(&self.time_buffer);
self.analysis_window.apply(&mut self.windowed_buffer);
for (i, &sample) in self.windowed_buffer.iter().enumerate() {
self.fft_buffer[i] = Complex::new(sample, 0.0);
}
self.fft.process(&mut self.fft_buffer);
processor(&mut self.fft_buffer);
self.ifft.process(&mut self.fft_buffer);
let scale = 1.0 / self.fft_size as f32;
for sample in &mut self.fft_buffer {
sample.re *= scale;
sample.im *= scale;
}
for (i, c) in self.fft_buffer.iter().enumerate() {
self.time_buffer[i] = c.re;
}
self.synthesis_window.apply(&mut self.time_buffer);
self.overlap_add_inplace();
}
fn overlap_add_inplace(&mut self) {
for i in 0..self.time_buffer.len() {
self.output_buffer[i] += self.time_buffer[i];
}
}
fn shift_output_buffer(&mut self) {
self.output_buffer.copy_within(self.hop_size.., 0);
let start = self.output_buffer.len() - self.hop_size;
self.output_buffer[start..].fill(0.0);
self.output_position = 0;
}
pub fn add_input(&mut self, input: &[f32]) {
self.input_buffer.extend(input.iter());
}
pub fn fft_size(&self) -> usize {
self.fft_size
}
pub fn hop_size(&self) -> usize {
self.hop_size
}
pub fn reset(&mut self) {
self.input_buffer.clear();
self.output_buffer.fill(0.0);
self.output_position = 0;
self.fft_buffer.fill(Complex::new(0.0, 0.0));
self.time_buffer.fill(0.0);
}
}
impl std::fmt::Debug for STFT {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("STFT")
.field("fft_size", &self.fft_size)
.field("hop_size", &self.hop_size)
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterType {
LowPass,
HighPass,
BandPass,
BandStop,
Notch,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_window_creation() {
let hann = Window::new(WindowType::Hann, 1024);
assert_eq!(hann.size, 1024);
assert_eq!(hann.coefficients.len(), 1024);
}
#[test]
fn test_hann_window_properties() {
let hann = Window::new(WindowType::Hann, 1024);
assert!(hann.coefficients[0] < 0.01);
assert!(hann.coefficients[1023] < 0.01);
assert!((hann.coefficients[512] - 1.0).abs() < 0.01);
}
#[test]
fn test_rectangular_window() {
let rect = Window::new(WindowType::Rectangular, 512);
for &coef in &*rect.coefficients {
assert_eq!(coef, 1.0);
}
}
#[test]
fn test_window_apply_simd() {
let window = Window::new(WindowType::Hann, 1024);
let mut buffer = vec![1.0; 1024];
window.apply(&mut buffer);
assert!(buffer[0] < 0.01);
assert!(buffer[1023] < 0.01);
assert!((buffer[512] - 1.0).abs() < 0.01);
}
#[test]
fn test_all_window_types() {
for window_type in [
WindowType::Rectangular,
WindowType::Hann,
WindowType::Hamming,
WindowType::Blackman,
WindowType::BlackmanHarris,
] {
let window = Window::new(window_type, 2048);
assert_eq!(window.coefficients.len(), 2048);
for &coef in &*window.coefficients {
assert!(coef.is_finite());
assert!(coef >= -0.1 && coef <= 1.1);
}
}
}
#[test]
fn test_window_gains() {
let hann = Window::new(WindowType::Hann, 1024);
let gain = hann.coherent_gain();
assert!((gain - 0.5).abs() < 0.01);
}
#[test]
fn test_complex_multiply() {
let a = vec![Complex::new(1.0, 2.0); 128];
let b = vec![Complex::new(3.0, 4.0); 128];
let mut result = vec![Complex::new(0.0, 0.0); 128];
ComplexOps::multiply(&mut result, &a, &b);
assert!((result[0].re - (-5.0)).abs() < 0.001);
assert!((result[0].im - 10.0).abs() < 0.001);
}
#[test]
fn test_complex_magnitude() {
let input = vec![Complex::new(3.0, 4.0); 256];
let mut magnitudes = vec![0.0; 256];
ComplexOps::magnitude(&mut magnitudes, &input);
for &mag in &magnitudes {
assert!((mag - 5.0).abs() < 0.001);
}
}
#[test]
fn test_complex_scale() {
let mut spectrum = vec![Complex::new(2.0, 4.0); 128];
ComplexOps::scale(&mut spectrum, 0.5);
for &c in &spectrum {
assert!((c.re - 1.0).abs() < 0.001);
assert!((c.im - 2.0).abs() < 0.001);
}
}
#[test]
fn test_stft_creation() {
let stft = STFT::new(2048, 512, WindowType::Hann);
assert_eq!(stft.fft_size(), 2048);
assert_eq!(stft.hop_size(), 512);
}
#[test]
fn test_stft_add_input() {
let mut stft = STFT::new(1024, 256, WindowType::Hann);
let input = vec![1.0; 256];
stft.add_input(&input);
assert_eq!(stft.input_buffer.len(), 256);
}
#[test]
fn test_stft_process_identity() {
let mut stft = STFT::new(1024, 256, WindowType::Hann);
let input = vec![0.5; 512];
stft.add_input(&input);
let mut output = vec![0.0; 256];
stft.process(&mut output, |_spectrum| {
});
assert!(output.len() == 256);
}
#[test]
fn test_stft_reset() {
let mut stft = STFT::new(2048, 512, WindowType::Hann);
let input = vec![1.0; 1024];
stft.add_input(&input);
stft.reset();
assert_eq!(stft.input_buffer.len(), 0);
}
#[test]
fn test_complex_multiply_identity() {
let a = vec![Complex::new(5.0, 7.0); 64];
let identity = vec![Complex::new(1.0, 0.0); 64];
let mut result = vec![Complex::new(0.0, 0.0); 64];
ComplexOps::multiply(&mut result, &a, &identity);
assert!((result[0].re - 5.0).abs() < 0.001);
assert!((result[0].im - 7.0).abs() < 0.001);
}
#[test]
#[should_panic(expected = "FFT size must be power of 2")]
fn test_stft_requires_power_of_two() {
STFT::new(1000, 250, WindowType::Hann);
}
#[test]
#[should_panic(expected = "Hop size must be <= FFT size")]
fn test_stft_hop_size_validation() {
STFT::new(1024, 2048, WindowType::Hann);
}
#[test]
fn test_stft_process_silent() {
let mut stft = STFT::new(1024, 256, WindowType::Hann);
let mut output = vec![0.0; 512];
stft.process(&mut output, |_spectrum| {
});
for &sample in &output {
assert!(sample.abs() < 0.001, "Expected silence, got {}", sample);
}
}
#[test]
fn test_stft_spectral_callback() {
let mut stft = STFT::new(1024, 256, WindowType::Hann);
let mut output = vec![0.0; 512];
let mut callback_invoked = false;
stft.process(&mut output, |spectrum| {
callback_invoked = true;
assert_eq!(spectrum.len(), 1024);
});
assert!(
callback_invoked,
"Spectral processing callback was never called"
);
}
#[test]
fn test_stft_spectral_zeroing() {
let mut stft = STFT::new(512, 128, WindowType::Hann);
let mut output = vec![0.0; 256];
stft.process(&mut output, |spectrum| {
for s in spectrum.iter_mut() {
*s = Complex::new(0.0, 0.0);
}
});
for &sample in &output {
assert!(sample.abs() < 0.001);
}
}
#[test]
fn test_stft_different_hop_sizes() {
for hop_size in [128, 256, 512] {
let mut stft = STFT::new(1024, hop_size, WindowType::Hann);
let mut output = vec![0.0; 512];
stft.process(&mut output, |_| {});
assert_eq!(output.len(), 512);
}
}
#[test]
fn test_stft_all_window_types() {
for window_type in [
WindowType::Rectangular,
WindowType::Hann,
WindowType::Hamming,
WindowType::Blackman,
WindowType::BlackmanHarris,
] {
let mut stft = STFT::new(512, 128, window_type);
let mut output = vec![0.0; 256];
stft.process(&mut output, |_| {});
assert_eq!(output.len(), 256);
}
}
#[test]
fn test_stft_overlap_add_accumulation() {
let mut stft = STFT::new(256, 64, WindowType::Rectangular);
let mut output = vec![0.0; 128];
for _ in 0..5 {
stft.process(&mut output, |_spectrum| {
});
}
assert_eq!(output.len(), 128);
}
#[test]
fn test_stft_output_buffer_size() {
let mut stft = STFT::new(1024, 256, WindowType::Hann);
for size in [128, 256, 512, 1024] {
let mut output = vec![0.0; size];
stft.process(&mut output, |_| {});
assert_eq!(output.len(), size);
}
}
}