use super::*;
use rustfft::num_complex::Complex;
#[derive(Clone, Debug)]
pub struct SpectralWiden {
stft: STFT,
fft_size: usize,
sample_rate: f32,
width: f32,
low_cutoff: f32,
high_cutoff: f32,
enabled: bool,
}
impl SpectralWiden {
pub fn new(fft_size: usize, hop_size: usize, window_type: WindowType, sample_rate: f32) -> 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");
assert!(sample_rate > 0.0, "Sample rate must be positive");
let stft = STFT::new(fft_size, hop_size, window_type);
Self {
stft,
fft_size,
sample_rate,
width: 1.5,
low_cutoff: 200.0,
high_cutoff: 16000.0,
enabled: true,
}
}
pub fn set_width(&mut self, width: f32) {
self.width = width.clamp(0.0, 3.0);
}
pub fn set_low_cutoff(&mut self, frequency: f32) {
self.low_cutoff = frequency.max(20.0);
}
pub fn set_high_cutoff(&mut self, frequency: f32) {
self.high_cutoff = frequency.min(self.sample_rate * 0.45);
}
pub fn width(&self) -> f32 {
self.width
}
pub fn low_cutoff(&self) -> f32 {
self.low_cutoff
}
pub fn high_cutoff(&self) -> f32 {
self.high_cutoff
}
pub fn process(&mut self, output: &mut [f32], _input: &[f32]) {
if !self.enabled {
return;
}
let width = self.width;
let low_cutoff = self.low_cutoff;
let high_cutoff = self.high_cutoff;
let sample_rate = self.sample_rate;
let fft_size = self.fft_size;
self.stft.process(output, |spectrum| {
Self::apply_widen_static(spectrum, width, low_cutoff, high_cutoff, sample_rate, fft_size);
});
}
#[inline]
fn apply_widen_static(
spectrum: &mut [Complex<f32>],
width: f32,
low_cutoff: f32,
high_cutoff: f32,
sample_rate: f32,
fft_size: usize,
) {
let len = spectrum.len();
let hz_per_bin = sample_rate / fft_size as f32;
for (i, bin) in spectrum.iter_mut().enumerate().take(len) {
let freq = i as f32 * hz_per_bin;
if freq >= low_cutoff && freq <= high_cutoff {
let mag = bin.norm();
let phase = bin.arg();
let freq_ratio = ((freq - low_cutoff) / (high_cutoff - low_cutoff)).clamp(0.0, 1.0);
let phase_shift = (width - 1.0) * freq_ratio * 0.5;
let direction = if i % 2 == 0 { 1.0 } else { -1.0 };
let new_phase = phase + phase_shift * direction;
*bin = Complex::from_polar(mag, new_phase);
}
}
}
pub fn reset(&mut self) {
self.stft.reset();
}
pub fn fft_size(&self) -> usize {
self.fft_size
}
pub fn hop_size(&self) -> usize {
self.stft.hop_size
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
impl SpectralWiden {
pub fn subtle() -> Self {
let mut widen = Self::new(2048, 512, WindowType::Hann, 44100.0);
widen.set_width(1.2);
widen.set_low_cutoff(250.0);
widen
}
pub fn moderate() -> Self {
let mut widen = Self::new(2048, 512, WindowType::Hann, 44100.0);
widen.set_width(1.5);
widen.set_low_cutoff(200.0);
widen
}
pub fn wide() -> Self {
let mut widen = Self::new(2048, 512, WindowType::Hann, 44100.0);
widen.set_width(2.0);
widen.set_low_cutoff(150.0);
widen
}
pub fn ultra() -> Self {
let mut widen = Self::new(2048, 512, WindowType::Hann, 44100.0);
widen.set_width(2.5);
widen.set_low_cutoff(200.0);
widen
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spectral_widen_creation() {
let widen = SpectralWiden::new(2048, 512, WindowType::Hann, 44100.0);
assert!(widen.is_enabled());
assert_eq!(widen.fft_size(), 2048);
assert_eq!(widen.width(), 1.5);
assert_eq!(widen.low_cutoff(), 200.0);
assert_eq!(widen.high_cutoff(), 16000.0);
}
#[test]
#[should_panic(expected = "FFT size must be power of 2")]
fn test_requires_power_of_two() {
SpectralWiden::new(1000, 250, WindowType::Hann, 44100.0);
}
#[test]
#[should_panic(expected = "Hop size must be <= FFT size")]
fn test_hop_validation() {
SpectralWiden::new(512, 1024, WindowType::Hann, 44100.0);
}
#[test]
#[should_panic(expected = "Sample rate must be positive")]
fn test_sample_rate_validation() {
SpectralWiden::new(512, 128, WindowType::Hann, 0.0);
}
#[test]
fn test_set_width() {
let mut widen = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
widen.set_width(2.0);
assert_eq!(widen.width(), 2.0);
widen.set_width(-0.5);
assert_eq!(widen.width(), 0.0);
widen.set_width(5.0);
assert_eq!(widen.width(), 3.0);
}
#[test]
fn test_set_low_cutoff() {
let mut widen = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
widen.set_low_cutoff(300.0);
assert_eq!(widen.low_cutoff(), 300.0);
widen.set_low_cutoff(10.0);
assert_eq!(widen.low_cutoff(), 20.0);
}
#[test]
fn test_set_high_cutoff() {
let mut widen = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
widen.set_high_cutoff(10000.0);
assert_eq!(widen.high_cutoff(), 10000.0);
widen.set_high_cutoff(50000.0);
assert_eq!(widen.high_cutoff(), 44100.0 * 0.45);
}
#[test]
fn test_enable_disable() {
let mut widen = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
assert!(widen.is_enabled());
widen.set_enabled(false);
assert!(!widen.is_enabled());
widen.set_enabled(true);
assert!(widen.is_enabled());
}
#[test]
fn test_process_disabled() {
let mut widen = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
widen.set_enabled(false);
let input = vec![0.1; 512];
let mut output = vec![0.0; 512];
widen.process(&mut output, &input);
assert!(output.iter().all(|&x| x == 0.0));
}
#[test]
fn test_process_basic() {
let mut widen = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
let input = vec![0.1; 512];
let mut output = vec![0.0; 512];
widen.process(&mut output, &input);
}
#[test]
fn test_reset() {
let mut widen = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
let input = vec![0.1; 512];
let mut output = vec![0.0; 512];
widen.process(&mut output, &input);
widen.reset();
widen.process(&mut output, &input);
}
#[test]
fn test_preset_subtle() {
let widen = SpectralWiden::subtle();
assert_eq!(widen.width(), 1.2);
assert_eq!(widen.low_cutoff(), 250.0);
assert!(widen.is_enabled());
}
#[test]
fn test_preset_moderate() {
let widen = SpectralWiden::moderate();
assert_eq!(widen.width(), 1.5);
assert_eq!(widen.low_cutoff(), 200.0);
assert!(widen.is_enabled());
}
#[test]
fn test_preset_wide() {
let widen = SpectralWiden::wide();
assert_eq!(widen.width(), 2.0);
assert_eq!(widen.low_cutoff(), 150.0);
assert!(widen.is_enabled());
}
#[test]
fn test_preset_ultra() {
let widen = SpectralWiden::ultra();
assert_eq!(widen.width(), 2.5);
assert_eq!(widen.low_cutoff(), 200.0);
assert!(widen.is_enabled());
}
#[test]
fn test_different_fft_sizes() {
let widen_512 = SpectralWiden::new(512, 128, WindowType::Hann, 44100.0);
let widen_1024 = SpectralWiden::new(1024, 256, WindowType::Hann, 44100.0);
let widen_2048 = SpectralWiden::new(2048, 512, WindowType::Hann, 44100.0);
assert_eq!(widen_512.fft_size(), 512);
assert_eq!(widen_1024.fft_size(), 1024);
assert_eq!(widen_2048.fft_size(), 2048);
}
}