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