1#[allow(unused_imports)]
6use crate::filter_design::single_pole_decay_from_time_constant;
7#[allow(unused_imports)]
8use crate::math::FloatMath;
9use crate::transform::cfft_f32;
10use crate::types::Status;
11
12#[derive(Debug, Clone, Copy, Default)]
18pub struct GoertzelDetector {
19 coeff: f32,
20 s_prev: f32,
21 s_prev2: f32,
22 count: u32,
23}
24
25impl GoertzelDetector {
26 pub fn new(target_freq_hz: f32, sample_rate_hz: f32) -> Self {
28 let w = 2.0 * core::f32::consts::PI * target_freq_hz / sample_rate_hz;
29 Self {
30 coeff: 2.0 * w.cos(),
31 s_prev: 0.0,
32 s_prev2: 0.0,
33 count: 0,
34 }
35 }
36
37 #[inline(always)]
39 pub fn process_sample(&mut self, x: f32) {
40 let s = x + self.coeff * self.s_prev - self.s_prev2;
41 self.s_prev2 = self.s_prev;
42 self.s_prev = s;
43 self.count += 1;
44 }
45
46 pub fn magnitude(&self) -> f32 {
50 if self.count == 0 {
51 return 0.0;
52 }
53 let mag_sq = self.s_prev * self.s_prev + self.s_prev2 * self.s_prev2
54 - self.coeff * self.s_prev * self.s_prev2;
55 mag_sq.max(0.0).sqrt() / (self.count as f32 / 2.0)
56 }
57
58 pub fn reset(&mut self) {
60 self.s_prev = 0.0;
61 self.s_prev2 = 0.0;
62 self.count = 0;
63 }
64}
65
66#[derive(Debug, Clone, Copy, Default)]
71pub struct PeakEnvelopeFollower {
72 attack_coeff: f32,
73 release_coeff: f32,
74 envelope: f32,
75}
76
77impl PeakEnvelopeFollower {
78 pub fn new(attack_samples: f32, release_samples: f32) -> Self {
81 Self {
82 attack_coeff: 1.0 - single_pole_decay_from_time_constant(attack_samples),
83 release_coeff: 1.0 - single_pole_decay_from_time_constant(release_samples),
84 envelope: 0.0,
85 }
86 }
87
88 #[inline(always)]
90 pub fn process(&mut self, x: f32) -> f32 {
91 let rectified = x.abs();
92 let coeff = if rectified > self.envelope {
93 self.attack_coeff
94 } else {
95 self.release_coeff
96 };
97 self.envelope += coeff * (rectified - self.envelope);
98 self.envelope
99 }
100
101 pub fn reset(&mut self) {
103 self.envelope = 0.0;
104 }
105}
106
107#[derive(Debug, Clone, Copy, Default)]
110pub struct RmsEnvelopeFollower {
111 coeff: f32,
112 mean_sq: f32,
113}
114
115impl RmsEnvelopeFollower {
116 pub fn new(time_constant_samples: f32) -> Self {
119 Self {
120 coeff: 1.0 - single_pole_decay_from_time_constant(time_constant_samples),
121 mean_sq: 0.0,
122 }
123 }
124
125 #[inline(always)]
127 pub fn process(&mut self, x: f32) -> f32 {
128 self.mean_sq += self.coeff * (x * x - self.mean_sq);
129 self.mean_sq.max(0.0).sqrt()
130 }
131
132 pub fn reset(&mut self) {
134 self.mean_sq = 0.0;
135 }
136}
137
138pub fn hz_to_mel(hz: f32) -> f32 {
142 2595.0 * (1.0 + hz / 700.0).log10()
143}
144
145pub fn mel_to_hz(mel: f32) -> f32 {
147 700.0 * ((10.0f32).powf(mel / 2595.0) - 1.0)
148}
149
150pub fn mel_filterbank_f32(
161 power_spectrum: &[f32],
162 fft_size: usize,
163 sample_rate_hz: f32,
164 low_freq_hz: f32,
165 high_freq_hz: f32,
166 mel_energies: &mut [f32],
167) -> Status {
168 let num_filters = mel_energies.len();
169 if num_filters == 0 || num_filters > 64 {
170 return Status::ArgumentError;
171 }
172 let num_bins = fft_size / 2 + 1;
173 if power_spectrum.len() < num_bins {
174 return Status::LengthError;
175 }
176
177 let mel_low = hz_to_mel(low_freq_hz);
178 let mel_high = hz_to_mel(high_freq_hz);
179
180 let mut bin_points = [0usize; 66];
181 for (i, bp) in bin_points.iter_mut().enumerate().take(num_filters + 2) {
182 let mel = mel_low + (mel_high - mel_low) * (i as f32) / (num_filters + 1) as f32;
183 let hz = mel_to_hz(mel);
184 let bin = (hz * fft_size as f32 / sample_rate_hz) as usize;
185 *bp = bin.min(num_bins - 1);
186 }
187
188 for (m, out) in mel_energies.iter_mut().enumerate() {
189 let left = bin_points[m];
190 let center = bin_points[m + 1];
191 let right = bin_points[m + 2];
192
193 let mut energy = 0.0f32;
194 if center > left {
195 let span = (center - left) as f32;
196 for bin in left..center {
197 energy += ((bin - left) as f32 / span) * power_spectrum[bin];
198 }
199 }
200 if right > center {
201 let span = (right - center) as f32;
202 for bin in center..=right {
203 energy += ((right - bin) as f32 / span) * power_spectrum[bin];
204 }
205 }
206 *out = energy;
207 }
208
209 Status::Success
210}
211
212pub fn mfcc_f32(
225 frame: &[f32],
226 sample_rate_hz: f32,
227 low_freq_hz: f32,
228 high_freq_hz: f32,
229 mel_energies_scratch: &mut [f32],
230 mfcc_out: &mut [f32],
231) -> Status {
232 let fft_size = frame.len();
233 if fft_size < 2 || (fft_size & (fft_size - 1)) != 0 || 2 * fft_size > 1024 {
234 return Status::ArgumentError;
235 }
236 if mfcc_out.len() > mel_energies_scratch.len() {
237 return Status::ArgumentError;
238 }
239
240 let mut c_data = [0.0f32; 1024];
241 for (i, &x) in frame.iter().enumerate() {
242 c_data[2 * i] = x;
243 c_data[2 * i + 1] = 0.0;
244 }
245 cfft_f32(&mut c_data[..2 * fft_size], fft_size, 0, 1);
246
247 let num_bins = fft_size / 2 + 1;
248 let mut power_spectrum = [0.0f32; 513];
249 for k in 0..num_bins {
250 let re = c_data[2 * k];
251 let im = c_data[2 * k + 1];
252 power_spectrum[k] = re * re + im * im;
253 }
254
255 let status = mel_filterbank_f32(
256 &power_spectrum[..num_bins],
257 fft_size,
258 sample_rate_hz,
259 low_freq_hz,
260 high_freq_hz,
261 mel_energies_scratch,
262 );
263 if status != Status::Success {
264 return status;
265 }
266
267 for e in mel_energies_scratch.iter_mut() {
268 *e = e.max(1e-10).ln();
269 }
270
271 let num_mel = mel_energies_scratch.len() as f32;
272 for (k, out) in mfcc_out.iter_mut().enumerate() {
273 let mut sum = 0.0f32;
274 for (m, &log_e) in mel_energies_scratch.iter().enumerate() {
275 let angle = core::f32::consts::PI * k as f32 * (m as f32 + 0.5) / num_mel;
276 sum += log_e * angle.cos();
277 }
278 *out = sum;
279 }
280
281 Status::Success
282}