@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.
- package/README.md +7 -7
- package/lib/dist/console-codes.d.ts +2 -1
- package/lib/dist/console-codes.d.ts.map +1 -1
- package/lib/dist/console-codes.js +1 -0
- package/lib/dist/core/classes/AudioClip.d.ts +27 -16
- package/lib/dist/core/classes/AudioClip.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioClip.js +133 -48
- package/lib/dist/core/classes/AudioClipPlayer.d.ts +8 -8
- package/lib/dist/core/classes/AudioClipPlayer.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioClipPlayer.js +22 -24
- package/lib/dist/core/classes/AudioDevice.d.ts +46 -5
- package/lib/dist/core/classes/AudioDevice.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioDevice.js +47 -6
- package/lib/dist/core/classes/Channel.d.ts +20 -20
- package/lib/dist/core/classes/Channel.d.ts.map +1 -1
- package/lib/dist/core/classes/Channel.js +42 -42
- package/lib/dist/core/classes/DpsPipeline.d.ts +6 -6
- package/lib/dist/core/classes/DpsPipeline.d.ts.map +1 -1
- package/lib/dist/core/classes/DpsPipeline.js +18 -18
- package/lib/dist/core/classes/Effector.d.ts +5 -5
- package/lib/dist/core/classes/Effector.d.ts.map +1 -1
- package/lib/dist/core/classes/Effector.js +8 -8
- package/lib/dist/core/classes/Master.d.ts +6 -6
- package/lib/dist/core/classes/Master.d.ts.map +1 -1
- package/lib/dist/core/classes/Master.js +18 -18
- package/lib/dist/effects/classes/Analyser.d.ts +10 -6
- package/lib/dist/effects/classes/Analyser.d.ts.map +1 -1
- package/lib/dist/effects/classes/Analyser.js +29 -13
- package/lib/dist/effects/classes/Chorus.d.ts +7 -7
- package/lib/dist/effects/classes/Chorus.d.ts.map +1 -1
- package/lib/dist/effects/classes/Chorus.js +25 -25
- package/lib/dist/effects/classes/Distortion.d.ts +1 -1
- package/lib/dist/effects/classes/Distortion.d.ts.map +1 -1
- package/lib/dist/effects/classes/Distortion.js +1 -1
- package/lib/dist/effects/classes/Reverb.d.ts +32 -0
- package/lib/dist/effects/classes/Reverb.d.ts.map +1 -0
- package/lib/dist/effects/classes/Reverb.js +61 -0
- package/lib/dist/effects/classes/clips/HardClip.d.ts +5 -5
- package/lib/dist/effects/classes/clips/HardClip.d.ts.map +1 -1
- package/lib/dist/effects/classes/clips/HardClip.js +14 -14
- package/lib/dist/effects/classes/clips/SoftClip.d.ts +4 -4
- package/lib/dist/effects/classes/clips/SoftClip.d.ts.map +1 -1
- package/lib/dist/effects/classes/clips/SoftClip.js +13 -13
- package/lib/dist/effects/classes/filters/HighPassFilter.d.ts +18 -0
- package/lib/dist/effects/classes/filters/HighPassFilter.d.ts.map +1 -0
- package/lib/dist/effects/classes/filters/HighPassFilter.js +59 -0
- package/lib/dist/effects/classes/filters/LowPassFilter.d.ts +5 -5
- package/lib/dist/effects/classes/filters/LowPassFilter.d.ts.map +1 -1
- package/lib/dist/effects/classes/filters/LowPassFilter.js +17 -17
- package/lib/dist/effects/classes/filters/NotchFilter.d.ts +18 -0
- package/lib/dist/effects/classes/filters/NotchFilter.d.ts.map +1 -0
- package/lib/dist/effects/classes/filters/NotchFilter.js +59 -0
- package/lib/dist/effects/exports.d.ts +2 -5
- package/lib/dist/effects/exports.d.ts.map +1 -1
- package/lib/dist/effects/exports.js +2 -5
- package/lib/dist/index.d.ts +4 -4
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +3 -3
- package/lib/dist/typings.d.ts +33 -0
- package/lib/dist/typings.d.ts.map +1 -1
- package/lib/dist/typings.js +12 -0
- package/lib/dist/utilities/constants.d.ts +1 -0
- package/lib/dist/utilities/constants.d.ts.map +1 -1
- package/lib/dist/utilities/constants.js +1 -0
- package/lib/dist/utilities/debugger.d.ts +4 -4
- package/lib/dist/utilities/debugger.js +8 -8
- package/lib/dist/utilities/helpers.d.ts +13 -13
- package/lib/dist/utilities/helpers.d.ts.map +1 -1
- package/lib/dist/utilities/helpers.js +45 -59
- package/lib/dist/utilities/web-assembly.js +1 -1
- package/lib/src/console-codes.ts +2 -1
- package/lib/src/core/classes/AudioClip.ts +166 -55
- package/lib/src/core/classes/AudioClipPlayer.ts +22 -24
- package/lib/src/core/classes/AudioDevice.ts +49 -6
- package/lib/src/core/classes/Channel.ts +42 -42
- package/lib/src/core/classes/DpsPipeline.ts +19 -19
- package/lib/src/core/classes/Effector.ts +9 -9
- package/lib/src/core/classes/Master.ts +18 -18
- package/lib/src/effects/classes/Analyser.ts +33 -13
- package/lib/src/effects/classes/Chorus.ts +25 -25
- package/lib/src/effects/classes/Distortion.ts +1 -1
- package/lib/src/effects/classes/Reverb.ts +81 -0
- package/lib/src/effects/classes/clips/HardClip.ts +14 -14
- package/lib/src/effects/classes/clips/SoftClip.ts +13 -13
- package/lib/src/effects/classes/filters/HighPassFilter.ts +81 -0
- package/lib/src/effects/classes/filters/LowPassFilter.ts +17 -17
- package/lib/src/effects/classes/filters/NotchFilter.ts +81 -0
- package/lib/src/effects/exports.ts +2 -6
- package/lib/src/index.ts +15 -17
- package/lib/src/typings.ts +39 -1
- package/lib/src/utilities/constants.ts +3 -1
- package/lib/src/utilities/debugger.ts +4 -4
- package/lib/src/utilities/helpers.ts +47 -60
- package/lib/src/utilities/web-assembly.ts +1 -1
- package/package.json +3 -2
- package/lib/src/effects/classes/reverbs/ChamberReverb.ts +0 -1
- package/lib/src/effects/classes/reverbs/ConvolverReverb.ts +0 -1
- package/lib/src/effects/classes/reverbs/GenericReverb.ts +0 -1
- package/lib/src/effects/classes/reverbs/HallReverb.ts +0 -1
- 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
|
|
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
|
|
70
|
-
Debug.
|
|
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
|
|
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
|
-
|
|
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
|
|
116
|
+
public initialize(audioClipPlayer: AudioClipPlayer) {
|
|
117
|
+
|
|
118
|
+
const startTimestamp: number = Date.now(),
|
|
119
|
+
self: AudioClip = this;
|
|
87
120
|
|
|
88
|
-
if (!audioClipPlayer.context) return Debug.
|
|
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
|
|
155
|
+
public play(timestamp?: number, offset?: number) {
|
|
101
156
|
|
|
102
|
-
if (!this.context) return Debug.
|
|
103
|
-
"Make sure to attach this AudioClip to an AudioClipPlayer instance, before calling .
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
|
232
|
+
public seek(seconds: number) {
|
|
170
233
|
|
|
171
|
-
if (!this.context) return Debug.
|
|
172
|
-
`Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .
|
|
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.
|
|
186
|
-
this.
|
|
248
|
+
this.stop();
|
|
249
|
+
this.play(undefined, clamped);
|
|
187
250
|
|
|
188
251
|
return this;
|
|
189
252
|
}
|
|
190
253
|
|
|
191
|
-
public
|
|
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
|
|
272
|
+
public setVolume(volume: number): AudioClip {
|
|
210
273
|
|
|
211
|
-
if (!this.context) Debug.
|
|
212
|
-
"Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .
|
|
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
|
|
282
|
+
public setPanLevel(panLevel: number): AudioClip {
|
|
220
283
|
|
|
221
|
-
if (!this.context) Debug.
|
|
222
|
-
"Make sure to attach this AudioClip to an AudioClipPlayer instance before calling .
|
|
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.
|
|
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
|
|
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
|
|
307
|
+
public setMaxAudioBufferSourceNodes(value: number): AudioClip {
|
|
245
308
|
|
|
246
309
|
if (!DSP.overrideMaxAudioBufferNodes) {
|
|
247
|
-
Debug.
|
|
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
|
|
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
|
|
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.
|
|
337
|
+
return () => this.removeEventListener(event, cb);
|
|
275
338
|
}
|
|
276
339
|
|
|
277
|
-
public
|
|
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.
|
|
346
|
+
this.removeEventListener(event, wrapper as unknown as AudioClipEventMap[K]);
|
|
284
347
|
}) as unknown as AudioClipEventMap[K];
|
|
285
348
|
|
|
286
|
-
return this.
|
|
349
|
+
return this.addEventListener(event, wrapper);
|
|
287
350
|
}
|
|
288
351
|
|
|
289
|
-
public
|
|
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
|
|
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
|
|
376
|
+
public getChannelData(channel: number = 0): Float32Array {
|
|
314
377
|
return this.data.audioBuffer.getChannelData(channel);
|
|
315
378
|
}
|
|
316
379
|
|
|
317
|
-
public
|
|
380
|
+
public send(channel: Channel | Master) {
|
|
318
381
|
|
|
319
|
-
if (!channel.audioClipPlayer) return Debug.
|
|
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.
|
|
388
|
+
channel.audioClipPlayer.attachAudioClip(this);
|
|
326
389
|
}
|
|
327
390
|
|
|
328
|
-
public
|
|
391
|
+
public unsend(channel: Channel | Master) {
|
|
329
392
|
|
|
330
|
-
if (!channel.audioClipPlayer) return Debug.
|
|
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.
|
|
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
|
|
25
|
+
public attachAudioClip(audioClip: AudioClip): void {
|
|
26
26
|
|
|
27
|
-
if (this.audioClips.includes(audioClip)) return Debug.
|
|
28
|
-
"Call .
|
|
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.
|
|
31
|
+
audioClip.initialize(this);
|
|
32
32
|
this.audioClips.push(audioClip);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
public
|
|
35
|
+
public detachAudioClip(clip: AudioClip): void {
|
|
36
36
|
|
|
37
|
-
if (!this.audioClips.includes(clip)) return Debug.
|
|
38
|
-
"Call .
|
|
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
|
-
|
|
41
|
+
const idx = this.audioClips.indexOf(clip);
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
if (idx >= 0)
|
|
44
|
+
this.audioClips.splice(idx, 1);
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
if (clip.id === _clip.id)
|
|
47
|
-
return self.audioClips.splice(index, 1);
|
|
48
|
-
})
|
|
46
|
+
clip.detachFromAudioClipPlayer(this);
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
public
|
|
49
|
+
public send(channel: Channel | Master): void {
|
|
52
50
|
|
|
53
|
-
if (!this.outputGainNode || !channel.input) return Debug.
|
|
51
|
+
if (!this.outputGainNode || !channel.input) return Debug.error("Could not send AudioClipPlayer signal to a channel/master.");
|
|
54
52
|
|
|
55
|
-
this.
|
|
53
|
+
this.unsend();
|
|
56
54
|
|
|
57
55
|
this.channel = channel;
|
|
58
56
|
this.outputGainNode.connect(channel.input);
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
public
|
|
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
|
|
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
|
|
82
|
+
public stopAll(): void {
|
|
85
83
|
|
|
86
84
|
this.audioClips.forEach(function (clip: AudioClip) {
|
|
87
|
-
clip.
|
|
85
|
+
clip.stop();
|
|
88
86
|
});
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
public
|
|
89
|
+
public dispose(): void {
|
|
92
90
|
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|