use std::f64::consts::PI;
#[derive(Clone, Debug)]
pub struct Partial {
pub frequency: f64,
pub amplitude: f64,
pub phase: f64,
}
pub fn harmonic_series(fundamental: f64, n_partials: u32) -> Vec<Partial> {
(1..=n_partials)
.map(|n| Partial {
frequency: fundamental * n as f64,
amplitude: 1.0 / n as f64,
phase: 0.0,
})
.collect()
}
pub fn inharmonic_series(fundamental: f64, n_partials: u32, inharmonicity: f64) -> Vec<Partial> {
(1..=n_partials)
.map(|n| {
let n_f = n as f64;
let freq = n_f * fundamental * (1.0 + inharmonicity * n_f * n_f).sqrt();
Partial {
frequency: freq,
amplitude: 1.0 / n_f,
phase: 0.0,
}
})
.collect()
}
pub fn synthesize(partials: &[Partial], sample_rate: f64, duration_sec: f64) -> Vec<f64> {
let n_samples = (sample_rate * duration_sec).round() as usize;
let mut buf = vec![0.0f64; n_samples];
let inv_sr = 1.0 / sample_rate;
for (i, s) in buf.iter_mut().enumerate() {
let t = i as f64 * inv_sr;
*s = partials
.iter()
.map(|p| p.amplitude * (2.0 * PI * p.frequency * t + p.phase).sin())
.sum();
}
buf
}
pub struct FormantFilter {
pub center_freq: f64,
pub bandwidth: f64,
pub gain: f64,
}
impl FormantFilter {
pub fn response(&self, freq: f64) -> f64 {
if self.bandwidth == 0.0 {
return 0.0;
}
let x = (freq - self.center_freq) / self.bandwidth;
self.gain * (-0.5 * x * x).exp()
}
}
pub struct TimbreModel {
pub partials: Vec<Partial>,
pub formants: Vec<FormantFilter>,
}
impl TimbreModel {
pub fn apply_formants(&self, partials: &[Partial]) -> Vec<Partial> {
partials
.iter()
.map(|p| {
let gain: f64 = self.formants.iter().map(|f| f.response(p.frequency)).sum();
Partial {
frequency: p.frequency,
amplitude: p.amplitude * gain,
phase: p.phase,
}
})
.collect()
}
pub fn brightness(&self) -> f64 {
let sum_a: f64 = self.partials.iter().map(|p| p.amplitude).sum();
if sum_a == 0.0 {
return 0.0;
}
self.partials
.iter()
.map(|p| p.frequency * p.amplitude)
.sum::<f64>()
/ sum_a
}
pub fn roughness(&self) -> f64 {
let mut total = 0.0f64;
let n = self.partials.len();
for i in 0..n {
for j in (i + 1)..n {
let f1 = self.partials[i].frequency;
let f2 = self.partials[j].frequency;
if f1 <= 0.0 || f2 <= 0.0 {
continue;
}
let cbw = 1.72 * (f1 * f2).sqrt() * 0.24;
let diff = (f2 - f1).abs();
if diff < cbw {
let beating = self.partials[i].amplitude * self.partials[j].amplitude;
total += beating * (1.0 - diff / cbw);
}
}
}
total
}
pub fn vowel_formants(vowel: char) -> Vec<FormantFilter> {
let (f1, f2, f3): (f64, f64, f64) = match vowel {
'a' | 'A' => (800.0, 1200.0, 2500.0),
'e' | 'E' => (400.0, 2000.0, 2600.0),
'i' | 'I' => (300.0, 2300.0, 3200.0),
'o' | 'O' => (500.0, 900.0, 2500.0),
'u' | 'U' => (300.0, 800.0, 2300.0),
_ => (500.0, 1500.0, 2500.0), };
vec![
FormantFilter { center_freq: f1, bandwidth: 80.0, gain: 1.0 },
FormantFilter { center_freq: f2, bandwidth: 80.0, gain: 0.8 },
FormantFilter { center_freq: f3, bandwidth: 80.0, gain: 0.6 },
]
}
}
pub fn resynthesis_error(original: &[f64], partials: &[Partial], sample_rate: f64) -> f64 {
if original.is_empty() {
return 0.0;
}
let duration_sec = original.len() as f64 / sample_rate;
let synth = synthesize(partials, sample_rate, duration_sec);
let n = original.len().min(synth.len());
let mse: f64 = (0..n)
.map(|i| {
let diff = original[i] - synth[i];
diff * diff
})
.sum::<f64>()
/ n as f64;
mse.sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn harmonic_series_amplitudes() {
let partials = harmonic_series(440.0, 5);
assert_eq!(partials.len(), 5);
assert!((partials[0].frequency - 440.0).abs() < 1e-9);
assert!((partials[0].amplitude - 1.0).abs() < 1e-9);
assert!((partials[4].amplitude - 0.2).abs() < 1e-9);
}
#[test]
fn inharmonic_higher_than_harmonic() {
let harmonic = harmonic_series(100.0, 4);
let inharmonic = inharmonic_series(100.0, 4, 0.01);
for (h, ih) in harmonic.iter().zip(inharmonic.iter()) {
assert!(ih.frequency >= h.frequency - 1e-9);
}
}
#[test]
fn synthesize_length() {
let partials = harmonic_series(440.0, 3);
let samples = synthesize(&partials, 44100.0, 0.1);
assert_eq!(samples.len(), 4410);
}
#[test]
fn formant_response_peak() {
let f = FormantFilter { center_freq: 1000.0, bandwidth: 100.0, gain: 1.0 };
let at_center = f.response(1000.0);
let off_center = f.response(1200.0);
assert!((at_center - 1.0).abs() < 1e-9);
assert!(off_center < at_center);
}
#[test]
fn timbre_brightness() {
let model = TimbreModel {
partials: harmonic_series(100.0, 5),
formants: vec![],
};
let b = model.brightness();
assert!(b > 100.0);
}
#[test]
fn vowel_formants_count() {
for v in ['a', 'e', 'i', 'o', 'u'] {
let fs = TimbreModel::vowel_formants(v);
assert_eq!(fs.len(), 3);
}
}
#[test]
fn resynthesis_error_self() {
let partials = harmonic_series(220.0, 4);
let sr = 44100.0;
let dur = 0.05;
let signal = synthesize(&partials, sr, dur);
let err = resynthesis_error(&signal, &partials, sr);
assert!(err < 1e-9, "self-resynthesis error should be ~0, got {err}");
}
}