use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::filter::{BiquadFilter, FilterType};
const NUM_FORMANTS: usize = 3;
type FormantParams = [(f32, f32, f32); NUM_FORMANTS];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Vowel {
A,
E,
I,
O,
U,
}
impl Vowel {
#[must_use]
pub fn formants(self) -> FormantParams {
match self {
Vowel::A => [
(730.0, 90.0, 1.0),
(1090.0, 110.0, 0.5),
(2440.0, 170.0, 0.25),
],
Vowel::E => [
(530.0, 60.0, 1.0),
(1840.0, 100.0, 0.4),
(2480.0, 120.0, 0.2),
],
Vowel::I => [
(270.0, 60.0, 1.0),
(2290.0, 90.0, 0.3),
(3010.0, 100.0, 0.15),
],
Vowel::O => [
(570.0, 80.0, 1.0),
(840.0, 100.0, 0.5),
(2410.0, 120.0, 0.2),
],
Vowel::U => [
(300.0, 70.0, 1.0),
(870.0, 100.0, 0.35),
(2240.0, 120.0, 0.15),
],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FormantFilter {
filters: [BiquadFilter; NUM_FORMANTS],
amplitudes: [f32; NUM_FORMANTS],
}
impl FormantFilter {
pub fn new(formants: &[(f32, f32, f32)], sample_rate: f32) -> Result<Self> {
if formants.len() != NUM_FORMANTS {
return Err(crate::error::NaadError::InvalidParameter {
name: "formants".to_string(),
reason: format!(
"expected exactly {NUM_FORMANTS} formant bands, got {}",
formants.len()
),
});
}
let q = |freq: f32, bw: f32| if bw > 0.0 { freq / bw } else { 1.0 };
let filters: [BiquadFilter; NUM_FORMANTS] = [
BiquadFilter::new(
FilterType::BandPass,
sample_rate,
formants[0].0,
q(formants[0].0, formants[0].1),
)?,
BiquadFilter::new(
FilterType::BandPass,
sample_rate,
formants[1].0,
q(formants[1].0, formants[1].1),
)?,
BiquadFilter::new(
FilterType::BandPass,
sample_rate,
formants[2].0,
q(formants[2].0, formants[2].1),
)?,
];
let amplitudes = [formants[0].2, formants[1].2, formants[2].2];
Ok(Self {
filters,
amplitudes,
})
}
#[inline]
#[must_use]
pub fn process_sample(&mut self, input: f32) -> f32 {
let mut sum = 0.0f32;
for (filter, &) in self.filters.iter_mut().zip(self.amplitudes.iter()) {
sum += filter.process_sample(input) * amp;
}
sum
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FormantSynth {
filters: [BiquadFilter; NUM_FORMANTS],
amplitudes: [f32; NUM_FORMANTS],
target_params: FormantParams,
current_vowel: Vowel,
sample_rate: f32,
}
impl FormantSynth {
pub fn new(vowel: Vowel, sample_rate: f32) -> Result<Self> {
let params = vowel.formants();
let mut filters: [BiquadFilter; NUM_FORMANTS] = [
BiquadFilter::new(
FilterType::BandPass,
sample_rate,
params[0].0,
params[0].0 / params[0].1,
)?,
BiquadFilter::new(
FilterType::BandPass,
sample_rate,
params[1].0,
params[1].0 / params[1].1,
)?,
BiquadFilter::new(
FilterType::BandPass,
sample_rate,
params[2].0,
params[2].0 / params[2].1,
)?,
];
let _ = &mut filters;
let amplitudes = [params[0].2, params[1].2, params[2].2];
Ok(Self {
filters,
amplitudes,
target_params: params,
current_vowel: vowel,
sample_rate,
})
}
pub fn set_vowel(&mut self, vowel: Vowel) -> Result<()> {
self.current_vowel = vowel;
let params = vowel.formants();
self.apply_params(¶ms)
}
pub fn morph(&mut self, vowel_a: Vowel, vowel_b: Vowel, t: f32) -> Result<()> {
let t = t.clamp(0.0, 1.0);
let a = vowel_a.formants();
let b = vowel_b.formants();
let mut params: FormantParams = [(0.0, 0.0, 0.0); NUM_FORMANTS];
for i in 0..NUM_FORMANTS {
params[i].0 = a[i].0 + (b[i].0 - a[i].0) * t;
params[i].1 = a[i].1 + (b[i].1 - a[i].1) * t;
params[i].2 = a[i].2 + (b[i].2 - a[i].2) * t;
}
self.apply_params(¶ms)
}
#[inline]
#[must_use]
pub fn process_sample(&mut self, input: f32) -> f32 {
let mut sum = 0.0f32;
for i in 0..NUM_FORMANTS {
sum += self.filters[i].process_sample(input) * self.amplitudes[i];
}
sum
}
#[inline]
pub fn process_buffer(&mut self, buffer: &mut [f32]) {
for s in buffer.iter_mut() {
*s = self.process_sample(*s);
}
}
#[must_use]
pub fn current_vowel(&self) -> Vowel {
self.current_vowel
}
fn apply_params(&mut self, params: &FormantParams) -> Result<()> {
self.target_params = *params;
for (i, &(freq, bw, amp)) in params.iter().enumerate().take(NUM_FORMANTS) {
let q = if bw > 0.0 { freq / bw } else { 1.0 };
self.filters[i].set_params(freq, q, 0.0)?;
self.amplitudes[i] = amp;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_different_vowels_produce_different_output() {
let mut synth_a = FormantSynth::new(Vowel::A, 44100.0).unwrap();
let mut synth_i = FormantSynth::new(Vowel::I, 44100.0).unwrap();
let mut out_a = Vec::with_capacity(512);
let mut out_i = Vec::with_capacity(512);
for n in 0..512 {
let input = if n % 100 == 0 { 1.0 } else { 0.0 };
out_a.push(synth_a.process_sample(input));
out_i.push(synth_i.process_sample(input));
}
let diff: f32 = out_a
.iter()
.zip(out_i.iter())
.map(|(a, b)| (a - b).abs())
.sum();
assert!(
diff > 0.01,
"different vowels should produce different spectra"
);
}
#[test]
fn test_morph() {
let mut synth = FormantSynth::new(Vowel::A, 44100.0).unwrap();
synth.morph(Vowel::A, Vowel::O, 0.5).unwrap();
let input = 1.0;
let out = synth.process_sample(input);
assert!(out.is_finite());
}
#[test]
fn test_formant_filter_standalone() {
let params = Vowel::E.formants();
let mut ff = FormantFilter::new(¶ms, 44100.0).unwrap();
let out = ff.process_sample(1.0);
assert!(out.is_finite());
}
#[test]
fn test_serde_roundtrip() {
let synth = FormantSynth::new(Vowel::A, 44100.0).unwrap();
let json = serde_json::to_string(&synth).unwrap();
let back: FormantSynth = serde_json::from_str(&json).unwrap();
assert_eq!(synth.current_vowel, back.current_vowel);
}
#[test]
fn test_vowel_formants_are_valid() {
for vowel in &[Vowel::A, Vowel::E, Vowel::I, Vowel::O, Vowel::U] {
let params = vowel.formants();
for (freq, bw, amp) in ¶ms {
assert!(*freq > 0.0);
assert!(*bw > 0.0);
assert!(*amp > 0.0);
}
}
}
}