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, Q8F7, 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
67type Q2F14 = fixed::FixedI16<fixed::types::extra::U14>;
70
71#[derive(Debug, Clone, Copy, Default)]
74pub struct GoertzelDetectorQ15 {
75 coeff: Q2F14,
76 s_prev: i32,
77 s_prev2: i32,
78 count: u32,
79}
80
81impl GoertzelDetectorQ15 {
82 pub fn new(target_freq_hz: f32, sample_rate_hz: f32) -> Self {
84 let w = 2.0 * core::f32::consts::PI * target_freq_hz / sample_rate_hz;
85 let coeff = Q2F14::saturating_from_num(2.0 * w.cos());
86 Self {
87 coeff,
88 s_prev: 0,
89 s_prev2: 0,
90 count: 0,
91 }
92 }
93
94 #[inline(always)]
96 pub fn process_sample(&mut self, x: q15) {
97 let s = x.to_bits() as i32
98 + ((((self.coeff.to_bits() as i64) * (self.s_prev as i64)) >> 14) as i32)
99 - self.s_prev2;
100 self.s_prev2 = self.s_prev;
101 self.s_prev = s;
102 self.count += 1;
103 }
104
105 pub fn magnitude(&self) -> q15 {
107 if self.count == 0 {
108 return q15::ZERO;
109 }
110 let s = self.s_prev as i64;
111 let s2 = self.s_prev2 as i64;
112 let c = self.coeff.to_bits() as i64;
113 let mag_sq = s * s + s2 * s2 - ((c * s * s2) >> 14);
114 if mag_sq <= 0 {
115 return q15::ZERO;
116 }
117 let mag = isqrt_u64(mag_sq as u64);
118 let out = (mag * 2) / (self.count as u64);
119 q15::from_bits(out.min(32767) as i16)
120 }
121
122 pub fn reset(&mut self) {
124 self.s_prev = 0;
125 self.s_prev2 = 0;
126 self.count = 0;
127 }
128}
129
130#[derive(Debug, Clone, Copy, Default)]
135pub struct PeakEnvelopeFollower {
136 attack_coeff: f32,
137 release_coeff: f32,
138 envelope: f32,
139}
140
141impl PeakEnvelopeFollower {
142 pub fn new(attack_samples: f32, release_samples: f32) -> Self {
145 Self {
146 attack_coeff: 1.0 - single_pole_decay_from_time_constant(attack_samples),
147 release_coeff: 1.0 - single_pole_decay_from_time_constant(release_samples),
148 envelope: 0.0,
149 }
150 }
151
152 #[inline(always)]
154 pub fn process(&mut self, x: f32) -> f32 {
155 let rectified = x.abs();
156 let coeff = if rectified > self.envelope {
157 self.attack_coeff
158 } else {
159 self.release_coeff
160 };
161 self.envelope += coeff * (rectified - self.envelope);
162 self.envelope
163 }
164
165 pub fn reset(&mut self) {
167 self.envelope = 0.0;
168 }
169}
170
171#[derive(Debug, Clone, Copy, Default)]
174pub struct RmsEnvelopeFollower {
175 coeff: f32,
176 mean_sq: f32,
177}
178
179impl RmsEnvelopeFollower {
180 pub fn new(time_constant_samples: f32) -> Self {
183 Self {
184 coeff: 1.0 - single_pole_decay_from_time_constant(time_constant_samples),
185 mean_sq: 0.0,
186 }
187 }
188
189 #[inline(always)]
191 pub fn process(&mut self, x: f32) -> f32 {
192 self.mean_sq += self.coeff * (x * x - self.mean_sq);
193 self.mean_sq.max(0.0).sqrt()
194 }
195
196 pub fn reset(&mut self) {
198 self.mean_sq = 0.0;
199 }
200}
201
202#[derive(Debug, Clone, Copy, Default)]
204pub struct PeakEnvelopeFollowerQ15 {
205 attack_coeff: q15,
206 release_coeff: q15,
207 envelope: q15,
208}
209
210impl PeakEnvelopeFollowerQ15 {
211 pub fn new(attack_samples: f32, release_samples: f32) -> Self {
212 let attack = 1.0 - single_pole_decay_from_time_constant(attack_samples);
213 let release = 1.0 - single_pole_decay_from_time_constant(release_samples);
214 Self {
215 attack_coeff: q15::saturating_from_num(attack),
216 release_coeff: q15::saturating_from_num(release),
217 envelope: q15::ZERO,
218 }
219 }
220
221 #[inline(always)]
222 pub fn process(&mut self, x: q15) -> q15 {
223 let rectified = x.to_bits().unsigned_abs() as i32;
224 let env = self.envelope.to_bits() as i32;
225 let coeff = if rectified > env {
226 self.attack_coeff
227 } else {
228 self.release_coeff
229 }
230 .to_bits() as i32;
231 let y = env + ((coeff * (rectified - env)) >> 15);
232 self.envelope = q15::from_bits(y.clamp(0, 32767) as i16);
233 self.envelope
234 }
235
236 pub fn reset(&mut self) {
237 self.envelope = q15::ZERO;
238 }
239}
240
241#[derive(Debug, Clone, Copy, Default)]
243pub struct RmsEnvelopeFollowerQ15 {
244 coeff: q15,
245 mean_sq: q15,
246}
247
248impl RmsEnvelopeFollowerQ15 {
249 pub fn new(time_constant_samples: f32) -> Self {
250 let c = 1.0 - single_pole_decay_from_time_constant(time_constant_samples);
251 Self {
252 coeff: q15::saturating_from_num(c),
253 mean_sq: q15::ZERO,
254 }
255 }
256
257 #[inline(always)]
258 pub fn process(&mut self, x: q15) -> q15 {
259 let inst = ((x.to_bits() as i32 * x.to_bits() as i32) >> 15).clamp(0, 32767);
260 let ms = self.mean_sq.to_bits() as i32;
261 let y = ms + ((self.coeff.to_bits() as i32 * (inst - ms)) >> 15);
262 self.mean_sq = q15::from_bits(y.clamp(0, 32767) as i16);
263 let mag = isqrt_u64((self.mean_sq.to_bits() as u64) << 15);
264 q15::from_bits(mag.min(32767) as i16)
265 }
266
267 pub fn reset(&mut self) {
268 self.mean_sq = q15::ZERO;
269 }
270}
271
272pub fn hz_to_mel(hz: f32) -> f32 {
276 2595.0 * (1.0 + hz / 700.0).log10()
277}
278
279pub fn mel_to_hz(mel: f32) -> f32 {
281 700.0 * ((10.0f32).powf(mel / 2595.0) - 1.0)
282}
283
284pub fn mel_filterbank_f32(
295 power_spectrum: &[f32],
296 fft_size: usize,
297 sample_rate_hz: f32,
298 low_freq_hz: f32,
299 high_freq_hz: f32,
300 mel_energies: &mut [f32],
301) -> Status {
302 let num_filters = mel_energies.len();
303 if num_filters == 0 || num_filters > 64 {
304 return Status::ArgumentError;
305 }
306 let num_bins = fft_size / 2 + 1;
307 if power_spectrum.len() < num_bins {
308 return Status::LengthError;
309 }
310
311 let mel_low = hz_to_mel(low_freq_hz);
312 let mel_high = hz_to_mel(high_freq_hz);
313
314 let mut bin_points = [0usize; 66];
315 for (i, bp) in bin_points.iter_mut().enumerate().take(num_filters + 2) {
316 let mel = mel_low + (mel_high - mel_low) * (i as f32) / (num_filters + 1) as f32;
317 let hz = mel_to_hz(mel);
318 let bin = (hz * fft_size as f32 / sample_rate_hz) as usize;
319 *bp = bin.min(num_bins - 1);
320 }
321
322 for (m, out) in mel_energies.iter_mut().enumerate() {
323 let left = bin_points[m];
324 let center = bin_points[m + 1];
325 let right = bin_points[m + 2];
326
327 let mut energy = 0.0f32;
328 if center > left {
329 let span = (center - left) as f32;
330 for bin in left..center {
331 energy += ((bin - left) as f32 / span) * power_spectrum[bin];
332 }
333 }
334 if right > center {
335 let span = (right - center) as f32;
336 for bin in center..=right {
337 energy += ((right - bin) as f32 / span) * power_spectrum[bin];
338 }
339 }
340 *out = energy;
341 }
342
343 Status::Success
344}
345
346pub fn mfcc_f32(
359 frame: &[f32],
360 sample_rate_hz: f32,
361 low_freq_hz: f32,
362 high_freq_hz: f32,
363 mel_energies_scratch: &mut [f32],
364 mfcc_out: &mut [f32],
365) -> Status {
366 let fft_size = frame.len();
367 if fft_size < 2 || (fft_size & (fft_size - 1)) != 0 || 2 * fft_size > 1024 {
368 return Status::ArgumentError;
369 }
370 if mfcc_out.len() > mel_energies_scratch.len() {
371 return Status::ArgumentError;
372 }
373
374 let mut c_data = [0.0f32; 1024];
375 for (i, &x) in frame.iter().enumerate() {
376 c_data[2 * i] = x;
377 c_data[2 * i + 1] = 0.0;
378 }
379 cfft_f32(&mut c_data[..2 * fft_size], fft_size, 0, 1);
380
381 let num_bins = fft_size / 2 + 1;
382 let mut power_spectrum = [0.0f32; 513];
383 for k in 0..num_bins {
384 let re = c_data[2 * k];
385 let im = c_data[2 * k + 1];
386 power_spectrum[k] = re * re + im * im;
387 }
388
389 let status = mel_filterbank_f32(
390 &power_spectrum[..num_bins],
391 fft_size,
392 sample_rate_hz,
393 low_freq_hz,
394 high_freq_hz,
395 mel_energies_scratch,
396 );
397 if status != Status::Success {
398 return status;
399 }
400
401 for e in mel_energies_scratch.iter_mut() {
402 *e = e.max(1e-10).ln();
403 }
404
405 let num_mel = mel_energies_scratch.len() as f32;
406 for (k, out) in mfcc_out.iter_mut().enumerate() {
407 let mut sum = 0.0f32;
408 for (m, &log_e) in mel_energies_scratch.iter().enumerate() {
409 let angle = core::f32::consts::PI * k as f32 * (m as f32 + 0.5) / num_mel;
410 sum += log_e * angle.cos();
411 }
412 *out = sum;
413 }
414
415 Status::Success
416}
417
418pub fn generalized_triangular_filterbank(
427 power_spectrum: &[f32],
428 left_bins: &[usize],
429 center_bins: &[usize],
430 right_bins: &[usize],
431 energies_out: &mut [f32],
432) -> Status {
433 let num_filters = energies_out.len();
434 if left_bins.len() < num_filters
435 || center_bins.len() < num_filters
436 || right_bins.len() < num_filters
437 || num_filters == 0
438 {
439 return Status::LengthError;
440 }
441
442 for (i, energy) in energies_out.iter_mut().enumerate() {
443 let left = left_bins[i];
444 let center = center_bins[i];
445 let right = right_bins[i];
446
447 if left > center || center > right || right >= power_spectrum.len() {
448 return Status::ArgumentError;
449 }
450
451 let mut sum = 0.0f32;
452 if center > left {
453 let span = (center - left) as f32;
454 for bin in left..=center {
455 let weight = (bin - left) as f32 / span;
456 sum += weight * power_spectrum[bin];
457 }
458 }
459 if right > center {
460 let span = (right - center) as f32;
461 for bin in (center + 1)..=right {
462 let weight = (right - bin) as f32 / span;
463 sum += weight * power_spectrum[bin];
464 }
465 }
466 *energy = sum;
467 }
468
469 Status::Success
470}
471
472#[inline]
477pub fn fast_log2_q15(x: q15) -> Q8F7 {
478 if x <= q15::ZERO {
479 return Q8F7::MIN;
480 }
481 let lz = (x.to_bits() as u16).leading_zeros() as i32;
482 let int_part = 14 - lz;
484 let shifted = (x.to_bits() as i32) << lz;
486 let frac = (shifted & 0x7FFF) >> 8; let log_val = (int_part << 7) + frac;
488 Q8F7::from_bits((log_val - (15 << 7)).clamp(i16::MIN as i32, i16::MAX as i32) as i16)
489}
490
491#[derive(Debug, Clone, Copy)]
496pub struct VadDetectorQ15 {
497 energy_threshold: i32,
498 zcr_threshold: u16,
499}
500
501impl VadDetectorQ15 {
502 pub const fn new(energy_threshold: i32, zcr_threshold: u16) -> Self {
504 Self {
505 energy_threshold,
506 zcr_threshold,
507 }
508 }
509
510 pub fn is_active(&self, frame: &[q15]) -> bool {
512 if frame.is_empty() {
513 return false;
514 }
515
516 let mut energy_acc: i64 = 0;
517 let mut zcr_count: u16 = 0;
518
519 for i in 0..frame.len() {
520 let sample = frame[i].to_bits() as i64;
521 energy_acc += (sample * sample) >> 15;
522
523 if i > 0 {
524 let prev = frame[i - 1];
525 let cur = frame[i];
526 if (prev >= 0 && cur < 0) || (prev < 0 && cur >= 0) {
527 zcr_count += 1;
528 }
529 }
530 }
531
532 let avg_energy = (energy_acc / frame.len() as i64) as i32;
533 avg_energy >= self.energy_threshold && zcr_count >= self.zcr_threshold
534 }
535}