@fluex/fluexgl-dsp 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -38
- package/lib/dist/core/classes/AudioClip.d.ts +3 -1
- package/lib/dist/core/classes/AudioClip.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioClip.js +18 -5
- package/lib/dist/core/classes/AudioClipPlayer.d.ts +8 -2
- package/lib/dist/core/classes/AudioClipPlayer.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioClipPlayer.js +48 -1
- package/lib/dist/core/classes/AudioDevice.d.ts +2 -0
- package/lib/dist/core/classes/AudioDevice.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioDevice.js +4 -0
- package/lib/dist/core/classes/Channel.d.ts +10 -2
- package/lib/dist/core/classes/Channel.d.ts.map +1 -1
- package/lib/dist/core/classes/Channel.js +67 -0
- package/lib/dist/core/classes/DpsPipeline.d.ts +1 -1
- package/lib/dist/core/classes/DpsPipeline.d.ts.map +1 -1
- package/lib/dist/core/classes/DpsPipeline.js +7 -3
- package/lib/dist/core/classes/Effector.d.ts +2 -2
- package/lib/dist/core/classes/Effector.d.ts.map +1 -1
- package/lib/dist/core/classes/Effector.js +1 -1
- package/lib/dist/core/classes/Master.d.ts +12 -0
- package/lib/dist/core/classes/Master.d.ts.map +1 -1
- package/lib/dist/core/classes/Master.js +77 -7
- package/lib/dist/effects/classes/Analyser.d.ts +16 -0
- package/lib/dist/effects/classes/Analyser.d.ts.map +1 -0
- package/lib/dist/effects/classes/Analyser.js +62 -0
- package/lib/dist/effects/classes/Chorus.d.ts +4 -3
- package/lib/dist/effects/classes/Chorus.d.ts.map +1 -1
- package/lib/dist/effects/classes/Chorus.js +21 -12
- package/lib/dist/effects/classes/clips/HardClip.d.ts +4 -3
- package/lib/dist/effects/classes/clips/HardClip.d.ts.map +1 -1
- package/lib/dist/effects/classes/clips/HardClip.js +15 -9
- package/lib/dist/effects/classes/clips/SoftClip.d.ts +4 -3
- package/lib/dist/effects/classes/clips/SoftClip.d.ts.map +1 -1
- package/lib/dist/effects/classes/clips/SoftClip.js +15 -9
- package/lib/dist/effects/classes/filters/LowPassFilter.d.ts +4 -3
- package/lib/dist/effects/classes/filters/LowPassFilter.d.ts.map +1 -1
- package/lib/dist/effects/classes/filters/LowPassFilter.js +23 -14
- package/lib/dist/effects/exports.d.ts +1 -0
- package/lib/dist/effects/exports.d.ts.map +1 -1
- package/lib/dist/effects/exports.js +1 -0
- package/lib/dist/index.d.ts +5 -4
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +13 -18
- package/lib/dist/typings.d.ts +13 -11
- package/lib/dist/typings.d.ts.map +1 -1
- package/lib/dist/typings.js +5 -0
- package/lib/dist/utilities/debugger.js +6 -6
- package/lib/dist/utilities/helpers.d.ts +2 -0
- package/lib/dist/utilities/helpers.d.ts.map +1 -1
- package/lib/dist/utilities/helpers.js +35 -12
- package/lib/src/core/classes/AudioClip.ts +23 -7
- package/lib/src/core/classes/AudioClipPlayer.ts +67 -7
- package/lib/src/core/classes/AudioDevice.ts +5 -0
- package/lib/src/core/classes/Channel.ts +97 -4
- package/lib/src/core/classes/DpsPipeline.ts +7 -3
- package/lib/src/core/classes/Effector.ts +3 -2
- package/lib/src/core/classes/Master.ts +98 -11
- package/lib/src/effects/classes/Analyser.ts +84 -0
- package/lib/src/effects/classes/Chorus.ts +22 -13
- package/lib/src/effects/classes/clips/HardClip.ts +17 -10
- package/lib/src/effects/classes/clips/SoftClip.ts +17 -10
- package/lib/src/effects/classes/filters/LowPassFilter.ts +28 -15
- package/lib/src/effects/exports.ts +2 -1
- package/lib/src/index.ts +27 -29
- package/lib/src/typings.ts +14 -14
- package/lib/src/utilities/debugger.ts +6 -6
- package/lib/src/utilities/helpers.ts +38 -14
- package/package.json +1 -1
- package/lib/dist/effects/classes/Filter.d.ts +0 -3
- package/lib/dist/effects/classes/Filter.d.ts.map +0 -1
- package/lib/dist/effects/classes/Filter.js +0 -2
- package/lib/dist/effects/classes/SoftClip.d.ts +0 -10
- package/lib/dist/effects/classes/SoftClip.d.ts.map +0 -1
- package/lib/dist/effects/classes/SoftClip.js +0 -39
- package/lib/dist/test/linkable-channels.d.ts +0 -2
- package/lib/dist/test/linkable-channels.d.ts.map +0 -1
- package/lib/dist/test/linkable-channels.js +0 -16
- package/lib/src/core/functions/rebuild-effect-chain.ts +0 -5
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DSP } from "../index";
|
|
2
2
|
export var Debug;
|
|
3
3
|
(function (Debug) {
|
|
4
4
|
function Log(message, additionalDetails) {
|
|
5
|
-
if (!
|
|
5
|
+
if (!DSP.debugger.showInfo)
|
|
6
6
|
return;
|
|
7
7
|
let outputString = `%c[INFO]: %c${message} %c`;
|
|
8
8
|
additionalDetails && additionalDetails.forEach(function (detail) {
|
|
@@ -12,7 +12,7 @@ export var Debug;
|
|
|
12
12
|
}
|
|
13
13
|
Debug.Log = Log;
|
|
14
14
|
function Success(message, additionalDetails) {
|
|
15
|
-
if (!
|
|
15
|
+
if (!DSP.debugger.showInfo)
|
|
16
16
|
return;
|
|
17
17
|
let outputString = `%c[SUCCESS]: %c${message} %c`;
|
|
18
18
|
additionalDetails && additionalDetails.forEach(function (detail) {
|
|
@@ -22,7 +22,7 @@ export var Debug;
|
|
|
22
22
|
}
|
|
23
23
|
Debug.Success = Success;
|
|
24
24
|
function Warn(message, additionalDetails, warningCode) {
|
|
25
|
-
if (!
|
|
25
|
+
if (!DSP.debugger.showWarnings)
|
|
26
26
|
return;
|
|
27
27
|
let outputString = `%c(${warningCode ? warningCode : "Unknown".toUpperCase()}) %c[WARNING]: %c${message} %c`;
|
|
28
28
|
(additionalDetails && additionalDetails !== null) && additionalDetails.forEach(function (detail) {
|
|
@@ -32,14 +32,14 @@ export var Debug;
|
|
|
32
32
|
}
|
|
33
33
|
Debug.Warn = Warn;
|
|
34
34
|
function Error(message, additionalDetails, errorCode) {
|
|
35
|
-
if (!
|
|
35
|
+
if (!DSP.debugger.showErrors)
|
|
36
36
|
return;
|
|
37
37
|
let outputString = `%c(${errorCode ? errorCode : "Unknown".toUpperCase()}) %c[ERROR]: %c${message} %c`;
|
|
38
38
|
(additionalDetails && additionalDetails !== null) && additionalDetails.forEach(function (detail) {
|
|
39
39
|
outputString += `\n\u0009 > ${detail}`;
|
|
40
40
|
});
|
|
41
41
|
console.log(outputString, "color: red", "color: red;", "color: white", "color: white; font-style: italic;");
|
|
42
|
-
if (
|
|
42
|
+
if (DSP.debugger.breakOnError) {
|
|
43
43
|
for (let char of message)
|
|
44
44
|
outputString = outputString.replace("%c", "");
|
|
45
45
|
throw new window.Error(outputString);
|
|
@@ -42,5 +42,7 @@ export declare function LoadAudioSourceFromBlob(blob: Blob): Promise<AudioSource
|
|
|
42
42
|
export declare function ConstructProcessorWorklet(code: string): string;
|
|
43
43
|
export declare function LoadWorkletOnAudioDevice(audioDevice: AudioDevice, workletBlobUrl: string): Promise<boolean>;
|
|
44
44
|
export declare function SendMessageToWorklet<T, K = any>(node: AudioWorkletNode | null, commandId: T, data: K): boolean;
|
|
45
|
+
export declare function IsFiniteNumber(value: unknown): value is number;
|
|
46
|
+
export declare function CoerceFiniteNumber(value: unknown, fallback: number): number;
|
|
45
47
|
export declare function CreateAudioWorkletNode<T = any>(context: AudioContext, name: AudioWorkletProcessorNames | string, data: T): AudioWorkletNode | null;
|
|
46
48
|
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utilities/helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAS1D,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,gCAAgC,EAAE,8BAA8B,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAEnK;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,8BAA8B,GAAG,IAAI,CAAC,CAwCrI;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAUxE;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAUvE;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CAAC,IAAI,EAAE,8BAA8B,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAmBvH;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,8BAA8B,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAmBtH;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,CAAC,sBAAsB,CAAoC,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAoChK;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAYzF;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO9D;AAED,wBAAsB,wBAAwB,CAAC,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAgB9F;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utilities/helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAS1D,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,gCAAgC,EAAE,8BAA8B,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAEnK;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,8BAA8B,GAAG,IAAI,CAAC,CAwCrI;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAUxE;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAUvE;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CAAC,IAAI,EAAE,8BAA8B,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAmBvH;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,8BAA8B,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAmBtH;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,CAAC,sBAAsB,CAAoC,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAoChK;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAYzF;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO9D;AAED,wBAAsB,wBAAwB,CAAC,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAgB9F;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,WAepG;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE9D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,sBAAsB,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,0BAA0B,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,2BA8BxH"}
|
|
@@ -161,9 +161,22 @@ export async function LoadWorkletOnAudioDevice(audioDevice, workletBlobUrl) {
|
|
|
161
161
|
export function SendMessageToWorklet(node, commandId, data) {
|
|
162
162
|
if (!node)
|
|
163
163
|
return false;
|
|
164
|
+
if (typeof data === "number" && !Number.isFinite(data)) {
|
|
165
|
+
Debug.Warn("Refusing to send non-finite numeric value to AudioWorkletNode.", [
|
|
166
|
+
`Command: ${String(commandId)}`,
|
|
167
|
+
`Value: ${String(data)}`
|
|
168
|
+
]);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
164
171
|
node.port.postMessage({ commandId, data });
|
|
165
172
|
return true;
|
|
166
173
|
}
|
|
174
|
+
export function IsFiniteNumber(value) {
|
|
175
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
176
|
+
}
|
|
177
|
+
export function CoerceFiniteNumber(value, fallback) {
|
|
178
|
+
return IsFiniteNumber(value) ? value : fallback;
|
|
179
|
+
}
|
|
167
180
|
export function CreateAudioWorkletNode(context, name, data) {
|
|
168
181
|
if (!compiledWebAssemblyModule) {
|
|
169
182
|
Debug.Error("Failed to create audio worklet node, because the web assembly module has not been compiled properly.", [
|
|
@@ -171,16 +184,26 @@ export function CreateAudioWorkletNode(context, name, data) {
|
|
|
171
184
|
]);
|
|
172
185
|
return null;
|
|
173
186
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
try {
|
|
188
|
+
return new AudioWorkletNode(context, name, {
|
|
189
|
+
numberOfInputs: 1,
|
|
190
|
+
numberOfOutputs: 1,
|
|
191
|
+
outputChannelCount: [2],
|
|
192
|
+
parameterData: {
|
|
193
|
+
...data,
|
|
194
|
+
sampleRate: context.sampleRate
|
|
195
|
+
},
|
|
196
|
+
processorOptions: {
|
|
197
|
+
module: compiledWebAssemblyModule
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
Debug.Error("Failed to create audio worklet node.", [
|
|
203
|
+
`Request audio worklet node: ${name}.`,
|
|
204
|
+
"Make sure you called InitializeDspPipeline/InitializeDpsPipeline and loaded the worklet module on the same AudioContext.",
|
|
205
|
+
String(err)
|
|
206
|
+
]);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
186
209
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { v4 } from "uuid";
|
|
2
2
|
import { format } from "date-fns";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { AudioClipEventMap, AudioClipEvents, AudioSourceData } from "../../typings";
|
|
5
5
|
import { Debug } from "../../utilities/debugger";
|
|
6
6
|
import { Channel } from "./Channel";
|
|
7
7
|
import { AudioClipPlayer } from "./AudioClipPlayer";
|
|
8
|
+
import { Master } from "./Master";
|
|
9
|
+
import { DSP } from "../../index";
|
|
8
10
|
|
|
9
11
|
type ProgressPayload = Parameters<AudioClipEventMap["progress"]>[0];
|
|
10
12
|
|
|
@@ -248,9 +250,12 @@ export class AudioClip {
|
|
|
248
250
|
|
|
249
251
|
public SetMaxAudioBufferSourceNodes(value: number): AudioClip {
|
|
250
252
|
|
|
251
|
-
|
|
252
|
-
"
|
|
253
|
-
|
|
253
|
+
if (!DSP.overrideMaxAudioBufferNodes) {
|
|
254
|
+
Debug.Warn("Cannot set maxAudioBufferSourceNodes because the option to override the current maximum value is disabled.", [
|
|
255
|
+
"Set overrideMaxAudioBufferNodes on DSP to true, in order to override the maximum audio buffer source nodes."
|
|
256
|
+
]);
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
254
259
|
|
|
255
260
|
this.maxAudioBufferSourceNodes = value;
|
|
256
261
|
return this;
|
|
@@ -316,15 +321,26 @@ export class AudioClip {
|
|
|
316
321
|
return this.data.audioBuffer.getChannelData(channel);
|
|
317
322
|
}
|
|
318
323
|
|
|
319
|
-
public Send(channel: Channel) {
|
|
324
|
+
public Send(channel: Channel | Master) {
|
|
325
|
+
|
|
326
|
+
if (!channel.audioClipPlayer) return Debug.Error("Could not send AudioClip signal to channel, because the channel's AudioClipPlayer is undefined.", [
|
|
327
|
+
"Make sure to initialize the Channel.",
|
|
328
|
+
`Channel id: ${channel.id}.`,
|
|
329
|
+
`AudioClip id: ${this.id}.`
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
channel.audioClipPlayer.AttachAudioClip(this);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public Unsend(channel: Channel | Master) {
|
|
320
336
|
|
|
321
|
-
if(!channel.
|
|
337
|
+
if (!channel.audioClipPlayer) return Debug.Error("Could not unsend AudioClip signal to channel, because the channel's AudioClipPlayer is undefined.", [
|
|
322
338
|
"Make sure to initialize the Channel.",
|
|
323
339
|
`Channel id: ${channel.id}.`,
|
|
324
340
|
`AudioClip id: ${this.id}.`
|
|
325
341
|
]);
|
|
326
342
|
|
|
327
|
-
channel.
|
|
343
|
+
channel.audioClipPlayer.DetachAudioClip(this);
|
|
328
344
|
}
|
|
329
345
|
|
|
330
346
|
public get currentPlaybackTime(): number {
|
|
@@ -2,8 +2,8 @@ import { v4 } from "uuid";
|
|
|
2
2
|
|
|
3
3
|
import { AudioClip } from "./AudioClip";
|
|
4
4
|
import { Debug } from "../../utilities/debugger";
|
|
5
|
-
import { Master } from "./Master";
|
|
6
5
|
import { Channel } from "./Channel";
|
|
6
|
+
import { Master } from "./Master";
|
|
7
7
|
|
|
8
8
|
export class AudioClipPlayer {
|
|
9
9
|
|
|
@@ -14,14 +14,14 @@ export class AudioClipPlayer {
|
|
|
14
14
|
public outputGainNode: GainNode | null = null;
|
|
15
15
|
|
|
16
16
|
public context: AudioContext | null = null;
|
|
17
|
-
public channel: Channel | null = null;
|
|
17
|
+
public channel: Channel | Master | null = null;
|
|
18
18
|
|
|
19
19
|
constructor(context: AudioContext) {
|
|
20
20
|
this.context = context;
|
|
21
21
|
this.outputGainNode = new GainNode(context);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
public AttachAudioClip(audioClip: AudioClip) {
|
|
24
|
+
public AttachAudioClip(audioClip: AudioClip): void {
|
|
25
25
|
|
|
26
26
|
if (this.audioClips.includes(audioClip)) return Debug.Error("Could not attach audio clip because it is already part of this channel", [
|
|
27
27
|
"Call .DetachAudioClip([clip AudioClip]) before attaching audio clip."
|
|
@@ -31,15 +31,75 @@ export class AudioClipPlayer {
|
|
|
31
31
|
this.audioClips.push(audioClip);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
public
|
|
34
|
+
public DetachAudioClip(clip: AudioClip): void {
|
|
35
|
+
|
|
36
|
+
if (!this.audioClips.includes(clip)) return Debug.Error("Could not detach audio clip because it is not part of this channel", [
|
|
37
|
+
"Call .AttachAudioClip([clip AudioClip]) before detaching audio clip."
|
|
38
|
+
]);
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
clip.Stop();
|
|
41
|
+
|
|
42
|
+
const self = this;
|
|
43
|
+
|
|
44
|
+
this.audioClips.forEach(function (_clip: AudioClip, index: number) {
|
|
45
|
+
if (clip.id === _clip.id)
|
|
46
|
+
return self.audioClips.splice(index, 1);
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public Send(channel: Channel | Master): void {
|
|
51
|
+
|
|
52
|
+
if (!this.outputGainNode || !channel.input) return Debug.Error("Could not send AudioClipPlayer signal to a channel/master.");
|
|
53
|
+
|
|
54
|
+
this.Unsend();
|
|
37
55
|
|
|
38
56
|
this.channel = channel;
|
|
39
57
|
this.outputGainNode.connect(channel.input);
|
|
40
58
|
}
|
|
41
59
|
|
|
42
|
-
public
|
|
60
|
+
public Unsend(): void {
|
|
61
|
+
|
|
62
|
+
if (!this.outputGainNode) return;
|
|
63
|
+
|
|
64
|
+
if (this.channel && this.channel.input) {
|
|
65
|
+
this.outputGainNode.disconnect(this.channel.input);
|
|
66
|
+
} else {
|
|
67
|
+
this.outputGainNode.disconnect();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.channel = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public SetVolume(value: number): void {
|
|
74
|
+
|
|
75
|
+
if (!this.outputGainNode) return;
|
|
76
|
+
|
|
77
|
+
if (value < 0) value = 0;
|
|
78
|
+
if (value > 1) value = 1;
|
|
79
|
+
|
|
80
|
+
this.outputGainNode.gain.value = value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public StopAll(): void {
|
|
84
|
+
|
|
85
|
+
this.audioClips.forEach(function (clip: AudioClip) {
|
|
86
|
+
clip.Stop();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public Dispose(): void {
|
|
91
|
+
|
|
92
|
+
this.StopAll();
|
|
93
|
+
this.Unsend();
|
|
94
|
+
this.outputGainNode?.disconnect();
|
|
95
|
+
|
|
96
|
+
this.audioClips = [];
|
|
97
|
+
this.outputGainNode = null;
|
|
98
|
+
this.channel = null;
|
|
99
|
+
this.context = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public SetLabel(label: string): void {
|
|
43
103
|
this.label = label;
|
|
44
104
|
}
|
|
45
|
-
}
|
|
105
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { v4 } from "uuid";
|
|
2
2
|
|
|
3
3
|
import { Master } from "./Master";
|
|
4
|
+
import { Channel } from "./Channel";
|
|
4
5
|
|
|
5
6
|
import { ErrorCodes } from "../../console-codes";
|
|
6
7
|
import { Debug } from "../../utilities/debugger";
|
|
@@ -42,4 +43,8 @@ export class AudioDevice {
|
|
|
42
43
|
public GetContext(): AudioContext {
|
|
43
44
|
return this.context;
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
public CreateChannel(): Channel {
|
|
48
|
+
return new Channel(this.context);
|
|
49
|
+
}
|
|
45
50
|
}
|
|
@@ -3,6 +3,7 @@ import { Debug } from "../../utilities/debugger";
|
|
|
3
3
|
import { AudioClipPlayer } from "./AudioClipPlayer";
|
|
4
4
|
import { AudioClip } from "./AudioClip";
|
|
5
5
|
import { Master } from "./Master";
|
|
6
|
+
import { Effector } from "./Effector";
|
|
6
7
|
|
|
7
8
|
export class Channel {
|
|
8
9
|
|
|
@@ -15,10 +16,11 @@ export class Channel {
|
|
|
15
16
|
public gainNode: GainNode | null = null;
|
|
16
17
|
public output: AudioNode | null = null;
|
|
17
18
|
|
|
19
|
+
public effects: Effector[] = [];
|
|
18
20
|
public context: AudioContext | null = null;
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
public sends: Channel[] = [];
|
|
23
|
+
public audioClipPlayer: AudioClipPlayer | null = null;
|
|
22
24
|
|
|
23
25
|
constructor(context: AudioContext) {
|
|
24
26
|
this.context = context;
|
|
@@ -41,6 +43,92 @@ export class Channel {
|
|
|
41
43
|
this.audioClipPlayer.Send(this);
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
private rebuildEffectChain(): void {
|
|
47
|
+
|
|
48
|
+
if (!this.input || !this.stereoPannerNode) {
|
|
49
|
+
Debug.Error("Could not rebuild effect chain because one or more required audio nodes are undefined.", [
|
|
50
|
+
`Channel id: ${this.id}.`,
|
|
51
|
+
`Input defined: ${!!this.input}.`,
|
|
52
|
+
`StereoPannerNode defined: ${!!this.stereoPannerNode}.`
|
|
53
|
+
]);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.input.disconnect();
|
|
58
|
+
|
|
59
|
+
for (const effect of this.effects)
|
|
60
|
+
effect.audioWorkletNode?.disconnect();
|
|
61
|
+
|
|
62
|
+
const activeEffects = this.effects.filter(function (e: Effector): boolean {
|
|
63
|
+
return !!e.audioWorkletNode;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (activeEffects.length === 0) {
|
|
67
|
+
this.input.connect(this.stereoPannerNode);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.input.connect(activeEffects[0].audioWorkletNode as AudioNode);
|
|
72
|
+
|
|
73
|
+
for (let i: number = 0; i < activeEffects.length - 1; i++) {
|
|
74
|
+
|
|
75
|
+
const current = activeEffects[i].audioWorkletNode as AudioNode;
|
|
76
|
+
const next = activeEffects[i + 1].audioWorkletNode as AudioNode;
|
|
77
|
+
|
|
78
|
+
current.connect(next);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
(activeEffects[activeEffects.length - 1].audioWorkletNode as AudioNode).connect(this.stereoPannerNode);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public AddEffect(effect: Effector): void {
|
|
85
|
+
|
|
86
|
+
if (!this.context) {
|
|
87
|
+
Debug.Error("Could not add effect because this channel has no AudioContext.");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.effects.includes(effect)) {
|
|
92
|
+
Debug.Error("Could not add effect because it is already part of this channel", [
|
|
93
|
+
"Call .RemoveEffect([effect Effector]) before adding effect."
|
|
94
|
+
]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.effects.push(effect);
|
|
99
|
+
effect.InitializeOnAttachment(this.context);
|
|
100
|
+
this.rebuildEffectChain();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public AttachEffect(effect: Effector): void {
|
|
104
|
+
return this.AddEffect(effect);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public RemoveEffect(effect: Effector): void {
|
|
108
|
+
|
|
109
|
+
if (!this.effects.includes(effect)) {
|
|
110
|
+
Debug.Error("Could not remove effect, because it is not part of this channel.", [
|
|
111
|
+
"Call .AddEffect([effect Effector]) before removing effect."
|
|
112
|
+
]);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const self: Channel = this;
|
|
117
|
+
|
|
118
|
+
this.effects.forEach(function (_effect: Effector, index: number): void {
|
|
119
|
+
if (effect.id === _effect.id)
|
|
120
|
+
self.effects.splice(index, 1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
effect.audioWorkletNode?.disconnect();
|
|
124
|
+
|
|
125
|
+
this.rebuildEffectChain();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public DetachEffect(effect: Effector): void {
|
|
129
|
+
return this.RemoveEffect(effect);
|
|
130
|
+
}
|
|
131
|
+
|
|
44
132
|
private disconnectAudioNodes(gc?: boolean) {
|
|
45
133
|
|
|
46
134
|
this.input?.disconnect();
|
|
@@ -49,6 +137,10 @@ export class Channel {
|
|
|
49
137
|
this.gainNode?.disconnect();
|
|
50
138
|
this.output?.disconnect();
|
|
51
139
|
|
|
140
|
+
for (const effect of this.effects) {
|
|
141
|
+
effect.audioWorkletNode?.disconnect();
|
|
142
|
+
}
|
|
143
|
+
|
|
52
144
|
if (gc) {
|
|
53
145
|
this.input = null;
|
|
54
146
|
this.stereoPannerNode = null;
|
|
@@ -88,7 +180,8 @@ export class Channel {
|
|
|
88
180
|
|
|
89
181
|
public Send(channel: Channel | Master) {
|
|
90
182
|
|
|
91
|
-
if (channel instanceof Master)
|
|
183
|
+
if (channel instanceof Master)
|
|
184
|
+
return (channel as Master).AttachChannel(this);
|
|
92
185
|
|
|
93
186
|
if (channel.id === this.id) return Debug.Error("Could not link channel to itself.", [
|
|
94
187
|
`This channel id: ${this.id}`
|
|
@@ -121,7 +214,7 @@ export class Channel {
|
|
|
121
214
|
|
|
122
215
|
public Unsend(channel: Channel | Master) {
|
|
123
216
|
|
|
124
|
-
if(channel instanceof Master)
|
|
217
|
+
if (channel instanceof Master)
|
|
125
218
|
return (channel as Master).DetachChannel(this);
|
|
126
219
|
|
|
127
220
|
const idx: number = this.sends.indexOf(channel);
|
|
@@ -7,6 +7,7 @@ import { ConstructProcessorWorklet, LoadWorkletOnAudioDevice } from "../../utili
|
|
|
7
7
|
|
|
8
8
|
import { ErrorCodes, WarningCodes } from "../../console-codes";
|
|
9
9
|
import { DspPipelineInitializationOptions } from "../../typings";
|
|
10
|
+
import { DSP } from "../../index";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Represents the digital signal processing (DSP) pipeline responsible for initializing
|
|
@@ -45,9 +46,12 @@ export class DspPipeline {
|
|
|
45
46
|
* @param options - Initialization options for the DSP pipeline.
|
|
46
47
|
* Must include valid paths to the WebAssembly and AudioWorklet files.
|
|
47
48
|
*/
|
|
48
|
-
constructor(options: DspPipelineInitializationOptions) {
|
|
49
|
-
this.pathToWasm =
|
|
50
|
-
this.pathToWorklet =
|
|
49
|
+
constructor({ pathToWasm, pathToWorklet, options }: DspPipelineInitializationOptions) {
|
|
50
|
+
this.pathToWasm = pathToWasm;
|
|
51
|
+
this.pathToWorklet = pathToWorklet;
|
|
52
|
+
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
for(const key in options) DSP[key] = options[key];
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/**
|
|
@@ -8,8 +8,9 @@ export abstract class Effector {
|
|
|
8
8
|
public id: string = v4();
|
|
9
9
|
public label: string | null = null;
|
|
10
10
|
public name: string = "Effector";
|
|
11
|
+
|
|
11
12
|
public audioWorkletNode: AudioWorkletNode | null = null;
|
|
12
|
-
public
|
|
13
|
+
public context: AudioContext | null = null;
|
|
13
14
|
|
|
14
15
|
private hasRegisteredMessageEventListener: boolean = false;
|
|
15
16
|
|
|
@@ -88,5 +89,5 @@ export abstract class Effector {
|
|
|
88
89
|
return this;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
public abstract InitializeOnAttachment(
|
|
92
|
+
public abstract InitializeOnAttachment(context: AudioContext): Promise<void>;
|
|
92
93
|
}
|
|
@@ -1,29 +1,107 @@
|
|
|
1
1
|
import { v4 } from "uuid";
|
|
2
2
|
|
|
3
3
|
import { Channel } from "./Channel";
|
|
4
|
-
|
|
5
4
|
import { Debug } from "../../utilities/debugger";
|
|
5
|
+
import { Effector } from "./Effector";
|
|
6
6
|
import { AudioClipPlayer } from "./AudioClipPlayer";
|
|
7
|
+
import { AudioClip } from "./AudioClip";
|
|
7
8
|
|
|
8
9
|
export class Master {
|
|
9
10
|
|
|
10
11
|
public id: string = v4();
|
|
12
|
+
|
|
11
13
|
public channels: Channel[] = [];
|
|
14
|
+
public effects: Effector[] = [];
|
|
12
15
|
|
|
16
|
+
public input: GainNode | null = null;
|
|
13
17
|
public gainNode: GainNode | null = null;
|
|
14
18
|
public analyserNode: AnalyserNode | null = null;
|
|
15
|
-
|
|
16
19
|
public context: AudioContext | null = null;
|
|
20
|
+
public audioClipPlayer: AudioClipPlayer | null = null;
|
|
17
21
|
|
|
18
22
|
constructor(context: AudioContext) {
|
|
19
23
|
|
|
20
24
|
this.context = context;
|
|
21
25
|
|
|
26
|
+
this.disconnectAudioNodes();
|
|
27
|
+
|
|
28
|
+
this.audioClipPlayer = new AudioClipPlayer(context);
|
|
29
|
+
|
|
30
|
+
this.input = new GainNode(context);
|
|
22
31
|
this.gainNode = new GainNode(context);
|
|
23
32
|
this.analyserNode = new AnalyserNode(context);
|
|
24
33
|
|
|
34
|
+
this.input.connect(this.gainNode);
|
|
25
35
|
this.gainNode.connect(this.analyserNode);
|
|
26
36
|
this.analyserNode.connect(this.context.destination);
|
|
37
|
+
|
|
38
|
+
this.audioClipPlayer.Send(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private disconnectAudioNodes(): void {
|
|
42
|
+
|
|
43
|
+
if (this.input) this.input.disconnect();
|
|
44
|
+
if (this.gainNode) this.gainNode.disconnect();
|
|
45
|
+
if (this.analyserNode) this.analyserNode.disconnect();
|
|
46
|
+
|
|
47
|
+
this.effects.forEach(function (effect: Effector) {
|
|
48
|
+
if (effect.audioWorkletNode)
|
|
49
|
+
effect.audioWorkletNode.disconnect();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private rebuildEffectChain(): void {
|
|
54
|
+
|
|
55
|
+
if (!this.context) return;
|
|
56
|
+
if (!this.input || !this.gainNode || !this.analyserNode) return;
|
|
57
|
+
|
|
58
|
+
this.disconnectAudioNodes();
|
|
59
|
+
|
|
60
|
+
let currentNode: AudioNode = this.input;
|
|
61
|
+
|
|
62
|
+
this.effects.forEach(function (effect: Effector) {
|
|
63
|
+
|
|
64
|
+
if (!effect.audioWorkletNode) return;
|
|
65
|
+
|
|
66
|
+
currentNode.connect(effect.audioWorkletNode);
|
|
67
|
+
currentNode = effect.audioWorkletNode;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
currentNode.connect(this.gainNode);
|
|
71
|
+
|
|
72
|
+
this.gainNode.connect(this.analyserNode);
|
|
73
|
+
this.analyserNode.connect(this.context.destination);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public AttachEffect(effect: Effector): void {
|
|
77
|
+
|
|
78
|
+
if (this.effects.includes(effect)) return Debug.Error("Could not attach the effect because it is already part of this master channel.", [
|
|
79
|
+
"Call .DetachEffect([effect Effector]) before attaching the effect."
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
if(!this.context) return this.rebuildEffectChain();
|
|
83
|
+
|
|
84
|
+
this.effects.push(effect);
|
|
85
|
+
effect.InitializeOnAttachment(this.context);
|
|
86
|
+
this.rebuildEffectChain();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public DetachEffect(effect: Effector): void {
|
|
90
|
+
|
|
91
|
+
if (!this.effects.includes(effect)) return Debug.Error("Could not detach the effect because it is not part of this master channel.", [
|
|
92
|
+
"Call .AttachEffect([effect Effector]) before detaching the effect."
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const self = this;
|
|
96
|
+
|
|
97
|
+
this.effects.forEach(function (_effect: Effector, index: number) {
|
|
98
|
+
if (effect.id === _effect.id) {
|
|
99
|
+
self.effects.splice(index, 1);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
this.rebuildEffectChain();
|
|
27
105
|
}
|
|
28
106
|
|
|
29
107
|
public AttachChannel(channel: Channel): void {
|
|
@@ -34,9 +112,9 @@ export class Master {
|
|
|
34
112
|
|
|
35
113
|
this.channels.push(channel);
|
|
36
114
|
|
|
37
|
-
if (channel.output && this.
|
|
38
|
-
channel.output.connect(this.
|
|
39
|
-
|
|
115
|
+
if (channel.output && this.input)
|
|
116
|
+
channel.output.connect(this.input);
|
|
117
|
+
|
|
40
118
|
return;
|
|
41
119
|
}
|
|
42
120
|
|
|
@@ -46,17 +124,26 @@ export class Master {
|
|
|
46
124
|
"Call .AttachChannel([channel Channel]) before detaching the channel."
|
|
47
125
|
]);
|
|
48
126
|
|
|
49
|
-
if (channel.output && this.
|
|
50
|
-
channel.output.disconnect(this.
|
|
127
|
+
if (channel.output && this.input)
|
|
128
|
+
channel.output.disconnect(this.input);
|
|
51
129
|
|
|
52
130
|
const self = this;
|
|
53
131
|
|
|
54
132
|
this.channels.forEach(function (_channel: Channel, index: number) {
|
|
55
|
-
if (channel.id
|
|
133
|
+
if (channel.id !== _channel.id) return;
|
|
56
134
|
|
|
57
|
-
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
135
|
+
self.channels.splice(index, 1);
|
|
60
136
|
});
|
|
61
137
|
}
|
|
138
|
+
|
|
139
|
+
public HasAudioClipPlayer(): boolean {
|
|
140
|
+
return !!this.audioClipPlayer;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public AttachAudioClip(audioClip: AudioClip) {
|
|
144
|
+
|
|
145
|
+
if (!this.audioClipPlayer) return Debug.Error("Cannot not link AudioClip to this channel because this channel's AudioClipPlayer is undefined.");
|
|
146
|
+
|
|
147
|
+
this.audioClipPlayer.AttachAudioClip(audioClip);
|
|
148
|
+
}
|
|
62
149
|
}
|