use super::*;
use rustfft::num_complex::Complex;
use std::cell::RefCell;
#[allow(dead_code)]
#[derive(Clone)]
pub struct FormantShifter {
stft: STFT,
fft_size: usize,
sample_rate: f32,
shift_ratio: f32,
mix: f32,
enabled: bool,
dry_spectrum_buf: RefCell<Vec<Complex<f32>>>,
shifted_buf: RefCell<Vec<Complex<f32>>>,
temp_real_buf: RefCell<Vec<f32>>,
temp_imag_buf: RefCell<Vec<f32>>,
}
impl std::fmt::Debug for FormantShifter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FormantShifter")
.field("fft_size", &self.fft_size)
.field("sample_rate", &self.sample_rate)
.field("shift_ratio", &self.shift_ratio)
.field("mix", &self.mix)
.field("enabled", &self.enabled)
.finish()
}
}
impl FormantShifter {
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);
let spectrum_size = fft_size;
Self {
stft,
fft_size,
sample_rate,
shift_ratio: 1.0,
mix: 1.0,
enabled: true,
dry_spectrum_buf: RefCell::new(vec![Complex::new(0.0, 0.0); spectrum_size]),
shifted_buf: RefCell::new(vec![Complex::new(0.0, 0.0); spectrum_size]),
temp_real_buf: RefCell::new(vec![0.0; spectrum_size * 2]),
temp_imag_buf: RefCell::new(vec![0.0; spectrum_size * 2]),
}
}
pub fn set_shift_ratio(&mut self, ratio: f32) {
self.shift_ratio = ratio.max(0.1); }
pub fn set_mix(&mut self, mix: f32) {
self.mix = mix.clamp(0.0, 1.0);
}
pub fn shift_ratio(&self) -> f32 {
self.shift_ratio
}
pub fn mix(&self) -> f32 {
self.mix
}
pub fn process(&mut self, output: &mut [f32], _input: &[f32]) {
if !self.enabled {
return;
}
let shift_ratio = self.shift_ratio;
let mix = self.mix;
let dry_buf = &self.dry_spectrum_buf;
let shifted_buf = &self.shifted_buf;
let temp_re = &self.temp_real_buf;
let temp_im = &self.temp_imag_buf;
self.stft.process(output, |spectrum| {
Self::apply_formant_shift(
spectrum,
shift_ratio,
mix,
dry_buf,
shifted_buf,
temp_re,
temp_im,
);
});
}
#[inline]
fn apply_formant_shift(
spectrum: &mut [Complex<f32>],
shift_ratio: f32,
mix: f32,
dry_buf_cell: &RefCell<Vec<Complex<f32>>>,
shifted_buf_cell: &RefCell<Vec<Complex<f32>>>,
temp_re_cell: &RefCell<Vec<f32>>,
temp_im_cell: &RefCell<Vec<f32>>,
) {
let len = spectrum.len();
let mut dry_spectrum = dry_buf_cell.borrow_mut();
let mut shifted = shifted_buf_cell.borrow_mut();
if dry_spectrum.len() < len {
dry_spectrum.resize(len, Complex::new(0.0, 0.0));
}
if shifted.len() < len {
shifted.resize(len, Complex::new(0.0, 0.0));
}
dry_spectrum[..len].copy_from_slice(spectrum);
{
let mut temp_real = temp_re_cell.borrow_mut();
let mut temp_imag = temp_im_cell.borrow_mut();
if temp_real.len() < len {
temp_real.resize(len * 2, 0.0);
}
if temp_imag.len() < len {
temp_imag.resize(len * 2, 0.0);
}
let (magnitudes, _) = temp_real.split_at_mut(len);
let (phases, _) = temp_imag.split_at_mut(len);
ComplexOps::magnitude(magnitudes, spectrum);
for i in 0..len {
phases[i] = spectrum[i].im.atan2(spectrum[i].re);
}
for bin in shifted.iter_mut().take(len) {
*bin = Complex::new(0.0, 0.0);
}
for i in 0..len {
let src_bin = i as f32 * shift_ratio;
if src_bin >= 0.0 && src_bin < (len - 1) as f32 {
let bin_floor = src_bin.floor() as usize;
let bin_ceil = (bin_floor + 1).min(len - 1);
let frac = src_bin - bin_floor as f32;
let mag = magnitudes[bin_floor].mul_add(1.0 - frac, magnitudes[bin_ceil] * frac);
let phase_floor = phases[bin_floor];
let phase_ceil = phases[bin_ceil];
let mut phase_diff = phase_ceil - phase_floor;
if phase_diff > std::f32::consts::PI {
phase_diff -= 2.0 * std::f32::consts::PI;
} else if phase_diff < -std::f32::consts::PI {
phase_diff += 2.0 * std::f32::consts::PI;
}
let phase = phase_floor + phase_diff * frac;
shifted[i] = Complex::from_polar(mag, phase);
}
}
}
spectrum.copy_from_slice(&shifted[..len]);
if mix < 1.0 {
Self::mix_complex_simd(spectrum, &dry_spectrum[..len], mix, temp_re_cell, temp_im_cell);
}
}
#[inline]
fn mix_complex_simd(
spectrum: &mut [Complex<f32>],
dry: &[Complex<f32>],
wet: f32,
temp_re_cell: &RefCell<Vec<f32>>,
temp_im_cell: &RefCell<Vec<f32>>,
) {
let len = spectrum.len();
let dry_mix = 1.0 - wet;
let mut wet_re = temp_re_cell.borrow_mut();
let mut wet_im = temp_im_cell.borrow_mut();
if wet_re.len() < len * 2 {
wet_re.resize(len * 2, 0.0);
}
if wet_im.len() < len * 2 {
wet_im.resize(len * 2, 0.0);
}
let (wet_re_buf, dry_re_buf) = wet_re.split_at_mut(len);
let (wet_im_buf, dry_im_buf) = wet_im.split_at_mut(len);
for i in 0..len {
wet_re_buf[i] = spectrum[i].re;
wet_im_buf[i] = spectrum[i].im;
dry_re_buf[i] = dry[i].re;
dry_im_buf[i] = dry[i].im;
}
match SIMD.simd_width() {
SimdWidth::X8 => {
Self::mix_simd_impl::<8>(wet_re_buf, dry_re_buf, wet, dry_mix);
Self::mix_simd_impl::<8>(wet_im_buf, dry_im_buf, wet, dry_mix);
}
SimdWidth::X4 => {
Self::mix_simd_impl::<4>(wet_re_buf, dry_re_buf, wet, dry_mix);
Self::mix_simd_impl::<4>(wet_im_buf, dry_im_buf, wet, dry_mix);
}
SimdWidth::Scalar => {
Self::mix_scalar(wet_re_buf, dry_re_buf, wet, dry_mix);
Self::mix_scalar(wet_im_buf, dry_im_buf, wet, dry_mix);
}
}
for i in 0..len {
spectrum[i] = Complex::new(wet_re_buf[i], wet_im_buf[i]);
}
}
#[inline]
fn mix_simd_impl<const LANES: usize>(wet: &mut [f32], dry: &[f32], wet_mix: f32, dry_mix: f32) {
let len = wet.len();
let chunks = len / LANES;
let remainder = len % LANES;
if LANES == 8 {
let wet_factor = f32x8::splat(wet_mix);
let dry_factor = f32x8::splat(dry_mix);
for i in 0..chunks {
let offset = i * LANES;
let wet_vec = f32x8::new([
wet[offset],
wet[offset + 1],
wet[offset + 2],
wet[offset + 3],
wet[offset + 4],
wet[offset + 5],
wet[offset + 6],
wet[offset + 7],
]);
let dry_vec = f32x8::new([
dry[offset],
dry[offset + 1],
dry[offset + 2],
dry[offset + 3],
dry[offset + 4],
dry[offset + 5],
dry[offset + 6],
dry[offset + 7],
]);
let result = wet_vec * wet_factor + dry_vec * dry_factor;
let arr = result.to_array();
wet[offset..offset + LANES].copy_from_slice(&arr);
}
} else if LANES == 4 {
let wet_factor = f32x4::splat(wet_mix);
let dry_factor = f32x4::splat(dry_mix);
for i in 0..chunks {
let offset = i * LANES;
let wet_vec = f32x4::new([
wet[offset],
wet[offset + 1],
wet[offset + 2],
wet[offset + 3],
]);
let dry_vec = f32x4::new([
dry[offset],
dry[offset + 1],
dry[offset + 2],
dry[offset + 3],
]);
let result = wet_vec * wet_factor + dry_vec * dry_factor;
let arr = result.to_array();
wet[offset..offset + LANES].copy_from_slice(&arr);
}
}
if remainder > 0 {
let offset = chunks * LANES;
Self::mix_scalar(&mut wet[offset..], &dry[offset..], wet_mix, dry_mix);
}
}
#[inline]
fn mix_scalar(wet: &mut [f32], dry: &[f32], wet_mix: f32, dry_mix: f32) {
for i in 0..wet.len() {
wet[i] = wet[i] * wet_mix + dry[i] * dry_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 FormantShifter {
pub fn male_to_female() -> Self {
let mut shifter = Self::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(1.6);
shifter.set_mix(1.0);
shifter
}
pub fn female_to_male() -> Self {
let mut shifter = Self::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(0.65);
shifter.set_mix(1.0);
shifter
}
pub fn adult_to_child() -> Self {
let mut shifter = Self::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(2.0);
shifter.set_mix(1.0);
shifter
}
pub fn brighten() -> Self {
let mut shifter = Self::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(1.15);
shifter.set_mix(0.7);
shifter
}
pub fn darken() -> Self {
let mut shifter = Self::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(0.85);
shifter.set_mix(0.7);
shifter
}
pub fn monster() -> Self {
let mut shifter = Self::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(0.5);
shifter.set_mix(1.0);
shifter
}
pub fn chipmunk() -> Self {
let mut shifter = Self::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(2.5);
shifter.set_mix(1.0);
shifter
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_formant_shifter_creation() {
let shifter = FormantShifter::new(2048, 512, WindowType::Hann, 44100.0);
assert_eq!(shifter.shift_ratio(), 1.0);
assert_eq!(shifter.mix(), 1.0);
assert_eq!(shifter.fft_size(), 2048);
assert_eq!(shifter.hop_size(), 512);
assert!(shifter.is_enabled());
}
#[test]
#[should_panic(expected = "FFT size must be power of 2")]
fn test_formant_shifter_requires_power_of_two() {
FormantShifter::new(1000, 250, WindowType::Hann, 44100.0);
}
#[test]
#[should_panic(expected = "Hop size must be <= FFT size")]
fn test_formant_shifter_hop_validation() {
FormantShifter::new(1024, 2048, WindowType::Hann, 44100.0);
}
#[test]
#[should_panic(expected = "Sample rate must be positive")]
fn test_formant_shifter_sample_rate_validation() {
FormantShifter::new(1024, 256, WindowType::Hann, 0.0);
}
#[test]
fn test_formant_shifter_set_shift_ratio() {
let mut shifter = FormantShifter::new(1024, 256, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(2.0);
assert_eq!(shifter.shift_ratio(), 2.0);
shifter.set_shift_ratio(0.5);
assert_eq!(shifter.shift_ratio(), 0.5);
shifter.set_shift_ratio(1.5);
assert_eq!(shifter.shift_ratio(), 1.5);
}
#[test]
fn test_formant_shifter_shift_ratio_clamps_minimum() {
let mut shifter = FormantShifter::new(1024, 256, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(0.05);
assert_eq!(shifter.shift_ratio(), 0.1);
shifter.set_shift_ratio(0.0);
assert_eq!(shifter.shift_ratio(), 0.1);
shifter.set_shift_ratio(-1.0);
assert_eq!(shifter.shift_ratio(), 0.1);
}
#[test]
fn test_formant_shifter_set_mix() {
let mut shifter = FormantShifter::new(1024, 256, WindowType::Hann, 44100.0);
shifter.set_mix(0.5);
assert_eq!(shifter.mix(), 0.5);
shifter.set_mix(0.0);
assert_eq!(shifter.mix(), 0.0);
shifter.set_mix(1.0);
assert_eq!(shifter.mix(), 1.0);
}
#[test]
fn test_formant_shifter_mix_clamps() {
let mut shifter = FormantShifter::new(1024, 256, WindowType::Hann, 44100.0);
shifter.set_mix(-0.5);
assert_eq!(shifter.mix(), 0.0);
shifter.set_mix(1.5);
assert_eq!(shifter.mix(), 1.0);
}
#[test]
fn test_formant_shifter_enable_disable() {
let mut shifter = FormantShifter::new(1024, 256, WindowType::Hann, 44100.0);
assert!(shifter.is_enabled());
shifter.set_enabled(false);
assert!(!shifter.is_enabled());
shifter.set_enabled(true);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_process_doesnt_crash() {
let mut shifter = FormantShifter::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(1.5);
let mut output = vec![0.0; 512];
let input = vec![0.5; 512];
shifter.process(&mut output, &input);
}
#[test]
fn test_formant_shifter_process_disabled() {
let mut shifter = FormantShifter::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_enabled(false);
let mut output = vec![1.0; 512];
let input = vec![0.5; 512];
shifter.process(&mut output, &input);
for sample in output.iter() {
assert_eq!(*sample, 1.0);
}
}
#[test]
fn test_formant_shifter_process_with_zeros() {
let mut shifter = FormantShifter::new(2048, 512, WindowType::Hann, 44100.0);
let mut output = vec![0.0; 512];
let input = vec![0.0; 512];
shifter.process(&mut output, &input);
}
#[test]
fn test_formant_shifter_reset() {
let mut shifter = FormantShifter::new(1024, 256, WindowType::Hann, 44100.0);
let mut output = vec![0.0; 256];
let input = vec![0.5; 256];
shifter.process(&mut output, &input);
shifter.reset();
shifter.process(&mut output, &input);
}
#[test]
fn test_formant_shifter_male_to_female_preset() {
let shifter = FormantShifter::male_to_female();
assert_eq!(shifter.shift_ratio(), 1.6);
assert_eq!(shifter.mix(), 1.0);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_female_to_male_preset() {
let shifter = FormantShifter::female_to_male();
assert_eq!(shifter.shift_ratio(), 0.65);
assert_eq!(shifter.mix(), 1.0);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_adult_to_child_preset() {
let shifter = FormantShifter::adult_to_child();
assert_eq!(shifter.shift_ratio(), 2.0);
assert_eq!(shifter.mix(), 1.0);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_brighten_preset() {
let shifter = FormantShifter::brighten();
assert_eq!(shifter.shift_ratio(), 1.15);
assert_eq!(shifter.mix(), 0.7);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_darken_preset() {
let shifter = FormantShifter::darken();
assert_eq!(shifter.shift_ratio(), 0.85);
assert_eq!(shifter.mix(), 0.7);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_monster_preset() {
let shifter = FormantShifter::monster();
assert_eq!(shifter.shift_ratio(), 0.5);
assert_eq!(shifter.mix(), 1.0);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_chipmunk_preset() {
let shifter = FormantShifter::chipmunk();
assert_eq!(shifter.shift_ratio(), 2.5);
assert_eq!(shifter.mix(), 1.0);
assert!(shifter.is_enabled());
}
#[test]
fn test_formant_shifter_different_fft_sizes() {
for &fft_size in &[256, 512, 1024, 2048, 4096] {
let hop_size = fft_size / 4;
let shifter = FormantShifter::new(fft_size, hop_size, WindowType::Hann, 44100.0);
assert_eq!(shifter.fft_size(), fft_size);
assert_eq!(shifter.hop_size(), hop_size);
}
}
#[test]
fn test_formant_shifter_different_window_types() {
for &window_type in &[
WindowType::Rectangular,
WindowType::Hann,
WindowType::Hamming,
WindowType::Blackman,
WindowType::BlackmanHarris,
] {
let shifter = FormantShifter::new(1024, 256, window_type, 44100.0);
assert_eq!(shifter.shift_ratio(), 1.0);
}
}
#[test]
fn test_formant_shifter_bypass_mode() {
let mut shifter = FormantShifter::new(2048, 512, WindowType::Hann, 44100.0);
shifter.set_shift_ratio(1.0); shifter.set_mix(1.0);
let mut output = vec![0.0; 512];
let input = vec![0.5; 512];
shifter.process(&mut output, &input);
}
}