Subscribe Bookmark
Craige_Hales

Staff

Joined:

Mar 21, 2013

FFT and DTMF

winding.jpgAround 1960, before ZIP codes, before shopping malls, Grandmother shared a telephone connection with several other people on a party line.The Telephone Company supplied a distinctive ring for each customer, and please don't listen in, it isn't polite. This was in the capital city of North Carolina, 90,000 people. Within a few years she was the last one on the party line, but The Phone Company was required to continue offering the party line rate to existing customers. A few years later, The Telephone Company started offering Touch-Tone dialing to replace the round rotary dial (tick-tick-tick-tick-tick....tick-tick-tick...), but for years Dad kept the rotary phones so he wouldn't have to pay the $3.00 surcharge for faster dialing. Numbers with lots of zeros were particularly slow to dial. Touch-Tones are fast.

Touch-Tones are DTMF (Dual Tone Multi-Frequency) signals made from two simultaneous sine-waves in the human speech range so telephone systems can easily transmit them. There are eight tones available, arranged in a 4 x 4 grid. You've probably never seen the fourth column.

  1209 Hz 1336 Hz 1477 Hz 1633 Hz
697 Hz 1 2 3 A
770 Hz 4 5 6 B
852 Hz 7 8 9 C
941 Hz * 0 # D

(Hz is the unit that replaced CPS in 1960.)

At the end of this post there is a DTMF3.wav file attached; it was made with a computer microphone pressed against a telephone handset and is a bit quiet. Download it for the script that follows; turn up your speakers a bit to hear it.

The script recovers the key presses from the wav file by loading a matrix of samples in the time domain from the file, then running JMP's FFT function on short sections of the time domain data to convert it to frequency domain. In this graph each vertical column of dots represents what frequencies were especially loud during the time slot. The extra dots at the bottom left are probably from tapping the microphone on the earpiece; the sharp clicks have lots of frequencies.

DTMF_FFT.png

The first digit has a lot of 1477 Hz energy and a lot of 852 Hz energy. It is a 9. If you want to check your work on the rest of the digits, compare your answer to the JMP US technical support number.

Update 20Dec2016: the JSL is restored after not surviving the conversion from the old blog format. Try it again if you got an empty table and no graph in the last few months. Also, this is not a great model for reading WAV files; the example in Working With WAV Files is much faster and more likely to do what you need in the future.

x = Load Text File( "$DESKTOP/DTMF3.wav", blob ); // this file must be 22050 16 bit 1 channel   
// because the following code makes zero effort to handle anything else   
x = Hex( x ); // doubles the size of the data, but the hex characters are easy to parse   
If( Pat Match( x, Hex( "RIFF" ) + Pat Arb() + Hex( "data" ), "" ), // found the data chunk   
    grab = 2048; // samples per timeslice   
    period = grab / 22050; // seconds   
    binwidth = 1 / period; // 1 cycle in the bin's width is the lowest frequency the bins resolve   
    timeslice = 0; // count through the time slices   
    dt = New Table("sound",New Column("time"),New Column("frequency"),New Column("amp"));   
    dt << begin data update; // load the table without updating each time   
    While( Length( x ) > (grab) * 2 * 2, // grab samples * 2 bytesPerSample * 2 hexCharsPerByte   
        samples = J( grab, 1, 0 ); // an array to hold the samples; will be sent to the fft below   
        nsamples = 0; // grab counter   
        timeslice++; // each time we grab samples is a new time slice   
        Pat Match( x, // scan the hex data for pairs of hex digits   
            Pat Repeat(   
                Pat Len( 2 ) >> left1 + Pat Len( 2 ) >> left2 + // two pairs   
                // the following Expr evaluates to PatFence(), eventually, so it can be   
                // concatenated onto the preceding match.  But before it does that, it   
                // stores the preceding match into the samples[ ] matrix   
                Expr( nsamples++; // index into the sample array   
                    q = Hex To Number( left2 || left1 ); // build the 16 bit integer   
                    If( q > 32767, q = q - 65536 ); // make it signed, +/-32K   
                    samples[ Nsamples ] = q; // keep it   
                    Pat Fence(); ), // make matching faster by fencing off completed work   
                grab, grab // min,max number of repeats   
            ), "" // replace everything matched for this grab with nothing   
        );   
        freq = FFT( {samples} ); // run the fft.  freq is a list:  { real, imag }   
        amplitude = Sqrt( freq[1] :* freq[1] + freq[2] :* freq[2] ); // just the amplitude   
        For( f = 1, f < grab / 2, f++,   // write data table   
        // throw out small amplitude responses and out-of-interest frequencies   
        // a different file might need some tweaking of these values   
            If( amplitude[F] > 10000 & f > 50 & f < 150, // roughly 500 to 1500 Hz   
                dt << addrows( 1 );   
                dt:time = timeslice * period;   
                dt:frequency = (f - 1) * binwidth; // bin 1 is really frequency 0, DC  
                dt:amp = amplitude[F];   
            )   
        );   
    );   
    dt << end data update;   
  // the pretty picture   
    dt << Graph Builder( Size( 664, 470 ),  Show Control Panel( 0 ),   
        Variables( X( :time ), Y( :frequency ) ), Elements( Points( X, Y, Legend( 5 ) ) ),   
        SendToReport(   
            Dispatch( {}, "time", ScaleBox, {Min( 0.5 ), Max( 9 ), Inc( 1 ),   
              Minor Ticks( 1 ), Label Row Nesting( 1 )} ),   
            Dispatch( {}, "frequency", ScaleBox,   
                {Min( 250 ), Max( 1750 ), Inc( 2000 ), Minor Ticks( 0 ), Label Row Nesting( 1 ),   
                Add Ref Line( 697, "Solid", "Black", "697Hz", 1 ),   
                Add Ref Line( 770, "Solid", "Black", "770Hz", 1 ),   
                Add Ref Line( 852, "Solid", "Black", "852Hz", 1 ),   
                Add Ref Line( 941, "Solid", "Black", "941Hz", 1 ),   
                Add Ref Line( 1209, "Solid", "Black", "1209Hz", 1 ),   
                Add Ref Line( 1336, "Solid", "Black", "1336Hz", 1 ),   
                Add Ref Line( 1477, "Solid", "Black", "1477Hz", 1 ),   
                Add Ref Line( 1633, "Solid", "Black", "1633Hz", 1 )}   
            ),   
            Dispatch( {}, "graph title", TextEditBox, {Set Text( "DTMF Analyzer" )} )   
        )   
    );   
,  // else...  
    Print( "not a wav file" )   
); 

 

phoneAndMicrophone.jpgIn the early 1980's, The Phone Company split up into a bunch of smaller companies and it became legal to own your own telephone.

Notice the odd relationship between Grandmother's party line distinctive ring, which told who was being called, and today's ring tones, which can tell who is calling. 

I've still got a land line.

Article Tags