pub struct GoertzelDetector { /* private fields */ }Expand description
A Goertzel single-frequency detector: computes the DFT magnitude at one target frequency via a simple two-pole recursive filter, without a full FFT. Ideal for detecting a known tone (e.g. DTMF, a pilot tone) from a stream of samples on constrained hardware.
Implementations§
Source§impl GoertzelDetector
impl GoertzelDetector
Sourcepub fn new(target_freq_hz: f32, sample_rate_hz: f32) -> Self
pub fn new(target_freq_hz: f32, sample_rate_hz: f32) -> Self
Creates a detector tuned to target_freq_hz at the given sample_rate_hz.
Examples found in repository?
examples/audio_speech_pipeline.rs (line 215)
14fn main() {
15 println!("===============================================================================");
16 println!(" embedded-dsp Audio & Speech Processing Pipeline ");
17 println!("===============================================================================");
18 println!();
19
20 const FS: f32 = 16000.0; // 16 kHz sampling rate (standard for speech / voice AI)
21 const NUM_SAMPLES: usize = 1024;
22
23 // -----------------------------------------------------------------------------------------
24 // 1. Synthetic Audio Frame Generation
25 // -----------------------------------------------------------------------------------------
26 println!("--- 1. Generating Audio Signal with Mains Hum and DC Offset ---");
27 let mut raw_audio = [0.0f32; NUM_SAMPLES];
28 let dc_bias = 0.35f32;
29 let hum_freq = 60.0f32;
30 let voice_f0 = 440.0f32; // Pitch A4
31 let voice_f1 = 880.0f32; // Harmonic
32
33 for (i, sample) in raw_audio.iter_mut().enumerate() {
34 let t = i as f32 / FS;
35 // Signal: DC bias + 60Hz hum + Voice tones + small noise
36 let hum = 0.25 * (2.0 * core::f32::consts::PI * hum_freq * t).sin();
37 let voice = 0.5 * (2.0 * core::f32::consts::PI * voice_f0 * t).sin()
38 + 0.25 * (2.0 * core::f32::consts::PI * voice_f1 * t).sin();
39 let prng = ((i as u64).wrapping_mul(1103515245).wrapping_add(12345) % 2147483648) as f32
40 / 2147483648.0;
41 let noise = 0.05 * (prng - 0.5);
42 *sample = dc_bias + hum + voice + noise;
43 }
44
45 let mut mean_raw = 0.0f32;
46 let mut rms_raw = 0.0f32;
47 mean_f32(&raw_audio, &mut mean_raw);
48 rms_f32(&raw_audio, &mut rms_raw);
49 println!(
50 " Raw Signal: Mean (DC) = {:.4}, RMS = {:.4}",
51 mean_raw, rms_raw
52 );
53
54 // -----------------------------------------------------------------------------------------
55 // 2. DC Offset Removal (Highpass Single-Pole / DC Blocker)
56 // -----------------------------------------------------------------------------------------
57 println!("\n--- 2. DC Offset Removal (f32 & Q15 DC Blockers) ---");
58 let mut dc_blocked_f32 = [0.0f32; NUM_SAMPLES];
59 // Highpass filter with pole near 1.0 (decay = 0.995)
60 let mut dc_blocker = SinglePoleFilter::highpass(0.995);
61 for (i, &s) in raw_audio.iter().enumerate() {
62 dc_blocked_f32[i] = dc_blocker.process(s);
63 }
64
65 let mut mean_blocked = 0.0f32;
66 // Inspect after settling period (e.g. last 512 samples)
67 mean_f32(&dc_blocked_f32[512..], &mut mean_blocked);
68 println!(
69 " f32 Filter: Settled Mean after DC blocker = {:.6}",
70 mean_blocked
71 );
72
73 // Q15 fixed-point DC blocker verification
74 let mut dc_blocker_q15 = DcBlockerQ15::from_f32_decay(0.995);
75 let mut q15_raw = [q15::ZERO; NUM_SAMPLES];
76 f32_to_q15(&raw_audio, &mut q15_raw);
77 let mut q15_blocked = [q15::ZERO; NUM_SAMPLES];
78 for (i, &s) in q15_raw.iter().enumerate() {
79 q15_blocked[i] = dc_blocker_q15.process(s);
80 }
81 let mut mean_q15_out = q15::ZERO;
82 mean_q15(&q15_blocked[512..], &mut mean_q15_out);
83 println!(
84 " Q15 Filter: Settled Mean in Q15 = {} (expected ~ 0)",
85 mean_q15_out
86 );
87
88 // -----------------------------------------------------------------------------------------
89 // 3. Parametric Equalizer Cascade (60 Hz Notch + 440 Hz Peaking Boost)
90 // -----------------------------------------------------------------------------------------
91 println!("\n--- 3. Biquad Equalizer Cascade: 60 Hz Hum Rejection & 440 Hz Peaking ---");
92 // Stage 0: 60 Hz Notch Filter (Q = 10.0)
93 let notch_coeffs = biquad_notch_coeffs(60.0, FS, 10.0);
94 // Stage 1: 440 Hz Peaking EQ (+6 dB boost, Q = 2.0)
95 let peaking_coeffs = biquad_peaking_coeffs(440.0, FS, 2.0, 6.0);
96
97 let mut cascade_coeffs = [0.0f32; 10];
98 cascade_coeffs[..5].copy_from_slice(¬ch_coeffs);
99 cascade_coeffs[5..].copy_from_slice(&peaking_coeffs);
100
101 let mut eq_state = [0.0f32; 4 * 2]; // 4 state variables per biquad stage
102 let mut eq_cascade = BiquadCascadeInstanceF32 {
103 num_stages: 2,
104 coeffs: &cascade_coeffs,
105 state: &mut eq_state,
106 };
107
108 let mut equalized_audio = [0.0f32; NUM_SAMPLES];
109 biquad_cascade_df1_f32(&mut eq_cascade, &dc_blocked_f32, &mut equalized_audio);
110
111 println!(
112 " Notch Filter Coeffs: b0={:.4}, b1={:.4}, b2={:.4}, a1={:.4}, a2={:.4}",
113 notch_coeffs[0], notch_coeffs[1], notch_coeffs[2], notch_coeffs[3], notch_coeffs[4]
114 );
115 println!(
116 " Peaking Filter Coeffs: b0={:.4}, b1={:.4}, b2={:.4}, a1={:.4}, a2={:.4}",
117 peaking_coeffs[0],
118 peaking_coeffs[1],
119 peaking_coeffs[2],
120 peaking_coeffs[3],
121 peaking_coeffs[4]
122 );
123 println!(
124 " EQ Cascade filtered {} samples successfully.",
125 equalized_audio.len()
126 );
127
128 // -----------------------------------------------------------------------------------------
129 // 4. Dynamics Envelope Followers (Peak & RMS)
130 // -----------------------------------------------------------------------------------------
131 println!("\n--- 4. Dynamics Envelope Tracking (Peak and RMS Followers) ---");
132 // Attack = 5 ms (80 samples @ 16kHz), Release = 50 ms (800 samples)
133 let mut peak_follower = PeakEnvelopeFollower::new(80.0, 800.0);
134 let mut rms_follower = RmsEnvelopeFollower::new(160.0);
135
136 let mut peak_env = [0.0f32; NUM_SAMPLES];
137 let mut rms_env = [0.0f32; NUM_SAMPLES];
138
139 for i in 0..NUM_SAMPLES {
140 peak_env[i] = peak_follower.process(equalized_audio[i]);
141 rms_env[i] = rms_follower.process(equalized_audio[i]);
142 }
143
144 println!(" Settled Peak Envelope: {:.4}", peak_env[NUM_SAMPLES - 1]);
145 println!(" Settled RMS Envelope : {:.4}", rms_env[NUM_SAMPLES - 1]);
146
147 // Fixed-Point Q15 Followers
148 let mut peak_follower_q15 = PeakEnvelopeFollowerQ15::new(80.0, 800.0);
149 let mut q15_eq = [q15::ZERO; NUM_SAMPLES];
150 f32_to_q15(&equalized_audio, &mut q15_eq);
151 let mut last_q15_peak = q15::ZERO;
152 for &sample in &q15_eq {
153 last_q15_peak = peak_follower_q15.process(sample);
154 }
155 println!(" Q15 Peak Envelope Level: {} (Q15)", last_q15_peak);
156
157 // -----------------------------------------------------------------------------------------
158 // 5. ITU-T G.711 Telecom Companding (μ-law and A-law 8-bit Codecs)
159 // -----------------------------------------------------------------------------------------
160 println!("\n--- 5. ITU-T G.711 Telecom Companding Codec (16-bit PCM -> 8-bit Byte) ---");
161 let test_samples: [i16; 6] = [0, 100, -500, 4096, -16384, 30000];
162 println!(
163 " {:<12} {:<12} {:<12} {:<12} {:<12}",
164 "Original PCM", "μ-law Byte", "μ-law Recv", "A-law Byte", "A-law Recv"
165 );
166 println!(" ------------------------------------------------------------------");
167 for &orig in &test_samples {
168 let u_byte = linear_to_ulaw(orig);
169 let u_dec = ulaw_to_linear(u_byte);
170 let a_byte = linear_to_alaw(orig);
171 let a_dec = alaw_to_linear(a_byte);
172 println!(
173 " {:<12} 0x{:02X} ({:<5}) {:<12} 0x{:02X} ({:<5}) {:<12}",
174 orig, u_byte, u_byte, u_dec, a_byte, a_byte, a_dec
175 );
176 }
177
178 // Continuous non-linear curve verification
179 let x_f32 = 0.15f32;
180 let u_comp = mu_law_compress_f32(x_f32);
181 let u_exp = mu_law_expand_f32(u_comp);
182 let a_comp = a_law_compress_f32(x_f32);
183 let a_exp = a_law_expand_f32(a_comp);
184 println!(
185 " Floating-point μ-law roundtrip: x = {:.4} -> comp = {:.4} -> expand = {:.4}",
186 x_f32, u_comp, u_exp
187 );
188 println!(
189 " Floating-point A-law roundtrip: x = {:.4} -> comp = {:.4} -> expand = {:.4}",
190 x_f32, a_comp, a_exp
191 );
192
193 // -----------------------------------------------------------------------------------------
194 // 6. Goertzel Dual-Tone Multi-Frequency (DTMF) Detection
195 // -----------------------------------------------------------------------------------------
196 println!("\n--- 6. Goertzel Dual-Tone Multi-Frequency (DTMF) Detection ---");
197 // Synthesize DTMF Digit '9': Low group 852 Hz + High group 1477 Hz
198 let dtmf_low_freq = 852.0f32;
199 let dtmf_high_freq = 1477.0f32;
200 let mut dtmf_signal = [0.0f32; 256];
201
202 for (i, s) in dtmf_signal.iter_mut().enumerate() {
203 let t = i as f32 / FS;
204 *s = 0.5 * (2.0 * core::f32::consts::PI * dtmf_low_freq * t).sin()
205 + 0.5 * (2.0 * core::f32::consts::PI * dtmf_high_freq * t).sin();
206 }
207
208 // Evaluate against candidate DTMF frequencies
209 let candidate_freqs = [
210 697.0f32, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0, 1633.0,
211 ];
212 println!(" Scanning 8 DTMF frequency bins on 256-sample frame:");
213
214 for &freq in &candidate_freqs {
215 let mut detector = GoertzelDetector::new(freq, FS);
216 for &sample in &dtmf_signal {
217 detector.process_sample(sample);
218 }
219 let mag = detector.magnitude();
220 let is_detected = mag > 0.35;
221 let star = if is_detected {
222 " <== DETECTED TONE"
223 } else {
224 ""
225 };
226 println!(
227 " Frequency {:>6.1} Hz: Magnitude = {:.4}{}",
228 freq, mag, star
229 );
230 }
231
232 // -----------------------------------------------------------------------------------------
233 // 7. MFCC & Mel Filterbank Speech Feature Extraction
234 // -----------------------------------------------------------------------------------------
235 println!("\n--- 7. MFCC Speech Feature Extraction (Acoustic Frontend) ---");
236 // 256-point speech frame (16 ms @ 16 kHz)
237 const FRAME_SIZE: usize = 256;
238 let mut speech_frame = [0.0f32; FRAME_SIZE];
239 for (i, val) in speech_frame.iter_mut().enumerate() {
240 let t = i as f32 / FS;
241 // Formant simulation: 500 Hz + 1500 Hz + 2500 Hz
242 *val = 0.6 * (2.0 * core::f32::consts::PI * 500.0 * t).sin()
243 + 0.3 * (2.0 * core::f32::consts::PI * 1500.0 * t).sin()
244 + 0.1 * (2.0 * core::f32::consts::PI * 2500.0 * t).sin();
245 }
246
247 // Apply Hamming window to reduce spectral leakage
248 let mut window = [0.0f32; FRAME_SIZE];
249 hamming_f32(&mut window);
250 apply_window_f32(&mut speech_frame, &window);
251
252 // Compute MFCCs: 26 Mel filter channels -> 13 Cepstral Coefficients
253 let mut mel_scratch = [0.0f32; 26];
254 let mut mfcc_coeffs = [0.0f32; 13];
255
256 let status = mfcc_f32(
257 &speech_frame,
258 FS,
259 100.0, // Low freq: 100 Hz
260 7000.0, // High freq: 7000 Hz
261 &mut mel_scratch,
262 &mut mfcc_coeffs,
263 );
264
265 if status == Status::Success {
266 println!(" Extracted 13 MFCC Coefficients for Wake-Word / Speech AI:");
267 for (idx, coeff) in mfcc_coeffs.iter().enumerate() {
268 println!(" MFCC[{:>2}]: {:>9.4}", idx, coeff);
269 }
270 } else {
271 println!(" MFCC extraction failed with status: {:?}", status);
272 }
273
274 println!();
275 println!("===============================================================================");
276 println!(" Audio & Speech Pipeline Execution Complete! ");
277 println!("===============================================================================");
278}Sourcepub fn process_sample(&mut self, x: f32)
pub fn process_sample(&mut self, x: f32)
Feeds one input sample into the detector.
Examples found in repository?
examples/audio_speech_pipeline.rs (line 217)
14fn main() {
15 println!("===============================================================================");
16 println!(" embedded-dsp Audio & Speech Processing Pipeline ");
17 println!("===============================================================================");
18 println!();
19
20 const FS: f32 = 16000.0; // 16 kHz sampling rate (standard for speech / voice AI)
21 const NUM_SAMPLES: usize = 1024;
22
23 // -----------------------------------------------------------------------------------------
24 // 1. Synthetic Audio Frame Generation
25 // -----------------------------------------------------------------------------------------
26 println!("--- 1. Generating Audio Signal with Mains Hum and DC Offset ---");
27 let mut raw_audio = [0.0f32; NUM_SAMPLES];
28 let dc_bias = 0.35f32;
29 let hum_freq = 60.0f32;
30 let voice_f0 = 440.0f32; // Pitch A4
31 let voice_f1 = 880.0f32; // Harmonic
32
33 for (i, sample) in raw_audio.iter_mut().enumerate() {
34 let t = i as f32 / FS;
35 // Signal: DC bias + 60Hz hum + Voice tones + small noise
36 let hum = 0.25 * (2.0 * core::f32::consts::PI * hum_freq * t).sin();
37 let voice = 0.5 * (2.0 * core::f32::consts::PI * voice_f0 * t).sin()
38 + 0.25 * (2.0 * core::f32::consts::PI * voice_f1 * t).sin();
39 let prng = ((i as u64).wrapping_mul(1103515245).wrapping_add(12345) % 2147483648) as f32
40 / 2147483648.0;
41 let noise = 0.05 * (prng - 0.5);
42 *sample = dc_bias + hum + voice + noise;
43 }
44
45 let mut mean_raw = 0.0f32;
46 let mut rms_raw = 0.0f32;
47 mean_f32(&raw_audio, &mut mean_raw);
48 rms_f32(&raw_audio, &mut rms_raw);
49 println!(
50 " Raw Signal: Mean (DC) = {:.4}, RMS = {:.4}",
51 mean_raw, rms_raw
52 );
53
54 // -----------------------------------------------------------------------------------------
55 // 2. DC Offset Removal (Highpass Single-Pole / DC Blocker)
56 // -----------------------------------------------------------------------------------------
57 println!("\n--- 2. DC Offset Removal (f32 & Q15 DC Blockers) ---");
58 let mut dc_blocked_f32 = [0.0f32; NUM_SAMPLES];
59 // Highpass filter with pole near 1.0 (decay = 0.995)
60 let mut dc_blocker = SinglePoleFilter::highpass(0.995);
61 for (i, &s) in raw_audio.iter().enumerate() {
62 dc_blocked_f32[i] = dc_blocker.process(s);
63 }
64
65 let mut mean_blocked = 0.0f32;
66 // Inspect after settling period (e.g. last 512 samples)
67 mean_f32(&dc_blocked_f32[512..], &mut mean_blocked);
68 println!(
69 " f32 Filter: Settled Mean after DC blocker = {:.6}",
70 mean_blocked
71 );
72
73 // Q15 fixed-point DC blocker verification
74 let mut dc_blocker_q15 = DcBlockerQ15::from_f32_decay(0.995);
75 let mut q15_raw = [q15::ZERO; NUM_SAMPLES];
76 f32_to_q15(&raw_audio, &mut q15_raw);
77 let mut q15_blocked = [q15::ZERO; NUM_SAMPLES];
78 for (i, &s) in q15_raw.iter().enumerate() {
79 q15_blocked[i] = dc_blocker_q15.process(s);
80 }
81 let mut mean_q15_out = q15::ZERO;
82 mean_q15(&q15_blocked[512..], &mut mean_q15_out);
83 println!(
84 " Q15 Filter: Settled Mean in Q15 = {} (expected ~ 0)",
85 mean_q15_out
86 );
87
88 // -----------------------------------------------------------------------------------------
89 // 3. Parametric Equalizer Cascade (60 Hz Notch + 440 Hz Peaking Boost)
90 // -----------------------------------------------------------------------------------------
91 println!("\n--- 3. Biquad Equalizer Cascade: 60 Hz Hum Rejection & 440 Hz Peaking ---");
92 // Stage 0: 60 Hz Notch Filter (Q = 10.0)
93 let notch_coeffs = biquad_notch_coeffs(60.0, FS, 10.0);
94 // Stage 1: 440 Hz Peaking EQ (+6 dB boost, Q = 2.0)
95 let peaking_coeffs = biquad_peaking_coeffs(440.0, FS, 2.0, 6.0);
96
97 let mut cascade_coeffs = [0.0f32; 10];
98 cascade_coeffs[..5].copy_from_slice(¬ch_coeffs);
99 cascade_coeffs[5..].copy_from_slice(&peaking_coeffs);
100
101 let mut eq_state = [0.0f32; 4 * 2]; // 4 state variables per biquad stage
102 let mut eq_cascade = BiquadCascadeInstanceF32 {
103 num_stages: 2,
104 coeffs: &cascade_coeffs,
105 state: &mut eq_state,
106 };
107
108 let mut equalized_audio = [0.0f32; NUM_SAMPLES];
109 biquad_cascade_df1_f32(&mut eq_cascade, &dc_blocked_f32, &mut equalized_audio);
110
111 println!(
112 " Notch Filter Coeffs: b0={:.4}, b1={:.4}, b2={:.4}, a1={:.4}, a2={:.4}",
113 notch_coeffs[0], notch_coeffs[1], notch_coeffs[2], notch_coeffs[3], notch_coeffs[4]
114 );
115 println!(
116 " Peaking Filter Coeffs: b0={:.4}, b1={:.4}, b2={:.4}, a1={:.4}, a2={:.4}",
117 peaking_coeffs[0],
118 peaking_coeffs[1],
119 peaking_coeffs[2],
120 peaking_coeffs[3],
121 peaking_coeffs[4]
122 );
123 println!(
124 " EQ Cascade filtered {} samples successfully.",
125 equalized_audio.len()
126 );
127
128 // -----------------------------------------------------------------------------------------
129 // 4. Dynamics Envelope Followers (Peak & RMS)
130 // -----------------------------------------------------------------------------------------
131 println!("\n--- 4. Dynamics Envelope Tracking (Peak and RMS Followers) ---");
132 // Attack = 5 ms (80 samples @ 16kHz), Release = 50 ms (800 samples)
133 let mut peak_follower = PeakEnvelopeFollower::new(80.0, 800.0);
134 let mut rms_follower = RmsEnvelopeFollower::new(160.0);
135
136 let mut peak_env = [0.0f32; NUM_SAMPLES];
137 let mut rms_env = [0.0f32; NUM_SAMPLES];
138
139 for i in 0..NUM_SAMPLES {
140 peak_env[i] = peak_follower.process(equalized_audio[i]);
141 rms_env[i] = rms_follower.process(equalized_audio[i]);
142 }
143
144 println!(" Settled Peak Envelope: {:.4}", peak_env[NUM_SAMPLES - 1]);
145 println!(" Settled RMS Envelope : {:.4}", rms_env[NUM_SAMPLES - 1]);
146
147 // Fixed-Point Q15 Followers
148 let mut peak_follower_q15 = PeakEnvelopeFollowerQ15::new(80.0, 800.0);
149 let mut q15_eq = [q15::ZERO; NUM_SAMPLES];
150 f32_to_q15(&equalized_audio, &mut q15_eq);
151 let mut last_q15_peak = q15::ZERO;
152 for &sample in &q15_eq {
153 last_q15_peak = peak_follower_q15.process(sample);
154 }
155 println!(" Q15 Peak Envelope Level: {} (Q15)", last_q15_peak);
156
157 // -----------------------------------------------------------------------------------------
158 // 5. ITU-T G.711 Telecom Companding (μ-law and A-law 8-bit Codecs)
159 // -----------------------------------------------------------------------------------------
160 println!("\n--- 5. ITU-T G.711 Telecom Companding Codec (16-bit PCM -> 8-bit Byte) ---");
161 let test_samples: [i16; 6] = [0, 100, -500, 4096, -16384, 30000];
162 println!(
163 " {:<12} {:<12} {:<12} {:<12} {:<12}",
164 "Original PCM", "μ-law Byte", "μ-law Recv", "A-law Byte", "A-law Recv"
165 );
166 println!(" ------------------------------------------------------------------");
167 for &orig in &test_samples {
168 let u_byte = linear_to_ulaw(orig);
169 let u_dec = ulaw_to_linear(u_byte);
170 let a_byte = linear_to_alaw(orig);
171 let a_dec = alaw_to_linear(a_byte);
172 println!(
173 " {:<12} 0x{:02X} ({:<5}) {:<12} 0x{:02X} ({:<5}) {:<12}",
174 orig, u_byte, u_byte, u_dec, a_byte, a_byte, a_dec
175 );
176 }
177
178 // Continuous non-linear curve verification
179 let x_f32 = 0.15f32;
180 let u_comp = mu_law_compress_f32(x_f32);
181 let u_exp = mu_law_expand_f32(u_comp);
182 let a_comp = a_law_compress_f32(x_f32);
183 let a_exp = a_law_expand_f32(a_comp);
184 println!(
185 " Floating-point μ-law roundtrip: x = {:.4} -> comp = {:.4} -> expand = {:.4}",
186 x_f32, u_comp, u_exp
187 );
188 println!(
189 " Floating-point A-law roundtrip: x = {:.4} -> comp = {:.4} -> expand = {:.4}",
190 x_f32, a_comp, a_exp
191 );
192
193 // -----------------------------------------------------------------------------------------
194 // 6. Goertzel Dual-Tone Multi-Frequency (DTMF) Detection
195 // -----------------------------------------------------------------------------------------
196 println!("\n--- 6. Goertzel Dual-Tone Multi-Frequency (DTMF) Detection ---");
197 // Synthesize DTMF Digit '9': Low group 852 Hz + High group 1477 Hz
198 let dtmf_low_freq = 852.0f32;
199 let dtmf_high_freq = 1477.0f32;
200 let mut dtmf_signal = [0.0f32; 256];
201
202 for (i, s) in dtmf_signal.iter_mut().enumerate() {
203 let t = i as f32 / FS;
204 *s = 0.5 * (2.0 * core::f32::consts::PI * dtmf_low_freq * t).sin()
205 + 0.5 * (2.0 * core::f32::consts::PI * dtmf_high_freq * t).sin();
206 }
207
208 // Evaluate against candidate DTMF frequencies
209 let candidate_freqs = [
210 697.0f32, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0, 1633.0,
211 ];
212 println!(" Scanning 8 DTMF frequency bins on 256-sample frame:");
213
214 for &freq in &candidate_freqs {
215 let mut detector = GoertzelDetector::new(freq, FS);
216 for &sample in &dtmf_signal {
217 detector.process_sample(sample);
218 }
219 let mag = detector.magnitude();
220 let is_detected = mag > 0.35;
221 let star = if is_detected {
222 " <== DETECTED TONE"
223 } else {
224 ""
225 };
226 println!(
227 " Frequency {:>6.1} Hz: Magnitude = {:.4}{}",
228 freq, mag, star
229 );
230 }
231
232 // -----------------------------------------------------------------------------------------
233 // 7. MFCC & Mel Filterbank Speech Feature Extraction
234 // -----------------------------------------------------------------------------------------
235 println!("\n--- 7. MFCC Speech Feature Extraction (Acoustic Frontend) ---");
236 // 256-point speech frame (16 ms @ 16 kHz)
237 const FRAME_SIZE: usize = 256;
238 let mut speech_frame = [0.0f32; FRAME_SIZE];
239 for (i, val) in speech_frame.iter_mut().enumerate() {
240 let t = i as f32 / FS;
241 // Formant simulation: 500 Hz + 1500 Hz + 2500 Hz
242 *val = 0.6 * (2.0 * core::f32::consts::PI * 500.0 * t).sin()
243 + 0.3 * (2.0 * core::f32::consts::PI * 1500.0 * t).sin()
244 + 0.1 * (2.0 * core::f32::consts::PI * 2500.0 * t).sin();
245 }
246
247 // Apply Hamming window to reduce spectral leakage
248 let mut window = [0.0f32; FRAME_SIZE];
249 hamming_f32(&mut window);
250 apply_window_f32(&mut speech_frame, &window);
251
252 // Compute MFCCs: 26 Mel filter channels -> 13 Cepstral Coefficients
253 let mut mel_scratch = [0.0f32; 26];
254 let mut mfcc_coeffs = [0.0f32; 13];
255
256 let status = mfcc_f32(
257 &speech_frame,
258 FS,
259 100.0, // Low freq: 100 Hz
260 7000.0, // High freq: 7000 Hz
261 &mut mel_scratch,
262 &mut mfcc_coeffs,
263 );
264
265 if status == Status::Success {
266 println!(" Extracted 13 MFCC Coefficients for Wake-Word / Speech AI:");
267 for (idx, coeff) in mfcc_coeffs.iter().enumerate() {
268 println!(" MFCC[{:>2}]: {:>9.4}", idx, coeff);
269 }
270 } else {
271 println!(" MFCC extraction failed with status: {:?}", status);
272 }
273
274 println!();
275 println!("===============================================================================");
276 println!(" Audio & Speech Pipeline Execution Complete! ");
277 println!("===============================================================================");
278}Sourcepub fn magnitude(&self) -> f32
pub fn magnitude(&self) -> f32
Returns the magnitude of the target-frequency component accumulated so far, normalized by the number of samples processed so it approximates the input sinusoid’s amplitude regardless of block length.
Examples found in repository?
examples/audio_speech_pipeline.rs (line 219)
14fn main() {
15 println!("===============================================================================");
16 println!(" embedded-dsp Audio & Speech Processing Pipeline ");
17 println!("===============================================================================");
18 println!();
19
20 const FS: f32 = 16000.0; // 16 kHz sampling rate (standard for speech / voice AI)
21 const NUM_SAMPLES: usize = 1024;
22
23 // -----------------------------------------------------------------------------------------
24 // 1. Synthetic Audio Frame Generation
25 // -----------------------------------------------------------------------------------------
26 println!("--- 1. Generating Audio Signal with Mains Hum and DC Offset ---");
27 let mut raw_audio = [0.0f32; NUM_SAMPLES];
28 let dc_bias = 0.35f32;
29 let hum_freq = 60.0f32;
30 let voice_f0 = 440.0f32; // Pitch A4
31 let voice_f1 = 880.0f32; // Harmonic
32
33 for (i, sample) in raw_audio.iter_mut().enumerate() {
34 let t = i as f32 / FS;
35 // Signal: DC bias + 60Hz hum + Voice tones + small noise
36 let hum = 0.25 * (2.0 * core::f32::consts::PI * hum_freq * t).sin();
37 let voice = 0.5 * (2.0 * core::f32::consts::PI * voice_f0 * t).sin()
38 + 0.25 * (2.0 * core::f32::consts::PI * voice_f1 * t).sin();
39 let prng = ((i as u64).wrapping_mul(1103515245).wrapping_add(12345) % 2147483648) as f32
40 / 2147483648.0;
41 let noise = 0.05 * (prng - 0.5);
42 *sample = dc_bias + hum + voice + noise;
43 }
44
45 let mut mean_raw = 0.0f32;
46 let mut rms_raw = 0.0f32;
47 mean_f32(&raw_audio, &mut mean_raw);
48 rms_f32(&raw_audio, &mut rms_raw);
49 println!(
50 " Raw Signal: Mean (DC) = {:.4}, RMS = {:.4}",
51 mean_raw, rms_raw
52 );
53
54 // -----------------------------------------------------------------------------------------
55 // 2. DC Offset Removal (Highpass Single-Pole / DC Blocker)
56 // -----------------------------------------------------------------------------------------
57 println!("\n--- 2. DC Offset Removal (f32 & Q15 DC Blockers) ---");
58 let mut dc_blocked_f32 = [0.0f32; NUM_SAMPLES];
59 // Highpass filter with pole near 1.0 (decay = 0.995)
60 let mut dc_blocker = SinglePoleFilter::highpass(0.995);
61 for (i, &s) in raw_audio.iter().enumerate() {
62 dc_blocked_f32[i] = dc_blocker.process(s);
63 }
64
65 let mut mean_blocked = 0.0f32;
66 // Inspect after settling period (e.g. last 512 samples)
67 mean_f32(&dc_blocked_f32[512..], &mut mean_blocked);
68 println!(
69 " f32 Filter: Settled Mean after DC blocker = {:.6}",
70 mean_blocked
71 );
72
73 // Q15 fixed-point DC blocker verification
74 let mut dc_blocker_q15 = DcBlockerQ15::from_f32_decay(0.995);
75 let mut q15_raw = [q15::ZERO; NUM_SAMPLES];
76 f32_to_q15(&raw_audio, &mut q15_raw);
77 let mut q15_blocked = [q15::ZERO; NUM_SAMPLES];
78 for (i, &s) in q15_raw.iter().enumerate() {
79 q15_blocked[i] = dc_blocker_q15.process(s);
80 }
81 let mut mean_q15_out = q15::ZERO;
82 mean_q15(&q15_blocked[512..], &mut mean_q15_out);
83 println!(
84 " Q15 Filter: Settled Mean in Q15 = {} (expected ~ 0)",
85 mean_q15_out
86 );
87
88 // -----------------------------------------------------------------------------------------
89 // 3. Parametric Equalizer Cascade (60 Hz Notch + 440 Hz Peaking Boost)
90 // -----------------------------------------------------------------------------------------
91 println!("\n--- 3. Biquad Equalizer Cascade: 60 Hz Hum Rejection & 440 Hz Peaking ---");
92 // Stage 0: 60 Hz Notch Filter (Q = 10.0)
93 let notch_coeffs = biquad_notch_coeffs(60.0, FS, 10.0);
94 // Stage 1: 440 Hz Peaking EQ (+6 dB boost, Q = 2.0)
95 let peaking_coeffs = biquad_peaking_coeffs(440.0, FS, 2.0, 6.0);
96
97 let mut cascade_coeffs = [0.0f32; 10];
98 cascade_coeffs[..5].copy_from_slice(¬ch_coeffs);
99 cascade_coeffs[5..].copy_from_slice(&peaking_coeffs);
100
101 let mut eq_state = [0.0f32; 4 * 2]; // 4 state variables per biquad stage
102 let mut eq_cascade = BiquadCascadeInstanceF32 {
103 num_stages: 2,
104 coeffs: &cascade_coeffs,
105 state: &mut eq_state,
106 };
107
108 let mut equalized_audio = [0.0f32; NUM_SAMPLES];
109 biquad_cascade_df1_f32(&mut eq_cascade, &dc_blocked_f32, &mut equalized_audio);
110
111 println!(
112 " Notch Filter Coeffs: b0={:.4}, b1={:.4}, b2={:.4}, a1={:.4}, a2={:.4}",
113 notch_coeffs[0], notch_coeffs[1], notch_coeffs[2], notch_coeffs[3], notch_coeffs[4]
114 );
115 println!(
116 " Peaking Filter Coeffs: b0={:.4}, b1={:.4}, b2={:.4}, a1={:.4}, a2={:.4}",
117 peaking_coeffs[0],
118 peaking_coeffs[1],
119 peaking_coeffs[2],
120 peaking_coeffs[3],
121 peaking_coeffs[4]
122 );
123 println!(
124 " EQ Cascade filtered {} samples successfully.",
125 equalized_audio.len()
126 );
127
128 // -----------------------------------------------------------------------------------------
129 // 4. Dynamics Envelope Followers (Peak & RMS)
130 // -----------------------------------------------------------------------------------------
131 println!("\n--- 4. Dynamics Envelope Tracking (Peak and RMS Followers) ---");
132 // Attack = 5 ms (80 samples @ 16kHz), Release = 50 ms (800 samples)
133 let mut peak_follower = PeakEnvelopeFollower::new(80.0, 800.0);
134 let mut rms_follower = RmsEnvelopeFollower::new(160.0);
135
136 let mut peak_env = [0.0f32; NUM_SAMPLES];
137 let mut rms_env = [0.0f32; NUM_SAMPLES];
138
139 for i in 0..NUM_SAMPLES {
140 peak_env[i] = peak_follower.process(equalized_audio[i]);
141 rms_env[i] = rms_follower.process(equalized_audio[i]);
142 }
143
144 println!(" Settled Peak Envelope: {:.4}", peak_env[NUM_SAMPLES - 1]);
145 println!(" Settled RMS Envelope : {:.4}", rms_env[NUM_SAMPLES - 1]);
146
147 // Fixed-Point Q15 Followers
148 let mut peak_follower_q15 = PeakEnvelopeFollowerQ15::new(80.0, 800.0);
149 let mut q15_eq = [q15::ZERO; NUM_SAMPLES];
150 f32_to_q15(&equalized_audio, &mut q15_eq);
151 let mut last_q15_peak = q15::ZERO;
152 for &sample in &q15_eq {
153 last_q15_peak = peak_follower_q15.process(sample);
154 }
155 println!(" Q15 Peak Envelope Level: {} (Q15)", last_q15_peak);
156
157 // -----------------------------------------------------------------------------------------
158 // 5. ITU-T G.711 Telecom Companding (μ-law and A-law 8-bit Codecs)
159 // -----------------------------------------------------------------------------------------
160 println!("\n--- 5. ITU-T G.711 Telecom Companding Codec (16-bit PCM -> 8-bit Byte) ---");
161 let test_samples: [i16; 6] = [0, 100, -500, 4096, -16384, 30000];
162 println!(
163 " {:<12} {:<12} {:<12} {:<12} {:<12}",
164 "Original PCM", "μ-law Byte", "μ-law Recv", "A-law Byte", "A-law Recv"
165 );
166 println!(" ------------------------------------------------------------------");
167 for &orig in &test_samples {
168 let u_byte = linear_to_ulaw(orig);
169 let u_dec = ulaw_to_linear(u_byte);
170 let a_byte = linear_to_alaw(orig);
171 let a_dec = alaw_to_linear(a_byte);
172 println!(
173 " {:<12} 0x{:02X} ({:<5}) {:<12} 0x{:02X} ({:<5}) {:<12}",
174 orig, u_byte, u_byte, u_dec, a_byte, a_byte, a_dec
175 );
176 }
177
178 // Continuous non-linear curve verification
179 let x_f32 = 0.15f32;
180 let u_comp = mu_law_compress_f32(x_f32);
181 let u_exp = mu_law_expand_f32(u_comp);
182 let a_comp = a_law_compress_f32(x_f32);
183 let a_exp = a_law_expand_f32(a_comp);
184 println!(
185 " Floating-point μ-law roundtrip: x = {:.4} -> comp = {:.4} -> expand = {:.4}",
186 x_f32, u_comp, u_exp
187 );
188 println!(
189 " Floating-point A-law roundtrip: x = {:.4} -> comp = {:.4} -> expand = {:.4}",
190 x_f32, a_comp, a_exp
191 );
192
193 // -----------------------------------------------------------------------------------------
194 // 6. Goertzel Dual-Tone Multi-Frequency (DTMF) Detection
195 // -----------------------------------------------------------------------------------------
196 println!("\n--- 6. Goertzel Dual-Tone Multi-Frequency (DTMF) Detection ---");
197 // Synthesize DTMF Digit '9': Low group 852 Hz + High group 1477 Hz
198 let dtmf_low_freq = 852.0f32;
199 let dtmf_high_freq = 1477.0f32;
200 let mut dtmf_signal = [0.0f32; 256];
201
202 for (i, s) in dtmf_signal.iter_mut().enumerate() {
203 let t = i as f32 / FS;
204 *s = 0.5 * (2.0 * core::f32::consts::PI * dtmf_low_freq * t).sin()
205 + 0.5 * (2.0 * core::f32::consts::PI * dtmf_high_freq * t).sin();
206 }
207
208 // Evaluate against candidate DTMF frequencies
209 let candidate_freqs = [
210 697.0f32, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0, 1633.0,
211 ];
212 println!(" Scanning 8 DTMF frequency bins on 256-sample frame:");
213
214 for &freq in &candidate_freqs {
215 let mut detector = GoertzelDetector::new(freq, FS);
216 for &sample in &dtmf_signal {
217 detector.process_sample(sample);
218 }
219 let mag = detector.magnitude();
220 let is_detected = mag > 0.35;
221 let star = if is_detected {
222 " <== DETECTED TONE"
223 } else {
224 ""
225 };
226 println!(
227 " Frequency {:>6.1} Hz: Magnitude = {:.4}{}",
228 freq, mag, star
229 );
230 }
231
232 // -----------------------------------------------------------------------------------------
233 // 7. MFCC & Mel Filterbank Speech Feature Extraction
234 // -----------------------------------------------------------------------------------------
235 println!("\n--- 7. MFCC Speech Feature Extraction (Acoustic Frontend) ---");
236 // 256-point speech frame (16 ms @ 16 kHz)
237 const FRAME_SIZE: usize = 256;
238 let mut speech_frame = [0.0f32; FRAME_SIZE];
239 for (i, val) in speech_frame.iter_mut().enumerate() {
240 let t = i as f32 / FS;
241 // Formant simulation: 500 Hz + 1500 Hz + 2500 Hz
242 *val = 0.6 * (2.0 * core::f32::consts::PI * 500.0 * t).sin()
243 + 0.3 * (2.0 * core::f32::consts::PI * 1500.0 * t).sin()
244 + 0.1 * (2.0 * core::f32::consts::PI * 2500.0 * t).sin();
245 }
246
247 // Apply Hamming window to reduce spectral leakage
248 let mut window = [0.0f32; FRAME_SIZE];
249 hamming_f32(&mut window);
250 apply_window_f32(&mut speech_frame, &window);
251
252 // Compute MFCCs: 26 Mel filter channels -> 13 Cepstral Coefficients
253 let mut mel_scratch = [0.0f32; 26];
254 let mut mfcc_coeffs = [0.0f32; 13];
255
256 let status = mfcc_f32(
257 &speech_frame,
258 FS,
259 100.0, // Low freq: 100 Hz
260 7000.0, // High freq: 7000 Hz
261 &mut mel_scratch,
262 &mut mfcc_coeffs,
263 );
264
265 if status == Status::Success {
266 println!(" Extracted 13 MFCC Coefficients for Wake-Word / Speech AI:");
267 for (idx, coeff) in mfcc_coeffs.iter().enumerate() {
268 println!(" MFCC[{:>2}]: {:>9.4}", idx, coeff);
269 }
270 } else {
271 println!(" MFCC extraction failed with status: {:?}", status);
272 }
273
274 println!();
275 println!("===============================================================================");
276 println!(" Audio & Speech Pipeline Execution Complete! ");
277 println!("===============================================================================");
278}Trait Implementations§
Source§impl Clone for GoertzelDetector
impl Clone for GoertzelDetector
Source§fn clone(&self) -> GoertzelDetector
fn clone(&self) -> GoertzelDetector
Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreimpl Copy for GoertzelDetector
Source§impl Debug for GoertzelDetector
impl Debug for GoertzelDetector
Source§impl Default for GoertzelDetector
impl Default for GoertzelDetector
Source§fn default() -> GoertzelDetector
fn default() -> GoertzelDetector
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for GoertzelDetector
impl RefUnwindSafe for GoertzelDetector
impl Send for GoertzelDetector
impl Sync for GoertzelDetector
impl Unpin for GoertzelDetector
impl UnsafeUnpin for GoertzelDetector
impl UnwindSafe for GoertzelDetector
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CheckedAs for T
impl<T> CheckedAs for T
Source§fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
Casts the value.
Source§impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
Source§fn checked_cast_from(src: Src) -> Option<Dst>
fn checked_cast_from(src: Src) -> Option<Dst>
Casts the value.
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Src, Dst> LosslessTryInto<Dst> for Srcwhere
Dst: LosslessTryFrom<Src>,
impl<Src, Dst> LosslessTryInto<Dst> for Srcwhere
Dst: LosslessTryFrom<Src>,
Source§fn lossless_try_into(self) -> Option<Dst>
fn lossless_try_into(self) -> Option<Dst>
Performs the conversion.
Source§impl<Src, Dst> LossyInto<Dst> for Srcwhere
Dst: LossyFrom<Src>,
impl<Src, Dst> LossyInto<Dst> for Srcwhere
Dst: LossyFrom<Src>,
Source§fn lossy_into(self) -> Dst
fn lossy_into(self) -> Dst
Performs the conversion.
Source§impl<T> OverflowingAs for T
impl<T> OverflowingAs for T
Source§fn overflowing_as<Dst>(self) -> (Dst, bool)where
T: OverflowingCast<Dst>,
fn overflowing_as<Dst>(self) -> (Dst, bool)where
T: OverflowingCast<Dst>,
Casts the value.
Source§impl<Src, Dst> OverflowingCastFrom<Src> for Dstwhere
Src: OverflowingCast<Dst>,
impl<Src, Dst> OverflowingCastFrom<Src> for Dstwhere
Src: OverflowingCast<Dst>,
Source§fn overflowing_cast_from(src: Src) -> (Dst, bool)
fn overflowing_cast_from(src: Src) -> (Dst, bool)
Casts the value.
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> SaturatingAs for T
impl<T> SaturatingAs for T
Source§fn saturating_as<Dst>(self) -> Dstwhere
T: SaturatingCast<Dst>,
fn saturating_as<Dst>(self) -> Dstwhere
T: SaturatingCast<Dst>,
Casts the value.
Source§impl<Src, Dst> SaturatingCastFrom<Src> for Dstwhere
Src: SaturatingCast<Dst>,
impl<Src, Dst> SaturatingCastFrom<Src> for Dstwhere
Src: SaturatingCast<Dst>,
Source§fn saturating_cast_from(src: Src) -> Dst
fn saturating_cast_from(src: Src) -> Dst
Casts the value.
Source§impl<T> StrictAs for T
impl<T> StrictAs for T
Source§fn strict_as<Dst>(self) -> Dstwhere
T: StrictCast<Dst>,
fn strict_as<Dst>(self) -> Dstwhere
T: StrictCast<Dst>,
Casts the value.
Source§impl<Src, Dst> StrictCastFrom<Src> for Dstwhere
Src: StrictCast<Dst>,
impl<Src, Dst> StrictCastFrom<Src> for Dstwhere
Src: StrictCast<Dst>,
Source§fn strict_cast_from(src: Src) -> Dst
fn strict_cast_from(src: Src) -> Dst
Casts the value.
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
The inverse inclusion map: attempts to construct
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
Checks if
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
Use with care! Same as
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
The inclusion map: converts
self to the equivalent element of its superset.Source§impl<T> UnwrappedAs for T
impl<T> UnwrappedAs for T
Source§fn unwrapped_as<Dst>(self) -> Dstwhere
T: UnwrappedCast<Dst>,
fn unwrapped_as<Dst>(self) -> Dstwhere
T: UnwrappedCast<Dst>,
Casts the value.
Source§impl<Src, Dst> UnwrappedCastFrom<Src> for Dstwhere
Src: UnwrappedCast<Dst>,
impl<Src, Dst> UnwrappedCastFrom<Src> for Dstwhere
Src: UnwrappedCast<Dst>,
Source§fn unwrapped_cast_from(src: Src) -> Dst
fn unwrapped_cast_from(src: Src) -> Dst
Casts the value.
Source§impl<T> WrappingAs for T
impl<T> WrappingAs for T
Source§fn wrapping_as<Dst>(self) -> Dstwhere
T: WrappingCast<Dst>,
fn wrapping_as<Dst>(self) -> Dstwhere
T: WrappingCast<Dst>,
Casts the value.
Source§impl<Src, Dst> WrappingCastFrom<Src> for Dstwhere
Src: WrappingCast<Dst>,
impl<Src, Dst> WrappingCastFrom<Src> for Dstwhere
Src: WrappingCast<Dst>,
Source§fn wrapping_cast_from(src: Src) -> Dst
fn wrapping_cast_from(src: Src) -> Dst
Casts the value.