use super::*;
use rustfft::num_complex::Complex;
#[derive(Clone, Debug)]
pub struct SpectralExciter {
stft: STFT,
fft_size: usize,
sample_rate: f32,
frequency: f32,
drive: f32,
harmonics: f32,
mix: f32,
enabled: bool,
}
impl SpectralExciter {
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,
frequency: 3000.0,
drive: 2.0,
harmonics: 0.5,
mix: 0.5,
enabled: true,
}
}
pub fn set_frequency(&mut self, frequency: f32) {
self.frequency = frequency.max(100.0).min(self.sample_rate * 0.45);
}
pub fn set_drive(&mut self, drive: f32) {
self.drive = drive.clamp(1.0, 10.0);
}
pub fn set_harmonics(&mut self, harmonics: f32) {
self.harmonics = harmonics.clamp(0.0, 1.0);
}
pub fn set_mix(&mut self, mix: f32) {
self.mix = mix.clamp(0.0, 1.0);
}
pub fn frequency(&self) -> f32 {
self.frequency
}
pub fn drive(&self) -> f32 {
self.drive
}
pub fn harmonics(&self) -> f32 {
self.harmonics
}
pub fn mix(&self) -> f32 {
self.mix
}
pub fn process(&mut self, output: &mut [f32], _input: &[f32]) {
if !self.enabled {
return;
}
let frequency = self.frequency;
let drive = self.drive;
let harmonics = self.harmonics;
let mix = self.mix;
let sample_rate = self.sample_rate;
let fft_size = self.fft_size;
self.stft.process(output, |spectrum| {
Self::apply_exciter_static(spectrum, frequency, drive, harmonics, mix, sample_rate, fft_size);
});
}
#[inline]
fn apply_exciter_static(
spectrum: &mut [Complex<f32>],
frequency: f32,
drive: f32,
harmonics: f32,
mix: f32,
sample_rate: f32,
fft_size: usize,
) {
let len = spectrum.len();
let mut dry_spectrum = vec![Complex::new(0.0, 0.0); len];
dry_spectrum.copy_from_slice(spectrum);
let hz_per_bin = sample_rate / fft_size as f32;
let crossover_bin = (frequency / hz_per_bin) as usize;
for (i, bin) in spectrum.iter_mut().enumerate().take(len).skip(crossover_bin) {
let mag = bin.norm();
let phase = bin.arg();
if mag > 1e-10 {
let bin_freq = i as f32 * hz_per_bin;
let freq_ratio = (bin_freq - frequency) / (sample_rate * 0.5 - frequency);
let boost = 1.0 + (freq_ratio * drive * 0.5);
let driven_mag = mag * boost;
let excited_mag = if driven_mag > 1.0 {
1.0 + (driven_mag - 1.0).tanh() * 0.5
} else {
driven_mag
};
let phase_mod = if i % 2 == 1 {
0.1 * harmonics * drive * 0.1 } else {
-0.1 * (1.0 - harmonics) * drive * 0.1 };
*bin = Complex::from_polar(excited_mag, phase + phase_mod);
}
}
if mix < 1.0 {
for i in 0..len {
spectrum[i] = Complex::new(
spectrum[i].re * mix + dry_spectrum[i].re * (1.0 - mix),
spectrum[i].im * mix + dry_spectrum[i].im * (1.0 - mix),
);
}
}
}
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 SpectralExciter {
pub fn gentle() -> Self {
let mut exciter = Self::new(2048, 512, WindowType::Hann, 44100.0);
exciter.set_frequency(5000.0);
exciter.set_drive(1.5);
exciter.set_harmonics(0.3);
exciter.set_mix(0.25);
exciter
}
pub fn moderate() -> Self {
let mut exciter = Self::new(2048, 512, WindowType::Hann, 44100.0);
exciter.set_frequency(3500.0);
exciter.set_drive(2.5);
exciter.set_harmonics(0.5);
exciter.set_mix(0.4);
exciter
}
pub fn aggressive() -> Self {
let mut exciter = Self::new(2048, 512, WindowType::Hann, 44100.0);
exciter.set_frequency(2500.0);
exciter.set_drive(4.0);
exciter.set_harmonics(0.7);
exciter.set_mix(0.6);
exciter
}
pub fn air() -> Self {
let mut exciter = Self::new(2048, 512, WindowType::Hann, 44100.0);
exciter.set_frequency(8000.0);
exciter.set_drive(2.0);
exciter.set_harmonics(0.8);
exciter.set_mix(0.3);
exciter
}
pub fn presence() -> Self {
let mut exciter = Self::new(2048, 512, WindowType::Hann, 44100.0);
exciter.set_frequency(2000.0);
exciter.set_drive(3.0);
exciter.set_harmonics(0.4);
exciter.set_mix(0.5);
exciter
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spectral_exciter_creation() {
let exciter = SpectralExciter::new(2048, 512, WindowType::Hann, 44100.0);
assert!(exciter.is_enabled());
assert_eq!(exciter.fft_size(), 2048);
assert_eq!(exciter.hop_size(), 512);
assert_eq!(exciter.frequency(), 3000.0);
assert_eq!(exciter.drive(), 2.0);
assert_eq!(exciter.harmonics(), 0.5);
assert_eq!(exciter.mix(), 0.5);
}
#[test]
#[should_panic(expected = "FFT size must be power of 2")]
fn test_spectral_exciter_requires_power_of_two() {
SpectralExciter::new(1000, 250, WindowType::Hann, 44100.0);
}
#[test]
#[should_panic(expected = "Hop size must be <= FFT size")]
fn test_spectral_exciter_hop_validation() {
SpectralExciter::new(512, 1024, WindowType::Hann, 44100.0);
}
#[test]
#[should_panic(expected = "Sample rate must be positive")]
fn test_spectral_exciter_sample_rate_validation() {
SpectralExciter::new(512, 128, WindowType::Hann, 0.0);
}
#[test]
fn test_set_frequency() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
exciter.set_frequency(5000.0);
assert_eq!(exciter.frequency(), 5000.0);
exciter.set_frequency(50.0);
assert_eq!(exciter.frequency(), 100.0);
exciter.set_frequency(30000.0);
assert_eq!(exciter.frequency(), 44100.0 * 0.45);
}
#[test]
fn test_set_drive() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
exciter.set_drive(3.0);
assert_eq!(exciter.drive(), 3.0);
exciter.set_drive(0.5);
assert_eq!(exciter.drive(), 1.0);
exciter.set_drive(15.0);
assert_eq!(exciter.drive(), 10.0);
}
#[test]
fn test_set_harmonics() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
exciter.set_harmonics(0.7);
assert_eq!(exciter.harmonics(), 0.7);
exciter.set_harmonics(-0.5);
assert_eq!(exciter.harmonics(), 0.0);
exciter.set_harmonics(1.5);
assert_eq!(exciter.harmonics(), 1.0);
}
#[test]
fn test_set_mix() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
exciter.set_mix(0.7);
assert_eq!(exciter.mix(), 0.7);
exciter.set_mix(-0.5);
assert_eq!(exciter.mix(), 0.0);
exciter.set_mix(1.5);
assert_eq!(exciter.mix(), 1.0);
}
#[test]
fn test_enable_disable() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
assert!(exciter.is_enabled());
exciter.set_enabled(false);
assert!(!exciter.is_enabled());
exciter.set_enabled(true);
assert!(exciter.is_enabled());
}
#[test]
fn test_process_disabled() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
exciter.set_enabled(false);
let input = vec![0.1; 512];
let mut output = vec![0.0; 512];
exciter.process(&mut output, &input);
assert!(output.iter().all(|&x| x == 0.0));
}
#[test]
fn test_process_basic() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
let input = vec![0.1; 512];
let mut output = vec![0.0; 512];
exciter.process(&mut output, &input);
}
#[test]
fn test_reset() {
let mut exciter = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
let input = vec![0.1; 512];
let mut output = vec![0.0; 512];
exciter.process(&mut output, &input);
exciter.reset();
exciter.process(&mut output, &input);
}
#[test]
fn test_preset_gentle() {
let exciter = SpectralExciter::gentle();
assert_eq!(exciter.frequency(), 5000.0);
assert_eq!(exciter.drive(), 1.5);
assert_eq!(exciter.harmonics(), 0.3);
assert_eq!(exciter.mix(), 0.25);
assert!(exciter.is_enabled());
}
#[test]
fn test_preset_moderate() {
let exciter = SpectralExciter::moderate();
assert_eq!(exciter.frequency(), 3500.0);
assert_eq!(exciter.drive(), 2.5);
assert_eq!(exciter.harmonics(), 0.5);
assert_eq!(exciter.mix(), 0.4);
assert!(exciter.is_enabled());
}
#[test]
fn test_preset_aggressive() {
let exciter = SpectralExciter::aggressive();
assert_eq!(exciter.frequency(), 2500.0);
assert_eq!(exciter.drive(), 4.0);
assert_eq!(exciter.harmonics(), 0.7);
assert_eq!(exciter.mix(), 0.6);
assert!(exciter.is_enabled());
}
#[test]
fn test_preset_air() {
let exciter = SpectralExciter::air();
assert_eq!(exciter.frequency(), 8000.0);
assert_eq!(exciter.drive(), 2.0);
assert_eq!(exciter.harmonics(), 0.8);
assert_eq!(exciter.mix(), 0.3);
assert!(exciter.is_enabled());
}
#[test]
fn test_preset_presence() {
let exciter = SpectralExciter::presence();
assert_eq!(exciter.frequency(), 2000.0);
assert_eq!(exciter.drive(), 3.0);
assert_eq!(exciter.harmonics(), 0.4);
assert_eq!(exciter.mix(), 0.5);
assert!(exciter.is_enabled());
}
#[test]
fn test_different_fft_sizes() {
let exciter_512 = SpectralExciter::new(512, 128, WindowType::Hann, 44100.0);
let exciter_1024 = SpectralExciter::new(1024, 256, WindowType::Hann, 44100.0);
let exciter_2048 = SpectralExciter::new(2048, 512, WindowType::Hann, 44100.0);
assert_eq!(exciter_512.fft_size(), 512);
assert_eq!(exciter_1024.fft_size(), 1024);
assert_eq!(exciter_2048.fft_size(), 2048);
}
}