use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::error::Result;
use crate::filter::{BiquadFilter, FilterType};
const VOCODER_INLINE: usize = 16;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VocoderBand {
analysis_filter: BiquadFilter,
synthesis_filter: BiquadFilter,
envelope_follower: f32,
attack_coeff: f32,
release_coeff: f32,
}
impl VocoderBand {
#[inline]
#[must_use]
pub fn process(&mut self, modulator: f32, carrier: f32) -> f32 {
let mod_filtered = self.analysis_filter.process_sample(modulator);
let mod_level = mod_filtered.abs();
let coeff = if mod_level > self.envelope_follower {
self.attack_coeff
} else {
self.release_coeff
};
self.envelope_follower = crate::flush_denormal(
self.envelope_follower + coeff * (mod_level - self.envelope_follower),
);
let car_filtered = self.synthesis_filter.process_sample(carrier);
car_filtered * self.envelope_follower
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vocoder {
bands: SmallVec<[VocoderBand; VOCODER_INLINE]>,
sample_rate: f32,
}
impl Vocoder {
pub fn new(num_bands: usize, low_freq: f32, high_freq: f32, sample_rate: f32) -> Result<Self> {
if sample_rate <= 0.0 || !sample_rate.is_finite() {
return Err(crate::error::NaadError::InvalidSampleRate { sample_rate });
}
if num_bands == 0 {
return Err(crate::error::NaadError::InvalidParameter {
name: "num_bands".to_string(),
reason: "must be > 0".to_string(),
});
}
if low_freq <= 0.0 || high_freq <= low_freq {
return Err(crate::error::NaadError::InvalidParameter {
name: "frequency range".to_string(),
reason: "low_freq must be > 0 and < high_freq".to_string(),
});
}
let log_low = low_freq.ln();
let log_high = high_freq.ln();
let step = if num_bands > 1 {
(log_high - log_low) / (num_bands - 1) as f32
} else {
0.0
};
let attack_coeff = 1.0 - (-1.0 / (0.005 * sample_rate)).exp();
let release_coeff = 1.0 - (-1.0 / (0.020 * sample_rate)).exp();
let mut bands: SmallVec<[VocoderBand; VOCODER_INLINE]> = SmallVec::new();
for i in 0..num_bands {
let center = (log_low + step * i as f32).exp();
let center = center.min(sample_rate * 0.49);
let q = if step > 0.0 {
(1.0 / (step.exp() - 1.0)).clamp(1.0, 20.0)
} else {
4.0 };
let analysis_filter = BiquadFilter::new(FilterType::BandPass, sample_rate, center, q)?;
let synthesis_filter = BiquadFilter::new(FilterType::BandPass, sample_rate, center, q)?;
bands.push(VocoderBand {
analysis_filter,
synthesis_filter,
envelope_follower: 0.0,
attack_coeff,
release_coeff,
});
}
Ok(Self { bands, sample_rate })
}
#[inline]
#[must_use]
pub fn process_sample(&mut self, modulator: f32, carrier: f32) -> f32 {
let mut sum = 0.0f32;
for band in &mut self.bands {
sum += band.process(modulator, carrier);
}
sum
}
pub fn process_buffer(&mut self, modulator: &[f32], carrier: &[f32], output: &mut [f32]) {
let len = modulator.len().min(carrier.len()).min(output.len());
for i in 0..len {
output[i] = self.process_sample(modulator[i], carrier[i]);
}
}
#[must_use]
pub fn num_bands(&self) -> usize {
self.bands.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_produces_output() {
let mut vocoder = Vocoder::new(8, 200.0, 8000.0, 44100.0).unwrap();
let mut has_output = false;
for i in 0..2048 {
let t = i as f32 / 44100.0;
let modulator = (t * 440.0 * std::f32::consts::TAU).sin();
let carrier = (t * 110.0 * std::f32::consts::TAU).sin();
let out = vocoder.process_sample(modulator, carrier);
assert!(out.is_finite());
if out.abs() > 0.001 {
has_output = true;
}
}
assert!(has_output, "vocoder should produce output");
}
#[test]
fn test_different_modulators_differ() {
let mut vocoder1 = Vocoder::new(8, 200.0, 8000.0, 44100.0).unwrap();
let mut vocoder2 = Vocoder::new(8, 200.0, 8000.0, 44100.0).unwrap();
let mut out1 = Vec::with_capacity(1024);
let mut out2 = Vec::with_capacity(1024);
for i in 0..1024 {
let t = i as f32 / 44100.0;
let carrier = (t * 110.0 * std::f32::consts::TAU).sin();
let mod1 = (t * 440.0 * std::f32::consts::TAU).sin();
let mod2 = (t * 1200.0 * std::f32::consts::TAU).sin();
out1.push(vocoder1.process_sample(mod1, carrier));
out2.push(vocoder2.process_sample(mod2, carrier));
}
let diff: f32 = out1
.iter()
.zip(out2.iter())
.map(|(a, b)| (a - b).abs())
.sum();
assert!(
diff > 0.01,
"different modulators should produce different timbres"
);
}
#[test]
fn test_serde_roundtrip() {
let vocoder = Vocoder::new(12, 100.0, 10000.0, 44100.0).unwrap();
let json = serde_json::to_string(&vocoder).unwrap();
let back: Vocoder = serde_json::from_str(&json).unwrap();
assert_eq!(vocoder.bands.len(), back.bands.len());
}
#[test]
fn test_process_buffer() {
let mut vocoder = Vocoder::new(8, 200.0, 8000.0, 44100.0).unwrap();
let modulator = vec![0.5; 64];
let carrier = vec![0.3; 64];
let mut output = vec![0.0; 64];
vocoder.process_buffer(&modulator, &carrier, &mut output);
assert!(output.iter().all(|s| s.is_finite()));
}
}