@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.
Files changed (78) hide show
  1. package/README.md +16 -38
  2. package/lib/dist/core/classes/AudioClip.d.ts +3 -1
  3. package/lib/dist/core/classes/AudioClip.d.ts.map +1 -1
  4. package/lib/dist/core/classes/AudioClip.js +18 -5
  5. package/lib/dist/core/classes/AudioClipPlayer.d.ts +8 -2
  6. package/lib/dist/core/classes/AudioClipPlayer.d.ts.map +1 -1
  7. package/lib/dist/core/classes/AudioClipPlayer.js +48 -1
  8. package/lib/dist/core/classes/AudioDevice.d.ts +2 -0
  9. package/lib/dist/core/classes/AudioDevice.d.ts.map +1 -1
  10. package/lib/dist/core/classes/AudioDevice.js +4 -0
  11. package/lib/dist/core/classes/Channel.d.ts +10 -2
  12. package/lib/dist/core/classes/Channel.d.ts.map +1 -1
  13. package/lib/dist/core/classes/Channel.js +67 -0
  14. package/lib/dist/core/classes/DpsPipeline.d.ts +1 -1
  15. package/lib/dist/core/classes/DpsPipeline.d.ts.map +1 -1
  16. package/lib/dist/core/classes/DpsPipeline.js +7 -3
  17. package/lib/dist/core/classes/Effector.d.ts +2 -2
  18. package/lib/dist/core/classes/Effector.d.ts.map +1 -1
  19. package/lib/dist/core/classes/Effector.js +1 -1
  20. package/lib/dist/core/classes/Master.d.ts +12 -0
  21. package/lib/dist/core/classes/Master.d.ts.map +1 -1
  22. package/lib/dist/core/classes/Master.js +77 -7
  23. package/lib/dist/effects/classes/Analyser.d.ts +16 -0
  24. package/lib/dist/effects/classes/Analyser.d.ts.map +1 -0
  25. package/lib/dist/effects/classes/Analyser.js +62 -0
  26. package/lib/dist/effects/classes/Chorus.d.ts +4 -3
  27. package/lib/dist/effects/classes/Chorus.d.ts.map +1 -1
  28. package/lib/dist/effects/classes/Chorus.js +21 -12
  29. package/lib/dist/effects/classes/clips/HardClip.d.ts +4 -3
  30. package/lib/dist/effects/classes/clips/HardClip.d.ts.map +1 -1
  31. package/lib/dist/effects/classes/clips/HardClip.js +15 -9
  32. package/lib/dist/effects/classes/clips/SoftClip.d.ts +4 -3
  33. package/lib/dist/effects/classes/clips/SoftClip.d.ts.map +1 -1
  34. package/lib/dist/effects/classes/clips/SoftClip.js +15 -9
  35. package/lib/dist/effects/classes/filters/LowPassFilter.d.ts +4 -3
  36. package/lib/dist/effects/classes/filters/LowPassFilter.d.ts.map +1 -1
  37. package/lib/dist/effects/classes/filters/LowPassFilter.js +23 -14
  38. package/lib/dist/effects/exports.d.ts +1 -0
  39. package/lib/dist/effects/exports.d.ts.map +1 -1
  40. package/lib/dist/effects/exports.js +1 -0
  41. package/lib/dist/index.d.ts +5 -4
  42. package/lib/dist/index.d.ts.map +1 -1
  43. package/lib/dist/index.js +13 -18
  44. package/lib/dist/typings.d.ts +13 -11
  45. package/lib/dist/typings.d.ts.map +1 -1
  46. package/lib/dist/typings.js +5 -0
  47. package/lib/dist/utilities/debugger.js +6 -6
  48. package/lib/dist/utilities/helpers.d.ts +2 -0
  49. package/lib/dist/utilities/helpers.d.ts.map +1 -1
  50. package/lib/dist/utilities/helpers.js +35 -12
  51. package/lib/src/core/classes/AudioClip.ts +23 -7
  52. package/lib/src/core/classes/AudioClipPlayer.ts +67 -7
  53. package/lib/src/core/classes/AudioDevice.ts +5 -0
  54. package/lib/src/core/classes/Channel.ts +97 -4
  55. package/lib/src/core/classes/DpsPipeline.ts +7 -3
  56. package/lib/src/core/classes/Effector.ts +3 -2
  57. package/lib/src/core/classes/Master.ts +98 -11
  58. package/lib/src/effects/classes/Analyser.ts +84 -0
  59. package/lib/src/effects/classes/Chorus.ts +22 -13
  60. package/lib/src/effects/classes/clips/HardClip.ts +17 -10
  61. package/lib/src/effects/classes/clips/SoftClip.ts +17 -10
  62. package/lib/src/effects/classes/filters/LowPassFilter.ts +28 -15
  63. package/lib/src/effects/exports.ts +2 -1
  64. package/lib/src/index.ts +27 -29
  65. package/lib/src/typings.ts +14 -14
  66. package/lib/src/utilities/debugger.ts +6 -6
  67. package/lib/src/utilities/helpers.ts +38 -14
  68. package/package.json +1 -1
  69. package/lib/dist/effects/classes/Filter.d.ts +0 -3
  70. package/lib/dist/effects/classes/Filter.d.ts.map +0 -1
  71. package/lib/dist/effects/classes/Filter.js +0 -2
  72. package/lib/dist/effects/classes/SoftClip.d.ts +0 -10
  73. package/lib/dist/effects/classes/SoftClip.d.ts.map +0 -1
  74. package/lib/dist/effects/classes/SoftClip.js +0 -39
  75. package/lib/dist/test/linkable-channels.d.ts +0 -2
  76. package/lib/dist/test/linkable-channels.d.ts.map +0 -1
  77. package/lib/dist/test/linkable-channels.js +0 -16
  78. package/lib/src/core/functions/rebuild-effect-chain.ts +0 -5
@@ -1,8 +1,8 @@
1
- import { FluexGLAudio } from "../index";
1
+ import { DSP } from "../index";
2
2
  export var Debug;
3
3
  (function (Debug) {
4
4
  function Log(message, additionalDetails) {
5
- if (!FluexGLAudio.options.debugger.showInfo)
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 (!FluexGLAudio.options.debugger.showInfo)
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 (!FluexGLAudio.options.debugger.showWarnings)
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 (!FluexGLAudio.options.debugger.showErrors)
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 (FluexGLAudio.options.debugger.breakOnError) {
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,WAOpG;AAED,wBAAgB,sBAAsB,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,0BAA0B,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,2BAsBxH"}
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
- return new AudioWorkletNode(context, name, {
175
- numberOfInputs: 1,
176
- numberOfOutputs: 1,
177
- outputChannelCount: [2],
178
- parameterData: {
179
- ...data,
180
- sampleRate: context.sampleRate
181
- },
182
- processorOptions: {
183
- module: compiledWebAssemblyModule
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 { AudioClipAnalyserProperty, AudioClipAnalyserType, AudioClipEventMap, AudioClipEvents, AudioSourceData } from "../../typings";
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
- Debug.Warn("Changing the amount of buffer source nodes may cause some properties of this class instance to work inproperly.", [
252
- "The default value is 1."
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.HasAudioClipPlayer()) return Debug.Error("Could not send AudioClip signal to channel, because the channel's AudioClipPlayer is undefined.", [
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.AttachAudioClip(this);
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 Send(channel: Channel) {
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
- if(!this.outputGainNode || !channel.input) return Debug.Error("Could not send AudioClipPlayer signal to a channel.");
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 SetLabel(label: string) {
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
- private sends: Channel[] = [];
21
- private audioClipPlayer: AudioClipPlayer | null = null;
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) return (channel as Master).AttachChannel(this);
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 = options.pathToWasm;
50
- this.pathToWorklet = options.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 parentialContext: AudioContext | null = null;
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(parentialContext: AudioContext): Promise<void>;
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.gainNode)
38
- channel.output.connect(this.gainNode);
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.gainNode)
50
- channel.output.disconnect(this.gainNode);
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 === _channel.id) {
133
+ if (channel.id !== _channel.id) return;
56
134
 
57
- self.channels.splice(index, 1);
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
  }