Skip to content

Commit

Permalink
Add the ability to calculate current phase.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Titcomb committed Apr 11, 2018
1 parent 51521c4 commit a360871
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 17 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
29 changes: 22 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
/**
Expand Down Expand Up @@ -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<len){
frequency = frequencies[i];
normalizedFrequency = frequency / this.sampleRate;
this.coefficient[frequency] = 2.0 * cos(2.0 * PI * normalizedFrequency);
omega = 2.0 * PI * normalizedFrequency;
cosine = cos(omega);
this.sine[frequency] = sin(omega);
this.cosine[frequency] = cosine;
this.coefficient[frequency] = 2.0 * cosine;
i++;
}
}
Expand Down
9 changes: 3 additions & 6 deletions lib/dtmf.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ class DTMF {
// Downsample by choosing every Nth sample.
Goertzel.Utilities.eachDownsample(buffer, this.options.downsampleRate, (sample,i,downSampledBufferLength)=> {
let windowedSample = Goertzel.Utilities.exactBlackman(sample, i, downSampledBufferLength);
return this.goertzel.processSample(windowedSample);
}
);
this.goertzel.processSample(windowedSample);
});
let energies = {
high: [],
low: []
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down

0 comments on commit a360871

Please sign in to comment.