1#[allow(unused_imports)]
6use crate::filter_design::single_pole_decay_from_time_constant;
7#[allow(unused_imports)]
8use crate::math::FloatMath;
9use crate::math::isqrt_u64;
10use crate::transform::cfft_f32;
11use crate::types::{q15, Status};
12
13#[derive(Debug, Clone, Copy, Default)]
19pub struct GoertzelDetector {
20 coeff: f32,
21 s_prev: f32,
22 s_prev2: f32,
23 count: u32,
24}
25
26impl GoertzelDetector {
27 pub fn new(target_freq_hz: f32, sample_rate_hz: f32) -> Self {
29 let w = 2.0 * core::f32::consts::PI * target_freq_hz / sample_rate_hz;
30 Self {
31 coeff: 2.0 * w.cos(),
32 s_prev: 0.0,
33 s_prev2: 0.0,
34 count: 0,
35 }
36 }
37
38 #[inline(always)]
40 pub fn process_sample(&mut self, x: f32) {
41 let s = x + self.coeff * self.s_prev - self.s_prev2;
42 self.s_prev2 = self.s_prev;
43 self.s_prev = s;
44 self.count += 1;
45 }
46
47 pub fn magnitude(&self) -> f32 {
51 if self.count == 0 {
52 return 0.0;
53 }
54 let mag_sq = self.s_prev * self.s_prev + self.s_prev2 * self.s_prev2
55 - self.coeff * self.s_prev * self.s_prev2;
56 mag_sq.max(0.0).sqrt() / (self.count as f32 / 2.0)
57 }
58
59 pub fn reset(&mut self) {
61 self.s_prev = 0.0;
62 self.s_prev2 = 0.0;
63 self.count = 0;
64 }
65}
66
67#[derive(Debug, Clone, Copy, Default)]
70pub struct GoertzelDetectorQ15 {
71 coeff: q15,
72 s_prev: i32,
73 s_prev2: i32,
74 count: u32,
75}
76
77impl GoertzelDetectorQ15 {
78 pub fn new(target_freq_hz: f32, sample_rate_hz: f32) -> Self {
80 let w = 2.0 * core::f32::consts::PI * target_freq_hz / sample_rate_hz;
81 let coeff = (2.0 * w.cos() * 16384.0).clamp(-32768.0, 32767.0) as q15;
82 Self {
83 coeff,
84 s_prev: 0,
85 s_prev2: 0,
86 count: 0,
87 }
88 }
89
90 #[inline(always)]
92 pub fn process_sample(&mut self, x: q15) {
93 let s = x as i32
94 + ((((self.coeff as i64) * (self.s_prev as i64)) >> 14) as i32)
95 - self.s_prev2;
96 self.s_prev2 = self.s_prev;
97 self.s_prev = s;
98 self.count += 1;
99 }
100
101 pub fn magnitude(&self) -> q15 {
103 if self.count == 0 {
104 return 0;
105 }
106 let s = self.s_prev as i64;
107 let s2 = self.s_prev2 as i64;
108 let c = self.coeff as i64;
109 let mag_sq = s * s + s2 * s2 - ((c * s * s2) >> 14);
110 if mag_sq <= 0 {
111 return 0;
112 }
113 let mag = isqrt_u64(mag_sq as u64);
114 let out = (mag * 2) / (self.count as u64);
115 out.min(32767) as q15
116 }
117
118 pub fn reset(&mut self) {
120 self.s_prev = 0;
121 self.s_prev2 = 0;
122 self.count = 0;
123 }
124}
125
126#[derive(Debug, Clone, Copy, Default)]
131pub struct PeakEnvelopeFollower {
132 attack_coeff: f32,
133 release_coeff: f32,
134 envelope: f32,
135}
136
137impl PeakEnvelopeFollower {
138 pub fn new(attack_samples: f32, release_samples: f32) -> Self {
141 Self {
142 attack_coeff: 1.0 - single_pole_decay_from_time_constant(attack_samples),
143 release_coeff: 1.0 - single_pole_decay_from_time_constant(release_samples),
144 envelope: 0.0,
145 }
146 }
147
148 #[inline(always)]
150 pub fn process(&mut self, x: f32) -> f32 {
151 let rectified = x.abs();
152 let coeff = if rectified > self.envelope {
153 self.attack_coeff
154 } else {
155 self.release_coeff
156 };
157 self.envelope += coeff * (rectified - self.envelope);
158 self.envelope
159 }
160
161 pub fn reset(&mut self) {
163 self.envelope = 0.0;
164 }
165}
166
167#[derive(Debug, Clone, Copy, Default)]
170pub struct RmsEnvelopeFollower {
171 coeff: f32,
172 mean_sq: f32,
173}
174
175impl RmsEnvelopeFollower {
176 pub fn new(time_constant_samples: f32) -> Self {
179 Self {
180 coeff: 1.0 - single_pole_decay_from_time_constant(time_constant_samples),
181 mean_sq: 0.0,
182 }
183 }
184
185 #[inline(always)]
187 pub fn process(&mut self, x: f32) -> f32 {
188 self.mean_sq += self.coeff * (x * x - self.mean_sq);
189 self.mean_sq.max(0.0).sqrt()
190 }
191
192 pub fn reset(&mut self) {
194 self.mean_sq = 0.0;
195 }
196}
197
198#[derive(Debug, Clone, Copy, Default)]
200pub struct PeakEnvelopeFollowerQ15 {
201 attack_coeff: q15,
202 release_coeff: q15,
203 envelope: q15,
204}
205
206impl PeakEnvelopeFollowerQ15 {
207 pub fn new(attack_samples: f32, release_samples: f32) -> Self {
208 let attack = 1.0 - single_pole_decay_from_time_constant(attack_samples);
209 let release = 1.0 - single_pole_decay_from_time_constant(release_samples);
210 Self {
211 attack_coeff: (attack * 32767.0).clamp(0.0, 32767.0) as q15,
212 release_coeff: (release * 32767.0).clamp(0.0, 32767.0) as q15,
213 envelope: 0,
214 }
215 }
216
217 #[inline(always)]
218 pub fn process(&mut self, x: q15) -> q15 {
219 let rectified = x.unsigned_abs() as i32;
220 let env = self.envelope as i32;
221 let coeff = if rectified > env {
222 self.attack_coeff
223 } else {
224 self.release_coeff
225 } as i32;
226 let y = env + ((coeff * (rectified - env)) >> 15);
227 self.envelope = y.clamp(0, 32767) as q15;
228 self.envelope
229 }
230
231 pub fn reset(&mut self) {
232 self.envelope = 0;
233 }
234}
235
236#[derive(Debug, Clone, Copy, Default)]
238pub struct RmsEnvelopeFollowerQ15 {
239 coeff: q15,
240 mean_sq: q15,
241}
242
243impl RmsEnvelopeFollowerQ15 {
244 pub fn new(time_constant_samples: f32) -> Self {
245 let c = 1.0 - single_pole_decay_from_time_constant(time_constant_samples);
246 Self {
247 coeff: (c * 32767.0).clamp(0.0, 32767.0) as q15,
248 mean_sq: 0,
249 }
250 }
251
252 #[inline(always)]
253 pub fn process(&mut self, x: q15) -> q15 {
254 let inst = ((x as i32 * x as i32) >> 15).clamp(0, 32767);
255 let ms = self.mean_sq as i32;
256 let y = ms + ((self.coeff as i32 * (inst - ms)) >> 15);
257 self.mean_sq = y.clamp(0, 32767) as q15;
258 let mag = isqrt_u64((self.mean_sq as u64) << 15);
259 mag.min(32767) as q15
260 }
261
262 pub fn reset(&mut self) {
263 self.mean_sq = 0;
264 }
265}
266
267pub fn hz_to_mel(hz: f32) -> f32 {
271 2595.0 * (1.0 + hz / 700.0).log10()
272}
273
274pub fn mel_to_hz(mel: f32) -> f32 {
276 700.0 * ((10.0f32).powf(mel / 2595.0) - 1.0)
277}
278
279pub fn mel_filterbank_f32(
290 power_spectrum: &[f32],
291 fft_size: usize,
292 sample_rate_hz: f32,
293 low_freq_hz: f32,
294 high_freq_hz: f32,
295 mel_energies: &mut [f32],
296) -> Status {
297 let num_filters = mel_energies.len();
298 if num_filters == 0 || num_filters > 64 {
299 return Status::ArgumentError;
300 }
301 let num_bins = fft_size / 2 + 1;
302 if power_spectrum.len() < num_bins {
303 return Status::LengthError;
304 }
305
306 let mel_low = hz_to_mel(low_freq_hz);
307 let mel_high = hz_to_mel(high_freq_hz);
308
309 let mut bin_points = [0usize; 66];
310 for (i, bp) in bin_points.iter_mut().enumerate().take(num_filters + 2) {
311 let mel = mel_low + (mel_high - mel_low) * (i as f32) / (num_filters + 1) as f32;
312 let hz = mel_to_hz(mel);
313 let bin = (hz * fft_size as f32 / sample_rate_hz) as usize;
314 *bp = bin.min(num_bins - 1);
315 }
316
317 for (m, out) in mel_energies.iter_mut().enumerate() {
318 let left = bin_points[m];
319 let center = bin_points[m + 1];
320 let right = bin_points[m + 2];
321
322 let mut energy = 0.0f32;
323 if center > left {
324 let span = (center - left) as f32;
325 for bin in left..center {
326 energy += ((bin - left) as f32 / span) * power_spectrum[bin];
327 }
328 }
329 if right > center {
330 let span = (right - center) as f32;
331 for bin in center..=right {
332 energy += ((right - bin) as f32 / span) * power_spectrum[bin];
333 }
334 }
335 *out = energy;
336 }
337
338 Status::Success
339}
340
341pub fn mfcc_f32(
354 frame: &[f32],
355 sample_rate_hz: f32,
356 low_freq_hz: f32,
357 high_freq_hz: f32,
358 mel_energies_scratch: &mut [f32],
359 mfcc_out: &mut [f32],
360) -> Status {
361 let fft_size = frame.len();
362 if fft_size < 2 || (fft_size & (fft_size - 1)) != 0 || 2 * fft_size > 1024 {
363 return Status::ArgumentError;
364 }
365 if mfcc_out.len() > mel_energies_scratch.len() {
366 return Status::ArgumentError;
367 }
368
369 let mut c_data = [0.0f32; 1024];
370 for (i, &x) in frame.iter().enumerate() {
371 c_data[2 * i] = x;
372 c_data[2 * i + 1] = 0.0;
373 }
374 cfft_f32(&mut c_data[..2 * fft_size], fft_size, 0, 1);
375
376 let num_bins = fft_size / 2 + 1;
377 let mut power_spectrum = [0.0f32; 513];
378 for k in 0..num_bins {
379 let re = c_data[2 * k];
380 let im = c_data[2 * k + 1];
381 power_spectrum[k] = re * re + im * im;
382 }
383
384 let status = mel_filterbank_f32(
385 &power_spectrum[..num_bins],
386 fft_size,
387 sample_rate_hz,
388 low_freq_hz,
389 high_freq_hz,
390 mel_energies_scratch,
391 );
392 if status != Status::Success {
393 return status;
394 }
395
396 for e in mel_energies_scratch.iter_mut() {
397 *e = e.max(1e-10).ln();
398 }
399
400 let num_mel = mel_energies_scratch.len() as f32;
401 for (k, out) in mfcc_out.iter_mut().enumerate() {
402 let mut sum = 0.0f32;
403 for (m, &log_e) in mel_energies_scratch.iter().enumerate() {
404 let angle = core::f32::consts::PI * k as f32 * (m as f32 + 0.5) / num_mel;
405 sum += log_e * angle.cos();
406 }
407 *out = sum;
408 }
409
410 Status::Success
411}