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, Q2F14, 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
67#[derive(Debug, Clone, Copy, Default)]
70pub struct GoertzelDetectorQ15 {
71 coeff: Q2F14,
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 = Q2F14::saturating_from_num(2.0 * w.cos());
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.to_bits() as i32
94 + ((((self.coeff.to_bits() 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 q15::ZERO;
105 }
106 let s = self.s_prev as i64;
107 let s2 = self.s_prev2 as i64;
108 let c = self.coeff.to_bits() as i64;
109 let mag_sq = s * s + s2 * s2 - ((c * s * s2) >> 14);
110 if mag_sq <= 0 {
111 return q15::ZERO;
112 }
113 let mag = isqrt_u64(mag_sq as u64);
114 let out = (mag * 2) / (self.count as u64);
115 q15::from_bits(out.min(32767) as i16)
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: q15::saturating_from_num(attack),
212 release_coeff: q15::saturating_from_num(release),
213 envelope: q15::ZERO,
214 }
215 }
216
217 #[inline(always)]
218 pub fn process(&mut self, x: q15) -> q15 {
219 let rectified = x.to_bits().unsigned_abs() as i32;
220 let env = self.envelope.to_bits() as i32;
221 let coeff = if rectified > env {
222 self.attack_coeff
223 } else {
224 self.release_coeff
225 }
226 .to_bits() as i32;
227 let y = env + ((coeff * (rectified - env)) >> 15);
228 self.envelope = q15::from_bits(y.clamp(0, 32767) as i16);
229 self.envelope
230 }
231
232 pub fn reset(&mut self) {
233 self.envelope = q15::ZERO;
234 }
235}
236
237#[derive(Debug, Clone, Copy, Default)]
239pub struct RmsEnvelopeFollowerQ15 {
240 coeff: q15,
241 mean_sq: q15,
242}
243
244impl RmsEnvelopeFollowerQ15 {
245 pub fn new(time_constant_samples: f32) -> Self {
246 let c = 1.0 - single_pole_decay_from_time_constant(time_constant_samples);
247 Self {
248 coeff: q15::saturating_from_num(c),
249 mean_sq: q15::ZERO,
250 }
251 }
252
253 #[inline(always)]
254 pub fn process(&mut self, x: q15) -> q15 {
255 let inst = ((x.to_bits() as i32 * x.to_bits() as i32) >> 15).clamp(0, 32767);
256 let ms = self.mean_sq.to_bits() as i32;
257 let y = ms + ((self.coeff.to_bits() as i32 * (inst - ms)) >> 15);
258 self.mean_sq = q15::from_bits(y.clamp(0, 32767) as i16);
259 let mag = isqrt_u64((self.mean_sq.to_bits() as u64) << 15);
260 q15::from_bits(mag.min(32767) as i16)
261 }
262
263 pub fn reset(&mut self) {
264 self.mean_sq = q15::ZERO;
265 }
266}
267
268pub fn hz_to_mel(hz: f32) -> f32 {
272 2595.0 * (1.0 + hz / 700.0).log10()
273}
274
275pub fn mel_to_hz(mel: f32) -> f32 {
277 700.0 * ((10.0f32).powf(mel / 2595.0) - 1.0)
278}
279
280pub fn mel_filterbank_f32(
291 power_spectrum: &[f32],
292 fft_size: usize,
293 sample_rate_hz: f32,
294 low_freq_hz: f32,
295 high_freq_hz: f32,
296 mel_energies: &mut [f32],
297) -> Status {
298 let num_filters = mel_energies.len();
299 if num_filters == 0 || num_filters > 64 {
300 return Status::ArgumentError;
301 }
302 let num_bins = fft_size / 2 + 1;
303 if power_spectrum.len() < num_bins {
304 return Status::LengthError;
305 }
306
307 let mel_low = hz_to_mel(low_freq_hz);
308 let mel_high = hz_to_mel(high_freq_hz);
309
310 let mut bin_points = [0usize; 66];
311 for (i, bp) in bin_points.iter_mut().enumerate().take(num_filters + 2) {
312 let mel = mel_low + (mel_high - mel_low) * (i as f32) / (num_filters + 1) as f32;
313 let hz = mel_to_hz(mel);
314 let bin = (hz * fft_size as f32 / sample_rate_hz) as usize;
315 *bp = bin.min(num_bins - 1);
316 }
317
318 for (m, out) in mel_energies.iter_mut().enumerate() {
319 let left = bin_points[m];
320 let center = bin_points[m + 1];
321 let right = bin_points[m + 2];
322
323 let mut energy = 0.0f32;
324 if center > left {
325 let span = (center - left) as f32;
326 for bin in left..center {
327 energy += ((bin - left) as f32 / span) * power_spectrum[bin];
328 }
329 }
330 if right > center {
331 let span = (right - center) as f32;
332 for bin in center..=right {
333 energy += ((right - bin) as f32 / span) * power_spectrum[bin];
334 }
335 }
336 *out = energy;
337 }
338
339 Status::Success
340}
341
342pub fn mfcc_f32(
355 frame: &[f32],
356 sample_rate_hz: f32,
357 low_freq_hz: f32,
358 high_freq_hz: f32,
359 mel_energies_scratch: &mut [f32],
360 mfcc_out: &mut [f32],
361) -> Status {
362 let fft_size = frame.len();
363 if fft_size < 2 || (fft_size & (fft_size - 1)) != 0 || 2 * fft_size > 1024 {
364 return Status::ArgumentError;
365 }
366 if mfcc_out.len() > mel_energies_scratch.len() {
367 return Status::ArgumentError;
368 }
369
370 let mut c_data = [0.0f32; 1024];
371 for (i, &x) in frame.iter().enumerate() {
372 c_data[2 * i] = x;
373 c_data[2 * i + 1] = 0.0;
374 }
375 cfft_f32(&mut c_data[..2 * fft_size], fft_size, 0, 1);
376
377 let num_bins = fft_size / 2 + 1;
378 let mut power_spectrum = [0.0f32; 513];
379 for k in 0..num_bins {
380 let re = c_data[2 * k];
381 let im = c_data[2 * k + 1];
382 power_spectrum[k] = re * re + im * im;
383 }
384
385 let status = mel_filterbank_f32(
386 &power_spectrum[..num_bins],
387 fft_size,
388 sample_rate_hz,
389 low_freq_hz,
390 high_freq_hz,
391 mel_energies_scratch,
392 );
393 if status != Status::Success {
394 return status;
395 }
396
397 for e in mel_energies_scratch.iter_mut() {
398 *e = e.max(1e-10).ln();
399 }
400
401 let num_mel = mel_energies_scratch.len() as f32;
402 for (k, out) in mfcc_out.iter_mut().enumerate() {
403 let mut sum = 0.0f32;
404 for (m, &log_e) in mel_energies_scratch.iter().enumerate() {
405 let angle = core::f32::consts::PI * k as f32 * (m as f32 + 0.5) / num_mel;
406 sum += log_e * angle.cos();
407 }
408 *out = sum;
409 }
410
411 Status::Success
412}
413
414pub fn generalized_triangular_filterbank(
423 power_spectrum: &[f32],
424 left_bins: &[usize],
425 center_bins: &[usize],
426 right_bins: &[usize],
427 energies_out: &mut [f32],
428) -> Status {
429 let num_filters = energies_out.len();
430 if left_bins.len() < num_filters
431 || center_bins.len() < num_filters
432 || right_bins.len() < num_filters
433 || num_filters == 0
434 {
435 return Status::LengthError;
436 }
437
438 for (i, energy) in energies_out.iter_mut().enumerate() {
439 let left = left_bins[i];
440 let center = center_bins[i];
441 let right = right_bins[i];
442
443 if left > center || center > right || right >= power_spectrum.len() {
444 return Status::ArgumentError;
445 }
446
447 let mut sum = 0.0f32;
448 if center > left {
449 let span = (center - left) as f32;
450 for bin in left..=center {
451 let weight = (bin - left) as f32 / span;
452 sum += weight * power_spectrum[bin];
453 }
454 }
455 if right > center {
456 let span = (right - center) as f32;
457 for bin in (center + 1)..=right {
458 let weight = (right - bin) as f32 / span;
459 sum += weight * power_spectrum[bin];
460 }
461 }
462 *energy = sum;
463 }
464
465 Status::Success
466}
467
468#[inline]
473pub fn fast_log2_q15(x: q15) -> Q8F7 {
474 if x <= q15::ZERO {
475 return Q8F7::MIN;
476 }
477 let lz = (x.to_bits() as u16).leading_zeros() as i32;
478 let int_part = 14 - lz;
480 let shifted = (x.to_bits() as i32) << lz;
482 let frac = (shifted & 0x7FFF) >> 8; let log_val = (int_part << 7) + frac;
484 Q8F7::from_bits((log_val - (15 << 7)).clamp(i16::MIN as i32, i16::MAX as i32) as i16)
485}
486
487#[derive(Debug, Clone, Copy)]
492pub struct VadDetectorQ15 {
493 energy_threshold: i32,
494 zcr_threshold: u16,
495}
496
497impl VadDetectorQ15 {
498 pub const fn new(energy_threshold: i32, zcr_threshold: u16) -> Self {
500 Self {
501 energy_threshold,
502 zcr_threshold,
503 }
504 }
505
506 pub fn is_active(&self, frame: &[q15]) -> bool {
508 if frame.is_empty() {
509 return false;
510 }
511
512 let mut energy_acc: i64 = 0;
513 let mut zcr_count: u16 = 0;
514
515 for i in 0..frame.len() {
516 let sample = frame[i].to_bits() as i64;
517 energy_acc += (sample * sample) >> 15;
518
519 if i > 0 {
520 let prev = frame[i - 1];
521 let cur = frame[i];
522 if (prev >= 0 && cur < 0) || (prev < 0 && cur >= 0) {
523 zcr_count += 1;
524 }
525 }
526 }
527
528 let avg_energy = (energy_acc / frame.len() as i64) as i32;
529 avg_energy >= self.energy_threshold && zcr_count >= self.zcr_threshold
530 }
531}