Skip to content

Commit

Permalink
feat: implement BiquadFilterOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisguttandin committed Mar 26, 2018
1 parent 812f36d commit 853ce3d
Show file tree
Hide file tree
Showing 7 changed files with 772 additions and 195 deletions.
21 changes: 14 additions & 7 deletions src/audio-nodes/biquad-filter-node.ts
@@ -1,5 +1,7 @@
import { Injector } from '@angular/core';
import { INVALID_ACCES_ERROR_FACTORY_PROVIDER, InvalidAccessErrorFactory } from '../factories/invalid-access-error';
import { AUDIO_NODE_RENDERER_STORE } from '../globals';
import { createNativeBiquadFilterNode } from '../helpers/create-native-biquad-filter-node';
import { getNativeContext } from '../helpers/get-native-context';
import { isOfflineAudioContext } from '../helpers/is-offline-audio-context';
import { IAudioParam, IBiquadFilterNode, IBiquadFilterOptions, IMinimalBaseAudioContext } from '../interfaces';
Expand All @@ -10,11 +12,13 @@ import { NoneAudioDestinationNode } from './none-audio-destination-node';

const injector = Injector.create({
providers: [
AUDIO_PARAM_WRAPPER_PROVIDER
AUDIO_PARAM_WRAPPER_PROVIDER,
INVALID_ACCES_ERROR_FACTORY_PROVIDER
]
});

const audioParamWrapper = injector.get(AudioParamWrapper);
const invalidAccessErrorFactory = injector.get(InvalidAccessErrorFactory);

const DEFAULT_OPTIONS: IBiquadFilterOptions = {
Q: 1,
Expand All @@ -31,12 +35,10 @@ export class BiquadFilterNode extends NoneAudioDestinationNode<TNativeBiquadFilt

constructor (context: IMinimalBaseAudioContext, options: Partial<IBiquadFilterOptions> = DEFAULT_OPTIONS) {
const nativeContext = getNativeContext(context);
const { channelCount } = <IBiquadFilterOptions> { ...DEFAULT_OPTIONS, ...options };
const nativeNode = nativeContext.createBiquadFilter();
const mergedOptions = <IBiquadFilterOptions> { ...DEFAULT_OPTIONS, ...options };
const nativeNode = createNativeBiquadFilterNode(nativeContext, mergedOptions);

super(context, nativeNode, channelCount);

// @todo Set values for Q, detune, frequency and gain.
super(context, nativeNode, mergedOptions.channelCount);

if (isOfflineAudioContext(nativeContext)) {
const biquadFilterNodeRenderer = new BiquadFilterNodeRenderer(this);
Expand Down Expand Up @@ -75,7 +77,12 @@ export class BiquadFilterNode extends NoneAudioDestinationNode<TNativeBiquadFilt
}

public getFrequencyResponse (frequencyHz: Float32Array, magResponse: Float32Array, phaseResponse: Float32Array) {
return this._nativeNode.getFrequencyResponse(frequencyHz, magResponse, phaseResponse);
this._nativeNode.getFrequencyResponse(frequencyHz, magResponse, phaseResponse);

// Bug #68: Chrome does not throw an error if the parameters differ in their length.
if ((frequencyHz.length !== magResponse.length) || (magResponse.length !== phaseResponse.length)) {
throw invalidAccessErrorFactory.create();
}
}

}
34 changes: 34 additions & 0 deletions src/helpers/create-native-biquad-filter-node.ts
@@ -0,0 +1,34 @@
import { assignNativeAudioNodeOptions } from '../helpers/assign-native-audio-node-options';
import { IBiquadFilterOptions } from '../interfaces';
import { TNativeBiquadFilterNode, TUnpatchedAudioContext, TUnpatchedOfflineAudioContext } from '../types';

export const createNativeBiquadFilterNode = (
nativeContext: TUnpatchedAudioContext | TUnpatchedOfflineAudioContext,
options: Partial<IBiquadFilterOptions> = { }
): TNativeBiquadFilterNode => {
const nativeNode = nativeContext.createBiquadFilter();

assignNativeAudioNodeOptions(nativeNode, options);

if (options.detune !== undefined) {
nativeNode.detune.value = options.detune;
}

if (options.frequency !== undefined) {
nativeNode.frequency.value = options.frequency;
}

if (options.gain !== undefined) {
nativeNode.gain.value = options.gain;
}

if (options.type !== undefined) {
nativeNode.type = options.type;
}

if (options.Q !== undefined) {
nativeNode.Q.value = options.Q;
}

return nativeNode;
};
16 changes: 16 additions & 0 deletions test/expectation/chrome/current/audio-context-constructor.js
Expand Up @@ -52,6 +52,22 @@ describe('audioContextConstructor', () => {

});

describe('createBiquadFilter()', () => {

describe('getFrequencyResponse()', () => {

// bug #68

it('should throw no error', () => {
const biquadFilterNode = audioContext.createBiquadFilter();

biquadFilterNode.getFrequencyResponse(new Float32Array(), new Float32Array(1), new Float32Array(1));
});

});

});

describe('createBufferSource()', () => {

describe('stop()', () => {
Expand Down
Expand Up @@ -30,6 +30,22 @@ describe('offlineAudioContextConstructor', () => {

});

describe('createBiquadFilter()', () => {

describe('getFrequencyResponse()', () => {

// bug #68

it('should throw no error', () => {
const biquadFilterNode = offlineAudioContext.createBiquadFilter();

biquadFilterNode.getFrequencyResponse(new Float32Array(), new Float32Array(1), new Float32Array(1));
});

});

});

describe('createBufferSource()', () => {

// bug #14
Expand Down
104 changes: 2 additions & 102 deletions test/unit/audio-contexts/audio-context.js
Expand Up @@ -379,108 +379,8 @@ describe('AudioContext', () => {

describe('createBiquadFilter()', () => {

it('should return an instance of the BiquadFilterNode interface', () => {
const biquadFilterNode = audioContext.createBiquadFilter();

expect(biquadFilterNode.channelCount).to.equal(2);
expect(biquadFilterNode.channelCountMode).to.equal('max');
expect(biquadFilterNode.channelInterpretation).to.equal('speakers');

expect(biquadFilterNode.detune.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.detune.defaultValue).to.equal(0);
expect(biquadFilterNode.detune.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.detune.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.detune.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.detune.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.detune.value).to.equal(0);

expect(biquadFilterNode.frequency.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.frequency.defaultValue).to.equal(350);
expect(biquadFilterNode.frequency.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.value).to.equal(350);

expect(biquadFilterNode.gain.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.gain.defaultValue).to.equal(0);
expect(biquadFilterNode.gain.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.gain.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.gain.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.gain.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.gain.value).to.equal(0);

expect(biquadFilterNode.getFrequencyResponse).to.be.a('function');
expect(biquadFilterNode.numberOfInputs).to.equal(1);
expect(biquadFilterNode.numberOfOutputs).to.equal(1);

expect(biquadFilterNode.Q.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.Q.defaultValue).to.equal(1);
expect(biquadFilterNode.Q.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.Q.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.Q.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.Q.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.Q.value).to.equal(1);

expect(biquadFilterNode.type).to.be.a('string');
});

it('should throw an error if the AudioContext is closed', (done) => {
audioContext
.close()
.then(() => {
audioContext.createBiquadFilter();
})
.catch((err) => {
expect(err.code).to.equal(11);
expect(err.name).to.equal('InvalidStateError');

audioContext = new AudioContext();

done();
});
});

it('should be chainable', () => {
const biquadFilterNode = audioContext.createBiquadFilter();
const gainNode = audioContext.createGain();

expect(biquadFilterNode.connect(gainNode)).to.equal(gainNode);
});

it('should not be connectable to a node of another AudioContext', (done) => {
const anotherAudioContext = new AudioContext();
const biquadFilterNode = audioContext.createBiquadFilter();

try {
biquadFilterNode.connect(anotherAudioContext.destination);
} catch (err) {
expect(err.code).to.equal(15);
expect(err.name).to.equal('InvalidAccessError');

done();
} finally {
anotherAudioContext.close();
}
});

describe('getFrequencyResponse()', () => {

// bug #22 This is not yet implemented in Edge and Safari.

/*
* it('should fill the magResponse and phaseResponse arrays', () => {
* const biquadFilterNode = audioContext.createBiquadFilter();
* const magResponse = new Float32Array(5);
* const phaseResponse = new Float32Array(5);
*
* biquadFilterNode.getFrequencyResponse(new Float32Array([ 200, 400, 800, 1600, 3200 ]), magResponse, phaseResponse);
*
* expect(Array.from(magResponse)).to.deep.equal([ 1.184295654296875, 0.9401244521141052, 0.2128090262413025, 0.048817940056324005, 0.011635963805019855 ]);
* expect(Array.from(phaseResponse)).to.deep.equal([ -0.6473332643508911, -1.862880825996399, -2.692772388458252, -2.9405176639556885, -3.044968605041504 ]);
* });
*/

it('should be a function', () => {
expect(audioContext.createBiquadFilter).to.be.a('function');
});

});
Expand Down
88 changes: 2 additions & 86 deletions test/unit/audio-contexts/offline-audio-context.js
Expand Up @@ -363,92 +363,8 @@ describe('OfflineAudioContext', () => {
offlineAudioContext = new OfflineAudioContext({ length: 1, sampleRate: 44100 });
});

it('should return an instance of the BiquadFilterNode interface', () => {
const biquadFilterNode = offlineAudioContext.createBiquadFilter();

expect(biquadFilterNode.channelCount).to.equal(2);
expect(biquadFilterNode.channelCountMode).to.equal('max');
expect(biquadFilterNode.channelInterpretation).to.equal('speakers');

expect(biquadFilterNode.detune.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.detune.defaultValue).to.equal(0);
expect(biquadFilterNode.detune.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.detune.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.detune.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.detune.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.detune.value).to.equal(0);

expect(biquadFilterNode.frequency.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.frequency.defaultValue).to.equal(350);
expect(biquadFilterNode.frequency.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.frequency.value).to.equal(350);

expect(biquadFilterNode.gain.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.gain.defaultValue).to.equal(0);
expect(biquadFilterNode.gain.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.gain.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.gain.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.gain.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.gain.value).to.equal(0);

expect(biquadFilterNode.getFrequencyResponse).to.be.a('function');
expect(biquadFilterNode.numberOfInputs).to.equal(1);
expect(biquadFilterNode.numberOfOutputs).to.equal(1);

expect(biquadFilterNode.Q.cancelScheduledValues).to.be.a('function');
expect(biquadFilterNode.Q.defaultValue).to.equal(1);
expect(biquadFilterNode.Q.exponentialRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.Q.linearRampToValueAtTime).to.be.a('function');
expect(biquadFilterNode.Q.setTargetAtTime).to.be.a('function');
expect(biquadFilterNode.Q.setValueCurveAtTime).to.be.a('function');
expect(biquadFilterNode.Q.value).to.equal(1);

expect(biquadFilterNode.type).to.be.a('string');
});

// it('should throw an error if the AudioContext is closed', () => {});

it('should be chainable', () => {
const biquadFilterNode = offlineAudioContext.createBiquadFilter();
const gainNode = offlineAudioContext.createGain();

expect(biquadFilterNode.connect(gainNode)).to.equal(gainNode);
});

it('should not be connectable to a node of another AudioContext', (done) => {
const anotherOfflineAudioContext = new OfflineAudioContext(2, 129, 44100);
const biquadFilterNode = offlineAudioContext.createBiquadFilter();

try {
biquadFilterNode.connect(anotherOfflineAudioContext.destination);
} catch (err) {
expect(err.code).to.equal(15);
expect(err.name).to.equal('InvalidAccessError');

done();
}
});

describe('getFrequencyResponse()', () => {

// bug #22 This is not yet implemented in Edge and Safari.

/*
* it('should fill the magResponse and phaseResponse arrays', () => {
* const biquadFilterNode = offlineAudioContext.createBiquadFilter();
* const magResponse = new Float32Array(5);
* const phaseResponse = new Float32Array(5);
*
* biquadFilterNode.getFrequencyResponse(new Float32Array([ 200, 400, 800, 1600, 3200 ]), magResponse, phaseResponse);
*
* expect(Array.from(magResponse)).to.deep.equal([ 1.184295654296875, 0.9401244521141052, 0.2128090262413025, 0.048817940056324005, 0.011635963805019855 ]);
* expect(Array.from(phaseResponse)).to.deep.equal([ -0.6473332643508911, -1.862880825996399, -2.692772388458252, -2.9405176639556885, -3.044968605041504 ]);
* });
*/

it('should be a function', () => {
expect(offlineAudioContext.createBiquadFilter).to.be.a('function');
});

});
Expand Down

0 comments on commit 853ce3d

Please sign in to comment.