From 470788178a54576a5ba9940b7cedf7da33fba5c7 Mon Sep 17 00:00:00 2001 From: benjamin Date: Sun, 6 Apr 2014 20:03:58 -0700 Subject: [PATCH] Initial commit. --- LICENSE | 2 +- README.md | 2 +- demo.html | 14 +++++++++ demo.js | 35 +++++++++++++++++++++ dtmf.js | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++ goertzel.js | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 demo.html create mode 100644 demo.js create mode 100644 dtmf.js create mode 100644 goertzel.js diff --git a/LICENSE b/LICENSE index 044d948c..ea7fad27 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Ravenstine +Copyright (c) 2014 Benjamin Titcomb Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 01f13873..4f290dd5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -goertzeljs +goertzel.js ========== A pure JavaScript implementation of the Goertzel algorithm. diff --git a/demo.html b/demo.html new file mode 100644 index 00000000..1be3efca --- /dev/null +++ b/demo.html @@ -0,0 +1,14 @@ + + + + Goertzel.js DTMF Demo + + + + + + +
+ + + diff --git a/demo.js b/demo.js new file mode 100644 index 00000000..b79f831c --- /dev/null +++ b/demo.js @@ -0,0 +1,35 @@ +if (!navigator.getUserMedia) + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || navigator.msGetUserMedia + +if (navigator.getUserMedia){ + navigator.getUserMedia({audio:true}, success, function(e) { + alert('Error capturing audio.') + }) +} else alert('getUserMedia not supported in this browser.') + + +function success(e){ + audioContext = window.AudioContext || window.webkitAudioContext + context = new audioContext() + volume = context.createGain() + audioInput = context.createMediaStreamSource(e) + audioInput.connect(volume) + var bufferSize = 2048 + recorder = context.createJavaScriptNode(bufferSize, 1, 1) + var outputElement = document.querySelector('#output') + var dtmf = new DTMF(context.sampleRate,5,0) + dtmf.onDecode = function(value){ + outputElement.innerHTML = outputElement.innerHTML + value + } + recorder.onaudioprocess = function(e){ + var bin = e.inputBuffer.getChannelData (0) + dtmf.processBin(bin) + dtmf.refresh() + } + + volume.connect (recorder) + recorder.connect (context.destination) +} + + diff --git a/dtmf.js b/dtmf.js new file mode 100644 index 00000000..0214cd7e --- /dev/null +++ b/dtmf.js @@ -0,0 +1,87 @@ +function DTMF(samplerate,decimation,threshold){ + var self = this + var samplerate = samplerate / decimation + var decimation = decimation || 5 + var threshold = threshold || 0.0002 + var 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"} + } + var lowFrequencies = [] + for(var key in frequencyTable) lowFrequencies.push(parseInt(key)) + var highFrequencies = [] + for (var key in frequencyTable[lowFrequencies[0]]) highFrequencies.push(parseInt(key)) + var allFrequencies = lowFrequencies.concat(highFrequencies) + var frequencyData = { + frequencyTable: frequencyTable, + lowFrequencies: lowFrequencies, + highFrequencies: highFrequencies, + allFrequencies: allFrequencies + } + + self.repeatCounter = 0 + self.firstPreviousValue = "" + self.goertzel = new Goertzel(frequencyData,samplerate,threshold) + + self.processBin = function(bin){ + var value = "" + var register = self.generateFrequencyRegister() + // Downsample by decimation(choosing every Nth sample). + for ( var i=0; i< bin.length; i+=decimation ) { + floatSample = bin[i] * 32768 ; + if ( floatSample > 32767 ) { + floatSample = 32767 + } else if (floatSample < -32786) { + floatSample = -32768; + } + + intSample = Math.round(floatSample) + register.sample = intSample + + register = self.goertzel.getEnergyFromSample(register) + value = self.goertzel.energyProfileToCharacter(register) + } + if (value == self.firstPreviousValue && value != undefined ){ + self.repeatCounter+=1 + if (self.repeatCounter == 4 && typeof this.onDecode === "function"){ + // outputElement.innerHTML = outputElement.innerHTML + value + setTimeout(this.onDecode(value), 1); + } + } else { + self.repeatCounter = 0 + self.firstPreviousValue = value + } + + } + + self.generateFrequencyRegister = function(){ + var register = { + firstPrevious: {}, + secondPrevious: {}, + totalPower: {}, + filterLength: {}, + sample: 0, + energies: {}, + rememberSample: function(sample,frequency){ + this.secondPrevious[frequency] = this.firstPrevious[frequency] + this.firstPrevious[frequency] = sample + } + } + for ( var i=0; i< allFrequencies.length; i++ ){ + var frequency = allFrequencies[i] + register.firstPrevious[frequency] = 0.0 + register.secondPrevious[frequency] = 0.0 + register.totalPower[frequency] = 0.0 + register.filterLength[frequency] = 0.0 + register.energies[frequency] = 0.0 + } + return register + } + + self.refresh = function(){ + self.goertzel.refresh() + } + +} diff --git a/goertzel.js b/goertzel.js new file mode 100644 index 00000000..96e6d301 --- /dev/null +++ b/goertzel.js @@ -0,0 +1,85 @@ +function Goertzel(frequencyData,samplerate,threshold){ + var self = this + self.threshold = threshold + self.samplerate = samplerate + self.frequencyTable = frequencyData.frequencyTable + self.lowFrequencies = frequencyData.lowFrequencies + self.highFrequencies = frequencyData.highFrequencies + self.allFrequencies = frequencyData.allFrequencies + self.firstPrevious = {} + self.secondPrevious = {} + self.totalPower = {} + self.filterLength = {} + self.coefficient = {} + + for ( var i=0; i< self.allFrequencies.length; i++ ){ + var frequency = self.allFrequencies[i] + normalizedFrequency = frequency / self.samplerate + self.coefficient[frequency] = 2.0*Math.cos(2.0 * 3.14 * normalizedFrequency) + } + + self.refresh = function(){ + self.firstPrevious = {} + self.secondPrevious = {} + self.totalPower = {} + self.filterLength = {} + + for ( var i=0; i< self.allFrequencies.length; i++ ){ + var frequency = self.allFrequencies[i] + self.firstPrevious[frequency] = 0.0 + self.secondPrevious[frequency] = 0.0 + self.totalPower[frequency] = 0.0 + self.filterLength[frequency] = 0.0 + } + } + + self.energyProfileToCharacter = function(register){ + var energies = register.energies + var highFrequency = 0.0 + var highFrequencyEngergy = 0.0 + + for (var i=0; i highFrequencyEngergy && energies[f] > self.threshold){ + highFrequencyEngergy = energies[f] + highFrequency = f + } + } + + var lowFrequency = 0.0 + var lowFrequencyEnergy = 0.0 + + for (var i=0; i lowFrequencyEnergy && energies[f] > self.threshold){ + lowFrequencyEnergy = energies[f] + lowFrequency = f + } + } + register = null + delete register + if (self.frequencyTable[lowFrequency] != undefined){ + return self.frequencyTable[lowFrequency][highFrequency] || null + } + + } + + self.getEnergyFromSample = function(register){ + for ( var i=0; i< self.allFrequencies.length; i++ ){ + var frequency = self.allFrequencies[i] + sine = register.sample + (self.coefficient[frequency] * register.firstPrevious[frequency]) - register.secondPrevious[frequency] + register.rememberSample(sine,frequency) + register.filterLength[frequency]+=1 + power = (register.secondPrevious[frequency]*register.secondPrevious[frequency]) + (register.firstPrevious[frequency]*register.firstPrevious[frequency]) - (self.coefficient[frequency]*register.firstPrevious[frequency]*register.secondPrevious[frequency]) + register.totalPower[frequency]+=register.sample*register.sample + if(register.totalPower[frequency] == 0){ + register.totalPower[frequency] = 1 + } + register.energies[frequency] = power / register.totalPower[frequency] / register.filterLength[frequency] + } + return register + } + + self.refresh() + +}