Skip to main content

GoertzelDetector

Struct GoertzelDetector 

Source
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

Source

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(&notch_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}
Source

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(&notch_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}
Source

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(&notch_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}
Source

pub fn reset(&mut self)

Resets the detector’s internal state to start a new detection block.

Trait Implementations§

Source§

impl Clone for GoertzelDetector

Source§

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)

Performs copy-assignment from source. Read more
Source§

impl Copy for GoertzelDetector

Source§

impl Debug for GoertzelDetector

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for GoertzelDetector

Source§

fn default() -> GoertzelDetector

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Az for T

Source§

fn az<Dst>(self) -> Dst
where T: Cast<Dst>,

Casts the value.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<Src, Dst> CastFrom<Src> for Dst
where Src: Cast<Dst>,

Source§

fn cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CheckedAs for T

Source§

fn checked_as<Dst>(self) -> Option<Dst>
where T: CheckedCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> CheckedCastFrom<Src> for Dst
where Src: CheckedCast<Dst>,

Source§

fn checked_cast_from(src: Src) -> Option<Dst>

Casts the value.
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<Src, Dst> LosslessTryInto<Dst> for Src
where Dst: LosslessTryFrom<Src>,

Source§

fn lossless_try_into(self) -> Option<Dst>

Performs the conversion.
Source§

impl<Src, Dst> LossyInto<Dst> for Src
where Dst: LossyFrom<Src>,

Source§

fn lossy_into(self) -> Dst

Performs the conversion.
Source§

impl<T> OverflowingAs for T

Source§

fn overflowing_as<Dst>(self) -> (Dst, bool)
where T: OverflowingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> OverflowingCastFrom<Src> for Dst
where Src: OverflowingCast<Dst>,

Source§

fn overflowing_cast_from(src: Src) -> (Dst, bool)

Casts the value.
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> SaturatingAs for T

Source§

fn saturating_as<Dst>(self) -> Dst
where T: SaturatingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> SaturatingCastFrom<Src> for Dst
where Src: SaturatingCast<Dst>,

Source§

fn saturating_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> StrictAs for T

Source§

fn strict_as<Dst>(self) -> Dst
where T: StrictCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> StrictCastFrom<Src> for Dst
where Src: StrictCast<Dst>,

Source§

fn strict_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

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

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> UnwrappedAs for T

Source§

fn unwrapped_as<Dst>(self) -> Dst
where T: UnwrappedCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> UnwrappedCastFrom<Src> for Dst
where Src: UnwrappedCast<Dst>,

Source§

fn unwrapped_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> WrappingAs for T

Source§

fn wrapping_as<Dst>(self) -> Dst
where T: WrappingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> WrappingCastFrom<Src> for Dst
where Src: WrappingCast<Dst>,

Source§

fn wrapping_cast_from(src: Src) -> Dst

Casts the value.