Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Realtime dual-tone multi-frequency signaling (DTMF) decoder.
using System;
using System.Collections.Generic;
using System.Linq;
using NAudio.Wave;
using Frequency = GM.SP.Audio.SignalAnalysis.Frequency;
namespace GM.SP.Audio
{
/// <summary>
/// https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling
/// </summary>
public class DTMFDecoder : IDisposable
{
/// <summary>
/// Occurs when a new key is decoded.
/// </summary>
public event EventHandler<char> NewKey;
private WaveIn waveIn;
private List<float> buffer;
private char lastKey;
public void Dispose()
{
StopListening();
}
public void StartListening()
{
StopListening();
buffer = new List<float>();
lastKey = ' ';
waveIn = new WaveIn();
// 8000 sample rate is enough, because we will be analyzing 1024 samples at a time, which means that frequency resolution is 8000/1024 = 7.8125, which is enough for DTMF
waveIn.WaveFormat = new WaveFormat(8000, 16, 1);
waveIn.BufferMilliseconds = 100;
waveIn.DataAvailable += WaveIn_DataAvailable;
waveIn.StartRecording();
}
public void StopListening()
{
waveIn?.StopRecording();
waveIn?.Dispose();
}
private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
{
byte[] bytes = e.Buffer;
int newSampleCount = e.BytesRecorded / 2;
// convert bytes to floats
for(int i = 0, j = 0; i < newSampleCount; ++i, j += 2) {
short valueShort = (short)((bytes[j + 1] << 8) | bytes[j]);
float sample = valueShort / ((valueShort > 0) ? 32767f : 32768f);
buffer.Add(sample);
}
// are there enough samples for analysis?
while(buffer.Count >= 1024) {
float[] samplesToAnalyze = buffer.Take(1024).ToArray();
// move our window by 1024/8, for 8x larger time resolution
buffer.RemoveRange(0, 128);
Analyze(samplesToAnalyze);
}
}
private void Analyze(float[] samples)
{
char key = ' ';
try {
// first, we find the biggest frequency below and above 1050 Hz
Frequency biggestBelow1050 = null;
Frequency biggestAbove1050=null;
{
Frequency[] frequencies = SignalAnalysis.AnalyzeAudio(samples, 8000);
// only include frequencies between 50 and 2000
frequencies = frequencies.Where(f => f.Hz > 50 && f.Hz<2000).ToArray();
for(int i = frequencies.Length - 1; i > 1; --i) {
Frequency trenutna = frequencies[i];
if(trenutna.Hz <= 1050) {
if(biggestBelow1050==null || trenutna.Amplitude > biggestBelow1050.Amplitude)
biggestBelow1050 = trenutna;
}else {
if(biggestAbove1050==null || trenutna.Amplitude > biggestAbove1050.Amplitude)
biggestAbove1050 = trenutna;
}
}
// average amplitude of both frequencies
float avgOfPeak = (biggestBelow1050.Amplitude + biggestAbove1050.Amplitude) * 0.5f;
if(avgOfPeak < 0.001)
// too silent
return;
// let's se if these 2 frequencies are at least 10x louder than the average of all others
float avgOfOthers = 0;
for(int i = frequencies.Length - 1; i >= 0; --i) {
Frequency f = frequencies[i];
if(f.Hz == biggestBelow1050.Hz || f.Hz == biggestAbove1050.Hz)
continue;
avgOfOthers += f.Amplitude;
}
avgOfOthers /= (frequencies.Length - 2);
if(avgOfPeak < avgOfOthers * 10)
// they are not
return;
}
// DTMF analysis (https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling#Keypad)
int row;
int column;
{
Frequency f1 = biggestBelow1050;
Frequency f2 = biggestAbove1050;
int threshold = 16;
// row
row = 0;
if(f1.Hz > 697 - threshold && f1.Hz < 697 + threshold)
row = 1;
else if(f1.Hz > 770 - threshold && f1.Hz < 770 + threshold)
row = 2;
else if(f1.Hz > 852 - threshold && f1.Hz < 852 + threshold)
row = 3;
else if(f1.Hz > 941 - threshold && f1.Hz < 941 + threshold)
row = 4;
if(row == 0)
// not close enough to any of the row DTMF frequencies
return;
// column
column = 0;
if(f2.Hz > 1209 - threshold && f2.Hz < 1209 + threshold)
column = 1;
else if(f2.Hz > 1336 - threshold && f2.Hz < 1336 + threshold)
column = 2;
else if(f2.Hz > 1477 - threshold && f2.Hz < 1477 + threshold)
column = 3;
else if(f2.Hz > 1633 - threshold && f2.Hz < 1633 + threshold)
column = 4;
if(column == 0)
// not close enough to any of the column DTMF frequencies
return;
}
key = Decode(row, column);
if(key == lastKey)
// do not invoke the same key multiple consecutive times
return;
NewKey?.Invoke(this, key);
} finally {
lastKey = key;
}
}
/// <summary>
/// Returns the key for the specified row and column.
/// </summary>
private char Decode(int row,int column)
{
switch(row) {
case 1:
switch(column) {
case 1:
return '1';
case 2:
return '2';
case 3:
return '3';
case 4:
return 'A';
}
break;
case 2:
switch(column) {
case 1:
return '4';
case 2:
return '5';
case 3:
return '6';
case 4:
return 'B';
}
break;
case 3:
switch(column) {
case 1:
return '7';
case 2:
return '8';
case 3:
return '9';
case 4:
return 'C';
}
break;
case 4:
switch(column) {
case 1:
return '*';
case 2:
return '0';
case 3:
return '#';
case 4:
return 'D';
}
break;
}
throw new NotImplementedException("Wrong row and/or column.");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.