@fluex/fluexgl-dsp 0.1.3 → 0.2.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 (109) hide show
  1. package/README.md +86 -86
  2. package/lib/dist/console-codes.d.ts +13 -13
  3. package/lib/dist/console-codes.js +16 -16
  4. package/lib/dist/core/classes/AudioClip.d.ts +67 -62
  5. package/lib/dist/core/classes/AudioClip.d.ts.map +1 -1
  6. package/lib/dist/core/classes/AudioClip.js +406 -386
  7. package/lib/dist/core/classes/AudioDevice.d.ts +12 -12
  8. package/lib/dist/core/classes/AudioDevice.js +30 -30
  9. package/lib/dist/core/classes/Channel.d.ts +41 -29
  10. package/lib/dist/core/classes/Channel.d.ts.map +1 -1
  11. package/lib/dist/core/classes/Channel.js +210 -144
  12. package/lib/dist/core/classes/DpsPipeline.d.ts +59 -59
  13. package/lib/dist/core/classes/DpsPipeline.js +115 -115
  14. package/lib/dist/core/classes/Effector.d.ts +8 -11
  15. package/lib/dist/core/classes/Effector.d.ts.map +1 -1
  16. package/lib/dist/core/classes/Effector.js +8 -20
  17. package/lib/dist/core/classes/Master.d.ts +11 -11
  18. package/lib/dist/core/classes/Master.js +35 -35
  19. package/lib/dist/core/exports.d.ts +6 -6
  20. package/lib/dist/core/exports.js +6 -6
  21. package/lib/dist/effects/classes/Chorus.d.ts +19 -2
  22. package/lib/dist/effects/classes/Chorus.d.ts.map +1 -1
  23. package/lib/dist/effects/classes/Chorus.js +53 -2
  24. package/lib/dist/effects/classes/Distortion.d.ts +6 -2
  25. package/lib/dist/effects/classes/Distortion.d.ts.map +1 -1
  26. package/lib/dist/effects/classes/Distortion.js +9 -2
  27. package/lib/dist/effects/classes/Equalizer.d.ts +2 -2
  28. package/lib/dist/effects/classes/Equalizer.js +2 -2
  29. package/lib/dist/effects/classes/Limiter.d.ts +2 -2
  30. package/lib/dist/effects/classes/Limiter.js +2 -2
  31. package/lib/dist/effects/classes/Saturation.d.ts +2 -2
  32. package/lib/dist/effects/classes/Saturation.d.ts.map +1 -1
  33. package/lib/dist/effects/classes/Saturation.js +2 -2
  34. package/lib/dist/effects/classes/SoftClip.d.ts +12 -7
  35. package/lib/dist/effects/classes/SoftClip.d.ts.map +1 -1
  36. package/lib/dist/effects/classes/SoftClip.js +54 -38
  37. package/lib/dist/effects/classes/StereoPanner.d.ts +3 -2
  38. package/lib/dist/effects/classes/StereoPanner.d.ts.map +1 -1
  39. package/lib/dist/effects/classes/StereoPanner.js +3 -2
  40. package/lib/dist/effects/classes/compressors/Compressor.d.ts +2 -2
  41. package/lib/dist/effects/classes/compressors/Compressor.js +2 -2
  42. package/lib/dist/effects/classes/compressors/MultibandCompressor.d.ts +2 -2
  43. package/lib/dist/effects/classes/compressors/MultibandCompressor.js +2 -2
  44. package/lib/dist/effects/classes/delays/AdvancedDelay.d.ts +2 -2
  45. package/lib/dist/effects/classes/delays/AdvancedDelay.js +2 -2
  46. package/lib/dist/effects/classes/delays/MonoDelay.d.ts +2 -2
  47. package/lib/dist/effects/classes/delays/MonoDelay.js +2 -2
  48. package/lib/dist/effects/classes/delays/PingPongDelay.d.ts +2 -2
  49. package/lib/dist/effects/classes/delays/PingPongDelay.js +2 -2
  50. package/lib/dist/effects/classes/delays/StereoDelay.d.ts +2 -2
  51. package/lib/dist/effects/classes/delays/StereoDelay.js +2 -2
  52. package/lib/dist/effects/classes/filters/LowPassFilter.d.ts +14 -0
  53. package/lib/dist/effects/classes/filters/LowPassFilter.d.ts.map +1 -0
  54. package/lib/dist/effects/classes/filters/LowPassFilter.js +38 -0
  55. package/lib/dist/effects/classes/reverbs/ChamberReverb.d.ts +2 -2
  56. package/lib/dist/effects/classes/reverbs/ChamberReverb.js +2 -2
  57. package/lib/dist/effects/classes/reverbs/ConvolverReverb.d.ts +2 -2
  58. package/lib/dist/effects/classes/reverbs/ConvolverReverb.js +2 -2
  59. package/lib/dist/effects/classes/reverbs/GenericReverb.d.ts +2 -2
  60. package/lib/dist/effects/classes/reverbs/GenericReverb.js +2 -2
  61. package/lib/dist/effects/classes/reverbs/HallReverb.d.ts +2 -2
  62. package/lib/dist/effects/classes/reverbs/HallReverb.js +2 -2
  63. package/lib/dist/effects/classes/reverbs/RoomReverb.d.ts +2 -2
  64. package/lib/dist/effects/classes/reverbs/RoomReverb.js +2 -2
  65. package/lib/dist/effects/exports.d.ts +19 -19
  66. package/lib/dist/effects/exports.d.ts.map +1 -1
  67. package/lib/dist/effects/exports.js +19 -19
  68. package/lib/dist/index.d.ts +12 -12
  69. package/lib/dist/index.d.ts.map +1 -1
  70. package/lib/dist/index.js +28 -28
  71. package/lib/dist/typings.d.ts +97 -59
  72. package/lib/dist/typings.d.ts.map +1 -1
  73. package/lib/dist/typings.js +30 -1
  74. package/lib/dist/utilities/constants.d.ts +1 -1
  75. package/lib/dist/utilities/constants.js +9 -9
  76. package/lib/dist/utilities/debugger.d.ts +7 -7
  77. package/lib/dist/utilities/debugger.js +49 -49
  78. package/lib/dist/utilities/helpers.d.ts +46 -40
  79. package/lib/dist/utilities/helpers.d.ts.map +1 -1
  80. package/lib/dist/utilities/helpers.js +186 -143
  81. package/lib/dist/utilities/web-assembly.d.ts +3 -3
  82. package/lib/dist/utilities/web-assembly.d.ts.map +1 -1
  83. package/lib/dist/utilities/web-assembly.js +12 -12
  84. package/lib/src/console-codes.ts +15 -15
  85. package/lib/src/core/classes/AudioClip.ts +539 -539
  86. package/lib/src/core/classes/AudioDevice.ts +38 -38
  87. package/lib/src/core/classes/Channel.ts +288 -288
  88. package/lib/src/core/classes/DpsPipeline.ts +152 -152
  89. package/lib/src/core/classes/Effector.ts +11 -12
  90. package/lib/src/core/classes/Master.ts +49 -49
  91. package/lib/src/core/exports.ts +5 -5
  92. package/lib/src/effects/classes/Chorus.ts +65 -111
  93. package/lib/src/effects/classes/Distortion.ts +13 -13
  94. package/lib/src/effects/classes/Saturation.ts +2 -2
  95. package/lib/src/effects/classes/SoftClip.ts +60 -44
  96. package/lib/src/effects/classes/StereoPanner.ts +2 -2
  97. package/lib/src/effects/classes/filters/LowPassFilter.ts +54 -0
  98. package/lib/src/effects/exports.ts +23 -22
  99. package/lib/src/index.ts +98 -93
  100. package/lib/src/typings.ts +113 -78
  101. package/lib/src/utilities/constants.ts +8 -8
  102. package/lib/src/utilities/debugger.ts +65 -65
  103. package/lib/src/utilities/helpers.ts +265 -237
  104. package/lib/src/utilities/web-assembly.ts +12 -12
  105. package/package.json +51 -50
  106. package/lib/dist/effects/classes/Filter.d.ts +0 -3
  107. package/lib/dist/effects/classes/Filter.d.ts.map +0 -1
  108. package/lib/dist/effects/classes/Filter.js +0 -2
  109. package/lib/src/effects/classes/Filter.ts +0 -1
@@ -1,386 +1,406 @@
1
- import { v4 } from "uuid";
2
- import { format } from "date-fns";
3
- import { Debug } from "../../utilities/debugger";
4
- export class AudioClip {
5
- data;
6
- id = v4();
7
- hasAttachedToChannel = false;
8
- loop = false;
9
- isPlaying = false;
10
- startTime = 0;
11
- offsetAtStart = 0;
12
- gainNode = null;
13
- stereoPannerNode = null;
14
- parentialAudioContext = null;
15
- parentialChannel = null;
16
- preAnalyser = null;
17
- postAnalyser = null;
18
- preAnalyserEnabled = false;
19
- postAnalyserEnabled = false;
20
- preAnalyserOptions = {};
21
- postAnalyserOptions = {};
22
- preAnalyserFloatArrayBuffer = new Float32Array();
23
- postAnalyserFloatArrayBuffer = new Float32Array();
24
- preAnalyserByteArrayBuffer = new Uint8Array();
25
- postAnalyserByteArrayBuffer = new Uint8Array();
26
- audioBufferSourceNodes = [];
27
- maxAudioBufferSourceNodes = 1;
28
- progressInterval = 0;
29
- events = {
30
- "progress": []
31
- };
32
- constructor(data) {
33
- this.data = data;
34
- }
35
- // Private methods.
36
- createBufferSource() {
37
- if (!this.parentialAudioContext)
38
- return null;
39
- const context = this.parentialAudioContext;
40
- const bufferSource = context.createBufferSource();
41
- bufferSource.buffer = this.data.audioBuffer;
42
- bufferSource.loop = this.loop;
43
- return bufferSource;
44
- }
45
- connectSourcesTo(target) {
46
- for (const src of this.audioBufferSourceNodes) {
47
- src.disconnect();
48
- src.connect(target);
49
- }
50
- }
51
- safeDisconnect(node) {
52
- node?.disconnect();
53
- }
54
- rebuildNodeChain() {
55
- if (!this.parentialAudioContext || !this.gainNode || !this.stereoPannerNode || !this.parentialChannel || !this.parentialChannel.audioClipsInputGainNode) {
56
- Debug.Error("rebuildNodeChain: missing context or core nodes (gain/panner).");
57
- return false;
58
- }
59
- const destination = this.parentialChannel.audioClipsInputGainNode;
60
- this.safeDisconnect(this.gainNode);
61
- this.safeDisconnect(this.stereoPannerNode);
62
- this.safeDisconnect(this.preAnalyser);
63
- this.safeDisconnect(this.postAnalyser);
64
- let entry = this.gainNode;
65
- if (this.preAnalyserEnabled && this.preAnalyser) {
66
- entry = this.preAnalyser;
67
- this.preAnalyser.connect(this.gainNode);
68
- }
69
- this.gainNode.connect(this.stereoPannerNode);
70
- if (this.postAnalyserEnabled && this.postAnalyser) {
71
- this.stereoPannerNode.connect(this.postAnalyser);
72
- this.postAnalyser.connect(destination);
73
- }
74
- else {
75
- this.stereoPannerNode.connect(destination);
76
- }
77
- this.connectSourcesTo(entry);
78
- return true;
79
- }
80
- // Public methods
81
- InitializeAudioClipOnAttaching(channel) {
82
- if (!channel.parentialContext || !channel.audioClipsInputGainNode) {
83
- Debug.Error("Could not initialize audio clip on channel attachment, because channel it's master channel has not been defined.", [
84
- `Call .AttachChannel([channel<"${channel.id}"> Channel]) on the master channel.`
85
- ]);
86
- return null;
87
- }
88
- this.gainNode = new GainNode(channel.parentialContext);
89
- this.stereoPannerNode = new StereoPannerNode(channel.parentialContext);
90
- this.parentialAudioContext = channel.parentialContext;
91
- this.parentialChannel = channel;
92
- this.hasAttachedToChannel = true;
93
- return this;
94
- }
95
- Play(timestamp, offset = 0) {
96
- if (!this.hasAttachedToChannel || !this.parentialAudioContext || !this.parentialChannel) {
97
- Debug.Error("Could not play the audio node because it is not attached to a channel", [
98
- "Call 'AttachAudioClip([clip AudioClip])' on a channel, before playing this audio node."
99
- ]);
100
- return this;
101
- }
102
- const context = this.parentialAudioContext;
103
- const self = this;
104
- if (this.audioBufferSourceNodes.length > this.maxAudioBufferSourceNodes - 1)
105
- return null;
106
- const bufferSource = this.createBufferSource();
107
- if (!bufferSource) {
108
- Debug.Error("Something went wrong.");
109
- return null;
110
- }
111
- this.startTime = context.currentTime;
112
- this.offsetAtStart = offset;
113
- this.isPlaying = true;
114
- if (this.progressInterval)
115
- clearInterval(this.progressInterval);
116
- if (this.isPlaying)
117
- this.progressInterval = setInterval(function () {
118
- if (!self.isPlaying)
119
- return;
120
- const current = self.offsetAtStart + (context.currentTime - self.startTime);
121
- const date = new Date(current * 1000);
122
- const formattedTime = format(date, "mm:ss");
123
- const progressPayload = {
124
- current: parseFloat(current.toFixed(2)),
125
- startTime: self.startTime,
126
- offset: self.offsetAtStart,
127
- contextTimestamp: context.currentTime,
128
- formatted: formattedTime
129
- };
130
- self.events.progress.forEach(function (cb) {
131
- cb(progressPayload);
132
- });
133
- }, 20);
134
- bufferSource.addEventListener("ended", function () {
135
- const i = self.audioBufferSourceNodes.indexOf(bufferSource);
136
- bufferSource.disconnect();
137
- if (i === 0)
138
- self.isPlaying = false;
139
- if (i >= 0)
140
- return self.audioBufferSourceNodes.splice(i, 1);
141
- });
142
- this.audioBufferSourceNodes.push(bufferSource);
143
- this.rebuildNodeChain();
144
- bufferSource.start(timestamp ?? this.startTime, offset);
145
- return this;
146
- }
147
- Stop() {
148
- if (!this.hasAttachedToChannel || !this.parentialAudioContext) {
149
- Debug.Error("Could not stop the audio node because it is not attached to a channel", [
150
- "Call 'AttachAudioClip([node AudioNode])' on a channel, before stopping this audio node."
151
- ]);
152
- return null;
153
- }
154
- this.audioBufferSourceNodes.forEach(function (node) {
155
- node.stop();
156
- node.disconnect();
157
- });
158
- this.audioBufferSourceNodes.length = 0;
159
- this.isPlaying = false;
160
- if (this.progressInterval) {
161
- clearInterval(this.progressInterval);
162
- this.progressInterval = null;
163
- }
164
- return this;
165
- }
166
- SetVolume(volume) {
167
- if (!this.gainNode || !this.parentialAudioContext)
168
- return Debug.Error("Something went wrong while setting the volume.", [
169
- `Gain node on audio clip '${this.id}' is undefined.`
170
- ]);
171
- this.gainNode.gain.setValueAtTime(volume, this.parentialAudioContext.currentTime);
172
- return this;
173
- }
174
- SetPanLevel(panLevel) {
175
- if (!this.stereoPannerNode || !this.parentialAudioContext)
176
- return Debug.Error("Something went wrong while setting the pan level", [
177
- `Stereo panner node on audio clip '${this.id}' is undefined`
178
- ]);
179
- if (panLevel < -1 || panLevel > 1)
180
- return Debug.Error("Could not set the pan level because it is not between -1 and 1.", [
181
- "Provide this method with a value between -1 and 1"
182
- ]);
183
- this.stereoPannerNode.pan.setValueAtTime(panLevel, this.parentialAudioContext.currentTime);
184
- return this;
185
- }
186
- Loop(loop) {
187
- this.audioBufferSourceNodes.forEach(function (node) {
188
- node.loop = loop ?? true;
189
- });
190
- this.loop = loop ?? true;
191
- return this;
192
- }
193
- SetMaxAudioBufferSourceNodes(value) {
194
- Debug.Warn("Changing the amount of buffer source nodes may cause some properties of this class instance to work inproperly.", [
195
- "The default value is 1."
196
- ]);
197
- this.maxAudioBufferSourceNodes = value;
198
- return this;
199
- }
200
- DisconnectAllAudioBufferSourceNodes() {
201
- if (!this.parentialAudioContext) {
202
- Debug.Error("Could not disconnect audio buffer source nodes, because the parential audio contex has not been found", [
203
- `Clip ID: ${this.id}`,
204
- `Parential channel id: ${this.parentialChannel ? this.parentialChannel.id : "none"}`
205
- ]);
206
- return false;
207
- }
208
- const contextCurrentTime = this.parentialAudioContext?.currentTime;
209
- this.audioBufferSourceNodes.forEach(function (node) {
210
- node.stop(contextCurrentTime);
211
- node.disconnect();
212
- });
213
- return true;
214
- }
215
- AddEventListener(event, cb) {
216
- this.events[event].push(cb);
217
- return () => this.RemoveEventListener(event, cb);
218
- }
219
- Once(event, cb) {
220
- const wrapper = ((...args) => {
221
- // @ts-ignore
222
- cb(...args);
223
- this.RemoveEventListener(event, wrapper);
224
- });
225
- return this.AddEventListener(event, wrapper);
226
- }
227
- RemoveEventListener(event, cb) {
228
- const arr = this.events[event];
229
- for (let i = 0; i < arr.length; i++) {
230
- if (arr[i] === cb) {
231
- arr.splice(i, 1);
232
- break;
233
- }
234
- }
235
- return this;
236
- }
237
- ClearEventListeners(event) {
238
- if (event) {
239
- this.events[event].length = 0;
240
- }
241
- else {
242
- Object.keys(this.events).forEach((k) => (this.events[k].length = 0));
243
- }
244
- return this;
245
- }
246
- GetChannelData(channel = 0) {
247
- return this.data.audioBuffer.getChannelData(channel);
248
- }
249
- EnablePreAnalyser() {
250
- if (!this.parentialAudioContext || !this.parentialChannel || !this.hasAttachedToChannel) {
251
- Debug.Error("EnablePreAnalyser: clip niet aan channel gekoppeld.");
252
- return false;
253
- }
254
- if (!this.preAnalyser)
255
- this.preAnalyser = new AnalyserNode(this.parentialAudioContext, this.preAnalyserOptions);
256
- this.preAnalyserFloatArrayBuffer = new Float32Array(this.preAnalyser.fftSize);
257
- this.preAnalyserByteArrayBuffer = new Uint8Array(this.preAnalyser.fftSize);
258
- this.preAnalyserEnabled = true;
259
- return this.rebuildNodeChain();
260
- }
261
- DisablePreAnalyser() {
262
- this.preAnalyserEnabled = false;
263
- return this.rebuildNodeChain();
264
- }
265
- EnablePostAnalyser() {
266
- if (!this.parentialAudioContext || !this.parentialChannel || !this.hasAttachedToChannel) {
267
- Debug.Error("EnablePostAnalyser: clip niet aan channel gekoppeld.");
268
- return false;
269
- }
270
- if (!this.postAnalyser)
271
- this.postAnalyser = new AnalyserNode(this.parentialAudioContext, this.postAnalyserOptions);
272
- this.postAnalyserFloatArrayBuffer = new Float32Array(this.postAnalyser.fftSize);
273
- this.postAnalyserByteArrayBuffer = new Uint8Array(this.postAnalyser.fftSize);
274
- this.postAnalyserEnabled = true;
275
- return this.rebuildNodeChain();
276
- }
277
- DisablePostAnalyser() {
278
- this.postAnalyserEnabled = false;
279
- return this.rebuildNodeChain();
280
- }
281
- SetPreAnalyserOptions(options) {
282
- this.preAnalyserOptions = { ...options };
283
- if (!this.preAnalyser)
284
- return;
285
- this.preAnalyser.fftSize = options.fftSize ?? this.preAnalyser.fftSize;
286
- this.preAnalyser.minDecibels = options.minDecibels ?? this.preAnalyser.minDecibels;
287
- this.preAnalyser.maxDecibels = options.maxDecibels ?? this.preAnalyser.maxDecibels;
288
- this.preAnalyser.smoothingTimeConstant = options.smoothingTimeConstant ?? this.preAnalyser.smoothingTimeConstant;
289
- }
290
- SetPostAnalyserOptions(options) {
291
- this.postAnalyserOptions = { ...options };
292
- if (!this.postAnalyser)
293
- return;
294
- this.postAnalyser.fftSize = options.fftSize ?? this.postAnalyser.fftSize;
295
- this.postAnalyser.minDecibels = options.minDecibels ?? this.postAnalyser.minDecibels;
296
- this.postAnalyser.maxDecibels = options.maxDecibels ?? this.postAnalyser.maxDecibels;
297
- this.postAnalyser.smoothingTimeConstant = options.smoothingTimeConstant ?? this.postAnalyser.smoothingTimeConstant;
298
- }
299
- SetAnalyserOption(analyserType, property, value) {
300
- const node = analyserType === "pre" ? this.preAnalyser : this.postAnalyser;
301
- if (node)
302
- switch (property) {
303
- case "fftSize":
304
- node.fftSize = value;
305
- break;
306
- case "minDecibels":
307
- node.minDecibels = value;
308
- break;
309
- case "maxDecibels":
310
- node.maxDecibels = value;
311
- break;
312
- case "smoothingTimeConstant":
313
- node.smoothingTimeConstant = value;
314
- break;
315
- default: return false;
316
- }
317
- const opts = analyserType === "pre" ? this.preAnalyserOptions : this.postAnalyserOptions;
318
- switch (property) {
319
- case "fftSize":
320
- opts.fftSize = value;
321
- break;
322
- case "minDecibels":
323
- opts.minDecibels = value;
324
- break;
325
- case "maxDecibels":
326
- opts.maxDecibels = value;
327
- break;
328
- case "smoothingTimeConstant":
329
- opts.smoothingTimeConstant = value;
330
- break;
331
- default: return false;
332
- }
333
- return true;
334
- }
335
- GetWaveformFloatData(analyserType) {
336
- if (analyserType === "pre" && this.preAnalyser) {
337
- this.preAnalyser.getFloatTimeDomainData(this.preAnalyserFloatArrayBuffer);
338
- return this.preAnalyserFloatArrayBuffer;
339
- }
340
- else if (analyserType === "post" && this.postAnalyser) {
341
- this.postAnalyser.getFloatTimeDomainData(this.postAnalyserFloatArrayBuffer);
342
- return this.postAnalyserFloatArrayBuffer;
343
- }
344
- return null;
345
- }
346
- GetWaveformByteData(analyserType) {
347
- if (!this.preAnalyser || !this.postAnalyser) {
348
- Debug.Error("Could not get byte waveform data because the pre analyser or post analyser has not been enabled.", [
349
- "Call .EnablePreAnalyser() or .EnablePostAnalyser() before getting waveform data."
350
- ]);
351
- return null;
352
- }
353
- switch (analyserType) {
354
- case "pre":
355
- this.preAnalyser.getByteTimeDomainData(this.preAnalyserByteArrayBuffer);
356
- return this.preAnalyserByteArrayBuffer;
357
- case "post":
358
- this.postAnalyser.getByteTimeDomainData(this.postAnalyserByteArrayBuffer);
359
- return this.postAnalyserByteArrayBuffer;
360
- default:
361
- return null;
362
- }
363
- }
364
- // Public getters and setters
365
- get currentPlaybackTime() {
366
- return (!this.isPlaying || !this.parentialAudioContext)
367
- ? 0
368
- : this.offsetAtStart + (this.parentialAudioContext.currentTime - this.startTime);
369
- }
370
- get duration() {
371
- return this.data.audioBuffer.duration;
372
- }
373
- get formattedDuration() {
374
- const date = new Date(this.duration * 1000);
375
- return format(date, "mm:ss");
376
- }
377
- get sampleRate() {
378
- return this.data.audioBuffer.sampleRate;
379
- }
380
- get numberOfChannels() {
381
- return this.data.audioBuffer.numberOfChannels;
382
- }
383
- get byteLength() {
384
- return this.data.arrayBuffer.byteLength;
385
- }
386
- }
1
+ import { v4 } from "uuid";
2
+ import { format } from "date-fns";
3
+ import { Debug } from "../../utilities/debugger";
4
+ export class AudioClip {
5
+ data;
6
+ id = v4();
7
+ hasAttachedToChannel = false;
8
+ label = null;
9
+ loop = false;
10
+ isPlaying = false;
11
+ startTime = 0;
12
+ offsetAtStart = 0;
13
+ progressUpdateSpeed = 20;
14
+ gainNode = null;
15
+ stereoPannerNode = null;
16
+ parentialAudioContext = null;
17
+ parentialChannel = null;
18
+ preAnalyser = null;
19
+ postAnalyser = null;
20
+ preAnalyserEnabled = false;
21
+ postAnalyserEnabled = false;
22
+ preAnalyserOptions = {};
23
+ postAnalyserOptions = {};
24
+ preAnalyserFloatArrayBuffer = new Float32Array();
25
+ postAnalyserFloatArrayBuffer = new Float32Array();
26
+ preAnalyserByteArrayBuffer = new Uint8Array();
27
+ postAnalyserByteArrayBuffer = new Uint8Array();
28
+ audioBufferSourceNodes = [];
29
+ maxAudioBufferSourceNodes = 1;
30
+ progressInterval = 0;
31
+ events = {
32
+ "progress": []
33
+ };
34
+ constructor(data) {
35
+ this.data = data;
36
+ }
37
+ // Private methods.
38
+ createBufferSource() {
39
+ if (!this.parentialAudioContext)
40
+ return null;
41
+ const context = this.parentialAudioContext;
42
+ const bufferSource = context.createBufferSource();
43
+ bufferSource.buffer = this.data.audioBuffer;
44
+ bufferSource.loop = this.loop;
45
+ return bufferSource;
46
+ }
47
+ connectSourcesTo(target) {
48
+ for (const src of this.audioBufferSourceNodes) {
49
+ src.disconnect();
50
+ src.connect(target);
51
+ }
52
+ }
53
+ safeDisconnect(node) {
54
+ node?.disconnect();
55
+ }
56
+ rebuildNodeChain() {
57
+ if (!this.parentialAudioContext || !this.gainNode || !this.stereoPannerNode || !this.parentialChannel || !this.parentialChannel.audioClipsInputGainNode) {
58
+ Debug.Error("rebuildNodeChain: missing context or core nodes (gain/panner).");
59
+ return false;
60
+ }
61
+ const destination = this.parentialChannel.audioClipsInputGainNode;
62
+ this.safeDisconnect(this.gainNode);
63
+ this.safeDisconnect(this.stereoPannerNode);
64
+ this.safeDisconnect(this.preAnalyser);
65
+ this.safeDisconnect(this.postAnalyser);
66
+ let entry = this.gainNode;
67
+ if (this.preAnalyserEnabled && this.preAnalyser) {
68
+ entry = this.preAnalyser;
69
+ this.preAnalyser.connect(this.gainNode);
70
+ }
71
+ this.gainNode.connect(this.stereoPannerNode);
72
+ if (this.postAnalyserEnabled && this.postAnalyser) {
73
+ this.stereoPannerNode.connect(this.postAnalyser);
74
+ this.postAnalyser.connect(destination);
75
+ }
76
+ else {
77
+ this.stereoPannerNode.connect(destination);
78
+ }
79
+ this.connectSourcesTo(entry);
80
+ return true;
81
+ }
82
+ // Public methods
83
+ InitializeAudioClipOnAttaching(channel) {
84
+ if (!channel.parentialContext || !channel.audioClipsInputGainNode) {
85
+ Debug.Error("Could not initialize audio clip on channel attachment, because channel it's master channel has not been defined.", [
86
+ `Call .AttachChannel([channel<"${channel.id}"> Channel]) on the master channel.`
87
+ ]);
88
+ return null;
89
+ }
90
+ this.gainNode = new GainNode(channel.parentialContext);
91
+ this.stereoPannerNode = new StereoPannerNode(channel.parentialContext);
92
+ this.parentialAudioContext = channel.parentialContext;
93
+ this.parentialChannel = channel;
94
+ this.hasAttachedToChannel = true;
95
+ return this;
96
+ }
97
+ Play(timestamp, offset) {
98
+ if (!this.hasAttachedToChannel || !this.parentialAudioContext || !this.parentialChannel) {
99
+ Debug.Error("Could not play the audio node because it is not attached to a channel", [
100
+ "Call 'AttachAudioClip([clip AudioClip])' on a channel, before playing this audio node."
101
+ ]);
102
+ return this;
103
+ }
104
+ const context = this.parentialAudioContext;
105
+ const self = this;
106
+ if (this.audioBufferSourceNodes.length > this.maxAudioBufferSourceNodes - 1)
107
+ return null;
108
+ const bufferSource = this.createBufferSource();
109
+ if (!bufferSource) {
110
+ Debug.Error("Something went wrong.");
111
+ return null;
112
+ }
113
+ const actualOffset = offset ?? this.offsetAtStart ?? 0;
114
+ this.startTime = context.currentTime;
115
+ this.offsetAtStart = actualOffset;
116
+ this.isPlaying = true;
117
+ if (this.progressInterval)
118
+ clearInterval(this.progressInterval);
119
+ if (this.isPlaying)
120
+ this.progressInterval = setInterval(function () {
121
+ if (!self.isPlaying)
122
+ return;
123
+ const current = self.offsetAtStart + (context.currentTime - self.startTime);
124
+ const date = new Date(current * 1000);
125
+ const formattedTime = format(date, "mm:ss");
126
+ const progressPayload = {
127
+ current: parseFloat(current.toFixed(2)),
128
+ startTime: self.startTime,
129
+ offset: self.offsetAtStart,
130
+ contextTimestamp: context.currentTime,
131
+ formatted: formattedTime
132
+ };
133
+ self.events.progress.forEach(function (cb) {
134
+ cb(progressPayload);
135
+ });
136
+ }, this.progressUpdateSpeed);
137
+ bufferSource.addEventListener("ended", function () {
138
+ const i = self.audioBufferSourceNodes.indexOf(bufferSource);
139
+ bufferSource.disconnect();
140
+ if (i === 0)
141
+ self.isPlaying = false;
142
+ if (i >= 0)
143
+ return self.audioBufferSourceNodes.splice(i, 1);
144
+ });
145
+ this.audioBufferSourceNodes.push(bufferSource);
146
+ this.rebuildNodeChain();
147
+ bufferSource.start(timestamp ?? this.startTime, actualOffset);
148
+ return this;
149
+ }
150
+ Seek(seconds) {
151
+ if (!this.parentialAudioContext || !this.hasAttachedToChannel) {
152
+ Debug.Error("Could not seek because the clip is not attached to a channel.", [
153
+ `Clip ID: ${this.id}`
154
+ ]);
155
+ return null;
156
+ }
157
+ const clamped = Math.max(0, Math.min(seconds, this.duration));
158
+ if (!this.isPlaying) {
159
+ this.offsetAtStart = clamped;
160
+ return this;
161
+ }
162
+ this.Stop();
163
+ this.Play(undefined, clamped);
164
+ return this;
165
+ }
166
+ Stop() {
167
+ if (!this.hasAttachedToChannel || !this.parentialAudioContext) {
168
+ Debug.Error("Could not stop the audio node because it is not attached to a channel", [
169
+ "Call 'AttachAudioClip([node AudioNode])' on a channel, before stopping this audio node."
170
+ ]);
171
+ return null;
172
+ }
173
+ this.audioBufferSourceNodes.forEach(function (node) {
174
+ node.stop();
175
+ node.disconnect();
176
+ });
177
+ this.audioBufferSourceNodes.length = 0;
178
+ this.isPlaying = false;
179
+ if (this.progressInterval) {
180
+ clearInterval(this.progressInterval);
181
+ this.progressInterval = null;
182
+ }
183
+ return this;
184
+ }
185
+ SetVolume(volume) {
186
+ if (!this.gainNode || !this.parentialAudioContext)
187
+ return Debug.Error("Something went wrong while setting the volume.", [
188
+ `Gain node on audio clip '${this.id}' is undefined.`
189
+ ]);
190
+ this.gainNode.gain.setValueAtTime(volume, this.parentialAudioContext.currentTime);
191
+ return this;
192
+ }
193
+ SetPanLevel(panLevel) {
194
+ if (!this.stereoPannerNode || !this.parentialAudioContext)
195
+ return Debug.Error("Something went wrong while setting the pan level", [
196
+ `Stereo panner node on audio clip '${this.id}' is undefined`
197
+ ]);
198
+ if (panLevel < -1 || panLevel > 1)
199
+ return Debug.Error("Could not set the pan level because it is not between -1 and 1.", [
200
+ "Provide this method with a value between -1 and 1"
201
+ ]);
202
+ this.stereoPannerNode.pan.setValueAtTime(panLevel, this.parentialAudioContext.currentTime);
203
+ return this;
204
+ }
205
+ Loop(loop) {
206
+ this.audioBufferSourceNodes.forEach(function (node) {
207
+ node.loop = loop ?? true;
208
+ });
209
+ this.loop = loop ?? true;
210
+ return this;
211
+ }
212
+ SetMaxAudioBufferSourceNodes(value) {
213
+ Debug.Warn("Changing the amount of buffer source nodes may cause some properties of this class instance to work inproperly.", [
214
+ "The default value is 1."
215
+ ]);
216
+ this.maxAudioBufferSourceNodes = value;
217
+ return this;
218
+ }
219
+ DisconnectAllAudioBufferSourceNodes() {
220
+ if (!this.parentialAudioContext)
221
+ return false;
222
+ const contextCurrentTime = this.parentialAudioContext?.currentTime;
223
+ this.audioBufferSourceNodes.forEach(function (node) {
224
+ node.stop(contextCurrentTime);
225
+ node.disconnect();
226
+ });
227
+ return true;
228
+ }
229
+ AddEventListener(event, cb) {
230
+ this.events[event].push(cb);
231
+ return () => this.RemoveEventListener(event, cb);
232
+ }
233
+ Once(event, cb) {
234
+ const wrapper = ((...args) => {
235
+ // @ts-ignore
236
+ cb(...args);
237
+ this.RemoveEventListener(event, wrapper);
238
+ });
239
+ return this.AddEventListener(event, wrapper);
240
+ }
241
+ RemoveEventListener(event, cb) {
242
+ const arr = this.events[event];
243
+ for (let i = 0; i < arr.length; i++) {
244
+ if (arr[i] === cb) {
245
+ arr.splice(i, 1);
246
+ break;
247
+ }
248
+ }
249
+ return this;
250
+ }
251
+ ClearEventListeners(event) {
252
+ if (event) {
253
+ this.events[event].length = 0;
254
+ }
255
+ else {
256
+ Object.keys(this.events).forEach((k) => (this.events[k].length = 0));
257
+ }
258
+ return this;
259
+ }
260
+ GetChannelData(channel = 0) {
261
+ return this.data.audioBuffer.getChannelData(channel);
262
+ }
263
+ EnablePreAnalyser() {
264
+ if (!this.parentialAudioContext || !this.parentialChannel || !this.hasAttachedToChannel) {
265
+ Debug.Error("EnablePreAnalyser: clip niet aan channel gekoppeld.");
266
+ return false;
267
+ }
268
+ if (!this.preAnalyser)
269
+ this.preAnalyser = new AnalyserNode(this.parentialAudioContext, this.preAnalyserOptions);
270
+ this.preAnalyserFloatArrayBuffer = new Float32Array(this.preAnalyser.fftSize);
271
+ this.preAnalyserByteArrayBuffer = new Uint8Array(this.preAnalyser.fftSize);
272
+ this.preAnalyserEnabled = true;
273
+ return this.rebuildNodeChain();
274
+ }
275
+ DisablePreAnalyser() {
276
+ this.preAnalyserEnabled = false;
277
+ return this.rebuildNodeChain();
278
+ }
279
+ EnablePostAnalyser() {
280
+ if (!this.parentialAudioContext || !this.parentialChannel || !this.hasAttachedToChannel) {
281
+ Debug.Error("EnablePostAnalyser: clip niet aan channel gekoppeld.");
282
+ return false;
283
+ }
284
+ if (!this.postAnalyser)
285
+ this.postAnalyser = new AnalyserNode(this.parentialAudioContext, this.postAnalyserOptions);
286
+ this.postAnalyserFloatArrayBuffer = new Float32Array(this.postAnalyser.fftSize);
287
+ this.postAnalyserByteArrayBuffer = new Uint8Array(this.postAnalyser.fftSize);
288
+ this.postAnalyserEnabled = true;
289
+ return this.rebuildNodeChain();
290
+ }
291
+ DisablePostAnalyser() {
292
+ this.postAnalyserEnabled = false;
293
+ return this.rebuildNodeChain();
294
+ }
295
+ SetPreAnalyserOptions(options) {
296
+ this.preAnalyserOptions = { ...options };
297
+ if (!this.preAnalyser)
298
+ return;
299
+ this.preAnalyser.fftSize = options.fftSize ?? this.preAnalyser.fftSize;
300
+ this.preAnalyser.minDecibels = options.minDecibels ?? this.preAnalyser.minDecibels;
301
+ this.preAnalyser.maxDecibels = options.maxDecibels ?? this.preAnalyser.maxDecibels;
302
+ this.preAnalyser.smoothingTimeConstant = options.smoothingTimeConstant ?? this.preAnalyser.smoothingTimeConstant;
303
+ }
304
+ SetPostAnalyserOptions(options) {
305
+ this.postAnalyserOptions = { ...options };
306
+ if (!this.postAnalyser)
307
+ return;
308
+ this.postAnalyser.fftSize = options.fftSize ?? this.postAnalyser.fftSize;
309
+ this.postAnalyser.minDecibels = options.minDecibels ?? this.postAnalyser.minDecibels;
310
+ this.postAnalyser.maxDecibels = options.maxDecibels ?? this.postAnalyser.maxDecibels;
311
+ this.postAnalyser.smoothingTimeConstant = options.smoothingTimeConstant ?? this.postAnalyser.smoothingTimeConstant;
312
+ }
313
+ SetAnalyserOption(analyserType, property, value) {
314
+ const node = analyserType === "pre" ? this.preAnalyser : this.postAnalyser;
315
+ if (node)
316
+ switch (property) {
317
+ case "fftSize":
318
+ node.fftSize = value;
319
+ break;
320
+ case "minDecibels":
321
+ node.minDecibels = value;
322
+ break;
323
+ case "maxDecibels":
324
+ node.maxDecibels = value;
325
+ break;
326
+ case "smoothingTimeConstant":
327
+ node.smoothingTimeConstant = value;
328
+ break;
329
+ default: return false;
330
+ }
331
+ const opts = analyserType === "pre" ? this.preAnalyserOptions : this.postAnalyserOptions;
332
+ switch (property) {
333
+ case "fftSize":
334
+ opts.fftSize = value;
335
+ break;
336
+ case "minDecibels":
337
+ opts.minDecibels = value;
338
+ break;
339
+ case "maxDecibels":
340
+ opts.maxDecibels = value;
341
+ break;
342
+ case "smoothingTimeConstant":
343
+ opts.smoothingTimeConstant = value;
344
+ break;
345
+ default: return false;
346
+ }
347
+ return true;
348
+ }
349
+ GetWaveformFloatData(analyserType) {
350
+ if (analyserType === "pre" && this.preAnalyser) {
351
+ this.preAnalyser.getFloatTimeDomainData(this.preAnalyserFloatArrayBuffer);
352
+ return this.preAnalyserFloatArrayBuffer;
353
+ }
354
+ else if (analyserType === "post" && this.postAnalyser) {
355
+ this.postAnalyser.getFloatTimeDomainData(this.postAnalyserFloatArrayBuffer);
356
+ return this.postAnalyserFloatArrayBuffer;
357
+ }
358
+ return null;
359
+ }
360
+ GetWaveformByteData(analyserType) {
361
+ if (!this.preAnalyser || !this.postAnalyser) {
362
+ Debug.Error("Could not get byte waveform data because the pre analyser or post analyser has not been enabled.", [
363
+ "Call .EnablePreAnalyser() or .EnablePostAnalyser() before getting waveform data."
364
+ ]);
365
+ return null;
366
+ }
367
+ switch (analyserType) {
368
+ case "pre":
369
+ this.preAnalyser.getByteTimeDomainData(this.preAnalyserByteArrayBuffer);
370
+ return this.preAnalyserByteArrayBuffer;
371
+ case "post":
372
+ this.postAnalyser.getByteTimeDomainData(this.postAnalyserByteArrayBuffer);
373
+ return this.postAnalyserByteArrayBuffer;
374
+ default:
375
+ return null;
376
+ }
377
+ }
378
+ // Public getters and setters
379
+ get currentPlaybackTime() {
380
+ return (!this.isPlaying || !this.parentialAudioContext)
381
+ ? 0
382
+ : this.offsetAtStart + (this.parentialAudioContext.currentTime - this.startTime);
383
+ }
384
+ get duration() {
385
+ return this.data.audioBuffer.duration;
386
+ }
387
+ get volume() {
388
+ return this.gainNode?.gain.value ?? 0;
389
+ }
390
+ get stereoPanning() {
391
+ return this.stereoPannerNode?.pan.value ?? 1;
392
+ }
393
+ get formattedDuration() {
394
+ const date = new Date(this.duration * 1000);
395
+ return format(date, "mm:ss");
396
+ }
397
+ get sampleRate() {
398
+ return this.data.audioBuffer.sampleRate;
399
+ }
400
+ get numberOfChannels() {
401
+ return this.data.audioBuffer.numberOfChannels;
402
+ }
403
+ get byteLength() {
404
+ return this.data.arrayBuffer.byteLength;
405
+ }
406
+ }