use super::*;
use rustfft::num_complex::Complex;
#[derive(Clone)]
pub struct SpectralFreeze {
stft: STFT,
frozen_spectrum: Vec<Complex<f32>>,
fft_size: usize,
is_frozen: bool,
mix: f32,
has_captured: bool,
}
impl SpectralFreeze {
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 stft = STFT::new(fft_size, hop_size, window_type);
Self {
stft,
frozen_spectrum: vec![Complex::new(0.0, 0.0); fft_size],
fft_size,
is_frozen: false,
mix: 1.0, has_captured: false,
}
}
pub fn freeze(&mut self) {
self.is_frozen = true;
}
pub fn unfreeze(&mut self) {
self.is_frozen = false;
}
pub fn set_mix(&mut self, mix: f32) {
self.mix = mix.clamp(0.0, 1.0);
}
pub fn is_frozen(&self) -> bool {
self.is_frozen
}
pub fn mix(&self) -> f32 {
self.mix
}
pub fn process(&mut self, output: &mut [f32], _input: &[f32]) {
let is_frozen = self.is_frozen;
let mix = self.mix;
let frozen_spectrum = &mut self.frozen_spectrum;
let has_captured = &mut self.has_captured;
self.stft.process(output, |spectrum| {
if is_frozen {
if !*has_captured {
frozen_spectrum.copy_from_slice(spectrum);
*has_captured = true;
}
Self::mix_spectrums_simd(spectrum, frozen_spectrum, mix);
} else {
*has_captured = false;
}
});
}
#[inline]
fn mix_spectrums_simd(live: &mut [Complex<f32>], frozen: &[Complex<f32>], mix: f32) {
let len = live.len().min(frozen.len());
let live_gain = 1.0 - mix;
let mut live_re = vec![0.0f32; len];
let mut live_im = vec![0.0f32; len];
let mut frozen_re = vec![0.0f32; len];
let mut frozen_im = vec![0.0f32; len];
for i in 0..len {
live_re[i] = live[i].re;
live_im[i] = live[i].im;
frozen_re[i] = frozen[i].re;
frozen_im[i] = frozen[i].im;
}
SIMD.multiply_const(&mut live_re, live_gain);
SIMD.multiply_const(&mut live_im, live_gain);
SIMD.multiply_const(&mut frozen_re, mix);
SIMD.multiply_const(&mut frozen_im, mix);
for i in 0..len {
live_re[i] += frozen_re[i];
live_im[i] += frozen_im[i];
}
for i in 0..len {
live[i] = Complex::new(live_re[i], live_im[i]);
}
}
pub fn reset(&mut self) {
self.stft.reset();
self.frozen_spectrum.fill(Complex::new(0.0, 0.0));
self.is_frozen = false;
self.has_captured = false;
}
pub fn fft_size(&self) -> usize {
self.fft_size
}
pub fn hop_size(&self) -> usize {
self.stft.hop_size()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spectral_freeze_creation() {
let freeze = SpectralFreeze::new(2048, 512, WindowType::Hann);
assert_eq!(freeze.fft_size(), 2048);
assert_eq!(freeze.hop_size(), 512);
assert!(!freeze.is_frozen());
assert_eq!(freeze.mix(), 1.0);
}
#[test]
#[should_panic(expected = "FFT size must be power of 2")]
fn test_spectral_freeze_requires_power_of_two() {
SpectralFreeze::new(1000, 250, WindowType::Hann);
}
#[test]
#[should_panic(expected = "Hop size must be <= FFT size")]
fn test_spectral_freeze_hop_validation() {
SpectralFreeze::new(1024, 2048, WindowType::Hann);
}
#[test]
fn test_spectral_freeze_freeze_unfreeze() {
let mut freeze = SpectralFreeze::new(1024, 256, WindowType::Hann);
assert!(!freeze.is_frozen());
freeze.freeze();
assert!(freeze.is_frozen());
freeze.unfreeze();
assert!(!freeze.is_frozen());
}
#[test]
fn test_spectral_freeze_set_mix() {
let mut freeze = SpectralFreeze::new(1024, 256, WindowType::Hann);
freeze.set_mix(0.5);
assert_eq!(freeze.mix(), 0.5);
freeze.set_mix(0.0);
assert_eq!(freeze.mix(), 0.0);
freeze.set_mix(1.0);
assert_eq!(freeze.mix(), 1.0);
}
#[test]
fn test_spectral_freeze_mix_clamping() {
let mut freeze = SpectralFreeze::new(1024, 256, WindowType::Hann);
freeze.set_mix(1.5);
assert_eq!(freeze.mix(), 1.0);
freeze.set_mix(-0.5);
assert_eq!(freeze.mix(), 0.0);
}
#[test]
fn test_spectral_freeze_process_silent() {
let mut freeze = SpectralFreeze::new(1024, 256, WindowType::Hann);
let input = vec![0.0; 512];
let mut output = vec![0.0; 512];
freeze.process(&mut output, &input);
for &sample in &output {
assert!(sample.abs() < 0.001, "Expected silence, got {}", sample);
}
}
#[test]
fn test_spectral_freeze_process_frozen() {
let mut freeze = SpectralFreeze::new(512, 128, WindowType::Hann);
freeze.freeze();
freeze.set_mix(1.0);
let input = vec![0.0; 256];
let mut output = vec![0.0; 256];
freeze.process(&mut output, &input);
assert_eq!(output.len(), 256);
}
#[test]
fn test_spectral_freeze_process_live() {
let mut freeze = SpectralFreeze::new(512, 128, WindowType::Hann);
freeze.set_mix(0.0);
let input = vec![0.0; 256];
let mut output = vec![0.0; 256];
freeze.process(&mut output, &input);
assert_eq!(output.len(), 256);
}
#[test]
fn test_spectral_freeze_process_mixed() {
let mut freeze = SpectralFreeze::new(512, 128, WindowType::Hann);
freeze.freeze();
freeze.set_mix(0.5);
let input = vec![0.0; 256];
let mut output = vec![0.0; 256];
freeze.process(&mut output, &input);
assert_eq!(output.len(), 256);
}
#[test]
fn test_spectral_freeze_reset() {
let mut freeze = SpectralFreeze::new(512, 128, WindowType::Hann);
freeze.freeze();
let input = vec![0.0; 256];
let mut output = vec![0.0; 256];
freeze.process(&mut output, &input);
freeze.reset();
assert!(!freeze.is_frozen());
freeze.process(&mut output, &input);
assert_eq!(output.len(), 256);
}
#[test]
fn test_spectral_freeze_all_window_types() {
for window_type in [
WindowType::Rectangular,
WindowType::Hann,
WindowType::Hamming,
WindowType::Blackman,
WindowType::BlackmanHarris,
] {
let mut freeze = SpectralFreeze::new(512, 128, window_type);
freeze.freeze();
let input = vec![0.0; 256];
let mut output = vec![0.0; 256];
freeze.process(&mut output, &input);
assert_eq!(output.len(), 256);
}
}
#[test]
fn test_spectral_freeze_various_fft_sizes() {
for fft_size in [512, 1024, 2048, 4096] {
let hop_size = fft_size / 4;
let mut freeze = SpectralFreeze::new(fft_size, hop_size, WindowType::Hann);
freeze.freeze();
let input = vec![0.0; 512];
let mut output = vec![0.0; 512];
freeze.process(&mut output, &input);
assert_eq!(output.len(), 512);
}
}
#[test]
fn test_spectral_freeze_toggle() {
let mut freeze = SpectralFreeze::new(512, 128, WindowType::Hann);
let input = vec![0.0; 256];
let mut output = vec![0.0; 256];
freeze.process(&mut output, &input);
freeze.freeze();
freeze.process(&mut output, &input);
freeze.unfreeze();
freeze.process(&mut output, &input);
assert_eq!(output.len(), 256);
}
}