From a3608717c5deb7adfdc6ffbdbe2dc59aea4fdadf Mon Sep 17 00:00:00 2001 From: Ben Titcomb Date: Tue, 10 Apr 2018 17:27:24 -0700 Subject: [PATCH] Add the ability to calculate current phase. --- README.md | 16 ++++++++++++++++ index.js | 29 ++++++++++++++++++++++------- lib/dtmf.js | 9 +++------ lib/util.js | 6 +++--- package.json | 2 +- 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 56ace8fd..159bc609 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,22 @@ You would then look at each of the frequency keys under the `goertzel` object's The samplerate should be the sample rate of whatever sample buffers are being given to the goertzel object. Most of the time this is either 44100 or 48000 hz. This can be set as high or as low as necessary, though higher samplerates will create more overhead. Consider downsampling your audio for faster processing time. See dtmf.js on how samples can be downsampled. +It is now possible to calculate the phase of a given set of samples. Because this is a less common use of Goertzel, this feature is turned off by default. To enable it, set `options.getPhase` to true; + +```javascript + const goertzel = new Goertzel({ + frequencies: [697,770,852,941], + getPhase: true + }); + + buffer.forEach(function(sample){ + goertzel.processSample(sample); + }); + + goertzel.phases; + + // { 697: [Number], 770: [Number], 852: [Number], 941: [Number], } +``` #### Testing Tests are written with Mocha. To perform the tests, simply run `npm run test`. diff --git a/index.js b/index.js index 37abd3b7..219f6fcc 100644 --- a/index.js +++ b/index.js @@ -2,19 +2,21 @@ const GOERTZEL_ATTRIBUTES = ['firstPrevious', 'secondPrevious', 'totalPower', 'filterLength', 'energies', 'phases'], GOERTZEL_ATTRIBUTES_LENGTH = GOERTZEL_ATTRIBUTES.length, - { cos, PI } = Math; + { atan2, cos, sin, PI } = Math; /** * A pure JavaScript implementation of the Goertzel algorithm, a means of efficient DFT signal processing. - * @param {object} [options={}] - * @param {array} options.frequencies - The frequencies to be processed. - * @param {number=44100} options.sampleRate - The sample rate of the samples to be processed. Defaults to 44100. + * @param {object} options + * @param {array} options.frequencies - The frequencies to be processed. + * @param {number=44100} options.sampleRate - The sample rate of the samples to be processed. Defaults to 44100. + * @param {boolean=false} options.getPhase - Calculates the current phase of each frequency. Disabled by default. */ class Goertzel { constructor(options={}) { + this.options = options; this.sampleRate = options.sampleRate || 44100; this.frequencies = options.frequencies || []; - this._initializeCoefficients(this.frequencies); + this._initializeConstants(this.frequencies); this.refresh(); } /** @@ -72,20 +74,33 @@ class Goertzel { totalPower = this.totalPower[frequency] += sample * sample; if (totalPower === 0) this.totalPower[frequency] = 1; this.energies[frequency] = power / totalPower / this.filterLength[frequency]; + if(this.options.getPhase) { + let real = (f1 - f2 * this.cosine[frequency]), + imaginary = (f2 * this.sine[frequency]); + this.phases[frequency] = atan2(imaginary, real); + } this.firstPrevious[frequency] = f1; this.secondPrevious[frequency] = f2; } - _initializeCoefficients(frequencies) { + _initializeConstants(frequencies) { const len = frequencies.length; let frequency, normalizedFrequency, + omega, + cosine, i = 0; + this.sine = {}, + this.cosine = {}, this.coefficient = {}; while(i { let windowedSample = Goertzel.Utilities.exactBlackman(sample, i, downSampledBufferLength); - return this.goertzel.processSample(windowedSample); - } - ); + this.goertzel.processSample(windowedSample); + }); let energies = { high: [], low: [] @@ -138,9 +137,7 @@ class DTMF { lowFrequency = f; } } - if (this.frequencyTable[lowFrequency] !== undefined) { - return this.frequencyTable[lowFrequency][highFrequency] || null; - } + return this.frequencyTable[lowFrequency] ? this.frequencyTable[lowFrequency][highFrequency] : null; } _runJobs(jobName, buffer) { diff --git a/lib/util.js b/lib/util.js index 90ee8110..b6238a6d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -63,16 +63,16 @@ const Utilities = { return this.peakFilter(energies1, sensitivity) || this.peakFilter(energies2, sensitivity); }, - //# useful for testing purposes + // useful for testing purposes - generateSineBuffer(frequencies, sampleRate, numberOfSamples) { + generateSineBuffer(frequencies, sampleRate, numberOfSamples, phase=0) { let buffer = new Float32Array(numberOfSamples), volumePerSine = 1 / frequencies.length, i = 0; while (i < numberOfSamples) { let val = 0; for (let frequency of Array.from(frequencies)) { - val += (Math.sin(Math.PI * 2 * (i / sampleRate) * frequency) * volumePerSine); + val += (Math.sin(Math.PI * 2 * ((i + phase) / sampleRate) * frequency) * volumePerSine); } buffer[i] = val; i++; diff --git a/package.json b/package.json index 080398c7..2629ab8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "goertzeljs", - "version": "4.1.0", + "version": "4.2.0", "description": "A pure JavaScript implementation of the Goertzel algorithm.", "main": "index.js", "scripts": {