based on the current sample time (t).
Remember that t/SAMPLE_RATE will
produce the actual time. That is, if you
are taking 1,000 samples/second and
the sample number is 100, then the
“real time” is 100/1000 = 0.1 second.
If I were worried about memory
or speed, I’d change how this function
works. For example, the values could
be from a table lookup; a sine wave
table only needs the first quarter of
the data (the other three-quarters can
be easily computed). However, the
PC has plentiful memory and horsepower so this is not necessary.
1,209 Hz 1,336 Hz 1,477 Hz
TABLE 1. DTMF Frequencies.
When you hear DTMF tones on
your phone, you are really hearing
two discrete tones (see Table 1).
To create two tones, you simply
compute two sine waves and add
them together. You should, however,
scale both sine waves to keep the
output samples between -1 and 1.
Listing 2 shows an example callback
(the whole program is available
online at www.nutsvolts.com).
So, creating tones is easy, but
what about recognizing them? There
are several different ways a program
can detect tones. When recognizing a
few well-known tones, the Goertzel
algorithm is very efficient. However, a
Fourier transform is a more widely-known technique and is far more
flexible. To read DTMF, flexibility isn’t
required, but since you may want to
do something more sophisticated than
DTMF decoding, I decided to use the
Fourier transform anyway. Besides,
there are many more “off-the-shelf”
Fourier transforms available because it
is a more common technique.
The idea behind a Fourier transform is that it converts a time-domain
signal into a frequency-domain
signal. In other words, if you looked
at an oscilloscope showing a 1 kHz
sine wave, that’s a time-domain
signal. The same input to a spectrum
analyzer would show a single “peak”
at 1 kHz. That is a frequency-domain
signal — a plot of frequency vs.
amplitude instead of the more
familiar time vs. amplitude plot of
an oscilloscope. See Figure 1 for an
example of an input signal transformed to the frequency domain (this
shows a DTMF 8). The graph shows
the magnitude of the transform, and
you can easily correlate the peaks
with the frequencies in Table 1.
When you compute the transform of a signal that consists of sample points, you end up with a number
of “bins.” Each bin corresponds to a
band of frequency information. For
example, suppose you take a half
second of data at 8 kHz. That’s 4,000
samples. The transform will result
in 4,000 bins and each bin will
represent 2 Hz ( 8,000/4,000).
Because of the Nyquist limit,
only the first half of the bins contain
meaningful data (in other words, the
frequency bins above 4 kHz aren’t
usable). In fact, some libraries don’t
return the upper bins. The outputs of
nearly all algorithms, though, are
complex numbers. To effectively
compare the “amount” of two
frequencies, you must compute the
magnitude of the result.
The magnitude is easy to compute; just add the square of each part
of the complex number and then
take the square root. For example,
suppose you have a bin that contains
a complex number with a real part of
9 and an imaginary part of 12. Simply
add 81 and 144 to get 225. The
square root of 225 is 15. So the relative amount of energy at the frequency corresponding to that bin is 15.
Writing a Fourier transform isn’t
very difficult, but optimizing performance can be challenging. Luckily,
there are many readily-available
libraries. I decided to use the GNU
Scientific Library (GSL).
Like many libraries, GSL has its
own ideas about the format of input
and output data. Therefore, you may
wind up writing some glue code to
make everything work. Listing 3
(which is available on the Nuts &
Volts website) shows the code for a
simple DTMF decoder. The wrapper
around the GSL library here (named
fft) converts the PortAudio data into
the data format that GSL expects.
In addition, the wrapper then plucks
the 16 bins of interest out and
static int playCallback( void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
PaTimestamp outTime, void *userData )
static unsigned t=0;
unsigned long n=0;
SAMPLE *p=(SAMPLE *)outputBuffer;
if (frequency1!=SAMPLE_SILENCE && amp1!=0.0)
if (frequency2!=SAMPLE_SILENCE && amp2!=0.0)
if (t==SAMPLE_RATE) t=0;
December 2006 71