goertzeljs
Version:
A pure JavaScript implementation of the Goertzel algorithm.
160 lines (150 loc) • 4.79 kB
JavaScript
'use strict';
const Goertzel = require('../index');
class DTMF {
constructor(options) {
if (options == null) { options = {}; }
this.options = {
downsampleRate: 1,
energyThreshold: 0,
decibelThreshold: 0,
repeatMin: 0,
sampleRate: 44100
};
for (let option in options) {
this.options[option] = options[option];
}
this.sampleRate = this.options.sampleRate / this.options.downsampleRate;
this.frequencyTable = {
[697]: {
[1209]: '1',
[1336]: '2',
[1477]: '3',
[1633]: 'A'
},
[770]: {
[1209]: '4',
[1336]: '5',
[1477]: '6',
[1633]: 'B'
},
[852]: {
[1209]: '7',
[1336]: '8',
[1477]: '9',
[1633]: 'C'
},
[941]: {
[1209]: '*',
[1336]: '0',
[1477]: '#',
[1633]: 'D'
}
};
this.lowFrequencies = [];
for (var key in this.frequencyTable) {
this.lowFrequencies.push(parseInt(key));
}
this.highFrequencies = [];
for (key in this.frequencyTable[this.lowFrequencies[0]]) {
this.highFrequencies.push(parseInt(key));
}
this.allFrequencies = this.lowFrequencies.concat(this.highFrequencies);
this.repeatCounter = 0;
this.firstPreviousValue = '';
this.goertzel = new Goertzel({
frequencies: this.allFrequencies,
sampleRate: this.sampleRate
});
this.decodeHandlers = [];
this.jobs =
{beforeProcess: []};
}
processBuffer(buffer) {
let value = '';
let result = [];
this._runJobs('beforeProcess', buffer);
if (this.options.decibelThreshold && (Goertzel.Utilities.averageDecibels(buffer) < this.options.decibelThreshold)) { return result; }
// Downsample by choosing every Nth sample.
Goertzel.Utilities.eachDownsample(buffer, this.options.downsampleRate, (sample,i,downSampledBufferLength)=> {
let windowedSample = Goertzel.Utilities.exactBlackman(sample, i, downSampledBufferLength);
this.goertzel.processSample(windowedSample);
});
let energies = {
high: [],
low: []
};
for (let fType of ['high', 'low']) {
let i = 0;
while (i < this[`${fType}Frequencies`].length) {
let f = this[`${fType}Frequencies`][i];
energies[fType].push(this.goertzel.energies[f]);
i++;
}
}
if ((this.options.filter && this.options.filter({goertzel: this.goertzel, energies})) || !this.options.filter) {
value = this._energyProfileToCharacter(this.goertzel);
if (((value === this.firstPreviousValue) || (this.options.repeatMin === 0)) && (value !== undefined)) {
if (this.options.repeatMin !== 0) { this.repeatCounter += 1; }
if (this.repeatCounter === this.options.repeatMin) {
result.push(value);
for (let handler of Array.from(this.decodeHandlers)) {
setTimeout(function() { handler(value) }, 0);
}
}
} else {
this.repeatCounter = 0;
this.firstPreviousValue = value;
}
}
this.goertzel.refresh();
return result;
}
on(eventName, handler) {
switch (eventName) {
case "decode": return this.decodeHandlers.push(handler);
}
}
calibrate(multiplier){
if (multiplier == null) { multiplier = 1; }
if (!this.jobs.beforeProcess) { this.jobs.beforeProcess = []; }
return this.jobs.beforeProcess.push((buffer, dtmf) => dtmf.options.decibelThreshold = Goertzel.Utilities.averageDecibels(buffer) * multiplier);
}
// private
_energyProfileToCharacter(register) {
let { energies } = register;
// Find high frequency.
let highFrequency = 0.0;
let highFrequencyEngergy = 0.0;
for (var f of Array.from(this.highFrequencies)) {
if ((energies[f] > highFrequencyEngergy) && (energies[f] > this.options.energyThreshold)) {
highFrequencyEngergy = energies[f];
highFrequency = f;
}
}
// Find low frequency.
let lowFrequency = 0.0;
let lowFrequencyEnergy = 0.0;
for (f of Array.from(this.lowFrequencies)) {
if ((energies[f] > lowFrequencyEnergy) && (energies[f] > this.options.energyThreshold)) {
lowFrequencyEnergy = energies[f];
lowFrequency = f;
}
}
return this.frequencyTable[lowFrequency] ? this.frequencyTable[lowFrequency][highFrequency] : null;
}
_runJobs(jobName, buffer) {
if (this.jobs[jobName]) {
let queueLength = this.jobs[jobName].length;
let i = 0;
return (() => {
let result = [];
while (i < queueLength) {
this.jobs[jobName].pop()(buffer, this);
result.push(i++);
}
return result;
})();
}
}
}
module.exports = DTMF;