~ Is a frequency present in a signal? A C implementation.
» By Joren on Thursday 27 July 2023This post is an efficient way to determine whether a predefined frequency is present in a signal. If such an algorithm can be found, it can serve as a basis for a modem. With a modem data is modulated and demodulated at the receiving side. The modulation allows data to be send over a transmission channel.
With the ability to detect the presence of audible frequencies a modem can transform symbols into a combination of frequencies and send data over sound. This is exactly what happens with DTMF in the sound below. DTMF is also used in the dailup sound.
Audio: dail tone sequence: which numbers are pressed?
Typically, determining the presence of frequencies in a signal is done with an FFT: an FFT divides a signal into e.g. 512 linearly spaced frequency bands and determines the magnitude of each of these frequencies. The annoying thing is that a probe frequency can be right in between two bands: sample rate, FFT size and the frequency to look for need to be carefully chosen to reliably detect a frequency. Also, it is computationally inefficient to calculate the magnitudes for all frequency bands if only one band is actually needed.
Luckily there is an alternative approach which looks like the calculating the FFT but for only one predetermined frequencies. This algorithm is known as the Goertzel algorithm and is used in DTMF dail tone encoding and decoding. With the standard Goertzel algorithm it is still needed to consider sample rate and the frequency of interest.
Finally there is the “Generalized Goertzel”: algorithm. In this version of the algorithm employs a couple of tricks to allow an arbitrary frequency and sample rate while still respecting the Kotelnikov frequency limit, better known as the Nyquist frequency.
Recenlty I needed a piece of ANSI c code to detect the magnitude of an arbitrary frequency for a project. The following is a C implementation of this algorithm. It uses the C support for complex numbers in the complex.h
header:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <math.h>
#include <stddef.h>
#include <complex.h>
float detect_frequency(float frequency_to_detect,
float audio_sample_rate,
float * audio_block,
float * window,
size_t audio_block_size){
float audio_block_sizef = (float) audio_block_size;
float indvec = frequency_to_detect / audio_sample_rate * audio_block_sizef;
float pik_term = 2 * M_PI * indvec / audio_block_sizef;
float cos_pik_term2 = cosf(pik_term) * 2;
float s0 = 0;
float s1 = 0;
float s2 = 0;
for(size_t i = 0 ; i < audio_block_size; i ++){
//potential improvement, expect windowed samples
float windowed_audio_sample = window[i] * audio_block[i];
s0 = windowed_audio_sample + cos_pik_term2 * s1 - s2;
s2 = s1;
s1 = s0;
}
s0 = cos_pik_term2 * s1 -s2;
float complex cc = cexpf(0 + -1.0 * pik_term * I);
float complex neg_s1 = -s1 + 0 * I;
float complex pos_s0 = s0 + 0 * I;
float power = cabsf(cc * neg_s1 + pos_s0);
return power;
}
Demo
Below you can try out the algorithm. You can choose a frequency to detect and a playback frequency. The magnitude of the frequency is reported via the slider. The demo uses a javascript translation of the code above.
Dual-tone Multi-Frequency – DTMF
On the right you can find a demo of dual tone frequency modulation and demodulation. A combination of frequencies is played and immediately detected.
The green bars show which frequencies have been detected. If for example 1209 Hz is detected together with 770 Hz then this means that we are looking for the symbol in the first column on the second row. Both the first column and the second row are highlighted in green. At that spot we see 4
so we can decode a 4
. By using 2 combinations of four frequencies a total number of 16 symbols can be encoded.
Note that this code does not simply highlight the button press directly but encodes the symbol in audio, feeds it into an Web Audio API format and decodes audio, the result of the decoding step highlights the row and column detected.