Skip to content

Instantly share code, notes, and snippets.

@MayamaTakeshi
Created December 25, 2022 03:06
Show Gist options
  • Save MayamaTakeshi/6da8557da56650787c631294e26df5ff to your computer and use it in GitHub Desktop.
Save MayamaTakeshi/6da8557da56650787c631294e26df5ff to your computer and use it in GitHub Desktop.
Javascript module to detect DTMF using streams (written by ChatGPT)
const { Transform } = require('stream');
const LOW_FREQUENCIES = [697, 770, 852, 941, 1209, 1336, 1477, 1633];
const HIGH_FREQUENCIES = [1209, 1336, 1477, 1633, 1633, 1633, 1633, 1633];
const TONES = ['1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'];
class DtmfDetector extends Transform {
constructor() {
super({ objectMode: true });
this.samples = [];
}
_transform(chunk, encoding, callback) {
for (const sample of chunk) {
this.samples.push(sample);
if (this.samples.length >= 256) {
const tone = this.detectTone();
this.push(tone);
this.samples = [];
}
}
callback();
}
detectTone() {
let maxMagnitude = -1;
let maxFrequency = -1;
for (let i = 0; i < this.samples.length; i++) {
let magnitude = 0;
for (let j = 0; j < LOW_FREQUENCIES.length; j++) {
const lowFrequency = LOW_FREQUENCIES[j];
const highFrequency = HIGH_FREQUENCIES[j];
const lowReal = Math.cos(2 * Math.PI * lowFrequency * i / 8000);
const lowImag = Math.sin(2 * Math.PI * lowFrequency * i / 8000);
const highReal = Math.cos(2 * Math.PI * highFrequency * i / 8000);
const highImag = Math.sin(2 * Math.PI * highFrequency * i / 8000);
magnitude += lowReal * this.samples[i] + highReal * this.samples[i];
}
if (magnitude > maxMagnitude) {
maxMagnitude = magnitude;
maxFrequency = i;
}
}
const frequency = (maxFrequency / 256) * 8000;
let tone = null;
if (697 <= frequency && frequency <= 1209) {
if (697 <= frequency && frequency <= 770) {
tone = '1';
} else if (770 <= frequency && frequency <= 852) {
tone = '2';
} else if (852 <= frequency && frequency <= 941) {
tone = '3';
} else {
tone = 'A';
}
} else if (1336 <= frequency && frequency <= 1477) {
if (1336 <= frequency && frequency <= 1477) {
tone = '4';
} else if (1477 <= frequency && frequency <= 1633) {
tone = '5';
} else {
tone = '6';
}
} else if (1633 <= frequency && frequency <= 1209) {
if (1633 <= frequency && frequency <= 1750) {
tone = '7';
} else if (1750 <= frequency && frequency <= 1865) {
tone = '8';
} else if (1865 <= frequency && frequency <= 1976) {
tone = '9';
} else {
tone = '*';
}
} else if (2277 <= frequency && frequency <= 1633) {
if (2277 <= frequency && frequency <= 2024) {
tone = '0';
} else {
tone = '#';
}
} else {
tone = '?';
}
return tone;
}
}
module.exports = DtmfDetector;
// After asking to add tone start/end detection
const { Transform } = require('stream');
const LOW_FREQUENCIES = [697, 770, 852, 941, 1209, 1336, 1477, 1633];
const HIGH_FREQUENCIES = [1209, 1336, 1477, 1633, 1633, 1633, 1633, 1633];
const TONES = ['1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'];
class DtmfDetector extends Transform {
constructor() {
super({ objectMode: true });
this.samples = [];
this.currentTone = null;
}
_transform(chunk, encoding, callback) {
for (const sample of chunk) {
this.samples.push(sample);
if (this.samples.length >= 256) {
const tone = this.detectTone();
if (tone !== this.currentTone) {
if (this.currentTone) {
// DTMF tone ended
console.log(`DTMF tone ${this.currentTone} ended`);
}
if (tone) {
// DTMF tone started
console.log(`DTMF tone ${tone} started`);
}
this.currentTone = tone;
}
this.push(tone);
this.samples = [];
}
}
callback();
}
detectTone() {
let maxMagnitude = -1;
let maxFrequency = -1;
for (let i = 0; i < this.samples.length; i++) {
let magnitude = 0;
for (let j = 0; j < LOW_FREQUENCIES.length; j++) {
const lowFrequency = LOW_FREQUENCIES[j];
const highFrequency = HIGH_FREQUENCIES[j];
const lowReal = Math.cos(2 * Math.PI * lowFrequency * i / 8000);
const lowImag = Math.sin(2 * Math.PI * lowFrequency * i / 8000);
const highReal = Math.cos(2 * Math.PI * highFrequency * i / 8000);
const highImag = Math.sin(2 * Math.PI * highFrequency * i / 8000);
magnitude += lowReal * this.samples[i] + highReal * this.samples[i];
}
if (magnitude > maxMagnitude) {
maxMagnitude = magnitude;
maxFrequency = i;
}
}
const frequency = (maxFrequency / 256) * 8000;
let tone = null;
if (697 <= frequency && frequency <= 1209) {
if (697 <= frequency && frequency && frequency <= 770) {
tone = '1';
} else if (770 <= frequency && frequency <= 852) {
tone = '2';
} else if (852 <= frequency && frequency <= 941) {
tone = '3';
} else {
tone = 'A';
}
} else if (1336 <= frequency && frequency <= 1477) {
if (1336 <= frequency && frequency <= 1477) {
tone = '4';
} else if (1477 <= frequency && frequency <= 1633) {
tone = '5';
} else {
tone = '6';
}
} else if (1633 <= frequency && frequency <= 1209) {
if (1633 <= frequency && frequency <= 1750) {
tone = '7';
} else if (1750 <= frequency && frequency <= 1865) {
tone = '8';
} else if (1865 <= frequency && frequency <= 1976) {
tone = '9';
} else {
tone = '*';
}
} else if (2277 <= frequency && frequency <= 1633) {
if (2277 <= frequency && frequency <= 2024) {
tone = '0';
} else {
tone = '#';
}
} else {
tone = '?';
}
return tone;
}
}
module.exports = DtmfDetector;
// After asking to add format configuration
const { Transform } = require('stream');
const LOW_FREQUENCIES = [697, 770, 852, 941, 1209, 1336, 1477, 1633];
const HIGH_FREQUENCIES = [1209, 1336, 1477, 1633, 1633, 1633, 1633, 1633];
const TONES = ['1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'];
class DtmfDetector extends Transform {
constructor(format) {
super({ objectMode: true });
this.format = format;
this.samples = [];
this.currentTone = null;
}
_transform(chunk, encoding, callback) {
for (const sample of chunk) {
this.samples.push(sample);
if (this.samples.length >= this.format.sampleRate / 8) {
const tone = this.detectTone();
if (tone !== this.currentTone) {
if (this.currentTone) {
// DTMF tone ended
console.log(`DTMF tone ${this.currentTone} ended`);
}
if (tone) {
// DTMF tone started
console.log(`DTMF tone ${tone} started`);
}
this.currentTone = tone;
}
this.push(tone);
this.samples = [];
}
}
callback();
}
detectTone() {
let maxMagnitude = -1;
let maxFrequency = -1;
for (let i = 0; i < this.samples.length; i++) {
let magnitude = 0;
for (let j = 0; j < LOW_FREQUENCIES.length; j++) {
const lowFrequency = LOW_FREQUENCIES[j];
const highFrequency = HIGH_FREQUENCIES[j];
const lowReal = Math.cos(2 * Math.PI * lowFrequency * i / this.format.sampleRate);
const lowImag = Math.sin(2 * Math.PI * lowFrequency * i / this.format.sampleRate);
const highReal = Math.cos(2 * Math.PI * highFrequency * i / this.format.sampleRate);
const highImag = Math.sin(2 * Math.PI * highFrequency * i / this.format.sampleRate);
magnitude += lowReal * this.samples[i] + highReal * this.samples[i];
}
if (magnitude > maxMagnitude) {
maxMagnitude = magnitude;
maxFrequency = i;
}
}
const frequency = (maxFrequency / this.samples.length) * this.format.sampleRate;
let tone = null;
if (697 <= frequency && frequency <= 1209) {
if (697 <= frequency && frequency <= 770) {
tone = '1';
} else if (770 <= frequency && frequency <= 852) {
tone = '2';
} else if (852 <= frequency && frequency <= 941) {
tone = '3';
} else {
tone = 'A';
}
} else if (1336 <= frequency && frequency <= 1477) {
if (1336 <= frequency && frequency <= 1477) {
tone = '4';
} else if (1477 <= frequency && frequency <= 1633) {
tone = '5';
} else {
tone = '6';
}
} else if (1633 <= frequency && frequency <= 1209) {
if (1633 <= frequency && frequency <= 1750) {
tone = '7';
} else if (1750 <= frequency && frequency <= 1865) {
tone = '8';
} else if (1865 <= frequency && frequency <= 1976) {
tone = '9';
} else {
tone = '*';
}
} else if (2277 <= frequency && frequency <= 1633) {
if (2277 <= frequency && frequency <= 2024) {
tone = '0';
} else {
tone = '#';
}
} else {
tone = '?';
}
return tone;
}
}
module.exports = DtmfDetector;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment