@fluex/fluexgl-dsp 0.4.6 → 0.4.8

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 (100) hide show
  1. package/README.md +7 -7
  2. package/lib/dist/console-codes.d.ts +2 -1
  3. package/lib/dist/console-codes.d.ts.map +1 -1
  4. package/lib/dist/console-codes.js +1 -0
  5. package/lib/dist/core/classes/AudioClip.d.ts +27 -16
  6. package/lib/dist/core/classes/AudioClip.d.ts.map +1 -1
  7. package/lib/dist/core/classes/AudioClip.js +133 -48
  8. package/lib/dist/core/classes/AudioClipPlayer.d.ts +8 -8
  9. package/lib/dist/core/classes/AudioClipPlayer.d.ts.map +1 -1
  10. package/lib/dist/core/classes/AudioClipPlayer.js +22 -24
  11. package/lib/dist/core/classes/AudioDevice.d.ts +46 -5
  12. package/lib/dist/core/classes/AudioDevice.d.ts.map +1 -1
  13. package/lib/dist/core/classes/AudioDevice.js +47 -6
  14. package/lib/dist/core/classes/Channel.d.ts +20 -20
  15. package/lib/dist/core/classes/Channel.d.ts.map +1 -1
  16. package/lib/dist/core/classes/Channel.js +42 -42
  17. package/lib/dist/core/classes/DpsPipeline.d.ts +6 -6
  18. package/lib/dist/core/classes/DpsPipeline.d.ts.map +1 -1
  19. package/lib/dist/core/classes/DpsPipeline.js +18 -18
  20. package/lib/dist/core/classes/Effector.d.ts +5 -5
  21. package/lib/dist/core/classes/Effector.d.ts.map +1 -1
  22. package/lib/dist/core/classes/Effector.js +8 -8
  23. package/lib/dist/core/classes/Master.d.ts +6 -6
  24. package/lib/dist/core/classes/Master.d.ts.map +1 -1
  25. package/lib/dist/core/classes/Master.js +18 -18
  26. package/lib/dist/effects/classes/Analyser.d.ts +10 -6
  27. package/lib/dist/effects/classes/Analyser.d.ts.map +1 -1
  28. package/lib/dist/effects/classes/Analyser.js +29 -13
  29. package/lib/dist/effects/classes/Chorus.d.ts +7 -7
  30. package/lib/dist/effects/classes/Chorus.d.ts.map +1 -1
  31. package/lib/dist/effects/classes/Chorus.js +25 -25
  32. package/lib/dist/effects/classes/Distortion.d.ts +1 -1
  33. package/lib/dist/effects/classes/Distortion.d.ts.map +1 -1
  34. package/lib/dist/effects/classes/Distortion.js +1 -1
  35. package/lib/dist/effects/classes/Reverb.d.ts +32 -0
  36. package/lib/dist/effects/classes/Reverb.d.ts.map +1 -0
  37. package/lib/dist/effects/classes/Reverb.js +61 -0
  38. package/lib/dist/effects/classes/clips/HardClip.d.ts +5 -5
  39. package/lib/dist/effects/classes/clips/HardClip.d.ts.map +1 -1
  40. package/lib/dist/effects/classes/clips/HardClip.js +14 -14
  41. package/lib/dist/effects/classes/clips/SoftClip.d.ts +4 -4
  42. package/lib/dist/effects/classes/clips/SoftClip.d.ts.map +1 -1
  43. package/lib/dist/effects/classes/clips/SoftClip.js +13 -13
  44. package/lib/dist/effects/classes/filters/HighPassFilter.d.ts +18 -0
  45. package/lib/dist/effects/classes/filters/HighPassFilter.d.ts.map +1 -0
  46. package/lib/dist/effects/classes/filters/HighPassFilter.js +59 -0
  47. package/lib/dist/effects/classes/filters/LowPassFilter.d.ts +5 -5
  48. package/lib/dist/effects/classes/filters/LowPassFilter.d.ts.map +1 -1
  49. package/lib/dist/effects/classes/filters/LowPassFilter.js +17 -17
  50. package/lib/dist/effects/classes/filters/NotchFilter.d.ts +18 -0
  51. package/lib/dist/effects/classes/filters/NotchFilter.d.ts.map +1 -0
  52. package/lib/dist/effects/classes/filters/NotchFilter.js +59 -0
  53. package/lib/dist/effects/exports.d.ts +2 -5
  54. package/lib/dist/effects/exports.d.ts.map +1 -1
  55. package/lib/dist/effects/exports.js +2 -5
  56. package/lib/dist/index.d.ts +4 -4
  57. package/lib/dist/index.d.ts.map +1 -1
  58. package/lib/dist/index.js +3 -3
  59. package/lib/dist/typings.d.ts +33 -0
  60. package/lib/dist/typings.d.ts.map +1 -1
  61. package/lib/dist/typings.js +12 -0
  62. package/lib/dist/utilities/constants.d.ts +1 -0
  63. package/lib/dist/utilities/constants.d.ts.map +1 -1
  64. package/lib/dist/utilities/constants.js +1 -0
  65. package/lib/dist/utilities/debugger.d.ts +4 -4
  66. package/lib/dist/utilities/debugger.js +8 -8
  67. package/lib/dist/utilities/helpers.d.ts +13 -13
  68. package/lib/dist/utilities/helpers.d.ts.map +1 -1
  69. package/lib/dist/utilities/helpers.js +45 -59
  70. package/lib/dist/utilities/web-assembly.js +1 -1
  71. package/lib/src/console-codes.ts +2 -1
  72. package/lib/src/core/classes/AudioClip.ts +166 -55
  73. package/lib/src/core/classes/AudioClipPlayer.ts +22 -24
  74. package/lib/src/core/classes/AudioDevice.ts +49 -6
  75. package/lib/src/core/classes/Channel.ts +42 -42
  76. package/lib/src/core/classes/DpsPipeline.ts +19 -19
  77. package/lib/src/core/classes/Effector.ts +9 -9
  78. package/lib/src/core/classes/Master.ts +18 -18
  79. package/lib/src/effects/classes/Analyser.ts +33 -13
  80. package/lib/src/effects/classes/Chorus.ts +25 -25
  81. package/lib/src/effects/classes/Distortion.ts +1 -1
  82. package/lib/src/effects/classes/Reverb.ts +81 -0
  83. package/lib/src/effects/classes/clips/HardClip.ts +14 -14
  84. package/lib/src/effects/classes/clips/SoftClip.ts +13 -13
  85. package/lib/src/effects/classes/filters/HighPassFilter.ts +81 -0
  86. package/lib/src/effects/classes/filters/LowPassFilter.ts +17 -17
  87. package/lib/src/effects/classes/filters/NotchFilter.ts +81 -0
  88. package/lib/src/effects/exports.ts +2 -6
  89. package/lib/src/index.ts +15 -17
  90. package/lib/src/typings.ts +39 -1
  91. package/lib/src/utilities/constants.ts +3 -1
  92. package/lib/src/utilities/debugger.ts +4 -4
  93. package/lib/src/utilities/helpers.ts +47 -60
  94. package/lib/src/utilities/web-assembly.ts +1 -1
  95. package/package.json +3 -2
  96. package/lib/src/effects/classes/reverbs/ChamberReverb.ts +0 -1
  97. package/lib/src/effects/classes/reverbs/ConvolverReverb.ts +0 -1
  98. package/lib/src/effects/classes/reverbs/GenericReverb.ts +0 -1
  99. package/lib/src/effects/classes/reverbs/HallReverb.ts +0 -1
  100. package/lib/src/effects/classes/reverbs/RoomReverb.ts +0 -1
@@ -1,7 +1,7 @@
1
1
  import { v4 } from "uuid";
2
2
  import { format } from "date-fns";
3
3
 
4
- import { AudioClipEventMap, AudioClipEvents, AudioSourceData } from "../../typings";
4
+ import { AudioClipEventMap, AudioClipEvents, AudioClipOnInitializeEvent, AudioSourceData } from "../../typings";
5
5
  import { Debug } from "../../utilities/debugger";
6
6
  import { Channel } from "./Channel";
7
7
  import { AudioClipPlayer } from "./AudioClipPlayer";
@@ -9,7 +9,9 @@ import { Master } from "./Master";
9
9
  import { DSP } from "../../index";
10
10
  import { ErrorCodes } from "../../console-codes";
11
11
 
12
- type ProgressPayload = Parameters<AudioClipEventMap["progress"]>[0];
12
+ type OnProgressCallbackFunction = AudioClipEventMap["progress"];
13
+ type OnInitializeCallbackFunction = AudioClipEventMap["initialize"];
14
+ type OnPlayCallbackFunction = AudioClipEventMap["play"];
13
15
 
14
16
  export class AudioClip {
15
17
 
@@ -20,6 +22,11 @@ export class AudioClip {
20
22
  public isPlaying: boolean = false;
21
23
  public startTime: number = 0;
22
24
  public offsetAtStart: number = 0;
25
+ public playbackRate: number = 1;
26
+ public pitch: number = 0;
27
+
28
+ public minPitchSemitones: number = -24;
29
+ public maxPitchSemitones: number = 24;
23
30
 
24
31
  public progressUpdateSpeed: number = 20;
25
32
 
@@ -31,11 +38,14 @@ export class AudioClip {
31
38
 
32
39
  private audioBufferSourceNodes: AudioBufferSourceNode[] = [];
33
40
  private maxAudioBufferSourceNodes: number = 1;
41
+ private audioClipPlayers: AudioClipPlayer[] = [];
34
42
 
35
43
  private progressInterval: number | null = 0;
36
44
 
37
45
  private events: AudioClipEvents = {
38
- "progress": []
46
+ "progress": [],
47
+ "initialize": [],
48
+ "play": []
39
49
  }
40
50
 
41
51
  constructor(public data: AudioSourceData) { }
@@ -49,6 +59,7 @@ export class AudioClip {
49
59
  const bufferSource = context.createBufferSource();
50
60
  bufferSource.buffer = this.data.audioBuffer;
51
61
  bufferSource.loop = this.loop;
62
+ bufferSource.playbackRate.setValueAtTime(this.playbackRate, this.context.currentTime);
52
63
 
53
64
  return bufferSource;
54
65
  }
@@ -64,54 +75,98 @@ export class AudioClip {
64
75
  node?.disconnect();
65
76
  }
66
77
 
78
+ private getOutputNodes(): AudioNode[] {
79
+
80
+ const nodes: AudioNode[] = [];
81
+
82
+ for (const player of this.audioClipPlayers) {
83
+ if (player.outputGainNode && !nodes.includes(player.outputGainNode))
84
+ nodes.push(player.outputGainNode);
85
+ }
86
+
87
+ return nodes;
88
+ }
89
+
67
90
  private rebuildNodeChain(): boolean {
68
91
 
69
- if (!this.context || !this.gainNode || !this.stereoPannerNode || !this.audioClipPlayer || !this.audioClipPlayer.outputGainNode) {
70
- Debug.Error("Failed to rebuild node chain, because some of the core AudioNodes are missing.");
92
+ if (!this.context || !this.gainNode || !this.stereoPannerNode) {
93
+ Debug.error("Failed to rebuild node chain, because some of the core AudioNodes are missing.");
71
94
  return false;
72
95
  }
73
96
 
74
- const destination = this.audioClipPlayer.outputGainNode;
97
+ const destinations = this.getOutputNodes();
98
+
99
+ if (destinations.length === 0) {
100
+ Debug.error("Failed to rebuild node chain, because no output destinations are available.");
101
+ return false;
102
+ }
75
103
 
76
104
  this.safeDisconnect(this.gainNode);
77
105
  this.safeDisconnect(this.stereoPannerNode);
78
106
 
79
107
  this.gainNode.connect(this.stereoPannerNode);
80
- this.stereoPannerNode.connect(destination);
108
+ for (const destination of destinations) {
109
+ this.stereoPannerNode.connect(destination);
110
+ }
81
111
 
82
112
  this.connectSourcesTo(this.gainNode);
83
113
  return true;
84
114
  }
85
115
 
86
- public Initialize(audioClipPlayer: AudioClipPlayer) {
116
+ public initialize(audioClipPlayer: AudioClipPlayer) {
117
+
118
+ const startTimestamp: number = Date.now(),
119
+ self: AudioClip = this;
87
120
 
88
- if (!audioClipPlayer.context) return Debug.Error("Could not initialize AudioClip, because the AudioClipPlayer somehow has no audio context.", [
121
+ if (!audioClipPlayer.context) return Debug.error("Could not initialize AudioClip, because the AudioClipPlayer somehow has no audio context.", [
89
122
  `AudioClipPlayer id: ${audioClipPlayer.id}`,
90
123
  `AudioClip id: ${this.id}`
91
124
  ], ErrorCodes.AUDIO_CLIP_PLAYER_NO_CONTEXT);
92
125
 
126
+ if (this.context && this.context !== audioClipPlayer.context) {
127
+ return Debug.error("Could not initialize AudioClip, because the AudioClipPlayer does not share the same AudioContext.", [
128
+ `AudioClipPlayer id: ${audioClipPlayer.id}`,
129
+ `AudioClip id: ${this.id}`
130
+ ], ErrorCodes.AUDIO_CLIP_PLAYER_NO_CONTEXT);
131
+ }
132
+
93
133
  this.audioClipPlayer = audioClipPlayer;
94
134
  this.context = audioClipPlayer.context;
95
135
 
96
- this.gainNode = new GainNode(this.context);
97
- this.stereoPannerNode = new StereoPannerNode(this.context);
136
+ if (!this.gainNode) this.gainNode = new GainNode(this.context);
137
+ if (!this.stereoPannerNode) this.stereoPannerNode = new StereoPannerNode(this.context);
138
+
139
+ if (!this.audioClipPlayers.includes(audioClipPlayer))
140
+ this.audioClipPlayers.push(audioClipPlayer);
141
+
142
+ const endTimestamp: number = Date.now(),
143
+ difference: number = endTimestamp - startTimestamp;
144
+
145
+ this.events.initialize.forEach(function(cb: OnInitializeCallbackFunction) {
146
+ cb({
147
+ durationOfInitialization: difference,
148
+ context: self.context
149
+ });
150
+ });
151
+
152
+ this.rebuildNodeChain();
98
153
  }
99
154
 
100
- public Play(timestamp?: number, offset?: number) {
155
+ public play(timestamp?: number, offset?: number) {
101
156
 
102
- if (!this.context) return Debug.Error("Could not play audio clip, because context (AudioContext) is undefined.", [
103
- "Make sure to attach this AudioClip to an AudioClipPlayer instance, before calling .Play on this AudioClip."
157
+ if (!this.context) return Debug.error("Could not play audio clip, because context (AudioContext) is undefined.", [
158
+ "Make sure to attach this AudioClip to an AudioClipPlayer instance, before calling .play on this AudioClip."
104
159
  ]);
105
160
 
106
- const context = this.context;
107
- const self = this;
161
+ const context = this.context,
162
+ self = this;
108
163
 
109
164
  if (this.audioBufferSourceNodes.length > this.maxAudioBufferSourceNodes - 1) return null;
110
165
 
111
166
  const bufferSource: AudioBufferSourceNode | null = this.createBufferSource();
112
167
 
113
168
  if (!bufferSource) {
114
- Debug.Error("Something went wrong.");
169
+ Debug.error("Something went wrong.");
115
170
  return null;
116
171
  }
117
172
 
@@ -121,9 +176,9 @@ export class AudioClip {
121
176
  this.offsetAtStart = actualOffset;
122
177
  this.isPlaying = true;
123
178
 
124
- if (this.progressInterval) clearInterval(this.progressInterval);
179
+ if (this.progressInterval) window.clearInterval(this.progressInterval);
125
180
 
126
- if (this.isPlaying) this.progressInterval = setInterval(function () {
181
+ if (this.isPlaying) this.progressInterval = window.setInterval(function () {
127
182
 
128
183
  if (!self.isPlaying) return;
129
184
 
@@ -132,7 +187,7 @@ export class AudioClip {
132
187
 
133
188
  const formattedTime = format(date, "mm:ss");
134
189
 
135
- const progressPayload: ProgressPayload = {
190
+ const progressPayload: Parameters<AudioClipEventMap["progress"]>[0] = {
136
191
  current: parseFloat(current.toFixed(2)),
137
192
  startTime: self.startTime,
138
193
  offset: self.offsetAtStart,
@@ -140,7 +195,7 @@ export class AudioClip {
140
195
  formatted: formattedTime
141
196
  }
142
197
 
143
- self.events.progress.forEach(function (cb: (event: ProgressPayload) => void) {
198
+ self.events.progress.forEach(function (cb: OnProgressCallbackFunction) {
144
199
  cb(progressPayload);
145
200
  });
146
201
  }, this.progressUpdateSpeed);
@@ -163,13 +218,21 @@ export class AudioClip {
163
218
 
164
219
  bufferSource.start(timestamp ?? this.startTime, actualOffset);
165
220
 
221
+ this.events.play.forEach(function(cb: OnPlayCallbackFunction) {
222
+ cb({
223
+ timestamp: Date.now(),
224
+ audioBufferSourceNodes: self.audioBufferSourceNodes,
225
+ context: context,
226
+ })
227
+ });
228
+
166
229
  return this;
167
230
  }
168
231
 
169
- public Seek(seconds: number) {
232
+ public seek(seconds: number) {
170
233
 
171
- if (!this.context) return Debug.Error("Could not seek because the context (AudioContext) of this AudioClip is undefined.", [
172
- `Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .Seek on this AudioClip.`,
234
+ if (!this.context) return Debug.error("Could not seek because the context (AudioContext) of this AudioClip is undefined.", [
235
+ `Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .seek on this AudioClip.`,
173
236
  `AudioClip id: ${this.id}`
174
237
  ]);
175
238
 
@@ -182,13 +245,13 @@ export class AudioClip {
182
245
  return this;
183
246
  }
184
247
 
185
- this.Stop();
186
- this.Play(undefined, clamped);
248
+ this.stop();
249
+ this.play(undefined, clamped);
187
250
 
188
251
  return this;
189
252
  }
190
253
 
191
- public Stop(): AudioClip | null {
254
+ public stop(): AudioClip | null {
192
255
 
193
256
  this.audioBufferSourceNodes.forEach(function (node: AudioBufferSourceNode) {
194
257
  node.stop();
@@ -206,23 +269,23 @@ export class AudioClip {
206
269
  return this;
207
270
  }
208
271
 
209
- public SetVolume(volume: number): AudioClip {
272
+ public setVolume(volume: number): AudioClip {
210
273
 
211
- if (!this.context) Debug.Error("Could not set volume because the context (AudioContext) of this AudioClip is undefined.", [
212
- "Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .SetVolume() on this AudioClip.",
274
+ if (!this.context) Debug.error("Could not set volume because the context (AudioContext) of this AudioClip is undefined.", [
275
+ "Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .setVolume() on this AudioClip.",
213
276
  ], ErrorCodes.AUDIO_CLIP_PLAYER_NO_CONTEXT);
214
277
 
215
278
  this.gainNode?.gain.setValueAtTime(volume, this.context?.currentTime ?? 0);
216
279
  return this;
217
280
  }
218
281
 
219
- public SetPanLevel(panLevel: number): AudioClip {
282
+ public setPanLevel(panLevel: number): AudioClip {
220
283
 
221
- if (!this.context) Debug.Error("Could not set volume because the context (AudioContext) of this AudioClip is undefined.", [
222
- "Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .SetPanLevel() on this AudioClip.",
284
+ if (!this.context) Debug.error("Could not set volume because the context (AudioContext) of this AudioClip is undefined.", [
285
+ "Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .setPanLevel() on this AudioClip.",
223
286
  ], ErrorCodes.AUDIO_CLIP_PLAYER_NO_CONTEXT);
224
287
 
225
- if (panLevel < -1 || panLevel > 1) Debug.Error("Could not set the pan level because it is not between -1 and 1.", [
288
+ if (panLevel < -1 || panLevel > 1) Debug.error("Could not set the pan level because it is not between -1 and 1.", [
226
289
  `Given value: ${panLevel}.`,
227
290
  `Accepts a value between -1 and 1. Can be a floating number.`
228
291
  ], ErrorCodes.PANNING_OUT_OF_RANGE);
@@ -231,7 +294,7 @@ export class AudioClip {
231
294
  return this;
232
295
  }
233
296
 
234
- public Loop(loop?: boolean): AudioClip {
297
+ public setLoop(loop?: boolean): AudioClip {
235
298
 
236
299
  this.audioBufferSourceNodes.forEach(function (node: AudioBufferSourceNode) {
237
300
  node.loop = loop ?? true;
@@ -241,10 +304,10 @@ export class AudioClip {
241
304
  return this;
242
305
  }
243
306
 
244
- public SetMaxAudioBufferSourceNodes(value: number): AudioClip {
307
+ public setMaxAudioBufferSourceNodes(value: number): AudioClip {
245
308
 
246
309
  if (!DSP.overrideMaxAudioBufferNodes) {
247
- Debug.Warn("Cannot set maxAudioBufferSourceNodes because the option to override the current maximum value is disabled.", [
310
+ Debug.warn("Cannot set maxAudioBufferSourceNodes because the option to override the current maximum value is disabled.", [
248
311
  "Set overrideMaxAudioBufferNodes on DSP to true, in order to override the maximum audio buffer source nodes."
249
312
  ]);
250
313
  return this;
@@ -254,7 +317,7 @@ export class AudioClip {
254
317
  return this;
255
318
  }
256
319
 
257
- public DisconnectAllAudioBufferSourceNodes(): boolean {
320
+ public disconnectAllAudioBufferSourceNodes(): boolean {
258
321
 
259
322
  if (!this.context) return false;
260
323
 
@@ -268,25 +331,25 @@ export class AudioClip {
268
331
  return true;
269
332
  }
270
333
 
271
- public AddEventListener<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): () => void {
334
+ public addEventListener<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): () => void {
272
335
 
273
336
  this.events[event].push(cb);
274
- return () => this.RemoveEventListener(event, cb);
337
+ return () => this.removeEventListener(event, cb);
275
338
  }
276
339
 
277
- public Once<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): () => void {
340
+ public once<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): () => void {
278
341
 
279
342
  const wrapper = ((...args: unknown[]) => {
280
343
 
281
344
  // @ts-ignore
282
345
  cb(...args);
283
- this.RemoveEventListener(event, wrapper as unknown as AudioClipEventMap[K]);
346
+ this.removeEventListener(event, wrapper as unknown as AudioClipEventMap[K]);
284
347
  }) as unknown as AudioClipEventMap[K];
285
348
 
286
- return this.AddEventListener(event, wrapper);
349
+ return this.addEventListener(event, wrapper);
287
350
  }
288
351
 
289
- public RemoveEventListener<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): AudioClip {
352
+ public removeEventListener<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): AudioClip {
290
353
 
291
354
  const arr = this.events[event];
292
355
 
@@ -299,7 +362,7 @@ export class AudioClip {
299
362
  return this;
300
363
  }
301
364
 
302
- public ClearEventListeners(event?: keyof AudioClipEventMap): AudioClip {
365
+ public clearEventListeners(event?: keyof AudioClipEventMap): AudioClip {
303
366
 
304
367
  if (event) {
305
368
  this.events[event].length = 0;
@@ -310,30 +373,80 @@ export class AudioClip {
310
373
  return this;
311
374
  }
312
375
 
313
- public GetChannelData(channel: number = 0): Float32Array {
376
+ public getChannelData(channel: number = 0): Float32Array {
314
377
  return this.data.audioBuffer.getChannelData(channel);
315
378
  }
316
379
 
317
- public Send(channel: Channel | Master) {
380
+ public send(channel: Channel | Master) {
318
381
 
319
- if (!channel.audioClipPlayer) return Debug.Error("Could not send AudioClip signal to channel, because the channel's AudioClipPlayer is undefined.", [
382
+ if (!channel.audioClipPlayer) return Debug.error("Could not send AudioClip signal to channel, because the channel's AudioClipPlayer is undefined.", [
320
383
  "Make sure to initialize the Channel.",
321
384
  `Channel id: ${channel.id}.`,
322
385
  `AudioClip id: ${this.id}.`
323
386
  ]);
324
387
 
325
- channel.audioClipPlayer.AttachAudioClip(this);
388
+ channel.audioClipPlayer.attachAudioClip(this);
326
389
  }
327
390
 
328
- public Unsend(channel: Channel | Master) {
391
+ public unsend(channel: Channel | Master) {
329
392
 
330
- if (!channel.audioClipPlayer) return Debug.Error("Could not unsend AudioClip signal to channel, because the channel's AudioClipPlayer is undefined.", [
393
+ if (!channel.audioClipPlayer) return Debug.error("Could not unsend AudioClip signal to channel, because the channel's AudioClipPlayer is undefined.", [
331
394
  "Make sure to initialize the Channel.",
332
395
  `Channel id: ${channel.id}.`,
333
396
  `AudioClip id: ${this.id}.`
334
397
  ]);
335
398
 
336
- channel.audioClipPlayer.DetachAudioClip(this);
399
+ channel.audioClipPlayer.detachAudioClip(this);
400
+ }
401
+
402
+ public detachFromAudioClipPlayer(audioClipPlayer: AudioClipPlayer): void {
403
+
404
+ const idx = this.audioClipPlayers.indexOf(audioClipPlayer);
405
+
406
+ if (idx === -1) return;
407
+
408
+ this.audioClipPlayers.splice(idx, 1);
409
+
410
+ if (this.audioClipPlayer === audioClipPlayer) {
411
+ this.audioClipPlayer = this.audioClipPlayers[0] ?? null;
412
+ }
413
+
414
+ if (this.audioClipPlayers.length === 0) {
415
+ this.stop();
416
+ return;
417
+ }
418
+
419
+ this.rebuildNodeChain();
420
+ }
421
+
422
+ public setPitch(semitones: number): AudioClip {
423
+
424
+ if (!this.context) Debug.error("Could not set pitch because the context (AudioContext) of this AudioClip is undefined.", [
425
+ "Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .setPitch() on this AudioClip.",
426
+ ], ErrorCodes.AUDIO_CLIP_PLAYER_NO_CONTEXT);
427
+
428
+ if (semitones < this.minPitchSemitones || semitones > this.maxPitchSemitones) Debug.error("Could not set the pitch because it is not within the allowed semitone range.", [
429
+ `Given value: ${semitones}.`,
430
+ `Accepts a value between ${this.minPitchSemitones} and ${this.maxPitchSemitones} semitones.`
431
+ ], ErrorCodes.PITCH_OUT_OF_RANGE);
432
+
433
+ const calculatedPlaybackRate: number = Math.pow(2, semitones / 12);
434
+
435
+ for (const buffer of this.audioBufferSourceNodes)
436
+ buffer.playbackRate.setValueAtTime(calculatedPlaybackRate, this.context?.currentTime ?? 0);
437
+
438
+ this.pitch = semitones;
439
+ this.playbackRate = calculatedPlaybackRate;
440
+ return this;
441
+ }
442
+
443
+ public resetPitch(): AudioClip {
444
+ return this.setPitch(0);
445
+ }
446
+
447
+ /** @deprecated Use {@link setPitch} instead. */
448
+ public setPlaybackRateInSemitones(semitones: number): AudioClip {
449
+ return this.setPitch(semitones);
337
450
  }
338
451
 
339
452
  public get currentPlaybackTime(): number {
@@ -355,9 +468,7 @@ export class AudioClip {
355
468
  }
356
469
 
357
470
  public get formattedDuration(): string {
358
-
359
471
  const date = new Date(this.duration * 1000);
360
-
361
472
  return format(date, "mm:ss");
362
473
  }
363
474
 
@@ -372,4 +483,4 @@ export class AudioClip {
372
483
  public get byteLength(): number {
373
484
  return this.data.arrayBuffer.byteLength;
374
485
  }
375
- }
486
+ }
@@ -22,43 +22,41 @@ export class AudioClipPlayer {
22
22
  this.outputGainNode = new GainNode(context);
23
23
  }
24
24
 
25
- public AttachAudioClip(audioClip: AudioClip): void {
25
+ public attachAudioClip(audioClip: AudioClip): void {
26
26
 
27
- if (this.audioClips.includes(audioClip)) return Debug.Error("Could not attach audio clip because it is already part of this channel", [
28
- "Call .DetachAudioClip([clip AudioClip]) before attaching audio clip."
27
+ if (this.audioClips.includes(audioClip)) return Debug.error("Could not attach audio clip because it is already part of this channel", [
28
+ "Call .detachAudioClip([clip AudioClip]) before attaching audio clip."
29
29
  ], ErrorCodes.AUDIO_CLIP_ALREADY_ATTACHED);
30
30
 
31
- audioClip.Initialize(this);
31
+ audioClip.initialize(this);
32
32
  this.audioClips.push(audioClip);
33
33
  }
34
34
 
35
- public DetachAudioClip(clip: AudioClip): void {
35
+ public detachAudioClip(clip: AudioClip): void {
36
36
 
37
- if (!this.audioClips.includes(clip)) return Debug.Error("Could not detach audio clip because it is not part of this channel", [
38
- "Call .AttachAudioClip([clip AudioClip]) before detaching audio clip."
37
+ if (!this.audioClips.includes(clip)) return Debug.error("Could not detach audio clip because it is not part of this channel", [
38
+ "Call .attachAudioClip([clip AudioClip]) before detaching audio clip."
39
39
  ], ErrorCodes.AUDIO_CLIP_NOT_FOUND);
40
40
 
41
- clip.Stop();
41
+ const idx = this.audioClips.indexOf(clip);
42
42
 
43
- const self = this;
43
+ if (idx >= 0)
44
+ this.audioClips.splice(idx, 1);
44
45
 
45
- this.audioClips.forEach(function (_clip: AudioClip, index: number) {
46
- if (clip.id === _clip.id)
47
- return self.audioClips.splice(index, 1);
48
- })
46
+ clip.detachFromAudioClipPlayer(this);
49
47
  }
50
48
 
51
- public Send(channel: Channel | Master): void {
49
+ public send(channel: Channel | Master): void {
52
50
 
53
- if (!this.outputGainNode || !channel.input) return Debug.Error("Could not send AudioClipPlayer signal to a channel/master.");
51
+ if (!this.outputGainNode || !channel.input) return Debug.error("Could not send AudioClipPlayer signal to a channel/master.");
54
52
 
55
- this.Unsend();
53
+ this.unsend();
56
54
 
57
55
  this.channel = channel;
58
56
  this.outputGainNode.connect(channel.input);
59
57
  }
60
58
 
61
- public Unsend(): void {
59
+ public unsend(): void {
62
60
 
63
61
  if (!this.outputGainNode) return;
64
62
 
@@ -71,7 +69,7 @@ export class AudioClipPlayer {
71
69
  this.channel = null;
72
70
  }
73
71
 
74
- public SetVolume(value: number): void {
72
+ public setVolume(value: number): void {
75
73
 
76
74
  if (!this.outputGainNode) return;
77
75
 
@@ -81,17 +79,17 @@ export class AudioClipPlayer {
81
79
  this.outputGainNode.gain.value = value;
82
80
  }
83
81
 
84
- public StopAll(): void {
82
+ public stopAll(): void {
85
83
 
86
84
  this.audioClips.forEach(function (clip: AudioClip) {
87
- clip.Stop();
85
+ clip.stop();
88
86
  });
89
87
  }
90
88
 
91
- public Dispose(): void {
89
+ public dispose(): void {
92
90
 
93
- this.StopAll();
94
- this.Unsend();
91
+ this.stopAll();
92
+ this.unsend();
95
93
  this.outputGainNode?.disconnect();
96
94
 
97
95
  this.audioClips = [];
@@ -100,7 +98,7 @@ export class AudioClipPlayer {
100
98
  this.context = null;
101
99
  }
102
100
 
103
- public SetLabel(label: string): void {
101
+ public setLabel(label: string): void {
104
102
  this.label = label;
105
103
  }
106
104
 
@@ -16,15 +16,34 @@ export class AudioDevice {
16
16
  public masterChannel: Master = new Master(this.context);
17
17
  public masterChannels: Master[] = [];
18
18
 
19
+ readonly sampleRate: number = this.context.sampleRate;
20
+ readonly baseLatency: number = this.context.baseLatency;
21
+ readonly outputLatency: number = this.context.outputLatency;
22
+ readonly state: AudioContextState = this.context.state;
23
+ readonly currentTime: number = this.context.currentTime;
24
+
25
+ readonly maximumFrequency: number = this.context.sampleRate / 2;
26
+
19
27
  constructor(public deviceInfo: MediaDeviceInfo) {};
20
28
 
21
- public GetMasterChannel(): Master {
29
+ /**
30
+ * Returns the master channel associated with this audio device.
31
+ * The master channel is the main output channel for this audio device, and is used to control the overall volume and other properties of the audio output.
32
+ * @returns
33
+ */
34
+ public getMasterChannel(): Master {
22
35
  return this.masterChannel;
23
36
  }
24
37
 
25
- public SetMasterChannel(channel: Master): void {
38
+ /**
39
+ * Sets the master channel for this audio device.
40
+ * The master channel is normally automatically created when the audio device is initialized, but this method allows for the master channel to be changed if needed.
41
+ * @param channel
42
+ * @returns
43
+ */
44
+ public setMasterChannel(channel: Master): void {
26
45
 
27
- if (channel.id === this.masterChannel.id) return Debug.Error("The provided master channel is the same as the current channel.", [
46
+ if (channel.id === this.masterChannel.id) return Debug.error("The provided master channel is the same as the current channel.", [
28
47
  "Provide this method with a different master channel.",
29
48
  `Received master channel ID ${channel.id}.`
30
49
  ], ErrorCodes.SAME_MASTER_CHANNEL);
@@ -32,7 +51,12 @@ export class AudioDevice {
32
51
  this.masterChannel = channel;
33
52
  }
34
53
 
35
- public CreateMasterChannel(): Master {
54
+ /**
55
+ * Creates a new master channel based on the this audio device's audio context, and adds it to the master channels array.
56
+ * Normally, an audio device should only have one master channel, but this method allows for multiple master channels to be created and used if needed.
57
+ * @returns
58
+ */
59
+ public createMasterChannel(): Master {
36
60
 
37
61
  const master = new Master(this.context);
38
62
 
@@ -40,11 +64,30 @@ export class AudioDevice {
40
64
  return master;
41
65
  }
42
66
 
43
- public GetContext(): AudioContext {
67
+ /**
68
+ * Returns the audio context associated with this audio device.
69
+ * This method is provided for ease of use, and is a wrapper for the context property.
70
+ *
71
+ * The audio context can vary between audio devices, and is used to create channels and master channels that are associated with this audio device.
72
+ */
73
+ public getContext(): AudioContext {
44
74
  return this.context;
45
75
  }
46
76
 
47
- public CreateChannel(label?: string): Channel {
77
+ /**
78
+ * Creates a new channel based on the this audio device's audio context.
79
+ * This method is a wrapper for the Channel constructor, and is provided for ease of use.
80
+ * In case you want to create a channel with a different audio context, you can use the Channel constructor directly.
81
+ *
82
+ * @example
83
+ * ```
84
+ * const myChannel: Channel = audioDevice.createChannel("My Channel");
85
+ * ```
86
+ *
87
+ * @param label
88
+ * @returns
89
+ */
90
+ public createChannel(label?: string): Channel {
48
91
  return new Channel(this.context, label);
49
92
  }
50
93
  }