diff --git a/src/enums/audio-context-state.ts b/src/enums/audio-context-state.ts deleted file mode 100644 index f29ee3253..000000000 --- a/src/enums/audio-context-state.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum AudioContextState { - 'closed', - 'running', - 'suspended' -} diff --git a/src/factories/data-clone-error.ts b/src/factories/data-clone-error.ts index 0b7d836ca..ed30b32e2 100644 --- a/src/factories/data-clone-error.ts +++ b/src/factories/data-clone-error.ts @@ -1,9 +1,11 @@ +import { IErrorFactory } from '../interfaces'; + // @todo Remove this declaration again if TypeScript supports the DOMException constructor. declare const DOMException: { new (message: string, name: string): DOMException; }; -export class DataCloneErrorFactory { +export class DataCloneErrorFactory implements IErrorFactory { public create () { try { diff --git a/src/factories/encoding-error.ts b/src/factories/encoding-error.ts index 6b7b8678d..b47036d20 100644 --- a/src/factories/encoding-error.ts +++ b/src/factories/encoding-error.ts @@ -1,9 +1,11 @@ +import { IErrorFactory } from '../interfaces'; + // @todo Remove this declaration again if TypeScript supports the DOMException constructor. declare const DOMException: { new (message: string, name: string): DOMException; }; -export class EncodingErrorFactory { +export class EncodingErrorFactory implements IErrorFactory { public create () { try { diff --git a/src/factories/index-size-error.ts b/src/factories/index-size-error.ts index 67dabc8b1..c40441ca6 100644 --- a/src/factories/index-size-error.ts +++ b/src/factories/index-size-error.ts @@ -1,9 +1,11 @@ +import { IErrorFactory } from '../interfaces'; + // @todo Remove this declaration again if TypeScript supports the DOMException constructor. declare const DOMException: { new (message: string, name: string): DOMException; }; -export class IndexSizeErrorFactory { +export class IndexSizeErrorFactory implements IErrorFactory { public create () { try { diff --git a/src/factories/invalid-access-error.ts b/src/factories/invalid-access-error.ts index 628ed7c7a..7e229f9ea 100644 --- a/src/factories/invalid-access-error.ts +++ b/src/factories/invalid-access-error.ts @@ -1,9 +1,11 @@ +import { IErrorFactory } from '../interfaces'; + // @todo Remove this declaration again if TypeScript supports the DOMException constructor. declare const DOMException: { new (message: string, name: string): DOMException; }; -export class InvalidAccessErrorFactory { +export class InvalidAccessErrorFactory implements IErrorFactory { public create () { try { diff --git a/src/factories/invalid-state-error.ts b/src/factories/invalid-state-error.ts index e25ddb5c1..c9ff47d47 100644 --- a/src/factories/invalid-state-error.ts +++ b/src/factories/invalid-state-error.ts @@ -1,9 +1,11 @@ +import { IErrorFactory } from '../interfaces'; + // @todo Remove this declaration again if TypeScript supports the DOMException constructor. declare const DOMException: { new (message: string, name: string): DOMException; }; -export class InvalidStateErrorFactory { +export class InvalidStateErrorFactory implements IErrorFactory { public create () { try { diff --git a/src/factories/not-supported-error.ts b/src/factories/not-supported-error.ts index a233ba2ae..b96d82dcd 100644 --- a/src/factories/not-supported-error.ts +++ b/src/factories/not-supported-error.ts @@ -1,9 +1,11 @@ +import { IErrorFactory } from '../interfaces'; + // @todo Remove this declaration again if TypeScript supports the DOMException constructor. declare const DOMException: { new (message: string, name: string): DOMException; }; -export class NotSupportedErrorFactory { +export class NotSupportedErrorFactory implements IErrorFactory { public create () { try { diff --git a/src/factories/offline-audio-buffer-source-node.ts b/src/factories/offline-audio-buffer-source-node.ts index 95c0f74dd..9ae3805ab 100644 --- a/src/factories/offline-audio-buffer-source-node.ts +++ b/src/factories/offline-audio-buffer-source-node.ts @@ -1,15 +1,32 @@ +import { IAudioBufferSourceNode, IAudioNode, IOfflineAudioContext, IOfflineAudioNodeFaker } from '../interfaces'; import { OfflineAudioNodeProxy } from '../offline-audio-node'; +import { TUnpatchedOfflineAudioContext } from '../types'; -class OfflineAudioBufferSourceNodeFakerProxy extends OfflineAudioNodeProxy { +export interface IOfflineAudioBufferSourceNodeFakerProxyOptions { - private _buffer; + fakeNodeStore: WeakMap; - private _ownFakeNodeStore; + offlineAudioContext: IOfflineAudioContext; - constructor ({ fakeNodeStore }) { - super({ fakeNodeStore }); +} + +export class OfflineAudioBufferSourceNodeFakerProxy extends OfflineAudioNodeProxy implements IAudioBufferSourceNode { + + private _buffer: null | AudioBuffer; + + private _ownFakeNodeStore: WeakMap; - this._buffer = undefined; + constructor ({ fakeNodeStore, offlineAudioContext }: IOfflineAudioBufferSourceNodeFakerProxyOptions) { + super({ + channelCountMode: 'max', + channelInterpretation: 'speakers', + fakeNodeStore, + numberOfInputs: 0, + numberOfOutputs: 1, + offlineAudioContext + }); + + this._buffer = null; this._ownFakeNodeStore = fakeNodeStore; } @@ -22,27 +39,150 @@ class OfflineAudioBufferSourceNodeFakerProxy extends OfflineAudioNodeProxy { this._buffer = value; } - public start (when = 0, offset = 0, duration) { - const faker = this._ownFakeNodeStore.get(this); + public get detune (): AudioParam { + // @todo Fake a proper AudioParam. + const audioParam = { + cancelScheduledValues: (startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + defaultValue: 0, + exponentialRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + linearRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setTargetAtTime: (target: number, startTime: number, timeConstant: number) => { + startTime; // tslint:disable-line:no-unused-expression + target; // tslint:disable-line:no-unused-expression + timeConstant; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueAtTime: (value: number, startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueCurveAtTime: (values: Float32Array, startTime: number, duration: number) => { + duration; // tslint:disable-line:no-unused-expression + startTime; // tslint:disable-line:no-unused-expression + values; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + value: 0 + }; + + return audioParam; + } + + public get loop () { + // @todo + return false; + } + + public get loopEnd () { + // @todo + return 0; + } + + public get loopStart () { + // @todo + return 0; + } + + public get playbackRate (): AudioParam { + // @todo Fake a proper AudioParam. + const audioParam = { + cancelScheduledValues: (startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + defaultValue: 1, + exponentialRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + linearRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setTargetAtTime: (target: number, startTime: number, timeConstant: number) => { + startTime; // tslint:disable-line:no-unused-expression + target; // tslint:disable-line:no-unused-expression + timeConstant; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueAtTime: (value: number, startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueCurveAtTime: (values: Float32Array, startTime: number, duration: number) => { + duration; // tslint:disable-line:no-unused-expression + startTime; // tslint:disable-line:no-unused-expression + values; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + value: 1 + }; + + return audioParam; + } + + public start (when = 0, offset = 0, duration?: number) { + const faker = this._ownFakeNodeStore.get(this); faker.start = { duration, offset, when }; } + public stop (when = 0) { + // @todo + + when; // tslint:disable-line:no-unused-expression + } + +} + +export interface IOfflineAudioBufferSourceNodeFakerOptions { + + fakeNodeStore: WeakMap; + + offlineAudioContext: IOfflineAudioContext; + } -export class OfflineAudioBufferSourceNodeFaker { +export class OfflineAudioBufferSourceNodeFaker implements IOfflineAudioNodeFaker { - private _node; + private _node: null | AudioBufferSourceNode; - private _proxy; + private _proxy: OfflineAudioBufferSourceNodeFakerProxy; - private _sources; + private _sources: Map; - private _start; + private _start: null | { duration?: number, offset: number, when: number }; - constructor ({ fakeNodeStore }) { + constructor ({ fakeNodeStore, offlineAudioContext }: IOfflineAudioBufferSourceNodeFakerOptions) { this._node = null; - this._proxy = new OfflineAudioBufferSourceNodeFakerProxy({ fakeNodeStore }); + this._proxy = new OfflineAudioBufferSourceNodeFakerProxy({ fakeNodeStore, offlineAudioContext }); this._sources = new Map(); this._start = null; @@ -53,11 +193,11 @@ export class OfflineAudioBufferSourceNodeFaker { return this._proxy; } - public set start (value) { + public set start (value: { duration?: number, offset: number, when: number }) { this._start = value; } - public render (offlineAudioContext) { + public render (offlineAudioContext: TUnpatchedOfflineAudioContext) { if (this._node !== null) { return Promise.resolve(this._node); } @@ -81,7 +221,7 @@ export class OfflineAudioBufferSourceNodeFaker { for (const [ source, { input, output } ] of this._sources) { promises.push(source .render(offlineAudioContext) - .then((node) => node.connect(this._node, output, input))); + .then((node) => node.connect( this._node, output, input))); } return Promise @@ -89,18 +229,30 @@ export class OfflineAudioBufferSourceNodeFaker { .then(() => this._node); } - public wire (source, output, input) { + public wire (source: IOfflineAudioNodeFaker, output: number, input: number) { this._sources.set(source, { input, output }); return this._proxy; } + public unwire (source: IOfflineAudioNodeFaker) { + this._sources.delete(source); + } + +} + +export interface IOfflineAudioBufferSourceNodeFakerFactoryOptions { + + fakeNodeStore: WeakMap; + + offlineAudioContext: IOfflineAudioContext; + } export class OfflineAudioBufferSourceNodeFakerFactory { - public create ({ fakeNodeStore }) { - return new OfflineAudioBufferSourceNodeFaker({ fakeNodeStore }); + public create ({ fakeNodeStore, offlineAudioContext }: IOfflineAudioBufferSourceNodeFakerFactoryOptions) { + return new OfflineAudioBufferSourceNodeFaker({ fakeNodeStore, offlineAudioContext }); } } diff --git a/src/factories/offline-audio-destination-node.ts b/src/factories/offline-audio-destination-node.ts index 86f674ccc..efa58b9ae 100644 --- a/src/factories/offline-audio-destination-node.ts +++ b/src/factories/offline-audio-destination-node.ts @@ -1,14 +1,26 @@ +import { IAudioDestinationNode, IAudioNode, IOfflineAudioContext, IOfflineAudioNodeFaker } from '../interfaces'; import { OfflineAudioNodeProxy } from '../offline-audio-node'; +import { TUnpatchedOfflineAudioContext } from '../types'; -class OfflineAudioDestinationNodeFakerProxy extends OfflineAudioNodeProxy { +export interface IOfflineAudioDestinationNodeFakerProxyOptions { - constructor ({ fakeNodeStore }) { + fakeNodeStore: WeakMap; + + offlineAudioContext: IOfflineAudioContext; + +} + +export class OfflineAudioDestinationNodeFakerProxy extends OfflineAudioNodeProxy implements IAudioDestinationNode { + + constructor ({ fakeNodeStore, offlineAudioContext }: IOfflineAudioDestinationNodeFakerProxyOptions) { super({ + channelCount: 2, channelCountMode: 'max', channelInterpretation: 'speakers', fakeNodeStore, numberOfInputs: 1, - numberOfOutputs: 0 + numberOfOutputs: 0, + offlineAudioContext }); } @@ -19,17 +31,25 @@ class OfflineAudioDestinationNodeFakerProxy extends OfflineAudioNodeProxy { } -export class OfflineAudioDestinationNodeFaker { +export interface IOfflineAudioDestinationNodeFakerOptions { + + fakeNodeStore: WeakMap; + + offlineAudioContext: IOfflineAudioContext; + +} - private _node; +export class OfflineAudioDestinationNodeFaker implements IOfflineAudioNodeFaker { - private _proxy; + private _node: null | AudioDestinationNode; - private _sources; + private _proxy: OfflineAudioDestinationNodeFakerProxy; - constructor ({ fakeNodeStore }) { + private _sources: Map; + + constructor ({ fakeNodeStore, offlineAudioContext }: IOfflineAudioDestinationNodeFakerOptions) { this._node = null; - this._proxy = new OfflineAudioDestinationNodeFakerProxy({ fakeNodeStore }); + this._proxy = new OfflineAudioDestinationNodeFakerProxy({ fakeNodeStore, offlineAudioContext }); this._sources = new Map(); fakeNodeStore.set(this._proxy, this); @@ -39,7 +59,7 @@ export class OfflineAudioDestinationNodeFaker { return this._proxy; } - public render (offlineAudioContext) { + public render (offlineAudioContext: TUnpatchedOfflineAudioContext) { if (this._node !== null) { return Promise.resolve(this._node); } @@ -51,7 +71,7 @@ export class OfflineAudioDestinationNodeFaker { for (const [ source, { input, output } ] of this._sources) { promises.push(source .render(offlineAudioContext) - .then((node) => node.connect(this._node, output, input))); + .then((node) => node.connect( this._node, output, input))); } return Promise @@ -59,18 +79,30 @@ export class OfflineAudioDestinationNodeFaker { .then(() => this._node); } - public wire (source, output, input) { + public wire (source: IOfflineAudioNodeFaker, output: number, input: number) { this._sources.set(source, { input, output }); return this._proxy; } + public unwire (source: IOfflineAudioNodeFaker) { + this._sources.delete(source); + } + +} + +export interface IOfflineAudioDestinationNodeFakerFactoryOptions { + + fakeNodeStore: WeakMap; + + offlineAudioContext: IOfflineAudioContext; + } export class OfflineAudioDestinationNodeFakerFactory { - public create ({ fakeNodeStore }) { - return new OfflineAudioDestinationNodeFaker({ fakeNodeStore }); + public create ({ fakeNodeStore, offlineAudioContext }: IOfflineAudioDestinationNodeFakerFactoryOptions) { + return new OfflineAudioDestinationNodeFaker({ fakeNodeStore, offlineAudioContext }); } } diff --git a/src/factories/offline-biquad-filter-node.ts b/src/factories/offline-biquad-filter-node.ts index 4b50aa917..a9c20b531 100644 --- a/src/factories/offline-biquad-filter-node.ts +++ b/src/factories/offline-biquad-filter-node.ts @@ -1,74 +1,223 @@ +import { IAudioNode, IBiquadFilterNode, IOfflineAudioContext, IOfflineAudioNodeFaker } from '../interfaces'; import { OfflineAudioNodeProxy } from '../offline-audio-node'; +import { TBiquadFilterType, TUnpatchedOfflineAudioContext } from '../types'; -class OfflineBiquadFilterNodeFakerProxy extends OfflineAudioNodeProxy { +export interface IOfflineBiquadFilterNodeFakerProxyOptions { - private _nativeNode; + fakeNodeStore: WeakMap; - private _type; + nativeNode: BiquadFilterNode; - constructor ({ fakeNodeStore, nativeNode }) { + offlineAudioContext: IOfflineAudioContext; + +} + +export class OfflineBiquadFilterNodeFakerProxy extends OfflineAudioNodeProxy implements IBiquadFilterNode { + + private _nativeNode: BiquadFilterNode; + + private _type: TBiquadFilterType; + + constructor ({ fakeNodeStore, nativeNode, offlineAudioContext }: IOfflineBiquadFilterNodeFakerProxyOptions) { super({ channelCountMode: 'max', channelInterpretation: 'speakers', fakeNodeStore, numberOfInputs: 1, - numberOfOutputs: 1 + numberOfOutputs: 1, + offlineAudioContext }); this._nativeNode = nativeNode; - this._type = nativeNode.type; + this._type = nativeNode.type; } public get detune () { // @todo Fake a proper AudioParam. - return { - cancelScheduledValues: () => {}, // tslint:disable-line:no-empty + const audioParam = { + cancelScheduledValues: (startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + + return audioParam; + }, defaultValue: 0, - exponentialRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - linearRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - setTargetAtTime: () => {}, // tslint:disable-line:no-empty - setValueCurveAtTime: () => {}, // tslint:disable-line:no-empty + exponentialRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + linearRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setTargetAtTime: (target: number, startTime: number, timeConstant: number) => { + startTime; // tslint:disable-line:no-unused-expression + target; // tslint:disable-line:no-unused-expression + timeConstant; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueAtTime: (value: number, startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueCurveAtTime: (values: Float32Array, startTime: number, duration: number) => { + duration; // tslint:disable-line:no-unused-expression + startTime; // tslint:disable-line:no-unused-expression + values; // tslint:disable-line:no-unused-expression + + return audioParam; + }, value: 0 }; + + return audioParam; } public get frequency () { // @todo Fake a proper AudioParam. - return { - cancelScheduledValues: () => {}, // tslint:disable-line:no-empty + const audioParam = { + cancelScheduledValues: (startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + + return audioParam; + }, defaultValue: 350, - exponentialRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - linearRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - setTargetAtTime: () => {}, // tslint:disable-line:no-empty - setValueCurveAtTime: () => {}, // tslint:disable-line:no-empty + exponentialRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + linearRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setTargetAtTime: (target: number, startTime: number, timeConstant: number) => { + startTime; // tslint:disable-line:no-unused-expression + target; // tslint:disable-line:no-unused-expression + timeConstant; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueAtTime: (value: number, startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueCurveAtTime: (values: Float32Array, startTime: number, duration: number) => { + duration; // tslint:disable-line:no-unused-expression + startTime; // tslint:disable-line:no-unused-expression + values; // tslint:disable-line:no-unused-expression + + return audioParam; + }, value: 350 }; + + return audioParam; } public get gain () { // @todo Fake a proper AudioParam. - return { - cancelScheduledValues: () => {}, // tslint:disable-line:no-empty + const audioParam = { + cancelScheduledValues: (startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + + return audioParam; + }, defaultValue: 0, - exponentialRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - linearRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - setTargetAtTime: () => {}, // tslint:disable-line:no-empty - setValueCurveAtTime: () => {}, // tslint:disable-line:no-empty + exponentialRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + linearRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setTargetAtTime: (target: number, startTime: number, timeConstant: number) => { + startTime; // tslint:disable-line:no-unused-expression + target; // tslint:disable-line:no-unused-expression + timeConstant; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueAtTime: (value: number, startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueCurveAtTime: (values: Float32Array, startTime: number, duration: number) => { + duration; // tslint:disable-line:no-unused-expression + startTime; // tslint:disable-line:no-unused-expression + values; // tslint:disable-line:no-unused-expression + + return audioParam; + }, value: 0 }; + + return audioParam; } public get Q () { // @todo Fake a proper AudioParam. - return { - cancelScheduledValues: () => {}, // tslint:disable-line:no-empty + const audioParam = { + cancelScheduledValues: (startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + + return audioParam; + }, defaultValue: 1, - exponentialRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - linearRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - setTargetAtTime: () => {}, // tslint:disable-line:no-empty - setValueCurveAtTime: () => {}, // tslint:disable-line:no-empty + exponentialRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + linearRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setTargetAtTime: (target: number, startTime: number, timeConstant: number) => { + startTime; // tslint:disable-line:no-unused-expression + target; // tslint:disable-line:no-unused-expression + timeConstant; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueAtTime: (value: number, startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueCurveAtTime: (values: Float32Array, startTime: number, duration: number) => { + duration; // tslint:disable-line:no-unused-expression + startTime; // tslint:disable-line:no-unused-expression + values; // tslint:disable-line:no-unused-expression + + return audioParam; + }, value: 1 }; + + return audioParam; } public get type () { @@ -79,23 +228,33 @@ class OfflineBiquadFilterNodeFakerProxy extends OfflineAudioNodeProxy { this._type = value; } - public getFrequencyResponse (frequencyHz, magResponse, phaseResponse) { + public getFrequencyResponse (frequencyHz: Float32Array, magResponse: Float32Array, phaseResponse: Float32Array) { return this._nativeNode.getFrequencyResponse(frequencyHz, magResponse, phaseResponse); } } -export class OfflineBiquadFilterNodeFaker { +export interface IOfflineBiquadFilterNodeFakerOptions { + + fakeNodeStore: WeakMap; + + nativeNode: BiquadFilterNode; + + offlineAudioContext: IOfflineAudioContext; + +} + +export class OfflineBiquadFilterNodeFaker implements IOfflineAudioNodeFaker { - private _node; + private _node: null | BiquadFilterNode; - private _proxy; + private _proxy: OfflineBiquadFilterNodeFakerProxy; - private _sources; + private _sources: Map; - constructor ({ fakeNodeStore, nativeNode }) { + constructor ({ fakeNodeStore, nativeNode, offlineAudioContext }: IOfflineBiquadFilterNodeFakerOptions) { this._node = null; - this._proxy = new OfflineBiquadFilterNodeFakerProxy({ fakeNodeStore, nativeNode }); + this._proxy = new OfflineBiquadFilterNodeFakerProxy({ fakeNodeStore, nativeNode, offlineAudioContext }); this._sources = new Map(); fakeNodeStore.set(this._proxy, this); @@ -105,7 +264,7 @@ export class OfflineBiquadFilterNodeFaker { return this._proxy; } - public render (offlineAudioContext) { + public render (offlineAudioContext: TUnpatchedOfflineAudioContext) { if (this._node !== null) { return Promise.resolve(this._node); } @@ -119,7 +278,7 @@ export class OfflineBiquadFilterNodeFaker { for (const [ source, { input, output } ] of this._sources) { promises.push(source .render(offlineAudioContext) - .then((node) => node.connect(this._node, output, input))); + .then((node) => node.connect( this._node, output, input))); } return Promise @@ -127,24 +286,34 @@ export class OfflineBiquadFilterNodeFaker { .then(() => this._node); } - public wire (source, output, input) { + public wire (source: IOfflineAudioNodeFaker, output: number, input: number) { this._sources.set(source, { input, output }); return this._proxy; } - public unwire (source) { + public unwire (source: IOfflineAudioNodeFaker) { this._sources.delete(source); } } +export interface IOfflineBiquadFilterNodeFakerFactoryOptions { + + fakeNodeStore: WeakMap; + + nativeNode: BiquadFilterNode; + + offlineAudioContext: IOfflineAudioContext; + +} + export class OfflineBiquadFilterNodeFakerFactory { - public create ({ fakeNodeStore, nativeNode }) { + public create ({ fakeNodeStore, nativeNode, offlineAudioContext }: IOfflineBiquadFilterNodeFakerFactoryOptions) { return new OfflineBiquadFilterNodeFaker({ fakeNodeStore, - nativeNode + nativeNode, offlineAudioContext }); } diff --git a/src/factories/offline-gain-node.ts b/src/factories/offline-gain-node.ts index 54936759a..44e0914f3 100644 --- a/src/factories/offline-gain-node.ts +++ b/src/factories/offline-gain-node.ts @@ -1,43 +1,96 @@ +import { IAudioNode, IGainNode, IOfflineAudioContext, IOfflineAudioNodeFaker } from '../interfaces'; import { OfflineAudioNodeProxy } from '../offline-audio-node'; +import { TUnpatchedOfflineAudioContext } from '../types'; -class OfflineGainNodeFakerProxy extends OfflineAudioNodeProxy { +export interface IOfflineGainNodeFakerProxyOptions { - constructor ({ fakeNodeStore }) { + fakeNodeStore: WeakMap; + + offlineAudioContext: IOfflineAudioContext; + +} + +export class OfflineGainNodeFakerProxy extends OfflineAudioNodeProxy implements IGainNode { + + constructor ({ fakeNodeStore, offlineAudioContext }: IOfflineGainNodeFakerProxyOptions) { super({ channelCountMode: 'max', channelInterpretation: 'speakers', fakeNodeStore, numberOfInputs: 1, - numberOfOutputs: 1 + numberOfOutputs: 1, + offlineAudioContext }); } - public get gain () { + public get gain (): AudioParam { // @todo Fake a proper AudioParam. - return { - cancelScheduledValues: () => {}, // tslint:disable-line:no-empty + const audioParam = { + cancelScheduledValues: (startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + + return audioParam; + }, defaultValue: 1, - exponentialRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - linearRampToValueAtTime: () => {}, // tslint:disable-line:no-empty - setTargetAtTime: () => {}, // tslint:disable-line:no-empty - setValueCurveAtTime: () => {}, // tslint:disable-line:no-empty + exponentialRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + linearRampToValueAtTime: (value: number, endTime: number) => { + endTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setTargetAtTime: (target: number, startTime: number, timeConstant: number) => { + startTime; // tslint:disable-line:no-unused-expression + target; // tslint:disable-line:no-unused-expression + timeConstant; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueAtTime: (value: number, startTime: number) => { + startTime; // tslint:disable-line:no-unused-expression + value; // tslint:disable-line:no-unused-expression + + return audioParam; + }, + setValueCurveAtTime: (values: Float32Array, startTime: number, duration: number) => { + duration; // tslint:disable-line:no-unused-expression + startTime; // tslint:disable-line:no-unused-expression + values; // tslint:disable-line:no-unused-expression + + return audioParam; + }, value: 1 }; + + return audioParam; } } -export class OfflineGainNodeFaker { +export interface IOfflineGainNodeFakerOptions { - private _node; + fakeNodeStore: WeakMap; - private _proxy; + offlineAudioContext: IOfflineAudioContext; + +} - private _sources; +export class OfflineGainNodeFaker implements IOfflineAudioNodeFaker { - constructor ({ fakeNodeStore }) { + private _node: null | GainNode; + + private _proxy: OfflineGainNodeFakerProxy; + + private _sources: Map; + + constructor ({ fakeNodeStore, offlineAudioContext }: IOfflineGainNodeFakerOptions) { this._node = null; - this._proxy = new OfflineGainNodeFakerProxy({ fakeNodeStore }); + this._proxy = new OfflineGainNodeFakerProxy({ fakeNodeStore, offlineAudioContext }); this._sources = new Map(); fakeNodeStore.set(this._proxy, this); @@ -47,7 +100,7 @@ export class OfflineGainNodeFaker { return this._proxy; } - public render (offlineAudioContext) { + public render (offlineAudioContext: TUnpatchedOfflineAudioContext) { if (this._node !== null) { return Promise.resolve(this._node); } @@ -59,7 +112,7 @@ export class OfflineGainNodeFaker { for (const [ source, { input, output } ] of this._sources) { promises.push(source .render(offlineAudioContext) - .then((node) => node.connect(this._node, output, input))); + .then((node) => node.connect( this._node, output, input))); } return Promise @@ -67,22 +120,30 @@ export class OfflineGainNodeFaker { .then(() => this._node); } - public wire (source, output, input) { + public wire (source: IOfflineAudioNodeFaker, output: number, input: number) { this._sources.set(source, { input, output }); return this._proxy; } - public unwire (source) { + public unwire (source: IOfflineAudioNodeFaker) { this._sources.delete(source); } } +export interface IOfflineGainNodeFakerFactoryOptions { + + fakeNodeStore: WeakMap; + + offlineAudioContext: IOfflineAudioContext; + +} + export class OfflineGainNodeFakerFactory { - public create ({ fakeNodeStore }) { - return new OfflineGainNodeFaker({ fakeNodeStore }); + public create ({ fakeNodeStore, offlineAudioContext }: IOfflineGainNodeFakerFactoryOptions) { + return new OfflineGainNodeFaker({ fakeNodeStore, offlineAudioContext }); } } diff --git a/src/factories/offline-iir-filter-node.ts b/src/factories/offline-iir-filter-node.ts index 5ee2b9f6c..aa247889c 100644 --- a/src/factories/offline-iir-filter-node.ts +++ b/src/factories/offline-iir-filter-node.ts @@ -1,21 +1,29 @@ import { Inject, Injectable } from '@angular/core'; +import { + IAudioNode, + IIIRFilterNode, + IOfflineAudioContext, + IOfflineAudioNodeFaker, + IUnpatchedOfflineAudioContextConstructor +} from '../interfaces'; import { OfflineAudioNodeProxy } from '../offline-audio-node'; import { unpatchedOfflineAudioContextConstructor } from '../providers/unpatched-offline-audio-context-constructor'; import { PromiseSupportTester } from '../testers/promise-support'; +import { TUnpatchedOfflineAudioContext } from '../types'; import { InvalidStateErrorFactory } from './invalid-state-error'; import { NotSupportedErrorFactory } from './not-supported-error'; -function divide (a, b) { +function divide (a: number[], b: number[]) { const denominator = (b[0] * b[0]) + (b[1] * b[1]); return [ (((a[0] * b[0]) + (a[1] * b[1])) / denominator), (((a[1] * b[0]) - (a[0] * b[1])) / denominator) ]; } -function multiply (a, b) { +function multiply (a: number[], b: number[]) { return [ ((a[0] * b[0]) - (a[1] * b[1])), ((a[0] * b[1]) + (a[1] * b[0])) ]; } -function evaluatePolynomial (coefficient, z) { +function evaluatePolynomial (coefficient: number[], z: number[]) { let result = [ 0, 0 ]; for (let i = coefficient.length - 1; i >= 0; i -= 1) { @@ -27,37 +35,58 @@ function evaluatePolynomial (coefficient, z) { return result; } -class OfflineIIRFilterNodeProxy extends OfflineAudioNodeProxy { +export interface IOfflineIIRFilterNodeProxyOptions { - private _feedback; + fakeNodeStore: WeakMap; - private _feedforward; + feedback: number[]; - private _nativeNode; + feedforward: number[]; - private _notSupportedErrorFactory; + nativeNode: null | IIIRFilterNode; - private _nyquist; + notSupportedErrorFactory: NotSupportedErrorFactory; - constructor ({ fakeNodeStore, feedback, feedforward, nativeNode, notSupportedErrorFactory, sampleRate }) { + offlineAudioContext: IOfflineAudioContext; + + sampleRate: number; + +} + +export class OfflineIIRFilterNodeProxy extends OfflineAudioNodeProxy implements IIIRFilterNode { + + private _feedback: number[]; + + private _feedforward: number[]; + + private _nativeNode: null | IIIRFilterNode; + + private _notSupportedErrorFactory: NotSupportedErrorFactory; + + private _nyquist: number; + + constructor ({ + fakeNodeStore, feedback, feedforward, nativeNode, notSupportedErrorFactory, offlineAudioContext, sampleRate + }: IOfflineIIRFilterNodeProxyOptions) { super({ channelCountMode: 'max', channelInterpretation: 'speakers', fakeNodeStore, numberOfInputs: 1, - numberOfOutputs: 1 + numberOfOutputs: 1, + offlineAudioContext }); this._feedback = feedback; this._feedforward = feedforward; this._nativeNode = nativeNode; - this._notSupportedErrorFactory = notSupportedErrorFactory; + this._notSupportedErrorFactory = notSupportedErrorFactory; this._nyquist = sampleRate / 2; } - public getFrequencyResponse (frequencyHz, magResponse, phaseResponse) { + public getFrequencyResponse (frequencyHz: Float32Array, magResponse: Float32Array, phaseResponse: Float32Array) { // Bug #9: Safari does not support IIRFilterNodes. - if (this._nativeNode) { + if (this._nativeNode !== null) { return this._nativeNode.getFrequencyResponse(frequencyHz, magResponse, phaseResponse); } @@ -85,31 +114,59 @@ class OfflineIIRFilterNodeProxy extends OfflineAudioNodeProxy { } -export class OfflineIIRFilterNodeFaker { +export interface IOfflineIIRFilterNodeFakerOptions { + + fakeNodeStore: WeakMap; + + feedback: number[]; + + feedforward: number[]; + + invalidStateErrorFactory: InvalidStateErrorFactory; + + length: number; + + nativeNode: null | IIIRFilterNode; + + notSupportedErrorFactory: NotSupportedErrorFactory; + + numberOfChannels: number; + + offlineAudioContext: IOfflineAudioContext; + + promiseSupportTester: PromiseSupportTester; + + sampleRate: number; + + unpatchedOfflineAudioContextConstructor: IUnpatchedOfflineAudioContextConstructor; + +} + +export class OfflineIIRFilterNodeFaker implements IOfflineAudioNodeFaker { - private _feedback; + private _feedback: number[]; - private _feedforward; + private _feedforward: number[]; - private _invalidStateErrorFactory; + private _invalidStateErrorFactory: InvalidStateErrorFactory; - private _length; + private _length: number; - private _nativeNode; + private _nativeNode: null | IIIRFilterNode; - private _node; + private _node: null | AudioNode; - private _notSupportedErrorFactory; + private _notSupportedErrorFactory: NotSupportedErrorFactory; - private _numberOfChannels; + private _numberOfChannels: number; - private _promiseSupportTester; + private _promiseSupportTester: PromiseSupportTester; - private _proxy; + private _proxy: OfflineIIRFilterNodeProxy; - private _sources; + private _sources: Map; - private _UnpatchedOfflineAudioContext; + private _unpatchedOfflineAudioContextConstructor: IUnpatchedOfflineAudioContextConstructor; constructor ({ fakeNodeStore, @@ -120,10 +177,11 @@ export class OfflineIIRFilterNodeFaker { nativeNode, notSupportedErrorFactory, numberOfChannels, + offlineAudioContext, promiseSupportTester, sampleRate, - UnpatchedOfflineAudioContext // tslint:disable-line:variable-name - }) { + unpatchedOfflineAudioContextConstructor + }: IOfflineIIRFilterNodeFakerOptions) { if (feedback.length === 0 || feedback.length > 20) { throw notSupportedErrorFactory.create(); } @@ -155,10 +213,11 @@ export class OfflineIIRFilterNodeFaker { feedforward, nativeNode, notSupportedErrorFactory, + offlineAudioContext, sampleRate }); this._sources = new Map(); - this._UnpatchedOfflineAudioContext = UnpatchedOfflineAudioContext; + this._unpatchedOfflineAudioContextConstructor = unpatchedOfflineAudioContextConstructor; fakeNodeStore.set(this._proxy, this); } @@ -167,7 +226,7 @@ export class OfflineIIRFilterNodeFaker { return this._proxy; } - private _applyFilter (renderedBuffer, offlineAudioContext) { + private _applyFilter (renderedBuffer: AudioBuffer, offlineAudioContext: TUnpatchedOfflineAudioContext) { let bufferIndex = 0; const bufferLength = 32; @@ -257,7 +316,7 @@ export class OfflineIIRFilterNodeFaker { return filteredBuffer; } - public render (offlineAudioContext) { + public render (offlineAudioContext: TUnpatchedOfflineAudioContext) { if (this._node !== null) { return Promise.resolve(this._node); } @@ -271,7 +330,7 @@ export class OfflineIIRFilterNodeFaker { for (const [ source, { input, output } ] of this._sources) { promises.push(source .render(offlineAudioContext) - .then((node) => node.connect(this._node, output, input))); + .then((node) => node.connect( this._node, output, input))); } return Promise @@ -280,7 +339,7 @@ export class OfflineIIRFilterNodeFaker { } // @todo Somehow retrieve the number of channels. - const partialOfflineAudioContext = new this._UnpatchedOfflineAudioContext( + const partialOfflineAudioContext = new this._unpatchedOfflineAudioContextConstructor( this._numberOfChannels, this._length, offlineAudioContext.sampleRate @@ -307,39 +366,64 @@ export class OfflineIIRFilterNodeFaker { }); }) .then((renderedBuffer) => { - this._node = offlineAudioContext.createBufferSource(); - this._node.buffer = this._applyFilter(renderedBuffer, offlineAudioContext); - this._node.start(0); + const audioBufferSourceNode = offlineAudioContext.createBufferSource(); + + audioBufferSourceNode.buffer = this._applyFilter(renderedBuffer, offlineAudioContext); + audioBufferSourceNode.start(0); + + this._node = audioBufferSourceNode; return this._node; }); } - public wire (source, output, input) { + public wire (source: IOfflineAudioNodeFaker, output: number, input: number) { this._sources.set(source, { input, output }); return this._proxy; } - public unwire (source) { + public unwire (source: IOfflineAudioNodeFaker) { this._sources.delete(source); } } +export interface IOfflineIIRFilterNodeFakerFactoryOptions { + + fakeNodeStore: WeakMap; + + feedback: number[]; + + feedforward: number[]; + + length: number; + + nativeNode: null | IIIRFilterNode; + + numberOfChannels: number; + + offlineAudioContext: IOfflineAudioContext; + + sampleRate: number; + +} + @Injectable() export class OfflineIIRFilterNodeFakerFactory { constructor ( - @Inject(unpatchedOfflineAudioContextConstructor) private _UnpatchedOfflineAudioContext, - @Inject(InvalidStateErrorFactory) private _invalidStateErrorFactory, - @Inject(NotSupportedErrorFactory) private _notSupportedErrorFactory, - @Inject(PromiseSupportTester) private _promiseSupportTester + @Inject(unpatchedOfflineAudioContextConstructor) + private _unpatchedOfflineAudioContextConstructor: IUnpatchedOfflineAudioContextConstructor, + private _invalidStateErrorFactory: InvalidStateErrorFactory, + private _notSupportedErrorFactory: NotSupportedErrorFactory, + private _promiseSupportTester: PromiseSupportTester ) { } - public create ({ fakeNodeStore, feedback, feedforward, length, nativeNode, numberOfChannels, sampleRate }) { + public create ({ + fakeNodeStore, feedback, feedforward, length, nativeNode, numberOfChannels, offlineAudioContext, sampleRate + }: IOfflineIIRFilterNodeFakerFactoryOptions) { return new OfflineIIRFilterNodeFaker({ - UnpatchedOfflineAudioContext: this._UnpatchedOfflineAudioContext, fakeNodeStore, feedback, feedforward, @@ -348,8 +432,10 @@ export class OfflineIIRFilterNodeFakerFactory { nativeNode, notSupportedErrorFactory: this._notSupportedErrorFactory, numberOfChannels, + offlineAudioContext, promiseSupportTester: this._promiseSupportTester, - sampleRate + sampleRate, + unpatchedOfflineAudioContextConstructor: this._unpatchedOfflineAudioContextConstructor }); } diff --git a/src/fakers/iir-filter-node.ts b/src/fakers/iir-filter-node.ts index cf224d6dc..b6b89aca9 100644 --- a/src/fakers/iir-filter-node.ts +++ b/src/fakers/iir-filter-node.ts @@ -1,19 +1,21 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { InvalidAccessErrorFactory } from '../factories/invalid-access-error'; import { InvalidStateErrorFactory } from '../factories/invalid-state-error'; import { NotSupportedErrorFactory } from '../factories/not-supported-error'; +import { IAudioContext, IAudioNode } from '../interfaces'; +import { TUnpatchedAudioContext } from '../types'; -function divide (a, b) { +function divide (a: number[], b: number[]) { const denominator = (b[0] * b[0]) + (b[1] * b[1]); return [ (((a[0] * b[0]) + (a[1] * b[1])) / denominator), (((a[1] * b[0]) - (a[0] * b[1])) / denominator) ]; } -function multiply (a, b) { +function multiply (a: number[], b: number[]) { return [ ((a[0] * b[0]) - (a[1] * b[1])), ((a[0] * b[1]) + (a[1] * b[0])) ]; } -function evaluatePolynomial (coefficient, z) { +function evaluatePolynomial (coefficient: number[], z: number[]) { let result = [ 0, 0 ]; for (let i = coefficient.length - 1; i >= 0; i -= 1) { @@ -29,12 +31,12 @@ function evaluatePolynomial (coefficient, z) { export class IIRFilterNodeFaker { constructor ( - @Inject(InvalidAccessErrorFactory) private _invalidAccessErrorFactory, - @Inject(InvalidStateErrorFactory) private _invalidStateErrorFactory, - @Inject(NotSupportedErrorFactory) private _notSupportedErrorFactory + private _invalidAccessErrorFactory: InvalidAccessErrorFactory, + private _invalidStateErrorFactory: InvalidStateErrorFactory, + private _notSupportedErrorFactory: NotSupportedErrorFactory ) { } - public fake (feedforward, feedback, audioContext, unpatchedAudioContext) { + public fake (feedforward: number[], feedback: number[], audioContext: IAudioContext, unpatchedAudioContext: TUnpatchedAudioContext) { let bufferIndex = 0; const bufferLength = 32; @@ -61,8 +63,9 @@ export class IIRFilterNodeFaker { } } - const gainNode = audioContext.createGain(); + const gainNode = audioContext.createGain(); const nyquist = audioContext.sampleRate / 2; + // @todo Remove this once the audioContext supports the createScriptProcessor() method, too. const scriptProcessorNode = unpatchedAudioContext.createScriptProcessor(256, gainNode.channelCount, gainNode.channelCount); @@ -82,7 +85,7 @@ export class IIRFilterNodeFaker { // This implementation as shamelessly inspired by source code of // tslint:disable-next-line:max-line-length // {@link https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/platform/audio/IIRFilter.cpp|Chromium's IIRFilter}. - scriptProcessorNode.onaudioprocess = (event) => { + scriptProcessorNode.onaudioprocess = (event: AudioProcessingEvent) => { const inputBuffer = event.inputBuffer; const outputBuffer = event.outputBuffer; @@ -121,7 +124,7 @@ export class IIRFilterNodeFaker { } }; - gainNode.getFrequencyResponse = (frequencyHz, magResponse, phaseResponse) => { + gainNode.getFrequencyResponse = (frequencyHz: Float32Array, magResponse: Float32Array, phaseResponse: Float32Array) => { if (magResponse.length === 0 || phaseResponse.length === 0) { throw this._notSupportedErrorFactory.create(); } @@ -146,7 +149,7 @@ export class IIRFilterNodeFaker { gainNode.connect(scriptProcessorNode); - gainNode.connect = (destination, output = 0, input = 0) => { + gainNode.connect = (destination: IAudioNode, output = 0, input = 0) => { try { scriptProcessorNode.connect.call(scriptProcessorNode, destination, output, input); } catch (err) { diff --git a/src/interfaces/analyser-node.ts b/src/interfaces/analyser-node.ts new file mode 100644 index 000000000..a56de7caf --- /dev/null +++ b/src/interfaces/analyser-node.ts @@ -0,0 +1,23 @@ +import { IAudioNode } from './audio-node'; + +export interface IAnalyserNode extends IAudioNode { + + fftSize: number; + + readonly frequencyBinCount: number; + + maxDecibels: number; + + minDecibels: number; + + smoothingTimeConstant: number; + + getByteFrequencyData (array: Uint8Array): void; + + getByteTimeDomainData (array: Uint8Array): void; + + getFloatFrequencyData (array: Float32Array): void; + + getFloatTimeDomainData (array: Float32Array): void; + +} diff --git a/src/interfaces/audio-buffer-source-node.ts b/src/interfaces/audio-buffer-source-node.ts new file mode 100644 index 000000000..c1583e031 --- /dev/null +++ b/src/interfaces/audio-buffer-source-node.ts @@ -0,0 +1,19 @@ +import { IAudioScheduledSourceNode } from './audio-scheduled-source-node'; + +export interface IAudioBufferSourceNode extends IAudioScheduledSourceNode { + + buffer: null | AudioBuffer; + + detune: AudioParam; + + loop: boolean; + + loopEnd: number; + + loopStart: number; + + playbackRate: AudioParam; + + start (when?: number, offset?: number, duration?: number): void; + +} diff --git a/src/interfaces/audio-context-constructor.ts b/src/interfaces/audio-context-constructor.ts new file mode 100644 index 000000000..0649b25d2 --- /dev/null +++ b/src/interfaces/audio-context-constructor.ts @@ -0,0 +1,7 @@ +import { IAudioContext } from './audio-context'; + +export interface IAudioContextConstructor { + + new (): IAudioContext; + +} diff --git a/src/interfaces/audio-context.ts b/src/interfaces/audio-context.ts index 8de61649a..832b1c8d1 100644 --- a/src/interfaces/audio-context.ts +++ b/src/interfaces/audio-context.ts @@ -1,45 +1,17 @@ -import { AudioContextState } from '../enums/audio-context-state'; +import { IAnalyserNode } from './analyser-node'; +import { IBaseAudioContext } from './base-audio-context'; -export interface IAudioContext { - - readonly currentTime: number; - - readonly destination: AudioDestinationNode; - - onstatechange: Function; - - readonly sampleRate: number; - - readonly state: AudioContextState; +export interface IAudioContext extends IBaseAudioContext { close(): Promise; - createAnalyser(): AnalyserNode; - - createBiquadFilter(): BiquadFilterNode; - - createBuffer(numberOfChannels: number, length: number, sampleRate: number): AudioBuffer; - - createBufferSource(): AudioBufferSourceNode; + // @todo This should move into the IBaseAudioContext interface. + createAnalyser(): IAnalyserNode; + // @todo This should move into the IBaseAudioContext interface. createChannelMerger(numberOfInputs: number): ChannelMergerNode; + // @todo This should move into the IBaseAudioContext interface. createChannelSplitter(numberOfOutputs: number): ChannelSplitterNode; - createGain(): GainNode; - - createIIRFilter(feedforward: number[], feedback: number[]); // @todo IIRFilterNode; - - decodeAudioData( - audioData: ArrayBuffer, - successCallback?: (decodedData: AudioBuffer) => {}, - errorCallback?: (error: DOMException) => {} - ): Promise; - -} - -export interface IAudioContextConstructor { - - new (): IAudioContext; - } diff --git a/src/interfaces/audio-destination-node.ts b/src/interfaces/audio-destination-node.ts new file mode 100644 index 000000000..6a117873c --- /dev/null +++ b/src/interfaces/audio-destination-node.ts @@ -0,0 +1,7 @@ +import { IAudioNode } from './audio-node'; + +export interface IAudioDestinationNode extends IAudioNode { + + readonly maxChannelCount: number; + +} diff --git a/src/interfaces/audio-node.ts b/src/interfaces/audio-node.ts new file mode 100644 index 000000000..796188daa --- /dev/null +++ b/src/interfaces/audio-node.ts @@ -0,0 +1,24 @@ +import { TChannelCountMode, TChannelInterpretation } from '../types'; +import { IBaseAudioContext } from './base-audio-context'; + +export interface IAudioNode extends EventTarget { + + readonly context: IBaseAudioContext; + + channelCount: number; + + channelCountMode: TChannelCountMode; + + channelInterpretation: TChannelInterpretation; + + readonly numberOfInputs: number; + + readonly numberOfOutputs: number; + + // @todo connect (destination: IAudioParam, output?: number): void; + connect (destination: IAudioNode, output?: number, input?: number): IAudioNode; + + // @todo Consider all possible variations. + disconnect (destination?: IAudioNode): void; + +} diff --git a/src/interfaces/audio-scheduled-source-node.ts b/src/interfaces/audio-scheduled-source-node.ts new file mode 100644 index 000000000..1324ec67d --- /dev/null +++ b/src/interfaces/audio-scheduled-source-node.ts @@ -0,0 +1,11 @@ +import { IAudioNode } from './audio-node'; + +export interface IAudioScheduledSourceNode extends IAudioNode { + + // @todo onended: EventHandler; + + start (when?: number): void; + + stop (when?: number): void; + +} diff --git a/src/interfaces/base-audio-context.ts b/src/interfaces/base-audio-context.ts new file mode 100644 index 000000000..008da994d --- /dev/null +++ b/src/interfaces/base-audio-context.ts @@ -0,0 +1,38 @@ +import { TDecodeErrorCallback, TDecodeSuccessCallback, TStateChangeEventHandler } from '../types'; +import { IAudioBufferSourceNode } from './audio-buffer-source-node'; +import { IAudioDestinationNode } from './audio-destination-node'; +import { IBiquadFilterNode } from './biquad-filter-node'; +import { IGainNode } from './gain-node'; +import { IIIRFilterNode } from './iir-filter-node'; + +export interface IBaseAudioContext /* extends EventTarget */ { + + readonly currentTime: number; + + readonly destination: IAudioDestinationNode; + + // @todo listener + + onstatechange: null | TStateChangeEventHandler; + + readonly sampleRate: number; + + readonly state: AudioContextState; + + createBiquadFilter(): IBiquadFilterNode; + + createBuffer(numberOfChannels: number, length: number, sampleRate: number): AudioBuffer; + + createBufferSource(): IAudioBufferSourceNode; + + createGain(): IGainNode; + + createIIRFilter(feedforward: number[], feedback: number[]): IIIRFilterNode; + + decodeAudioData( + audioData: ArrayBuffer, + successCallback?: TDecodeSuccessCallback, + errorCallback?: TDecodeErrorCallback + ): Promise; + +} diff --git a/src/interfaces/biquad-filter-node.ts b/src/interfaces/biquad-filter-node.ts new file mode 100644 index 000000000..75c0b4497 --- /dev/null +++ b/src/interfaces/biquad-filter-node.ts @@ -0,0 +1,18 @@ +import { TBiquadFilterType } from '../types'; +import { IAudioNode } from './audio-node'; + +export interface IBiquadFilterNode extends IAudioNode { + + readonly detune: AudioParam; + + readonly frequency: AudioParam; + + readonly gain: AudioParam; + + readonly Q: AudioParam; + + type: TBiquadFilterType; + + getFrequencyResponse(frequencyHz: Float32Array, magResponse: Float32Array, phaseResponse: Float32Array): void; + +} diff --git a/src/interfaces/error-factory.ts b/src/interfaces/error-factory.ts new file mode 100644 index 000000000..693de7bf8 --- /dev/null +++ b/src/interfaces/error-factory.ts @@ -0,0 +1,5 @@ +export interface IErrorFactory { + + create: () => DOMException | Error; + +} diff --git a/src/interfaces/gain-node.ts b/src/interfaces/gain-node.ts new file mode 100644 index 000000000..3c05f7b27 --- /dev/null +++ b/src/interfaces/gain-node.ts @@ -0,0 +1,7 @@ +import { IAudioNode } from './audio-node'; + +export interface IGainNode extends IAudioNode { + + readonly gain: AudioParam; + +} diff --git a/src/interfaces/iir-filter-node.ts b/src/interfaces/iir-filter-node.ts new file mode 100644 index 000000000..f18110fe5 --- /dev/null +++ b/src/interfaces/iir-filter-node.ts @@ -0,0 +1,7 @@ +import { IAudioNode } from './audio-node'; + +export interface IIIRFilterNode extends IAudioNode { + + getFrequencyResponse(frequencyHz: Float32Array, magResponse: Float32Array, phaseResponse: Float32Array): void; + +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts new file mode 100644 index 000000000..117765fba --- /dev/null +++ b/src/interfaces/index.ts @@ -0,0 +1,20 @@ +export * from './analyser-node'; +export * from './audio-buffer-source-node'; +export * from './audio-context'; +export * from './audio-context-constructor'; +export * from './audio-destination-node'; +export * from './audio-node'; +export * from './audio-scheduled-source-node'; +export * from './base-audio-context'; +export * from './biquad-filter-node'; +export * from './error-factory'; +export * from './gain-node'; +export * from './iir-filter-node'; +export * from './modernizr'; +export * from './offline-audio-completion-event'; +export * from './offline-audio-context'; +export * from './offline-audio-context-constructor'; +export * from './offline-audio-node-faker'; +export * from './oscillator-node'; +export * from './unpatched-audio-context-constructor'; +export * from './unpatched-offline-audio-context-constructor'; diff --git a/src/interfaces/modernizr.ts b/src/interfaces/modernizr.ts new file mode 100644 index 000000000..d0efe260e --- /dev/null +++ b/src/interfaces/modernizr.ts @@ -0,0 +1,9 @@ +export interface IModernizr { + + promises: boolean; + + typedarrays: boolean; + + webaudio: boolean; + +} diff --git a/src/interfaces/offline-audio-completion-event.ts b/src/interfaces/offline-audio-completion-event.ts new file mode 100644 index 000000000..60824ee39 --- /dev/null +++ b/src/interfaces/offline-audio-completion-event.ts @@ -0,0 +1,5 @@ +export interface IOfflineAudioCompletionEvent extends Event { + + readonly renderedBuffer: AudioBuffer; + +} diff --git a/src/interfaces/offline-audio-context-constructor.ts b/src/interfaces/offline-audio-context-constructor.ts new file mode 100644 index 000000000..9a30f223f --- /dev/null +++ b/src/interfaces/offline-audio-context-constructor.ts @@ -0,0 +1,7 @@ +import { IOfflineAudioContext } from './offline-audio-context'; + +export interface IOfflineAudioContextConstructor { + + new (numberOfChannels: number, length: number, sampleRate: number): IOfflineAudioContext; + +} diff --git a/src/interfaces/offline-audio-context.ts b/src/interfaces/offline-audio-context.ts index 38559b109..de77c1289 100644 --- a/src/interfaces/offline-audio-context.ts +++ b/src/interfaces/offline-audio-context.ts @@ -1,37 +1,9 @@ -export interface IOfflineAudioContext { +import { IBaseAudioContext } from './base-audio-context'; - readonly currentTime: number; - - readonly destination: AudioDestinationNode; +export interface IOfflineAudioContext extends IBaseAudioContext { readonly length: number; - readonly sampleRate: number; - - // @todo new(numberOfChannels: number, length: number, sampleRate: number); - - createBiquadFilter(): BiquadFilterNode; - - createBuffer(numberOfChannels: number, length: number, sampleRate: number): AudioBuffer; - - createBufferSource(): AudioBufferSourceNode; - - createGain(): GainNode; - - createIIRFilter(feedforward: [ number ], feedback): [ number ]; // @todo IIRFilterNode; - - decodeAudioData( - audioData: ArrayBuffer, - successCallback?: (decodedData: AudioBuffer) => {}, - errorCallback?: (error: DOMException) => {} - ): Promise; - startRendering(): Promise; } - -export interface IOfflineAudioContextConstructor { - - new (numberOfChannels: number, length: number, sampleRate: number): IOfflineAudioContext; - -} diff --git a/src/interfaces/offline-audio-node-faker.ts b/src/interfaces/offline-audio-node-faker.ts new file mode 100644 index 000000000..8f8f2c560 --- /dev/null +++ b/src/interfaces/offline-audio-node-faker.ts @@ -0,0 +1,12 @@ +import { TUnpatchedOfflineAudioContext } from '../types'; +import { IAudioNode } from './audio-node'; + +export interface IOfflineAudioNodeFaker { + + render (offlineAudioContext: TUnpatchedOfflineAudioContext): Promise; + + unwire (source: IOfflineAudioNodeFaker): void; + + wire (source: IOfflineAudioNodeFaker, output: number, input: number): IAudioNode; + +} diff --git a/src/interfaces/oscillator-node.ts b/src/interfaces/oscillator-node.ts new file mode 100644 index 000000000..b738f4097 --- /dev/null +++ b/src/interfaces/oscillator-node.ts @@ -0,0 +1,16 @@ +import { TOscillatorType } from '../types'; +import { IAudioScheduledSourceNode } from './audio-scheduled-source-node'; + +export interface IOscillatorNode extends IAudioScheduledSourceNode { + + readonly detune: AudioParam; + + readonly frequency: AudioParam; + + // @todo onended + + type: TOscillatorType; + + // @todo setPeriodicWave (periodicWave: PeriodicWave): void; + +} diff --git a/src/interfaces/unpatched-audio-context-constructor.ts b/src/interfaces/unpatched-audio-context-constructor.ts new file mode 100644 index 000000000..9124adde2 --- /dev/null +++ b/src/interfaces/unpatched-audio-context-constructor.ts @@ -0,0 +1,7 @@ +import { TUnpatchedAudioContext } from '../types'; + +export interface IUnpatchedAudioContextConstructor { + + new (): TUnpatchedAudioContext; + +} diff --git a/src/interfaces/unpatched-offline-audio-context-constructor.ts b/src/interfaces/unpatched-offline-audio-context-constructor.ts new file mode 100644 index 000000000..bcc5c36e8 --- /dev/null +++ b/src/interfaces/unpatched-offline-audio-context-constructor.ts @@ -0,0 +1,7 @@ +import { TUnpatchedOfflineAudioContext } from '../types'; + +export interface IUnpatchedOfflineAudioContextConstructor { + + new (numberOfChannels: number, length: number, sampleRate: number): TUnpatchedOfflineAudioContext; + +} diff --git a/src/module.ts b/src/module.ts index 1db741607..6d802ff2d 100644 --- a/src/module.ts +++ b/src/module.ts @@ -12,8 +12,7 @@ import { OfflineBiquadFilterNodeFakerFactory } from './factories/offline-biquad import { OfflineGainNodeFakerFactory } from './factories/offline-gain-node'; import { OfflineIIRFilterNodeFakerFactory } from './factories/offline-iir-filter-node'; import { IIRFilterNodeFaker } from './fakers/iir-filter-node'; -import { IAudioContext, IAudioContextConstructor } from './interfaces/audio-context'; -import { IOfflineAudioContext, IOfflineAudioContextConstructor } from './interfaces/offline-audio-context'; +import { IAudioContext, IAudioContextConstructor, IOfflineAudioContext, IOfflineAudioContextConstructor } from './interfaces'; import { AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, audioContextConstructor } from './providers/audio-context-constructor'; import { DETACHED_AUDIO_BUFFERS_PROVIDER } from './providers/detached-audio-buffers'; import { IS_SUPPORTED_PROMISE_PROVIDER, IsSupportedPromise } from './providers/is-supported-promise'; @@ -84,7 +83,7 @@ const injector = ReflectiveInjector.resolveAndCreate([ WINDOW_PROVIDER ]); -export { AudioContextState } from './enums/audio-context-state'; +export { TAudioContextState } from './types'; export { IAudioContext }; diff --git a/src/offline-audio-node.ts b/src/offline-audio-node.ts index c5e665fa0..dc9cdb36c 100644 --- a/src/offline-audio-node.ts +++ b/src/offline-audio-node.ts @@ -1,32 +1,69 @@ +import { IOfflineAudioContext, IOfflineAudioNodeFaker, IAudioNode } from './interfaces'; +import { TChannelCountMode, TChannelInterpretation } from './types'; + // @todo Remove this declaration again if TypeScript supports the DOMException constructor. declare var DOMException: { new (message: string, name: string): DOMException; }; -export class OfflineAudioNodeProxy { +export interface IAudioNodeOptions { + + channelCount?: number; + + channelCountMode: TChannelCountMode; + + channelInterpretation: TChannelInterpretation; + + fakeNodeStore: WeakMap; + + numberOfInputs: number; + + numberOfOutputs: number; + + offlineAudioContext: IOfflineAudioContext; + +} + +export class OfflineAudioNodeProxy implements IAudioNode { + + private _channelCount: number; - private _channelCountMode; + private _channelCountMode: TChannelCountMode; - private _channelInterpretation; + private _channelInterpretation: TChannelInterpretation; - private _fakeNodeStore; + private _fakeNodeStore: WeakMap; - private _numberOfInputs; + private _numberOfInputs: number; - private _numberOfOutputs; + private _numberOfOutputs: number; + + private _offlineAudioContext: IOfflineAudioContext; constructor ({ + channelCount = 2, channelCountMode, channelInterpretation, fakeNodeStore, numberOfInputs, - numberOfOutputs - }: { channelCountMode?, channelInterpretation?, fakeNodeStore, numberOfInputs?, numberOfOutputs? }) { + numberOfOutputs, + offlineAudioContext + }: IAudioNodeOptions) { + this._channelCount = channelCount; this._channelCountMode = channelCountMode; this._channelInterpretation = channelInterpretation; this._fakeNodeStore = fakeNodeStore; this._numberOfInputs = numberOfInputs; this._numberOfOutputs = numberOfOutputs; + this._offlineAudioContext = offlineAudioContext; + } + + public get channelCount () { + return this._channelCount; + } + + public set channelCount (value) { + this._channelCount = value; } public get channelCountMode () { @@ -45,6 +82,10 @@ export class OfflineAudioNodeProxy { this._channelInterpretation = value; } + public get context () { + return this._offlineAudioContext; + } + public get numberOfInputs () { return this._numberOfInputs; } @@ -61,31 +102,69 @@ export class OfflineAudioNodeProxy { this._numberOfOutputs = value; } - public connect (destination, output = 0, input = 0) { + addEventListener (type: string, listener?: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) { + // @todo + type; + listener; + options; + } + + public connect (destination: IAudioNode, output = 0, input = 0): IAudioNode { const faker = this._fakeNodeStore.get(destination); if (faker === undefined) { let exception; + // @todo Use the error factory. try { exception = new DOMException('', 'InvalidAccessError'); } catch (err) { exception = new Error(); - exception.code = 15; + ( exception).code = 15; exception.name = 'InvalidAccessError'; } throw exception; } - return faker.wire(this._fakeNodeStore.get(this), output, input); + const source = this._fakeNodeStore.get(this); + + if (source === undefined) { + throw new Error(/* @todo */); + } + + return faker.wire(source, output, input); } - public disconnect (destination) { + public disconnect (destination: IAudioNode) { const faker = this._fakeNodeStore.get(destination); - return faker.unwire(this._fakeNodeStore.get(this)); + if (faker === undefined) { + throw new Error(/* @todo */); + } + + const source = this._fakeNodeStore.get(this); + + if (source === undefined) { + throw new Error(/* @todo */); + } + + return faker.unwire(source); + } + + dispatchEvent (evt: Event) { + // @todo + evt; + + return false; + } + + removeEventListener (type: string, listener?: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) { + // @todo + type; + listener; + options; } } diff --git a/src/providers/audio-context-constructor.ts b/src/providers/audio-context-constructor.ts index e6afd8423..b17f23b5f 100644 --- a/src/providers/audio-context-constructor.ts +++ b/src/providers/audio-context-constructor.ts @@ -1,10 +1,20 @@ import { OpaqueToken } from '@angular/core'; import { DataCloneErrorFactory } from '../factories/data-clone-error'; import { EncodingErrorFactory } from '../factories/encoding-error'; -import { InvalidAccessErrorFactory } from '../factories/invalid-access-error'; import { InvalidStateErrorFactory } from '../factories/invalid-state-error'; import { IIRFilterNodeFaker } from '../fakers/iir-filter-node'; -import { IAudioContext, IAudioContextConstructor } from '../interfaces/audio-context'; +import { + IAnalyserNode, + IAudioBufferSourceNode, + IAudioContext, + IAudioContextConstructor, + IAudioDestinationNode, + IBiquadFilterNode, + IGainNode, + IIIRFilterNode, + IOscillatorNode, + IUnpatchedAudioContextConstructor +} from '../interfaces'; import { AnalyserNodeGetFloatTimeDomainDataSupportTester } from '../testers/analyser-node-get-float-time-domain-data'; import { AudioBufferCopyChannelMethodsSupportTester } from '../testers/audio-buffer-copy-channel-methods-support'; import { ChainingSupportTester } from '../testers/chaining-support'; @@ -12,6 +22,13 @@ import { ConnectingSupportTester } from '../testers/connecting-support'; import { DisconnectingSupportTester } from '../testers/disconnecting-support'; import { PromiseSupportTester } from '../testers/promise-support'; import { StopStoppedSupportTester } from '../testers/stop-stopped-support'; +import { + TAudioContextState, + TDecodeErrorCallback, + TDecodeSuccessCallback, + TStateChangeEventHandler, + TUnpatchedAudioContext +} from '../types'; import { AnalyserNodeGetFloatTimeDomainDataMethodWrapper } from '../wrappers/analyser-node-get-float-time-domain-data-method'; import { AudioBufferWrapper } from '../wrappers/audio-buffer'; import { AudioBufferCopyChannelMethodsWrapper } from '../wrappers/audio-buffer-copy-channel-methods'; @@ -44,7 +61,6 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { DetachedAudioBuffers, DisconnectingSupportTester, EncodingErrorFactory, - InvalidAccessErrorFactory, InvalidStateErrorFactory, IIRFilterNodeFaker, IIRFilterNodeGetFrequencyResponseMethodWrapper, @@ -54,56 +70,55 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { ], provide: audioContextConstructor, useFactory: ( - analyserNodeGetFloatTimeDomainDataMethodWrapper, - analyserNodeGetFloatTimeDomainDataSupportTester, - audioBufferCopyChannelMethodsSupportTester, - audioBufferCopyChannelMethodsWrapper, - audioBufferSourceNodeStopMethodWrapper, - audioBufferWrapper, - audioNodeConnectMethodWrapper, - audioNodeDisconnectMethodWrapper, - chainingSupportTester, - channelMergerNodeWrapper, - channelSplitterNodeWrapper, - connectingSupportTester, - dataCloneErrorFactory, - detachedAudioBuffers, - disconnectingSupportTester, - encodingErrorFactory, - invalidAccessErrorFactory, - invalidStateErrorFactory, - iIRFilterNodeFaker, - iIRFilterNodeGetFrequencyResponseMethodWrapper, - promiseSupportTester, - stopStoppedSupportTester, - UnpatchedAudioContext // tslint:disable-line:variable-name + analyserNodeGetFloatTimeDomainDataMethodWrapper: AnalyserNodeGetFloatTimeDomainDataMethodWrapper, + analyserNodeGetFloatTimeDomainDataSupportTester: AnalyserNodeGetFloatTimeDomainDataSupportTester, + audioBufferCopyChannelMethodsSupportTester: AudioBufferCopyChannelMethodsSupportTester, + audioBufferCopyChannelMethodsWrapper: AudioBufferCopyChannelMethodsWrapper, + audioBufferSourceNodeStopMethodWrapper: AudioBufferSourceNodeStopMethodWrapper, + audioBufferWrapper: AudioBufferWrapper, + audioNodeConnectMethodWrapper: AudioNodeConnectMethodWrapper, + audioNodeDisconnectMethodWrapper: AudioNodeDisconnectMethodWrapper, + chainingSupportTester: ChainingSupportTester, + channelMergerNodeWrapper: ChannelMergerNodeWrapper, + channelSplitterNodeWrapper: ChannelSplitterNodeWrapper, + connectingSupportTester: ConnectingSupportTester, + dataCloneErrorFactory: DataCloneErrorFactory, + detachedAudioBuffers: WeakSet, + disconnectingSupportTester: DisconnectingSupportTester, + encodingErrorFactory: EncodingErrorFactory, + invalidStateErrorFactory: InvalidStateErrorFactory, + iIRFilterNodeFaker: IIRFilterNodeFaker, + iIRFilterNodeGetFrequencyResponseMethodWrapper: IIRFilterNodeGetFrequencyResponseMethodWrapper, + promiseSupportTester: PromiseSupportTester, + stopStoppedSupportTester: StopStoppedSupportTester, + unpatchedAudioContextConstructor: IUnpatchedAudioContextConstructor ): IAudioContextConstructor => { class AudioContext implements IAudioContext { - private _isSupportingAnalyserNodeGetFloatTimeDomainData; + private _isSupportingAnalyserNodeGetFloatTimeDomainData: boolean; - private _isSupportingChaining; + private _isSupportingChaining: boolean; - private _isSupportingCopyChannelMethods; + private _isSupportingCopyChannelMethods: boolean; - private _isSupportingConnecting; + private _isSupportingConnecting: boolean; - private _isSupportingDisconnecting; + private _isSupportingDisconnecting: boolean; - private _isSupportingGetFrequencyResponseErrors; + private _isSupportingGetFrequencyResponseErrors: boolean; - private _isSupportingPromises; + private _isSupportingPromises: boolean; - private _isSupportingStoppingOfStoppedNodes; + private _isSupportingStoppingOfStoppedNodes: boolean; - private _onStateChangeListener; + private _onStateChangeListener: null | TStateChangeEventHandler; - private _unpatchedAudioContext; + private _unpatchedAudioContext: TUnpatchedAudioContext; - private _state; + private _state: null | TAudioContextState; constructor () { - const unpatchedAudioContext = new UnpatchedAudioContext(); + const unpatchedAudioContext = new unpatchedAudioContextConstructor(); this._isSupportingAnalyserNodeGetFloatTimeDomainData = analyserNodeGetFloatTimeDomainDataSupportTester.test( unpatchedAudioContext @@ -152,34 +167,36 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } public set currentTime (value) { - this._unpatchedAudioContext.currentTime = value; + ( this._unpatchedAudioContext).currentTime = value; // If the unpatched AudioContext does not throw an error by itself, it has to be faked. + // @todo Test if this still needs to be patched. throw new TypeError(); } - public get destination () { - return this._unpatchedAudioContext.destination; + public get destination (): IAudioDestinationNode { + return this._unpatchedAudioContext.destination; } public set destination (value) { - this._unpatchedAudioContext.destination = value; + ( this._unpatchedAudioContext).destination = value; // If the unpatched AudioContext does not throw an error by itself, it has to be faked. + // @todo Test if this still needs to be patched. throw new TypeError(); } public get onstatechange () { if ('onstatechange' in this._unpatchedAudioContext) { - return this._unpatchedAudioContext.onstatechange; + return ( this._unpatchedAudioContext).onstatechange; } return this._onStateChangeListener; } - public set onstatechange (value) { + public set onstatechange (value: null | TStateChangeEventHandler) { if ('onstatechange' in this._unpatchedAudioContext) { - this._unpatchedAudioContext.onstatechange = value; + ( this._unpatchedAudioContext).onstatechange = value; } else { this._onStateChangeListener = (typeof value === 'function') ? value : null; } @@ -190,9 +207,10 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } public set sampleRate (value) { - this._unpatchedAudioContext.sampleRate = value; + ( this._unpatchedAudioContext).sampleRate = value; // If the unpatched AudioContext does not throw an error by itself, it has to be faked. + // @todo Test if this still needs to be patched. throw new TypeError(); } @@ -202,12 +220,14 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { public set state (value) { if (this._unpatchedAudioContext.state !== undefined) { - this._unpatchedAudioContext.state = value; + ( this._unpatchedAudioContext).state = value; } - // If the unpatched AudioContext does not have a property called state or does not throw an error by itself, it has to be - // tslint:disable-next-line:comment-format - // faked. + /* + * If the unpatched AudioContext does not have a property called state or does not throw an error by itself, it has to be + * faked. + * @todo Test if this still needs to be patched. + */ throw new TypeError(); } @@ -229,12 +249,12 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return this._unpatchedAudioContext.close(); } - public createAnalyser () { + public createAnalyser (): IAnalyserNode { if (this._unpatchedAudioContext === null) { throw invalidStateErrorFactory.create(); } - const analyserNode = this._unpatchedAudioContext.createAnalyser(); + const analyserNode = this._unpatchedAudioContext.createAnalyser(); // If the unpatched AudioContext throws an error by itself, this code will never get executed. If it does it will imitate // tslint:disable-next-line:comment-format @@ -256,7 +276,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #11: Safari does not support chaining yet. // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingChaining || !this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(analyserNode, this._isSupportingChaining, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(analyserNode, this._isSupportingChaining); } // Only Chrome and Opera support disconnecting of a specific destination. @@ -267,12 +287,12 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return analyserNode; } - public createBiquadFilter () { + public createBiquadFilter (): IBiquadFilterNode { if (this._unpatchedAudioContext === null) { throw invalidStateErrorFactory.create(); } - const biquadFilterNode = this._unpatchedAudioContext.createBiquadFilter(); + const biquadFilterNode = this._unpatchedAudioContext.createBiquadFilter(); // If the unpatched AudioContext throws an error by itself, this code will never get executed. If it does it will imitate // tslint:disable-next-line:comment-format @@ -284,13 +304,13 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #11: Safari does not support chaining yet. // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingChaining || !this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(biquadFilterNode, this._isSupportingChaining, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(biquadFilterNode, this._isSupportingChaining); } return biquadFilterNode; } - public createBuffer (numberOfChannels, length, sampleRate) { + public createBuffer (numberOfChannels: number, length: number, sampleRate: number): AudioBuffer { const audioBuffer = this._unpatchedAudioContext.createBuffer(numberOfChannels, length, sampleRate); // Bug #5: Safari does not support copyFromChannel() and copyToChannel(). @@ -304,8 +324,8 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return audioBuffer; } - public createBufferSource () { - const audioBufferSourceNode = this._unpatchedAudioContext.createBufferSource(); + public createBufferSource (): IAudioBufferSourceNode { + const audioBufferSourceNode = this._unpatchedAudioContext.createBufferSource(); // Bug #19: Safari does not ignore calls to stop() of an already stopped AudioBufferSourceNode. if (!this._isSupportingStoppingOfStoppedNodes) { @@ -318,7 +338,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(audioBufferSourceNode, true, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(audioBufferSourceNode, true); } return audioBufferSourceNode; @@ -341,7 +361,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #11: Safari does not support chaining yet. // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingChaining || !this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(channelMergerNode, this._isSupportingChaining, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(channelMergerNode, this._isSupportingChaining); } // Bug #15: Safari does not return the default properties. @@ -377,7 +397,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #11: Safari does not support chaining yet. // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingChaining || !this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(channelSplitterNode, this._isSupportingChaining, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(channelSplitterNode, this._isSupportingChaining); } // Bug #29 - #32: Only Chrome partially supports the spec yet. @@ -386,12 +406,12 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return channelSplitterNode; } - public createGain () { + public createGain (): IGainNode { if (this._unpatchedAudioContext === null) { throw invalidStateErrorFactory.create(); } - const gainNode = this._unpatchedAudioContext.createGain(); + const gainNode = this._unpatchedAudioContext.createGain(); // If the unpatched AudioContext throws an error by itself, this code will never get executed. If it does it will imitate // tslint:disable-next-line:comment-format @@ -403,7 +423,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #11: Safari does not support chaining yet. // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingChaining || !this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(gainNode, this._isSupportingChaining, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(gainNode, this._isSupportingChaining); } // Bug #12: Firefox and Safari do not support to disconnect a specific destination. @@ -414,7 +434,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return gainNode; } - public createIIRFilter (feedforward, feedback) { + public createIIRFilter (feedforward: number[], feedback: number[]): IIIRFilterNode { // Bug #10: Edge does not throw an error when the context is closed. if (this._unpatchedAudioContext === null && this.state === 'closed') { throw invalidStateErrorFactory.create(); @@ -425,7 +445,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return iIRFilterNodeFaker.fake(feedforward, feedback, this, this._unpatchedAudioContext); } - const iIRFilterNode = this._unpatchedAudioContext.createIIRFilter(feedforward, feedback); + const iIRFilterNode = this._unpatchedAudioContext.createIIRFilter(feedforward, feedback); // Bug 23 & 24: FirefoxDeveloper does not throw NotSupportedErrors anymore. if (!this._isSupportingGetFrequencyResponseErrors) { @@ -434,18 +454,18 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(iIRFilterNode, true, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(iIRFilterNode, true); } return iIRFilterNode; } - public createOscillator () { + public createOscillator (): IOscillatorNode { if (this._unpatchedAudioContext === null) { throw invalidStateErrorFactory.create(); } - const oscillatorNode = this._unpatchedAudioContext.createOscillator(); + const oscillatorNode = this._unpatchedAudioContext.createOscillator(); // If the unpatched AudioContext throws an error by itself, this code will never get executed. If it does it will imitate // tslint:disable-next-line:comment-format @@ -457,13 +477,15 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #11: Safari does not support chaining yet. // Bug #41: Only Chrome, Firefox and Opera throw the correct exception by now. if (!this._isSupportingChaining || !this._isSupportingConnecting) { - audioNodeConnectMethodWrapper.wrap(oscillatorNode, this._isSupportingChaining, this._isSupportingConnecting); + audioNodeConnectMethodWrapper.wrap(oscillatorNode, this._isSupportingChaining); } return oscillatorNode; } - public decodeAudioData (audioData, successCallback, errorCallback) { + public decodeAudioData ( + audioData: ArrayBuffer, successCallback?: TDecodeSuccessCallback, errorCallback?: TDecodeErrorCallback + ): Promise { // Bug #43: Only Chrome Canary does yet throw a DataCloneError. if (detachedAudioBuffers.has(audioData)) { const err = dataCloneErrorFactory.create(); @@ -490,7 +512,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } return this._unpatchedAudioContext - .decodeAudioData(audioData, successCallback, (err) => { + .decodeAudioData(audioData, successCallback, (err: DOMException | Error) => { if (typeof errorCallback === 'function') { // Bug #27: Edge is rejecting invalid arrayBuffers with a DOMException. if (err instanceof DOMException && err.name === 'NotSupportedError') { @@ -500,7 +522,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } } }) - .catch ((err) => { + .catch ((err: DOMException | Error) => { // Bug #6: Chrome, Firefox and Opera do not call the errorCallback in case of an invalid buffer. if (typeof errorCallback === 'function' && err instanceof TypeError) { errorCallback(err); @@ -517,7 +539,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #21: Safari does not return a Promise yet. return new Promise((resolve, reject) => { - const fail = (err) => { + const fail = (err: DOMException | Error) => { if (typeof errorCallback === 'function') { errorCallback(err); } @@ -525,7 +547,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { reject(err); }; - const succeed = (dBffrWrppr) => { + const succeed = (dBffrWrppr: AudioBuffer) => { resolve(dBffrWrppr); if (typeof successCallback === 'function') { @@ -536,7 +558,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #26: Safari throws a synchronous error. try { // Bug #1: Safari requires a successCallback. - this._unpatchedAudioContext.decodeAudioData(audioData, (audioBuffer) => { + this._unpatchedAudioContext.decodeAudioData(audioData, (audioBuffer: AudioBuffer) => { // Bug #5: Safari does not support copyFromChannel() and copyToChannel(). if (typeof audioBuffer.copyFromChannel !== 'function') { audioBufferWrapper.wrap(audioBuffer); @@ -546,7 +568,7 @@ export const AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } succeed(audioBuffer); - }, (err) => { + }, (err: DOMException | Error) => { // Bug #4: Safari returns null instead of an error. if (err === null) { fail(encodingErrorFactory.create()); diff --git a/src/providers/is-supported-promise.ts b/src/providers/is-supported-promise.ts index eaea1b27d..68c37dd20 100644 --- a/src/providers/is-supported-promise.ts +++ b/src/providers/is-supported-promise.ts @@ -1,4 +1,5 @@ import { OpaqueToken } from '@angular/core'; +import { IModernizr } from '../interfaces'; import { CloseSupportTester } from '../testers/close-support'; import { DecodeAudioDataTypeErrorSupportTester } from '../testers/decode-audio-data-type-error-support'; import { MergingSupportTester } from '../testers/merging-support'; @@ -9,7 +10,12 @@ export const IsSupportedPromise = new OpaqueToken('IS_SUPPORTED_PROMISE'); // ts export const IS_SUPPORTED_PROMISE_PROVIDER = { deps: [ CloseSupportTester, DecodeAudioDataTypeErrorSupportTester, MergingSupportTester, Modernizr ], provide: IsSupportedPromise, - useFactory: (closeSupportTester, decodeAudioDataTypeErrorSupportTester, mergingSupportTester, modernizr): Promise => { + useFactory: ( + closeSupportTester: CloseSupportTester, + decodeAudioDataTypeErrorSupportTester: DecodeAudioDataTypeErrorSupportTester, + mergingSupportTester: MergingSupportTester, + modernizr: IModernizr + ): Promise => { if (modernizr.promises && modernizr.typedarrays && modernizr.webaudio && closeSupportTester.test()) { return Promise .all([ diff --git a/src/providers/offline-audio-context-constructor.ts b/src/providers/offline-audio-context-constructor.ts index 2d81d7bf4..39c36876e 100644 --- a/src/providers/offline-audio-context-constructor.ts +++ b/src/providers/offline-audio-context-constructor.ts @@ -2,13 +2,25 @@ import { OpaqueToken } from '@angular/core'; import { DataCloneErrorFactory } from '../factories/data-clone-error'; import { EncodingErrorFactory } from '../factories/encoding-error'; import { OfflineAudioBufferSourceNodeFakerFactory } from '../factories/offline-audio-buffer-source-node'; -import { OfflineAudioDestinationNodeFakerFactory } from '../factories/offline-audio-destination-node'; +import { OfflineAudioDestinationNodeFaker, OfflineAudioDestinationNodeFakerFactory } from '../factories/offline-audio-destination-node'; import { OfflineBiquadFilterNodeFakerFactory } from '../factories/offline-biquad-filter-node'; import { OfflineGainNodeFakerFactory } from '../factories/offline-gain-node'; import { OfflineIIRFilterNodeFakerFactory } from '../factories/offline-iir-filter-node'; -import { IOfflineAudioContext, IOfflineAudioContextConstructor } from '../interfaces/offline-audio-context'; +import { + IAudioBufferSourceNode, + IAudioNode, + IBiquadFilterNode, + IGainNode, + IIIRFilterNode, + IOfflineAudioCompletionEvent, + IOfflineAudioContext, + IOfflineAudioContextConstructor, + IOfflineAudioNodeFaker, + IUnpatchedOfflineAudioContextConstructor +} from '../interfaces'; import { AudioBufferCopyChannelMethodsSupportTester } from '../testers/audio-buffer-copy-channel-methods-support'; import { PromiseSupportTester } from '../testers/promise-support'; +import { TDecodeErrorCallback, TDecodeSuccessCallback, TUnpatchedOfflineAudioContext } from '../types'; import { AudioBufferWrapper } from '../wrappers/audio-buffer'; import { AudioBufferCopyChannelMethodsWrapper } from '../wrappers/audio-buffer-copy-channel-methods'; import { IIRFilterNodeGetFrequencyResponseMethodWrapper } from '../wrappers/iir-filter-node-get-frequency-response-method'; @@ -36,45 +48,45 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { ], provide: offlineAudioContextConstructor, useFactory: ( - audioBufferCopyChannelMethodsSupportTester, - audioBufferCopyChannelMethodsWrapper, - audioBufferWrapper, - dataCloneErrorFactory, - detachedAudioBuffers, - encodingErrorFactory, - iIRFilterNodeGetFrequencyResponseMethodWrapper, - offlineAudioBufferSourceNodeFakerFactory, - offlineAudioDestinationNodeFakerFactory, - offlineBiquadFilterNodeFakerFactory, - offlineGainNodeFakerFactory, - offlineIIRFilterNodeFakerFactory, - promiseSupportTester, - UnpatchedOfflineAudioContext // tslint:disable-line:variable-name + audioBufferCopyChannelMethodsSupportTester: AudioBufferCopyChannelMethodsSupportTester, + audioBufferCopyChannelMethodsWrapper: AudioBufferCopyChannelMethodsWrapper, + audioBufferWrapper: AudioBufferWrapper, + dataCloneErrorFactory: DataCloneErrorFactory, + detachedAudioBuffers: WeakSet, + encodingErrorFactory: EncodingErrorFactory, + iIRFilterNodeGetFrequencyResponseMethodWrapper: IIRFilterNodeGetFrequencyResponseMethodWrapper, + offlineAudioBufferSourceNodeFakerFactory: OfflineAudioBufferSourceNodeFakerFactory, + offlineAudioDestinationNodeFakerFactory: OfflineAudioDestinationNodeFakerFactory, + offlineBiquadFilterNodeFakerFactory: OfflineBiquadFilterNodeFakerFactory, + offlineGainNodeFakerFactory: OfflineGainNodeFakerFactory, + offlineIIRFilterNodeFakerFactory: OfflineIIRFilterNodeFakerFactory, + promiseSupportTester: PromiseSupportTester, + unpatchedOfflineAudioContextConstructor: IUnpatchedOfflineAudioContextConstructor ): IOfflineAudioContextConstructor => { class OfflineAudioContext implements IOfflineAudioContext { - private _destination; + private _destination: OfflineAudioDestinationNodeFaker; - private _fakeNodeStore; + private _fakeNodeStore: WeakMap; - private _isSupportingCopyChannelMethods; + private _isSupportingCopyChannelMethods: boolean; - private _isSupportingGetFrequencyResponseErrors; + private _isSupportingGetFrequencyResponseErrors: boolean; - private _isSupportingPromises; + private _isSupportingPromises: boolean; - private _length; + private _length: number; - private _numberOfChannels; + private _numberOfChannels: number; - private _unpatchedOfflineAudioContext; + private _unpatchedOfflineAudioContext: TUnpatchedOfflineAudioContext; - constructor (numberOfChannels, length, sampleRate) { + constructor (numberOfChannels: number, length: number, sampleRate: number) { const fakeNodeStore = new WeakMap(); - const unpatchedOfflineAudioContext = new UnpatchedOfflineAudioContext(numberOfChannels, length, sampleRate); + const unpatchedOfflineAudioContext = new unpatchedOfflineAudioContextConstructor(numberOfChannels, length, sampleRate); - this._destination = offlineAudioDestinationNodeFakerFactory.create({ fakeNodeStore }); + this._destination = offlineAudioDestinationNodeFakerFactory.create({ fakeNodeStore, offlineAudioContext: this }); this._fakeNodeStore = fakeNodeStore; this._isSupportingCopyChannelMethods = audioBufferCopyChannelMethodsSupportTester.test(unpatchedOfflineAudioContext); this._isSupportingGetFrequencyResponseErrors = false; @@ -84,7 +96,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { this._unpatchedOfflineAudioContext = unpatchedOfflineAudioContext; } - public get currentTime () { + public get currentTime (): number { return this._unpatchedOfflineAudioContext.currentTime; } @@ -92,7 +104,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return this._destination.proxy; } - public get length () { + public get length (): number { // Bug #17: Safari does not yet expose the length. if (this._unpatchedOfflineAudioContext.length === undefined) { return this._length; @@ -101,40 +113,55 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { return this._unpatchedOfflineAudioContext.length; } - public get sampleRate () { + public get onstatechange () { + return ( this._unpatchedOfflineAudioContext).onstatechange; + } + + public set onstatechange (value: (this: IOfflineAudioContext, ev: Event) => any) { + ( this._unpatchedOfflineAudioContext).onstatechange = value; + } + + public get sampleRate (): number { return this._unpatchedOfflineAudioContext.sampleRate; } - public createBiquadFilter () { + public get state () { + return this._unpatchedOfflineAudioContext.state; + } + + public createBiquadFilter (): IBiquadFilterNode { return offlineBiquadFilterNodeFakerFactory.create({ fakeNodeStore: this._fakeNodeStore, - nativeNode: this._unpatchedOfflineAudioContext.createBiquadFilter() + nativeNode: this._unpatchedOfflineAudioContext.createBiquadFilter(), + offlineAudioContext: this }).proxy; } - public createBuffer (numberOfChannels, length, sampleRate) { + public createBuffer (numberOfChannels: number, length: number, sampleRate: number): AudioBuffer { // @todo Consider browsers which do not fully support this method yet. return this._unpatchedOfflineAudioContext.createBuffer(numberOfChannels, length, sampleRate); } - public createBufferSource () { + public createBufferSource (): IAudioBufferSourceNode { return offlineAudioBufferSourceNodeFakerFactory.create({ - fakeNodeStore: this._fakeNodeStore + fakeNodeStore: this._fakeNodeStore, + offlineAudioContext: this }).proxy; } - public createGain () { + public createGain (): IGainNode { return offlineGainNodeFakerFactory.create({ - fakeNodeStore: this._fakeNodeStore + fakeNodeStore: this._fakeNodeStore, + offlineAudioContext: this }).proxy; } - public createIIRFilter (feedforward, feedback) { + public createIIRFilter (feedforward: number[], feedback: number[]): IIIRFilterNode { let nativeNode = null; // Bug #9: Safari does not support IIRFilterNodes. if (this._unpatchedOfflineAudioContext.createIIRFilter !== undefined) { - nativeNode = this._unpatchedOfflineAudioContext.createIIRFilter(feedforward, feedback); + nativeNode = this._unpatchedOfflineAudioContext.createIIRFilter(feedforward, feedback); // Bug 23 & 24: FirefoxDeveloper does not throw NotSupportedErrors anymore. if (!this._isSupportingGetFrequencyResponseErrors) { @@ -149,12 +176,14 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { length: this.length, nativeNode, numberOfChannels: this._numberOfChannels, + offlineAudioContext: this, sampleRate: this._unpatchedOfflineAudioContext.sampleRate }).proxy; } - public decodeAudioData (audioData, successCallback, errorCallback) { - // Bug #43: Only Chrome Canary does yet throw a DataCloneError. + public decodeAudioData ( + audioData: ArrayBuffer, successCallback?: TDecodeSuccessCallback, errorCallback?: TDecodeErrorCallback + ): Promise { // Bug #43: Only Chrome Canary does yet throw a DataCloneError. if (detachedAudioBuffers.has(audioData)) { const err = dataCloneErrorFactory.create(); @@ -180,7 +209,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } return this._unpatchedOfflineAudioContext - .decodeAudioData(audioData, successCallback, (err) => { + .decodeAudioData(audioData, successCallback, (err: DOMException | Error) => { if (typeof errorCallback === 'function') { // Bug #27: Edge is rejecting invalid arrayBuffers with a DOMException. if (err instanceof DOMException && err.name === 'NotSupportedError') { @@ -190,7 +219,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } } }) - .catch ((err) => { + .catch ((err: DOMException | Error) => { // Bug #6: Chrome, Firefox and Opera do not call the errorCallback in case of an invalid buffer. if (typeof errorCallback === 'function' && err instanceof TypeError) { errorCallback(err); @@ -207,7 +236,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #21: Safari does not return a Promise yet. return new Promise((resolve, reject) => { - const fail = (err) => { + const fail = (err: DOMException | Error) => { if (typeof errorCallback === 'function') { errorCallback(err); } @@ -215,7 +244,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { reject(err); }; - const succeed = (dBffrWrppr) => { + const succeed = (dBffrWrppr: AudioBuffer) => { resolve(dBffrWrppr); if (typeof successCallback === 'function') { @@ -226,7 +255,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { // Bug #26: Safari throws a synchronous error. try { // Bug #1: Safari requires a successCallback. - this._unpatchedOfflineAudioContext.decodeAudioData(audioData, (audioBuffer) => { + this._unpatchedOfflineAudioContext.decodeAudioData(audioData, (audioBuffer: AudioBuffer) => { // Bug #5: Safari does not support copyFromChannel() and copyToChannel(). if (typeof audioBuffer.copyFromChannel !== 'function') { audioBufferWrapper.wrap(audioBuffer); @@ -236,7 +265,7 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } succeed(audioBuffer); - }, (err) => { + }, (err: DOMException | Error) => { // Bug #4: Safari returns null instead of an error. if (err === null) { fail(encodingErrorFactory.create()); @@ -264,7 +293,9 @@ export const OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { } return new Promise((resolve) => { - this._unpatchedOfflineAudioContext.oncomplete = (event) => resolve(event.renderedBuffer); + ( this._unpatchedOfflineAudioContext).oncomplete = (event: IOfflineAudioCompletionEvent) => { + resolve(event.renderedBuffer); + }; this._unpatchedOfflineAudioContext.startRendering(); }); diff --git a/src/providers/unpatched-audio-context-constructor.ts b/src/providers/unpatched-audio-context-constructor.ts index b42744e8f..1779b4de7 100644 --- a/src/providers/unpatched-audio-context-constructor.ts +++ b/src/providers/unpatched-audio-context-constructor.ts @@ -1,12 +1,12 @@ import { OpaqueToken } from '@angular/core'; -import { Window } from './window'; +import { window as windowToken } from './window'; export const unpatchedAudioContextConstructor = new OpaqueToken('UNPATCHED_AUDIO_CONTEXT_CONSTRUCTOR'); export const UNPATCHED_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { - deps: [ Window ], + deps: [ windowToken ], provide: unpatchedAudioContextConstructor, - useFactory: (window) => (window.hasOwnProperty('AudioContext')) ? + useFactory: (window: any) => (window.hasOwnProperty('AudioContext')) ? window.AudioContext : (window.hasOwnProperty('webkitAudioContext')) ? window.webkitAudioContext : diff --git a/src/providers/unpatched-offline-audio-context-constructor.ts b/src/providers/unpatched-offline-audio-context-constructor.ts index 2873d1c9b..221200c7a 100644 --- a/src/providers/unpatched-offline-audio-context-constructor.ts +++ b/src/providers/unpatched-offline-audio-context-constructor.ts @@ -1,12 +1,12 @@ import { OpaqueToken } from '@angular/core'; -import { Window } from './window'; +import { window as windowToken } from './window'; export const unpatchedOfflineAudioContextConstructor = new OpaqueToken('UNPATCHED_OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR'); export const UNPATCHED_OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER = { - deps: [ Window ], + deps: [ windowToken ], provide: unpatchedOfflineAudioContextConstructor, - useFactory: (window) => (window.hasOwnProperty('OfflineAudioContext')) ? + useFactory: (window: any) => (window.hasOwnProperty('OfflineAudioContext')) ? window.OfflineAudioContext : (window.hasOwnProperty('webkitOfflineAudioContext')) ? window.webkitOfflineAudioContext : diff --git a/src/providers/window.ts b/src/providers/window.ts index cf82bb0c9..8a8efe133 100644 --- a/src/providers/window.ts +++ b/src/providers/window.ts @@ -1,5 +1,7 @@ import { OpaqueToken } from '@angular/core'; -export const Window = new OpaqueToken('WINDOW'); // tslint:disable-line:variable-name +const windowToken = new OpaqueToken('WINDOW'); -export const WINDOW_PROVIDER = { provide: Window, useValue: window }; +export { windowToken as window }; + +export const WINDOW_PROVIDER = { provide: windowToken, useValue: (typeof window === 'undefined') ? {} : window }; diff --git a/src/testers/analyser-node-get-float-time-domain-data.ts b/src/testers/analyser-node-get-float-time-domain-data.ts index 808a2bdbe..b2be1bba9 100644 --- a/src/testers/analyser-node-get-float-time-domain-data.ts +++ b/src/testers/analyser-node-get-float-time-domain-data.ts @@ -1,6 +1,8 @@ +import { TUnpatchedAudioContext } from '../types'; + export class AnalyserNodeGetFloatTimeDomainDataSupportTester { - public test (audioContext) { + public test (audioContext: TUnpatchedAudioContext) { const analyserNode = audioContext.createAnalyser(); return typeof analyserNode.getFloatTimeDomainData === 'function'; diff --git a/src/testers/audio-buffer-copy-channel-methods-support.ts b/src/testers/audio-buffer-copy-channel-methods-support.ts index 6229b2430..17629a0ce 100644 --- a/src/testers/audio-buffer-copy-channel-methods-support.ts +++ b/src/testers/audio-buffer-copy-channel-methods-support.ts @@ -1,6 +1,8 @@ +import { TUnpatchedAudioContext, TUnpatchedOfflineAudioContext } from '../types'; + export class AudioBufferCopyChannelMethodsSupportTester { - public test (audioContext) { + public test (audioContext: TUnpatchedAudioContext | TUnpatchedOfflineAudioContext) { const audioBuffer = audioContext.createBuffer(1, 1, audioContext.sampleRate); const source = new Float32Array(2); diff --git a/src/testers/chaining-support.ts b/src/testers/chaining-support.ts index 916cdd78c..3152f35ef 100644 --- a/src/testers/chaining-support.ts +++ b/src/testers/chaining-support.ts @@ -1,6 +1,8 @@ +import { TUnpatchedAudioContext } from '../types'; + export class ChainingSupportTester { - public test (audioContext) { + public test (audioContext: TUnpatchedAudioContext) { const destination = audioContext.createGain(); const target = audioContext.createGain(); diff --git a/src/testers/close-support.ts b/src/testers/close-support.ts index 7f7445464..7346c3d89 100644 --- a/src/testers/close-support.ts +++ b/src/testers/close-support.ts @@ -1,17 +1,20 @@ import { Inject, Injectable } from '@angular/core'; +import { IUnpatchedAudioContextConstructor } from '../interfaces'; import { unpatchedAudioContextConstructor } from '../providers/unpatched-audio-context-constructor'; @Injectable() export class CloseSupportTester { - constructor (@Inject(unpatchedAudioContextConstructor) private _UnpatchedAudioContext) { } + constructor ( + @Inject(unpatchedAudioContextConstructor) private _unpatchedAudioContextConstructor: IUnpatchedAudioContextConstructor + ) { } public test () { - if (this._UnpatchedAudioContext === null) { + if (this._unpatchedAudioContextConstructor === null) { return false; } - const audioContext = new this._UnpatchedAudioContext(); + const audioContext = new this._unpatchedAudioContextConstructor(); const isAudioContextClosable = (audioContext.close !== undefined); diff --git a/src/testers/connecting-support.ts b/src/testers/connecting-support.ts index 60fefcb25..e36d61997 100644 --- a/src/testers/connecting-support.ts +++ b/src/testers/connecting-support.ts @@ -1,18 +1,22 @@ import { Inject, Injectable } from '@angular/core'; +import { IUnpatchedAudioContextConstructor } from '../interfaces'; import { unpatchedAudioContextConstructor } from '../providers/unpatched-audio-context-constructor'; +import { TUnpatchedAudioContext } from '../types'; @Injectable() export class ConnectingSupportTester { - constructor (@Inject(unpatchedAudioContextConstructor) private _UnpatchedAudioContext) { } + constructor ( + @Inject(unpatchedAudioContextConstructor) private _unpatchedAudioContextConstructor: IUnpatchedAudioContextConstructor + ) { } - public test (audioContext) { - if (this._UnpatchedAudioContext === null) { + public test (audioContext: TUnpatchedAudioContext) { + if (this._unpatchedAudioContextConstructor === null) { return false; } const analyserNode = audioContext.createAnalyser(); - const anotherAudioContext = new this._UnpatchedAudioContext(); + const anotherAudioContext = new this._unpatchedAudioContextConstructor(); try { analyserNode.connect(anotherAudioContext.destination); @@ -21,6 +25,8 @@ export class ConnectingSupportTester { } finally { anotherAudioContext.close(); } + + return false; } } diff --git a/src/testers/decode-audio-data-type-error-support.ts b/src/testers/decode-audio-data-type-error-support.ts index 190bb213f..f861ed4cd 100644 --- a/src/testers/decode-audio-data-type-error-support.ts +++ b/src/testers/decode-audio-data-type-error-support.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@angular/core'; +import { IUnpatchedAudioContextConstructor } from '../interfaces'; import { unpatchedAudioContextConstructor } from '../providers/unpatched-audio-context-constructor'; /** @@ -8,20 +9,22 @@ import { unpatchedAudioContextConstructor } from '../providers/unpatched-audio-c @Injectable() export class DecodeAudioDataTypeErrorSupportTester { - constructor (@Inject(unpatchedAudioContextConstructor) private _UnpatchedAudioContext) { } + constructor ( + @Inject(unpatchedAudioContextConstructor) private _unpatchedAudioContextConstructor: IUnpatchedAudioContextConstructor + ) { } public test (): Promise { - if (this._UnpatchedAudioContext === null) { + if (this._unpatchedAudioContextConstructor === null) { return Promise.resolve(false); } - const audioContext = new this._UnpatchedAudioContext(); + const audioContext = new this._unpatchedAudioContextConstructor(); // Bug #21: Safari does not support promises yet. // Bug #1: Chrome Canary & Safari requires a successCallback. return new Promise((resolve) => { audioContext - .decodeAudioData(null, () => { + .decodeAudioData( null, () => { // Ignore the success callback. }, (err) => { audioContext diff --git a/src/testers/disconnecting-support.ts b/src/testers/disconnecting-support.ts index df012b66c..58f5b7579 100644 --- a/src/testers/disconnecting-support.ts +++ b/src/testers/disconnecting-support.ts @@ -1,7 +1,9 @@ +import { TUnpatchedAudioContext } from '../types'; + export class DisconnectingSupportTester { - public test (audioContext) { - return new Promise((resolve, reject) => { + public test (audioContext: TUnpatchedAudioContext): Promise { + return new Promise((resolve) => { const analyzer = audioContext.createScriptProcessor(256, 1, 1); const dummy = audioContext.createGain(); @@ -27,7 +29,7 @@ export class DisconnectingSupportTester { analyzer.onaudioprocess = (event) => { const chnnlDt = event.inputBuffer.getChannelData(0); - if (Array.prototype.some.call(chnnlDt, (sample) => sample === 1)) { + if (Array.prototype.some.call(chnnlDt, (sample: number) => sample === 1)) { resolve(true); } else { resolve(false); @@ -35,7 +37,7 @@ export class DisconnectingSupportTester { source.stop(); - analyzer.onaudioprocess = null; + ( analyzer).onaudioprocess = null; source.disconnect(analyzer); analyzer.disconnect(audioContext.destination); diff --git a/src/testers/merging-support.ts b/src/testers/merging-support.ts index 474a5abef..127dd4c4e 100644 --- a/src/testers/merging-support.ts +++ b/src/testers/merging-support.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@angular/core'; +import { IUnpatchedAudioContextConstructor } from '../interfaces'; import { unpatchedAudioContextConstructor } from '../providers/unpatched-audio-context-constructor'; /** @@ -9,21 +10,23 @@ import { unpatchedAudioContextConstructor } from '../providers/unpatched-audio-c @Injectable() export class MergingSupportTester { - constructor (@Inject(unpatchedAudioContextConstructor) private _UnpatchedAudioContext) { } + constructor ( + @Inject(unpatchedAudioContextConstructor) private _unpatchedAudioContextConstructor: IUnpatchedAudioContextConstructor + ) { } public test () { - if (this._UnpatchedAudioContext === null) { + if (this._unpatchedAudioContextConstructor === null) { return Promise.resolve(false); } - const audioContext = new this._UnpatchedAudioContext(); + const audioContext = new this._unpatchedAudioContextConstructor(); const audioBufferSourceNode = audioContext.createBufferSource(); const audioBuffer = audioContext.createBuffer(2, 2, audioContext.sampleRate); const channelMergerNode = audioContext.createChannelMerger(2); - const scriptProcessorNode = audioContext.createScriptProcessor(256); + const scriptProcessorNode = ( audioContext).createScriptProcessor(256); return new Promise((resolve) => { - let startTime; + let startTime: number; // @todo Safari does not play/loop 1 sample buffers. This should be patched. audioBuffer.getChannelData(0)[0] = 1; @@ -34,7 +37,7 @@ export class MergingSupportTester { audioBufferSourceNode.buffer = audioBuffer; audioBufferSourceNode.loop = true; - scriptProcessorNode.onaudioprocess = (event) => { + scriptProcessorNode.onaudioprocess = (event: AudioProcessingEvent) => { const channelData = event.inputBuffer.getChannelData(1); const length = channelData.length; diff --git a/src/testers/promise-support.ts b/src/testers/promise-support.ts index 07eca224a..7516262cc 100644 --- a/src/testers/promise-support.ts +++ b/src/testers/promise-support.ts @@ -1,6 +1,8 @@ +import {  TUnpatchedAudioContext, TUnpatchedOfflineAudioContext } from '../types'; + export class PromiseSupportTester { - public test (audioContext) { + public test (audioContext: TUnpatchedAudioContext | TUnpatchedOfflineAudioContext) { // This 12 numbers represent the 48 bytes of an empty WAVE file with a single sample. const uint32Array = new Uint32Array([ 1179011410, diff --git a/src/testers/stop-stopped-support.ts b/src/testers/stop-stopped-support.ts index d47adff07..1a24bf18a 100644 --- a/src/testers/stop-stopped-support.ts +++ b/src/testers/stop-stopped-support.ts @@ -1,6 +1,8 @@ +import { TUnpatchedAudioContext } from '../types'; + export class StopStoppedSupportTester { - public test (audioContext) { + public test (audioContext: TUnpatchedAudioContext) { const audioBuffer = audioContext.createBuffer(1, 1, 44100); const audioBufferSourceNode = audioContext.createBufferSource(); diff --git a/src/tsconfig.json b/src/tsconfig.json index 0d9ae8c3b..0beddfb3e 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,13 +1,22 @@ { "compileOnSave": false, "compilerOptions": { + "alwaysStrict": true, "declaration": true, + "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": [ "dom", "es2015" ], "module": "es2015", "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, "outDir": "../build/es2015", "sourceMap": true, + "strictNullChecks": true, "target": "es2015", "typeRoots": [ "../node_modules/@types" diff --git a/src/types/audio-context-state.ts b/src/types/audio-context-state.ts new file mode 100644 index 000000000..fb74ab6bd --- /dev/null +++ b/src/types/audio-context-state.ts @@ -0,0 +1 @@ +export type TAudioContextState = 'closed' | 'running' | 'suspended'; diff --git a/src/types/biquad-filter-type.ts b/src/types/biquad-filter-type.ts new file mode 100644 index 000000000..a185158dd --- /dev/null +++ b/src/types/biquad-filter-type.ts @@ -0,0 +1 @@ +export type TBiquadFilterType = 'allpass' | 'bandpass' | 'highpass' | 'highshelf' | 'lowpass' | 'lowshelf' | 'notch' | 'peaking'; diff --git a/src/types/channel-count-mode.ts b/src/types/channel-count-mode.ts new file mode 100644 index 000000000..780bd4846 --- /dev/null +++ b/src/types/channel-count-mode.ts @@ -0,0 +1 @@ +export type TChannelCountMode = 'clamped-max' | 'explicit' | 'max'; diff --git a/src/types/channel-interpretation.ts b/src/types/channel-interpretation.ts new file mode 100644 index 000000000..6f3be6cf8 --- /dev/null +++ b/src/types/channel-interpretation.ts @@ -0,0 +1 @@ +export type TChannelInterpretation = 'discrete' | 'speakers'; diff --git a/src/types/decode-error-callback.ts b/src/types/decode-error-callback.ts new file mode 100644 index 000000000..d8e320cd5 --- /dev/null +++ b/src/types/decode-error-callback.ts @@ -0,0 +1 @@ +export type TDecodeErrorCallback = (error: DOMException | TypeError) => void; diff --git a/src/types/decode-success-callback.ts b/src/types/decode-success-callback.ts new file mode 100644 index 000000000..093f5dfdc --- /dev/null +++ b/src/types/decode-success-callback.ts @@ -0,0 +1 @@ +export type TDecodeSuccessCallback = (decodedData: AudioBuffer) => void; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 000000000..8d23fd1d5 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,10 @@ +export * from './audio-context-state'; +export * from './biquad-filter-type'; +export * from './channel-count-mode'; +export * from './channel-interpretation'; +export * from './decode-error-callback'; +export * from './decode-success-callback'; +export * from './oscillator-type'; +export * from './state-change-event-handler'; +export * from './unpatched-audio-context'; +export * from './unpatched-offline-audio-context'; diff --git a/src/types/oscillator-type.ts b/src/types/oscillator-type.ts new file mode 100644 index 000000000..6d610e667 --- /dev/null +++ b/src/types/oscillator-type.ts @@ -0,0 +1 @@ +export type TOscillatorType = 'custom' | 'sawtooth' | 'sine' | 'square' | 'triangle'; diff --git a/src/types/state-change-event-handler.ts b/src/types/state-change-event-handler.ts new file mode 100644 index 000000000..3457468e6 --- /dev/null +++ b/src/types/state-change-event-handler.ts @@ -0,0 +1,3 @@ +import { IBaseAudioContext } from '../interfaces'; + +export type TStateChangeEventHandler = (this: IBaseAudioContext, event: Event) => any; diff --git a/src/types/unpatched-audio-context.ts b/src/types/unpatched-audio-context.ts new file mode 100644 index 000000000..049e09caa --- /dev/null +++ b/src/types/unpatched-audio-context.ts @@ -0,0 +1 @@ +export type TUnpatchedAudioContext = AudioContext; diff --git a/src/types/unpatched-offline-audio-context.ts b/src/types/unpatched-offline-audio-context.ts new file mode 100644 index 000000000..c7f25c60b --- /dev/null +++ b/src/types/unpatched-offline-audio-context.ts @@ -0,0 +1 @@ +export type TUnpatchedOfflineAudioContext = OfflineAudioContext; diff --git a/src/wrappers/analyser-node-get-float-time-domain-data-method.ts b/src/wrappers/analyser-node-get-float-time-domain-data-method.ts index 2d585a431..88aa20203 100644 --- a/src/wrappers/analyser-node-get-float-time-domain-data-method.ts +++ b/src/wrappers/analyser-node-get-float-time-domain-data-method.ts @@ -1,7 +1,9 @@ +import { IAnalyserNode } from '../interfaces'; + export class AnalyserNodeGetFloatTimeDomainDataMethodWrapper { - public wrap (analyserNode) { - analyserNode.getFloatTimeDomainData = (array) => { + public wrap (analyserNode: IAnalyserNode) { + analyserNode.getFloatTimeDomainData = (array: Float32Array) => { const byteTimeDomainData = new Uint8Array(array.length); analyserNode.getByteTimeDomainData(byteTimeDomainData); diff --git a/src/wrappers/audio-buffer-copy-channel-methods.ts b/src/wrappers/audio-buffer-copy-channel-methods.ts index 0e537fd90..36da02940 100644 --- a/src/wrappers/audio-buffer-copy-channel-methods.ts +++ b/src/wrappers/audio-buffer-copy-channel-methods.ts @@ -1,8 +1,9 @@ export class AudioBufferCopyChannelMethodsWrapper { - public wrap (audioBuffer) { + public wrap (audioBuffer: AudioBuffer) { + audioBuffer.copyFromChannel = ((copyFromChannel) => { - return (destination, channelNumber, startInChannel = 0) => { + return (destination: Float32Array, channelNumber: number, startInChannel = 0) => { if (startInChannel < audioBuffer.length && audioBuffer.length - startInChannel < destination.length) { return copyFromChannel.call( audioBuffer, destination.subarray(0, audioBuffer.length - startInChannel), channelNumber, startInChannel @@ -14,7 +15,7 @@ export class AudioBufferCopyChannelMethodsWrapper { })(audioBuffer.copyFromChannel); audioBuffer.copyToChannel = ((copyToChannel) => { - return (source, channelNumber, startInChannel = 0) => { + return (source: Float32Array, channelNumber: number, startInChannel = 0) => { if (startInChannel < audioBuffer.length && audioBuffer.length - startInChannel < source.length) { return copyToChannel.call( audioBuffer, source.subarray(0, audioBuffer.length - startInChannel), channelNumber, startInChannel diff --git a/src/wrappers/audio-buffer-source-node-stop-method.ts b/src/wrappers/audio-buffer-source-node-stop-method.ts index c880fcfa2..7e1095b8f 100644 --- a/src/wrappers/audio-buffer-source-node-stop-method.ts +++ b/src/wrappers/audio-buffer-source-node-stop-method.ts @@ -1,12 +1,14 @@ +import { IAudioBufferSourceNode, IAudioContext, IAudioNode } from '../interfaces'; + export class AudioBufferSourceNodeStopMethodWrapper { - public wrap (audioBufferSourceNode, audioContext) { + public wrap (audioBufferSourceNode: IAudioBufferSourceNode, audioContext: IAudioContext) { const gainNode = audioContext.createGain(); audioBufferSourceNode.connect(gainNode); audioBufferSourceNode.addEventListener('ended', () => audioBufferSourceNode.disconnect(gainNode)); - audioBufferSourceNode.connect = (destination, output = 0, input = 0) => { + audioBufferSourceNode.connect = (destination: IAudioNode, output = 0, input = 0) => { gainNode.connect.call(gainNode, destination, output, input); return destination; diff --git a/src/wrappers/audio-buffer.ts b/src/wrappers/audio-buffer.ts index 36866cca3..f007b4527 100644 --- a/src/wrappers/audio-buffer.ts +++ b/src/wrappers/audio-buffer.ts @@ -1,12 +1,12 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { IndexSizeErrorFactory } from '../factories/index-size-error'; @Injectable() export class AudioBufferWrapper { - constructor (@Inject(IndexSizeErrorFactory) private _indexSizeErrorFactory) { } + constructor (private _indexSizeErrorFactory: IndexSizeErrorFactory) { } - public wrap (audioBuffer) { + public wrap (audioBuffer: AudioBuffer) { audioBuffer.copyFromChannel = (destination, channelNumber, startInChannel = 0) => { if (channelNumber >= audioBuffer.numberOfChannels || startInChannel >= audioBuffer.length) { throw this._indexSizeErrorFactory.create(); diff --git a/src/wrappers/audio-node-connect-method.ts b/src/wrappers/audio-node-connect-method.ts index 3a3bb8d58..3c054b91d 100644 --- a/src/wrappers/audio-node-connect-method.ts +++ b/src/wrappers/audio-node-connect-method.ts @@ -1,15 +1,16 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { InvalidAccessErrorFactory } from '../factories/invalid-access-error'; +import { IAudioNode } from '../interfaces'; @Injectable() export class AudioNodeConnectMethodWrapper { - constructor (@Inject(InvalidAccessErrorFactory) private _invalidAccessErrorFactory) { } + constructor (private _invalidAccessErrorFactory: InvalidAccessErrorFactory) { } - public wrap (audioNode) { - audioNode.connect = ((connect, isSupportingChaining, isSupportingConnecting) => { - if (isSupportingChaining) { - return (destination, output = 0, input = 0) => { + public wrap (audioNode: IAudioNode, isSupportingChaining: boolean) { + audioNode.connect = ((connect, sSpprtngChnng: boolean) => { + if (sSpprtngChnng) { + return (destination: IAudioNode, output = 0, input = 0) => { try { return connect.call(audioNode, destination, output, input); } catch (err) { @@ -21,7 +22,7 @@ export class AudioNodeConnectMethodWrapper { } }; } else { - return (destination, output = 0, input = 0) => { + return (destination: IAudioNode, output = 0, input = 0) => { try { connect.call(audioNode, destination, output, input); } catch (err) { @@ -35,7 +36,7 @@ export class AudioNodeConnectMethodWrapper { return destination; }; } - })(audioNode.connect); + })(audioNode.connect, isSupportingChaining); } } diff --git a/src/wrappers/audio-node-disconnect-method.ts b/src/wrappers/audio-node-disconnect-method.ts index 2fc8f0977..7ac46d227 100644 --- a/src/wrappers/audio-node-disconnect-method.ts +++ b/src/wrappers/audio-node-disconnect-method.ts @@ -1,10 +1,12 @@ +import { IAudioNode } from '../interfaces'; + export class AudioNodeDisconnectMethodWrapper { - public wrap (audioNode) { + public wrap (audioNode: IAudioNode) { const destinations = new Map(); audioNode.connect = ((connect) => { - return (destination, output = 0, input = 0) => { + return (destination: IAudioNode, output = 0, input = 0) => { destinations.set(destination, { input, output }); return connect.call(audioNode, destination, output, input); @@ -12,10 +14,10 @@ export class AudioNodeDisconnectMethodWrapper { })(audioNode.connect); audioNode.disconnect = ((disconnect) => { - return (destination = null) => { + return (destination?: IAudioNode) => { disconnect.apply(audioNode); - if (destination !== null && destinations.has(destination)) { + if (destination !== undefined && destinations.has(destination)) { destinations.delete(destination); destinations.forEach(({ input, output }, dstntn) => { diff --git a/src/wrappers/channel-merger-node.ts b/src/wrappers/channel-merger-node.ts index ba5abc872..3845936e8 100644 --- a/src/wrappers/channel-merger-node.ts +++ b/src/wrappers/channel-merger-node.ts @@ -1,12 +1,13 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { InvalidStateErrorFactory } from '../factories/invalid-state-error'; +import { TUnpatchedAudioContext } from '../types'; @Injectable() export class ChannelMergerNodeWrapper { - constructor (@Inject(InvalidStateErrorFactory) private _invalidStateErrorFactory) { } + constructor (private _invalidStateErrorFactory: InvalidStateErrorFactory) { } - public wrap (audioContext, channelMergerNode) { + public wrap (audioContext: TUnpatchedAudioContext, channelMergerNode: ChannelMergerNode) { const audioBufferSourceNode = audioContext.createBufferSource(); channelMergerNode.channelCount = 1; diff --git a/src/wrappers/channel-splitter-node.ts b/src/wrappers/channel-splitter-node.ts index bdb6e3b86..803eed016 100644 --- a/src/wrappers/channel-splitter-node.ts +++ b/src/wrappers/channel-splitter-node.ts @@ -1,12 +1,12 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { InvalidStateErrorFactory } from '../factories/invalid-state-error'; @Injectable() export class ChannelSplitterNodeWrapper { - constructor (@Inject(InvalidStateErrorFactory) private _invalidStateErrorFactory) { } + constructor (private _invalidStateErrorFactory: InvalidStateErrorFactory) { } - public wrap (channelSplitterNode) { + public wrap (channelSplitterNode: ChannelSplitterNode) { channelSplitterNode.channelCountMode = 'explicit'; channelSplitterNode.channelInterpretation = 'discrete'; diff --git a/src/wrappers/iir-filter-node-get-frequency-response-method.ts b/src/wrappers/iir-filter-node-get-frequency-response-method.ts index e2eba15f1..f1b39869d 100644 --- a/src/wrappers/iir-filter-node-get-frequency-response-method.ts +++ b/src/wrappers/iir-filter-node-get-frequency-response-method.ts @@ -1,14 +1,15 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { NotSupportedErrorFactory } from '../factories/not-supported-error'; +import { IIIRFilterNode } from '../interfaces'; @Injectable() export class IIRFilterNodeGetFrequencyResponseMethodWrapper { - constructor (@Inject(NotSupportedErrorFactory) private _notSupportedErrorFactory) { } + constructor (private _notSupportedErrorFactory: NotSupportedErrorFactory) { } - public wrap (iIRFilterNode) { + public wrap (iIRFilterNode: IIIRFilterNode) { iIRFilterNode.getFrequencyResponse = ((getFrequencyResponse) => { - return (frequencyHz, magResponse, phaseResponse) => { + return (frequencyHz: Float32Array, magResponse: Float32Array, phaseResponse: Float32Array) => { if (magResponse.length === 0 || phaseResponse.length === 0) { throw this._notSupportedErrorFactory.create(); } diff --git a/test/unit/providers/unpatched-audio-context-constructor.js b/test/unit/providers/unpatched-audio-context-constructor.js index fe6d8a196..705abdabd 100644 --- a/test/unit/providers/unpatched-audio-context-constructor.js +++ b/test/unit/providers/unpatched-audio-context-constructor.js @@ -1,7 +1,7 @@ import 'core-js/es7/reflect'; import { UNPATCHED_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, unpatchedAudioContextConstructor } from '../../../src/providers/unpatched-audio-context-constructor'; import { ReflectiveInjector } from '@angular/core'; -import { Window } from '../../../src/providers/window'; +import { window } from '../../../src/providers/window'; describe('UnpatchedAudioContext', () => { @@ -19,7 +19,7 @@ describe('UnpatchedAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedAudioContextConstructor)).to.equal(null); @@ -32,7 +32,7 @@ describe('UnpatchedAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedAudioContextConstructor)).to.equal(webkitAudioContext); @@ -45,7 +45,7 @@ describe('UnpatchedAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedAudioContextConstructor)).to.equal(AudioContext); @@ -59,7 +59,7 @@ describe('UnpatchedAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedAudioContextConstructor)).to.equal(AudioContext); diff --git a/test/unit/providers/unpatched-offline-audio-context-constructor.js b/test/unit/providers/unpatched-offline-audio-context-constructor.js index 9873a0be4..93432e5c0 100644 --- a/test/unit/providers/unpatched-offline-audio-context-constructor.js +++ b/test/unit/providers/unpatched-offline-audio-context-constructor.js @@ -1,7 +1,7 @@ import 'core-js/es7/reflect'; import { UNPATCHED_OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, unpatchedOfflineAudioContextConstructor } from '../../../src/providers/unpatched-offline-audio-context-constructor'; import { ReflectiveInjector } from '@angular/core'; -import { Window } from '../../../src/providers/window'; +import { window } from '../../../src/providers/window'; describe('UnpatchedOfflineAudioContext', () => { @@ -19,7 +19,7 @@ describe('UnpatchedOfflineAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedOfflineAudioContextConstructor)).to.equal(null); @@ -32,7 +32,7 @@ describe('UnpatchedOfflineAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedOfflineAudioContextConstructor)).to.equal(webkitOfflineAudioContext); @@ -45,7 +45,7 @@ describe('UnpatchedOfflineAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedOfflineAudioContextConstructor)).to.equal(OfflineAudioContext); @@ -59,7 +59,7 @@ describe('UnpatchedOfflineAudioContext', () => { const injector = ReflectiveInjector.resolveAndCreate([ UNPATCHED_OFFLINE_AUDIO_CONTEXT_CONSTRUCTOR_PROVIDER, - { provide: Window, useValue: fakeWindow } + { provide: window, useValue: fakeWindow } ]); expect(injector.get(unpatchedOfflineAudioContextConstructor)).to.equal(OfflineAudioContext); diff --git a/test/unit/providers/window.js b/test/unit/providers/window.js index 35b879ed5..14f17beb1 100644 --- a/test/unit/providers/window.js +++ b/test/unit/providers/window.js @@ -1,5 +1,5 @@ import 'core-js/es7/reflect'; -import { WINDOW_PROVIDER, Window } from '../../../src/providers/window'; +import { WINDOW_PROVIDER, window as windowToken } from '../../../src/providers/window'; import { ReflectiveInjector } from '@angular/core'; describe('window', () => { @@ -9,7 +9,7 @@ describe('window', () => { WINDOW_PROVIDER ]); - expect(injector.get(Window)).to.equal(window); + expect(injector.get(windowToken)).to.equal(window); }); });