diff --git a/.gitignore b/.gitignore index 00cf1e97..e2542c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ /node_modules /bower_components -/spec/*.js .DS_Store *.log /demo/bundle.js \ No newline at end of file diff --git a/spec/dtmf.spec.js b/spec/dtmf.spec.js new file mode 100644 index 00000000..1c666afe --- /dev/null +++ b/spec/dtmf.spec.js @@ -0,0 +1,154 @@ +'use strict'; + +let DTMF = require('../src/dtmf'); +let Goertzel = require('../src/goertzel'); + +require('jasmine-expect'); + +describe('DTMF', function() { + let pairs = [ + { + low: 697, + high: 1209, + char: '1' + }, { + low: 697, + high: 1336, + char: '2' + }, { + low: 697, + high: 1477, + char: '3' + }, { + low: 697, + high: 1633, + char: 'A' + }, { + low: 770, + high: 1209, + char: '4' + }, { + low: 770, + high: 1336, + char: '5' + }, { + low: 770, + high: 1477, + char: '6' + }, { + low: 770, + high: 1633, + char: 'B' + }, { + low: 852, + high: 1209, + char: '7' + }, { + low: 852, + high: 1336, + char: '8' + }, { + low: 852, + high: 1477, + char: '9' + }, { + low: 852, + high: 1633, + char: 'C' + }, { + low: 941, + high: 1209, + char: '*' + }, { + low: 941, + high: 1336, + char: '0' + }, { + low: 941, + high: 1477, + char: '#' + }, { + low: 941, + high: 1633, + char: 'D' + } + ]; + + + describe('#processBuffer', function() { + it('identifies all dial tones', function() { + let buffer, dtmf, dualTone, i, len, pair, results, vals; + dtmf = new DTMF({ + sampleRate: 44100, + repeatMin: 0 + }); + results = []; + for (i = 0, len = pairs.length; i < len; i++) { + pair = pairs[i]; + dualTone = Goertzel.Utilities.generateSineBuffer([pair.low, pair.high], 44100, 512); + buffer = Goertzel.Utilities.floatBufferToInt(dualTone); + vals = dtmf.processBuffer(buffer); + results.push(expect(vals).toContain(pair.char)); + } + return results; + }); + + it('does not identify dial tones in noise', function() { + let dtmf, result; + dtmf = new DTMF({ + sampleRate: 44100, + peakFilterSensitivity: 1.4, + repeatMin: 6 + }); + dtmf.processBuffer(Goertzel.Utilities.generateWhiteNoiseBuffer(44100, 512)); + dtmf.processBuffer(Goertzel.Utilities.generateWhiteNoiseBuffer(44100, 512)); + dtmf.processBuffer(Goertzel.Utilities.generateWhiteNoiseBuffer(44100, 512)); + dtmf.processBuffer(Goertzel.Utilities.generateWhiteNoiseBuffer(44100, 512)); + dtmf.processBuffer(Goertzel.Utilities.generateWhiteNoiseBuffer(44100, 512)); + dtmf.processBuffer(Goertzel.Utilities.generateWhiteNoiseBuffer(44100, 512)); + result = dtmf.processBuffer(Goertzel.Utilities.generateWhiteNoiseBuffer(44100, 512)); + return expect(result).toBeEmptyArray(); + }); + }); + + + describe('#calibrate', function() { + it('alters the decibelThreshold', function() { + let dtmf; + dtmf = new DTMF({ + sampleRate: 44100, + peakFilterSensitivity: 1.4, + repeatMin: 6 + }); + expect(dtmf.options.decibelThreshold).toEqual(0); + dtmf.calibrate(); + dtmf.processBuffer([1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1]); + return expect(dtmf.options.decibelThreshold).toBeGreaterThan(0); + }); + }); + + + describe('decibelThreshold', function() { + it('allows a louder signal to pass', function() { + let dtmf, result; + dtmf = new DTMF({ + sampleRate: 44100, + repeatMin: 0, + decibelThreshold: 0 + }); + result = dtmf.processBuffer(Goertzel.Utilities.generateSineBuffer([pairs[0].low, pairs[0].high], 44100, 512)); + return expect(result).not.toBeEmptyArray(); + }); + + it('prevents a signal from passing', function() { + let dtmf, result; + dtmf = new DTMF({ + sampleRate: 44100, + repeatMin: 0, + decibelThreshold: 1000 + }); + result = dtmf.processBuffer(Goertzel.Utilities.generateSineBuffer([pairs[0].low, pairs[0].high], 44100, 512)); + return expect(result).toBeEmptyArray(); + }); + }); +}); diff --git a/spec/goertzel.spec.js b/spec/goertzel.spec.js new file mode 100644 index 00000000..5047ebd0 --- /dev/null +++ b/spec/goertzel.spec.js @@ -0,0 +1,66 @@ +'use strict'; + +let Goertzel = require('../src/goertzel'); + +describe('Goertzel', function() { + let allFrequencies, goertzel; + goertzel = void 0; + allFrequencies = [697, 852, 1209, 1477]; + + describe('#processSample', function() { + beforeEach(function() { + return goertzel = new Goertzel({ + frequencies: allFrequencies, + sampleRate: 8000, + threshold: 0 + }); + }); + it('biases towards the expected frequency', function() { + let buffer, f, frequency, i, j, k, len, len1, len2, results, sample; + results = []; + for (i = 0, len = allFrequencies.length; i < len; i++) { + frequency = allFrequencies[i]; + buffer = Goertzel.Utilities.generateSineBuffer([frequency], 8000, 2000); + for (j = 0, len1 = buffer.length; j < len1; j++) { + sample = buffer[j]; + goertzel.processSample(sample); + } + for (k = 0, len2 = allFrequencies.length; k < len2; k++) { + f = allFrequencies[k]; + if (f !== frequency) { + expect(goertzel.energies[f] < goertzel.energies[frequency]); + } + } + results.push(goertzel.refresh()); + } + return results; + }); + }); + + describe('::Utilities#peakFilter', function() { + it('rejects a bad signal', function() { + let badsignal; + badsignal = Goertzel.Utilities.peakFilter([1, 4, 65, 14, 11, 318, 0], 20); + expect(badsignal).toEqual(true); + }); + it('accepts a good signal', function() { + let goodsignal; + goodsignal = Goertzel.Utilities.peakFilter([0, 0, 1900, 0, 0, 0, 0], 20); + expect(goodsignal).toEqual(false); + }); + }); + + describe('::Utilities#blackman', function() { + it('performs window function on a sample', function() { + expect(Goertzel.Utilities.exactBlackman(233, 0, 400)).toEqual(1.6025740000000053); + expect(Goertzel.Utilities.exactBlackman(233, 1, 400)).toEqual(1.608012138277554); + expect(Goertzel.Utilities.exactBlackman(233, 80, 400)).toEqual(49.15691270548219); + }); + }); + + describe('::Utilities#floatToIntSample', function() { + it('converts a float32 sample to int16', function() { + expect(Goertzel.Utilities.floatToIntSample(0.0225)).toEqual(737); + }); + }); +});