Skip to content

Commit

Permalink
feat: implement buffer singulartiy
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisguttandin committed Mar 28, 2018
1 parent f5b6c5a commit c8392cb
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 17 deletions.
38 changes: 35 additions & 3 deletions src/audio-nodes/audio-buffer-source-node.ts
@@ -1,4 +1,5 @@
import { Injector } from '@angular/core';
import { INVALID_STATE_ERROR_FACTORY_PROVIDER, InvalidStateErrorFactory } from '../factories/invalid-state-error';
import { AUDIO_NODE_RENDERER_STORE } from '../globals';
import { createNativeAudioBufferSourceNode } from '../helpers/create-native-audio-buffer-source-node';
import { getNativeContext } from '../helpers/get-native-context';
Expand All @@ -11,11 +12,13 @@ import { NoneAudioDestinationNode } from './none-audio-destination-node';

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

const audioParamWrapper = injector.get(AudioParamWrapper);
const invalidStateErrorFactory = injector.get(InvalidStateErrorFactory);

const DEFAULT_OPTIONS: IAudioBufferSourceOptions = {
buffer: null,
Expand All @@ -31,6 +34,10 @@ const DEFAULT_OPTIONS: IAudioBufferSourceOptions = {

export class AudioBufferSourceNode extends NoneAudioDestinationNode<TNativeAudioBufferSourceNode> implements IAudioBufferSourceNode {

private _isBufferNullified: boolean;

private _isBufferSet: boolean;

constructor (context: IMinimalBaseAudioContext, options: Partial<IAudioBufferSourceOptions> = DEFAULT_OPTIONS) {
const nativeContext = getNativeContext(context);
const mergedOptions = <IAudioBufferSourceOptions> { ...DEFAULT_OPTIONS, ...options };
Expand All @@ -46,15 +53,40 @@ export class AudioBufferSourceNode extends NoneAudioDestinationNode<TNativeAudio
audioParamWrapper.wrap(nativeNode, context, 'detune');
audioParamWrapper.wrap(nativeNode, context, 'playbackRate');
}

this._isBufferNullified = false;
this._isBufferSet = false;
}

public get buffer () {
if (this._isBufferNullified) {
return null;
}

return this._nativeNode.buffer;
}

public set buffer (value) {
// @todo Allow to set the buffer only once.
this._nativeNode.buffer = value;
// Bug #71: Edge does not allow to set the buffer to null.
try {
this._nativeNode.buffer = value;
} catch (err) {
if (value !== null || err.code !== 17) {
throw err;
}

// @todo Create a new internal nativeNode.
this._isBufferNullified = (this._nativeNode.buffer !== null);
}

// Bug #72: Only Chrome, Edge & Opera do not allow to reassign the buffer yet.
if (value !== null) {
if (this._isBufferSet) {
throw invalidStateErrorFactory.create();
}

this._isBufferSet = true;
}
}

public get onended () {
Expand Down
8 changes: 4 additions & 4 deletions src/helpers/create-native-audio-buffer-source-node.ts
Expand Up @@ -21,8 +21,8 @@ import {
} from '../support-testers/audio-scheduled-source-node-stop-method-negative-parameters';
import { TNativeAudioBufferSourceNode, TUnpatchedAudioContext, TUnpatchedOfflineAudioContext } from '../types';
import {
AUDIO_SCHEDULED_SOURCE_NODE_START_METHOD_CONSECUTIVE_CALLS_WRAPPER_PROVIDER,
AudioScheduledSourceNodeStartMethodConsecutiveCallsWrapper
AUDIO_BUFFER_SOURCE_NODE_START_METHOD_CONSECUTIVE_CALLS_WRAPPER_PROVIDER,
AudioBufferSourceNodeStartMethodConsecutiveCallsWrapper
} from '../wrappers/audio-buffer-source-node-start-method-consecutive-calls';
import {
AUDIO_SCHEDULED_SOURCE_NODE_START_METHOD_NEGATIVE_PARAMETERS_WRAPPER_PROVIDER,
Expand All @@ -40,7 +40,7 @@ import {
const injector = Injector.create({
providers: [
AUDIO_BUFFER_SOURCE_NODE_START_METHOD_CONSECUTIVE_CALLS_SUPPORT_TESTER_PROVIDER,
AUDIO_SCHEDULED_SOURCE_NODE_START_METHOD_CONSECUTIVE_CALLS_WRAPPER_PROVIDER,
AUDIO_BUFFER_SOURCE_NODE_START_METHOD_CONSECUTIVE_CALLS_WRAPPER_PROVIDER,
AUDIO_SCHEDULED_SOURCE_NODE_START_METHOD_NEGATIVE_PARAMETERS_SUPPORT_TESTER_PROVIDER,
AUDIO_SCHEDULED_SOURCE_NODE_START_METHOD_NEGATIVE_PARAMETERS_WRAPPER_PROVIDER,
AUDIO_SCHEDULED_SOURCE_NODE_STOP_METHOD_CONSECUTIVE_CALLS_SUPPORT_TESTER_PROVIDER,
Expand All @@ -53,7 +53,7 @@ const injector = Injector.create({

const startMethodConsecutiveCallsSupportTester = injector.get(AudioBufferSourceNodeStartMethodConsecutiveCallsSupportTester);
const startMethodConsecutiveCallsWrapper = injector
.get<AudioScheduledSourceNodeStartMethodConsecutiveCallsWrapper>(AudioScheduledSourceNodeStartMethodConsecutiveCallsWrapper);
.get<AudioBufferSourceNodeStartMethodConsecutiveCallsWrapper>(AudioBufferSourceNodeStartMethodConsecutiveCallsWrapper);
const startMethodNegativeParametersSupportTester = injector.get(AudioScheduledSourceNodeStartMethodNegativeParametersSupportTester);
const startMethodNegativeParametersWrapper = injector.get(AudioScheduledSourceNodeStartMethodNegativeParametersWrapper);
const stopMethodConsecutiveCallsSupportTester = injector.get(AudioScheduledSourceNodeStopMethodConsecutiveCallsSupportTester);
Expand Down
@@ -1,32 +1,31 @@
import { Injectable } from '@angular/core';
import { InvalidStateErrorFactory } from '../factories/invalid-state-error';
import { INativeConstantSourceNode } from '../interfaces';
import { TNativeAudioBufferSourceNode, TNativeOscillatorNode } from '../types';
import { TNativeAudioBufferSourceNode } from '../types';

@Injectable()
export class AudioScheduledSourceNodeStartMethodConsecutiveCallsWrapper {
export class AudioBufferSourceNodeStartMethodConsecutiveCallsWrapper {

constructor (private _invalidStateErrorFactory: InvalidStateErrorFactory) { }

public wrap (audioScheduledSourceNode: TNativeAudioBufferSourceNode | INativeConstantSourceNode | TNativeOscillatorNode) {
audioScheduledSourceNode.start = ((start) => {
public wrap (audioBufferSourceNode: TNativeAudioBufferSourceNode) {
audioBufferSourceNode.start = ((start) => {
let isScheduled = false;

return (when = 0, offset = 0, duration?: number) => {
if (isScheduled) {
throw this._invalidStateErrorFactory.create();
}

start.call(audioScheduledSourceNode, when, offset, duration);
start.call(audioBufferSourceNode, when, offset, duration);

isScheduled = true;
};
})(audioScheduledSourceNode.start);
})(audioBufferSourceNode.start);
}

}

export const AUDIO_SCHEDULED_SOURCE_NODE_START_METHOD_CONSECUTIVE_CALLS_WRAPPER_PROVIDER = {
export const AUDIO_BUFFER_SOURCE_NODE_START_METHOD_CONSECUTIVE_CALLS_WRAPPER_PROVIDER = {
deps: [ InvalidStateErrorFactory ],
provide: AudioScheduledSourceNodeStartMethodConsecutiveCallsWrapper
provide: AudioBufferSourceNodeStartMethodConsecutiveCallsWrapper
};
13 changes: 13 additions & 0 deletions test/expectation/firefox/any/audio-context-constructor.js
Expand Up @@ -124,6 +124,19 @@ describe('audioContextConstructor', () => {

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

describe('buffer', () => {

// bug #72

it('should allow to assign the buffer multiple times', () => {
const bufferSourceNode = audioContext.createBufferSource();

bufferSourceNode.buffer = audioContext.createBuffer(2, 100, 44100);
bufferSourceNode.buffer = audioContext.createBuffer(2, 100, 44100);
});

});

describe('playbackRate', () => {

// bug #45
Expand Down
13 changes: 13 additions & 0 deletions test/expectation/firefox/any/offline-audio-context-constructor.js
Expand Up @@ -55,6 +55,19 @@ describe('offlineAudioContextConstructor', () => {

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

describe('buffer', () => {

// bug #72

it('should allow to assign the buffer multiple times', () => {
const bufferSourceNode = offlineAudioContext.createBufferSource();

bufferSourceNode.buffer = offlineAudioContext.createBuffer(2, 100, 44100);
bufferSourceNode.buffer = offlineAudioContext.createBuffer(2, 100, 44100);
});

});

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

// bug #44
Expand Down
13 changes: 13 additions & 0 deletions test/expectation/safari/audio-context-constructor.js
Expand Up @@ -208,6 +208,19 @@ describe('audioContextConstructor', () => {
audioBufferSourceNode.stop();
});

describe('buffer', () => {

// bug #72

it('should allow to assign the buffer multiple times', () => {
const bufferSourceNode = audioContext.createBufferSource();

bufferSourceNode.buffer = audioContext.createBuffer(2, 100, 44100);
bufferSourceNode.buffer = audioContext.createBuffer(2, 100, 44100);
});

});

describe('playbackRate', () => {

// bug #45
Expand Down
13 changes: 13 additions & 0 deletions test/expectation/safari/offline-audio-context-constructor.js
Expand Up @@ -262,6 +262,19 @@ describe('offlineAudioContextConstructor', () => {
offlineAudioContext.startRendering();
});

describe('buffer', () => {

// bug #72

it('should allow to assign the buffer multiple times', () => {
const bufferSourceNode = offlineAudioContext.createBufferSource();

bufferSourceNode.buffer = offlineAudioContext.createBuffer(2, 100, 44100);
bufferSourceNode.buffer = offlineAudioContext.createBuffer(2, 100, 44100);
});

});

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

// bug #44
Expand Down
45 changes: 44 additions & 1 deletion test/unit/audio-nodes/audio-buffer-source-node.js
Expand Up @@ -287,7 +287,50 @@ describe('AudioBufferSourceNode', () => {

describe('buffer', () => {

// @todo
let audioBufferSourceNode;

beforeEach(() => {
audioBufferSourceNode = createAudioBufferSourceNode(context);
});

describe('without a previously assigned AudioBuffer', () => {

it('should be assignable to an AudioBuffer', () => {
const audioBuffer = new AudioBuffer({ length: 5, sampleRate: context.sampleRate });

audioBufferSourceNode.buffer = audioBuffer;

expect(audioBufferSourceNode.buffer).to.equal(audioBuffer);
});

});

describe('with a previously assigned AudioBuffer', () => {

beforeEach(() => {
audioBufferSourceNode.buffer = new AudioBuffer({ length: 5, sampleRate: context.sampleRate });
});

it('should throw an InvalidStateError', (done) => {
const audioBuffer = new AudioBuffer({ length: 5, sampleRate: context.sampleRate });

try {
audioBufferSourceNode.buffer = audioBuffer;
} catch (err) {
expect(err.code).to.equal(11);
expect(err.name).to.equal('InvalidStateError');

done();
}
});

it('should be assignable to null', () => {
audioBufferSourceNode.buffer = null;

expect(audioBufferSourceNode.buffer).to.be.null;
});

});

});

Expand Down

0 comments on commit c8392cb

Please sign in to comment.