sva_samples/measure/
formants.rs1use crate::measure::spectrum::db;
4
5pub const MAX_ORDER: usize = 64;
6
7const EDGE_HZ: f64 = 40.0;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Formant {
12 pub hz: f64,
13 pub bandwidth_hz: f64,
14 pub db: f64,
15}
16
17#[derive(Clone, Debug, PartialEq)]
19pub struct FormantFrame {
20 pub t_secs: f64,
21 pub order: usize,
22 pub energy: f64,
23 pub residual: f64,
24 pub formants: Vec<Formant>,
25}
26
27pub fn default_order(sample_rate: f64) -> usize {
30 (2.0 + sample_rate / 1000.0).clamp(2.0, MAX_ORDER as f64) as usize
31}
32
33pub fn track(
34 planes: &[&[f64]],
35 sample_rate: f64,
36 start_secs: f64,
37 frame_secs: f64,
38 order: usize,
39 max_formants: usize,
40) -> Vec<FormantFrame> {
41 let stride = ((frame_secs * sample_rate).round() as usize).max(1);
42 let len = crate::measure::held(planes);
43 (0..len)
44 .step_by(stride)
45 .enumerate()
46 .map(|(n, from)| {
47 let chunk: Vec<&[f64]> = planes
48 .iter()
49 .map(|p| &p[from..len.min(from + stride)])
50 .collect();
51 FormantFrame {
52 t_secs: start_secs + (n * stride) as f64 / sample_rate,
53 ..analyze(&chunk, sample_rate, order, max_formants)
54 }
55 })
56 .collect()
57}
58
59pub fn analyze(
62 planes: &[&[f64]],
63 sample_rate: f64,
64 order: usize,
65 max_formants: usize,
66) -> FormantFrame {
67 let len = crate::measure::held(planes);
68 let order = order.clamp(2, MAX_ORDER).min(len.saturating_sub(1));
69 let windowed: Vec<Vec<f64>> = planes
70 .iter()
71 .map(|frame| {
72 frame
73 .iter()
74 .enumerate()
75 .map(|(n, &x)| {
76 let turn = 2.0 * std::f64::consts::PI * n as f64 / len as f64;
77 let w = 0.54 - 0.46 * turn.cos();
78 x * w
79 })
80 .collect()
81 })
82 .collect();
83 let energy = match len {
84 0 => 0.0,
85 _ => {
86 let each = windowed
87 .iter()
88 .map(|w| w.iter().map(|x| x * x).sum::<f64>());
89 each.sum::<f64>() / (len * planes.len()) as f64
90 }
91 };
92 if order < 2 || energy <= 0.0 {
93 return FormantFrame {
94 t_secs: 0.0,
95 order,
96 energy,
97 residual: energy,
98 formants: Vec::new(),
99 };
100 }
101
102 let r = crate::measure::averaged(windowed.iter().map(|w| autocorrelation(w, order)).collect());
103 let (a, error) = levinson(&r, order);
104 let residual = error / len as f64;
105 let gain = residual.max(0.0).sqrt();
106
107 let mut formants: Vec<Formant> = roots(&a)
108 .into_iter()
109 .filter(|z| z.im > 0.0)
110 .filter_map(|z| {
111 let hz = z.im.atan2(z.re) * sample_rate / (2.0 * std::f64::consts::PI);
112 let radius = z.abs();
113 if !(EDGE_HZ..sample_rate / 2.0 - EDGE_HZ).contains(&hz) || radius >= 1.0 {
114 return None;
115 }
116 Some(Formant {
117 hz,
118 bandwidth_hz: -radius.ln() * sample_rate / std::f64::consts::PI,
119 db: db(gain / response(&a, hz, sample_rate)),
120 })
121 })
122 .collect();
123 formants.sort_by(|x, y| x.hz.total_cmp(&y.hz));
124 formants.truncate(max_formants);
125
126 FormantFrame {
127 t_secs: 0.0,
128 order,
129 energy,
130 residual,
131 formants,
132 }
133}
134
135fn autocorrelation(x: &[f64], lags: usize) -> Vec<f64> {
136 (0..=lags)
137 .map(|k| x.iter().skip(k).zip(x).map(|(a, b)| a * b).sum())
138 .collect()
139}
140
141fn levinson(r: &[f64], order: usize) -> (Vec<f64>, f64) {
143 let mut a = vec![0.0; order + 1];
144 a[0] = 1.0;
145 let mut error = r[0];
146
147 for i in 1..=order {
148 if error <= 0.0 {
149 return (a, error.max(0.0));
150 }
151 let acc: f64 = r[i] + (1..i).map(|j| a[j] * r[i - j]).sum::<f64>();
152 let k = -acc / error;
153 let held: Vec<f64> = a[1..i].to_vec();
154 for (j, prior) in held.iter().enumerate() {
155 a[j + 1] = prior + k * held[i - 2 - j];
156 }
157 a[i] = k;
158 error *= 1.0 - k * k;
159 }
160 (a, error.max(0.0))
161}
162
163fn response(a: &[f64], hz: f64, sample_rate: f64) -> f64 {
164 let w = 2.0 * std::f64::consts::PI * hz / sample_rate;
165 let (mut re, mut im) = (0.0, 0.0);
166 for (k, coefficient) in a.iter().enumerate() {
167 re += coefficient * (w * k as f64).cos();
168 im -= coefficient * (w * k as f64).sin();
169 }
170 (re * re + im * im).sqrt()
171}
172
173#[derive(Clone, Copy, Debug)]
174struct C {
175 re: f64,
176 im: f64,
177}
178
179impl C {
180 fn mul(self, o: C) -> C {
181 C {
182 re: self.re * o.re - self.im * o.im,
183 im: self.re * o.im + self.im * o.re,
184 }
185 }
186
187 fn sub(self, o: C) -> C {
188 C {
189 re: self.re - o.re,
190 im: self.im - o.im,
191 }
192 }
193
194 fn div(self, o: C) -> C {
195 let d = o.re * o.re + o.im * o.im;
196 match d == 0.0 {
197 true => C { re: 0.0, im: 0.0 },
198 false => C {
199 re: (self.re * o.re + self.im * o.im) / d,
200 im: (self.im * o.re - self.re * o.im) / d,
201 },
202 }
203 }
204
205 fn abs(self) -> f64 {
206 self.re.hypot(self.im)
207 }
208}
209
210fn roots(a: &[f64]) -> Vec<C> {
213 const ROUNDS: usize = 500;
214 const SETTLED: f64 = 1e-14;
215
216 let degree = a.len() - 1;
217 let seed = C { re: 0.4, im: 0.9 };
218 let mut z: Vec<C> = Vec::with_capacity(degree);
219 let mut power = C { re: 1.0, im: 0.0 };
220 for _ in 0..degree {
221 z.push(power);
222 power = power.mul(seed);
223 }
224
225 for _ in 0..ROUNDS {
226 let mut moved: f64 = 0.0;
227 for i in 0..degree {
228 let mut denominator = C { re: 1.0, im: 0.0 };
229 for j in 0..degree {
230 if j != i {
231 denominator = denominator.mul(z[i].sub(z[j]));
232 }
233 }
234 let step = evaluate(a, z[i]).div(denominator);
235 z[i] = z[i].sub(step);
236 moved = moved.max(step.abs());
237 }
238 if moved < SETTLED {
239 break;
240 }
241 }
242 z
243}
244
245fn evaluate(a: &[f64], at: C) -> C {
246 let mut acc = C { re: 0.0, im: 0.0 };
247 for coefficient in a {
248 acc = acc.mul(at);
249 acc.re += coefficient;
250 }
251 acc
252}