DtmfDetection
C# implementation of the Goertzel algorithm for DTMF tone (a.k.a. Touch-Tone) detection and localization in audio data. Includes wrappers and extensions for NAudio.
Install / Use
/learn @bert2/DtmfDetectionREADME
DtmfDetection & DtmfDetection.NAudio
Implementation of the Goertzel algorithm for the detection of DTMF tones (aka touch tones) in audio data.
| package | use case |
|---|---|
| DtmfDetection | Use this package if you are only working with raw PCM data (i.e. arrays of floats). |
| DtmfDetection.NAudio | Integrates with NAudio to detect DTMF tones in audio files and audio streams (e.g. mic-in or the current audio output). |
Quick start
In case DtmfDetection is not detecting any or only some of the DTMF tones in your audio data, have look at the troubleshooting section first.
With NAudio
How to detect and print DTMF changes (DTMF tone starting or stopping) in an mp3 file:
using System;
using DtmfDetection.NAudio;
using NAudio.Wave;
class Program {
static void Main() {
using var audioFile = new AudioFileReader("long_dtmf_tones.mp3");
var dtmfs = audioFile.DtmfChanges();
foreach (var dtmf in dtmfs) Console.WriteLine(dtmf);
}
}
<details><summary>Output</summary>
</details>1 started @ 00:00:02.7675736 (ch: 0)
1 stopped @ 00:00:05.5607029 (ch: 0)
2 started @ 00:00:06.7138321 (ch: 0)
2 stopped @ 00:00:06.8675736 (ch: 0)
3 started @ 00:00:07.3031972 (ch: 0)
3 stopped @ 00:00:07.4313378 (ch: 0)
4 started @ 00:00:08.2000680 (ch: 0)
4 stopped @ 00:00:10.5319501 (ch: 0)
5 started @ 00:00:12.0950793 (ch: 0)
5 stopped @ 00:00:12.2744444 (ch: 0)
6 started @ 00:00:12.7357142 (ch: 0)
6 stopped @ 00:00:12.8125850 (ch: 0)
7 started @ 00:00:14.5038321 (ch: 0)
7 stopped @ 00:00:14.5294557 (ch: 0)
7 started @ 00:00:14.5550793 (ch: 0)
7 stopped @ 00:00:16.8357142 (ch: 0)
8 started @ 00:00:17.6813378 (ch: 0)
8 stopped @ 00:00:17.7582086 (ch: 0)
9 started @ 00:00:18.4500680 (ch: 0)
9 stopped @ 00:00:18.5269614 (ch: 0)
# started @ 00:00:19.1163265 (ch: 0)
# stopped @ 00:00:19.1419501 (ch: 0)
# started @ 00:00:19.1675736 (ch: 0)
# stopped @ 00:00:19.3469614 (ch: 0)
0 started @ 00:00:19.8338321 (ch: 0)
0 stopped @ 00:00:19.8850793 (ch: 0)
* started @ 00:00:20.4744444 (ch: 0)
* stopped @ 00:00:20.6025850 (ch: 0)
1 started @ 00:00:22.0119501 (ch: 0)
1 stopped @ 00:00:23.7544444 (ch: 0)
How to detect and print multi-channel DTMF changes in a wav file while also merging the start and stop of each DTMF tone into a single data structure:
using System;
using DtmfDetection;
using DtmfDetection.NAudio;
using NAudio.Wave;
class Program {
static void Main() {
using var audioFile = new AudioFileReader("stereo_dtmf_tones.wav");
var dtmfs = audioFile.DtmfChanges(forceMono: false).ToDtmfTones();
foreach (var dtmf in dtmfs) Console.WriteLine(dtmf);
}
}
<details><summary>Output</summary>
</details>1 @ 00:00:00 (len: 00:00:00.9994557, ch: 0)
2 @ 00:00:01.9988208 (len: 00:00:00.9993878, ch: 1)
3 @ 00:00:03.9975736 (len: 00:00:01.9987529, ch: 0)
4 @ 00:00:04.9969614 (len: 00:00:01.9987528, ch: 1)
5 @ 00:00:07.9950793 (len: 00:00:00.9993651, ch: 0)
6 @ 00:00:07.9950793 (len: 00:00:00.9993651, ch: 1)
7 @ 00:00:09.9938321 (len: 00:00:02.9981180, ch: 0)
8 @ 00:00:11.0188208 (len: 00:00:00.9737642, ch: 1)
9 @ 00:00:14.0169614 (len: 00:00:00.9737415, ch: 0)
0 @ 00:00:15.0163265 (len: 00:00:00.9737415, ch: 0)
How to detect and print DTMF changes in audio output:
using System;
using DtmfDetection.NAudio;
using NAudio.CoreAudioApi;
using NAudio.Wave;
class Program {
static void Main() {
using var audioSource = new WasapiLoopbackCapture {
ShareMode = AudioClientShareMode.Shared
};
using var analyzer = new BackgroundAnalyzer(audioSource);
analyzer.OnDtmfDetected += dtmf => Console.WriteLine(dtmf);
_ = Console.ReadKey(intercept: true);
}
}
How to detect and print DTMF changes in microphone input while also lowering the detection threshold:
using System;
using DtmfDetection;
using DtmfDetection.NAudio;
using NAudio.Wave;
class Program {
static void Main() {
using var audioSource = new WaveInEvent {
WaveFormat = new WaveFormat(Config.Default.SampleRate, bits: 32, channels: 1)
};
using var analyzer = new BackgroundAnalyzer(
audioSource,
Config.Default.WithThreshold(10));
analyzer.OnDtmfDetected += dtmf => Console.WriteLine(dtmf);
_ = Console.ReadKey(intercept: true);
}
}
Without NAudio
How to detect and print DTMF tones in an array of PCM samples:
using System;
using System.Linq;
using DtmfDetection;
using static DtmfDetection.DtmfGenerator;
class Program {
static void Main() {
var samples = GenerateStereoSamples();
foreach (var dtmf in samples.DtmfChanges(channels: 2))
Console.WriteLine(dtmf);
}
// `DtmfDetection.DtmfGenerator` has helpers for generating DTMF tones.
static float[] GenerateStereoSamples() =>
Stereo(
left: Generate(PhoneKey.Star),
right: Concat(Mark(PhoneKey.One, ms: 40), Space(ms: 20), Mark(PhoneKey.Two, ms: 40)))
.Take(NumSamples(milliSeconds: 40 + 20 + 40, channels: 2))
.ToArray();
}
<details><summary>Output</summary>
</details>* started @ 00:00:00 (ch: 0)
1 started @ 00:00:00 (ch: 1)
1 stopped @ 00:00:00.0000026 (ch: 1)
2 started @ 00:00:00.0000051 (ch: 1)
* stopped @ 00:00:00.0000100 (ch: 0)
2 stopped @ 00:00:00.0000100 (ch: 1)
Pre-built example tool
TODO: deploy example tool to choco
DTMF tone localization accuracy
Be aware that this library cannot locate DTMF tones with 100% accuracy, because the detector analyzes the data in ~26 ms blocks with the default configuration. This block size determines the resolution of the localization and every DTMF tone starting position will be "rounded off" to the start of the nearest block.
For instance, if a DTMF tone starts at 35 ms into the audio, its calculated starting position will be around 26 ms, i.e. at the beginning of the second block.
A resolution of 26 ms might seem rather inaccurate relative to the typical duration of a DTMF tone (40 ms). However, keep in mind that DTMF analysis typically is about correctly detecting DTMF tones and not about accurately locating them.
Configuring the detector
The library is designed to be very configurable. Of course, each setting of the detector configuration can be changed. Additionally it is possible to replace any part of its logic with a custom implementation.
Adjusting the detection threshold
The detector's threshold value is probably the setting that needs to be tweaked most often. Depending on the audio source and quality, the threshold might have to be increased to reduce false positives or decreased to reduce false negatives.
Typical values are between 30 and 35 with enabled Goertzel response normalization and 100 to 115 without it. Its default value is 30.
Changing the threshold value is easy, because each of the three main entry points take an optional Config argument (defaulting to Config.Default):
List<DtmfChange> float[].DtmfChanges(int, int, Config?)List<DtmfChange> WaveStream.DtmfChanges(bool, Config?)BackgroundAnalyzer(IWaveIn, bool, Action<DtmfChange>?, Config?, IAnalyzer?)
Now, simply create your own Config instance and pass it to the entry point you want to use:
var mycfg = new Config(threshold: 20, sampleBlockSize: ..., ...);
var dmtfs = waveStream.DtmfChanges(config: mycfg);
Or you start off with the default config and adjust it with one of its builder methods:
var mycfg = Config.Default.WithThreshold(20);
Disabling Goertzel response normalization
As of version 1.0.0 the frequency response calculated with Goertzel algorithm will be normalized with the total energy of the input signal. This effectively makes the detector invariant against changes in the loudness of the signal with very little additional computational costs.
You can test this yourself with a simple example program that detects DTMF tones in your system's current audio output, but with disabled response normalization:
using System;
using DtmfDete
Related Skills
node-connect
339.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.9kCommit, push, and open a PR
