@fluex/fluexgl-dsp 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/lib/dist/console-codes.d.ts +14 -0
- package/lib/dist/console-codes.d.ts.map +1 -0
- package/lib/dist/console-codes.js +16 -0
- package/lib/dist/core/classes/AudioClip.d.ts +67 -0
- package/lib/dist/core/classes/AudioClip.d.ts.map +1 -0
- package/lib/dist/core/classes/AudioClip.js +410 -0
- package/lib/dist/core/classes/AudioDevice.d.ts +13 -0
- package/lib/dist/core/classes/AudioDevice.d.ts.map +1 -0
- package/lib/dist/core/classes/AudioDevice.js +30 -0
- package/lib/dist/core/classes/Channel.d.ts +30 -0
- package/lib/dist/core/classes/Channel.d.ts.map +1 -0
- package/lib/dist/core/classes/Channel.js +144 -0
- package/lib/dist/core/classes/DpsPipeline.d.ts +60 -0
- package/lib/dist/core/classes/DpsPipeline.d.ts.map +1 -0
- package/lib/dist/core/classes/DpsPipeline.js +115 -0
- package/lib/dist/core/classes/Effector.d.ts +8 -0
- package/lib/dist/core/classes/Effector.d.ts.map +1 -0
- package/lib/dist/core/classes/Effector.js +7 -0
- package/lib/dist/core/classes/Master.d.ts +12 -0
- package/lib/dist/core/classes/Master.d.ts.map +1 -0
- package/lib/dist/core/classes/Master.js +35 -0
- package/lib/dist/core/exports.d.ts +7 -0
- package/lib/dist/core/exports.d.ts.map +1 -0
- package/lib/dist/core/exports.js +6 -0
- package/lib/dist/effects/classes/Chorus.d.ts +19 -0
- package/lib/dist/effects/classes/Chorus.d.ts.map +1 -0
- package/lib/dist/effects/classes/Chorus.js +83 -0
- package/lib/dist/effects/classes/Distortion.d.ts +3 -0
- package/lib/dist/effects/classes/Distortion.d.ts.map +1 -0
- package/lib/dist/effects/classes/Distortion.js +2 -0
- package/lib/dist/effects/classes/Equalizer.d.ts +3 -0
- package/lib/dist/effects/classes/Equalizer.d.ts.map +1 -0
- package/lib/dist/effects/classes/Equalizer.js +2 -0
- package/lib/dist/effects/classes/Filter.d.ts +3 -0
- package/lib/dist/effects/classes/Filter.d.ts.map +1 -0
- package/lib/dist/effects/classes/Filter.js +2 -0
- package/lib/dist/effects/classes/Limiter.d.ts +3 -0
- package/lib/dist/effects/classes/Limiter.d.ts.map +1 -0
- package/lib/dist/effects/classes/Limiter.js +2 -0
- package/lib/dist/effects/classes/Saturation.d.ts +3 -0
- package/lib/dist/effects/classes/Saturation.d.ts.map +1 -0
- package/lib/dist/effects/classes/Saturation.js +2 -0
- package/lib/dist/effects/classes/SoftClip.d.ts +8 -0
- package/lib/dist/effects/classes/SoftClip.d.ts.map +1 -0
- package/lib/dist/effects/classes/SoftClip.js +37 -0
- package/lib/dist/effects/classes/StereoPanner.d.ts +3 -0
- package/lib/dist/effects/classes/StereoPanner.d.ts.map +1 -0
- package/lib/dist/effects/classes/StereoPanner.js +2 -0
- package/lib/dist/effects/classes/compressors/Compressor.d.ts +3 -0
- package/lib/dist/effects/classes/compressors/Compressor.d.ts.map +1 -0
- package/lib/dist/effects/classes/compressors/Compressor.js +2 -0
- package/lib/dist/effects/classes/compressors/MultibandCompressor.d.ts +3 -0
- package/lib/dist/effects/classes/compressors/MultibandCompressor.d.ts.map +1 -0
- package/lib/dist/effects/classes/compressors/MultibandCompressor.js +2 -0
- package/lib/dist/effects/classes/delays/AdvancedDelay.d.ts +3 -0
- package/lib/dist/effects/classes/delays/AdvancedDelay.d.ts.map +1 -0
- package/lib/dist/effects/classes/delays/AdvancedDelay.js +2 -0
- package/lib/dist/effects/classes/delays/MonoDelay.d.ts +3 -0
- package/lib/dist/effects/classes/delays/MonoDelay.d.ts.map +1 -0
- package/lib/dist/effects/classes/delays/MonoDelay.js +2 -0
- package/lib/dist/effects/classes/delays/PingPongDelay.d.ts +3 -0
- package/lib/dist/effects/classes/delays/PingPongDelay.d.ts.map +1 -0
- package/lib/dist/effects/classes/delays/PingPongDelay.js +2 -0
- package/lib/dist/effects/classes/delays/StereoDelay.d.ts +3 -0
- package/lib/dist/effects/classes/delays/StereoDelay.d.ts.map +1 -0
- package/lib/dist/effects/classes/delays/StereoDelay.js +2 -0
- package/lib/dist/effects/classes/reverbs/ChamberReverb.d.ts +3 -0
- package/lib/dist/effects/classes/reverbs/ChamberReverb.d.ts.map +1 -0
- package/lib/dist/effects/classes/reverbs/ChamberReverb.js +2 -0
- package/lib/dist/effects/classes/reverbs/ConvolverReverb.d.ts +3 -0
- package/lib/dist/effects/classes/reverbs/ConvolverReverb.d.ts.map +1 -0
- package/lib/dist/effects/classes/reverbs/ConvolverReverb.js +2 -0
- package/lib/dist/effects/classes/reverbs/GenericReverb.d.ts +3 -0
- package/lib/dist/effects/classes/reverbs/GenericReverb.d.ts.map +1 -0
- package/lib/dist/effects/classes/reverbs/GenericReverb.js +2 -0
- package/lib/dist/effects/classes/reverbs/HallReverb.d.ts +3 -0
- package/lib/dist/effects/classes/reverbs/HallReverb.d.ts.map +1 -0
- package/lib/dist/effects/classes/reverbs/HallReverb.js +2 -0
- package/lib/dist/effects/classes/reverbs/RoomReverb.d.ts +3 -0
- package/lib/dist/effects/classes/reverbs/RoomReverb.d.ts.map +1 -0
- package/lib/dist/effects/classes/reverbs/RoomReverb.js +2 -0
- package/lib/dist/effects/exports.d.ts +20 -0
- package/lib/dist/effects/exports.d.ts.map +1 -0
- package/lib/dist/effects/exports.js +19 -0
- package/lib/dist/index.d.ts +13 -0
- package/lib/dist/index.d.ts.map +1 -0
- package/lib/dist/index.js +28 -0
- package/lib/dist/typings.d.ts +67 -0
- package/lib/dist/typings.d.ts.map +1 -0
- package/lib/dist/typings.js +1 -0
- package/lib/dist/utilities/constants.d.ts +2 -0
- package/lib/dist/utilities/constants.d.ts.map +1 -0
- package/lib/dist/utilities/constants.js +9 -0
- package/lib/dist/utilities/debugger.d.ts +8 -0
- package/lib/dist/utilities/debugger.d.ts.map +1 -0
- package/lib/dist/utilities/debugger.js +49 -0
- package/lib/dist/utilities/helpers.d.ts +41 -0
- package/lib/dist/utilities/helpers.d.ts.map +1 -0
- package/lib/dist/utilities/helpers.js +148 -0
- package/lib/dist/utilities/web-assembly.d.ts +4 -0
- package/lib/dist/utilities/web-assembly.d.ts.map +1 -0
- package/lib/dist/utilities/web-assembly.js +12 -0
- package/lib/src/console-codes.ts +16 -0
- package/lib/src/core/classes/AudioClip.ts +545 -0
- package/lib/src/core/classes/AudioDevice.ts +39 -0
- package/lib/src/core/classes/Channel.ts +199 -0
- package/lib/src/core/classes/DpsPipeline.ts +153 -0
- package/lib/src/core/classes/Effector.ts +11 -0
- package/lib/src/core/classes/Master.ts +50 -0
- package/lib/src/core/exports.ts +6 -0
- package/lib/src/effects/classes/Chorus.ts +109 -0
- package/lib/src/effects/classes/Distortion.ts +1 -0
- package/lib/src/effects/classes/Equalizer.ts +1 -0
- package/lib/src/effects/classes/Filter.ts +1 -0
- package/lib/src/effects/classes/Limiter.ts +1 -0
- package/lib/src/effects/classes/Saturation.ts +1 -0
- package/lib/src/effects/classes/SoftClip.ts +42 -0
- package/lib/src/effects/classes/StereoPanner.ts +1 -0
- package/lib/src/effects/classes/compressors/Compressor.ts +1 -0
- package/lib/src/effects/classes/compressors/MultibandCompressor.ts +1 -0
- package/lib/src/effects/classes/delays/AdvancedDelay.ts +1 -0
- package/lib/src/effects/classes/delays/MonoDelay.ts +1 -0
- package/lib/src/effects/classes/delays/PingPongDelay.ts +1 -0
- package/lib/src/effects/classes/delays/StereoDelay.ts +1 -0
- package/lib/src/effects/classes/reverbs/ChamberReverb.ts +1 -0
- package/lib/src/effects/classes/reverbs/ConvolverReverb.ts +1 -0
- package/lib/src/effects/classes/reverbs/GenericReverb.ts +1 -0
- package/lib/src/effects/classes/reverbs/HallReverb.ts +1 -0
- package/lib/src/effects/classes/reverbs/RoomReverb.ts +1 -0
- package/lib/src/effects/exports.ts +22 -0
- package/lib/src/index.ts +93 -0
- package/lib/src/typings.ts +79 -0
- package/lib/src/utilities/constants.ts +9 -0
- package/lib/src/utilities/debugger.ts +66 -0
- package/lib/src/utilities/helpers.ts +215 -0
- package/lib/src/utilities/web-assembly.ts +14 -0
- package/package.json +50 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { format } from "date-fns";
|
|
3
|
+
|
|
4
|
+
import { AudioClipAnalyserProperty, AudioClipAnalyserType, AudioClipEventMap, AudioClipEvents, AudioSourceData } from "../../typings";
|
|
5
|
+
import { Debug } from "../../utilities/debugger";
|
|
6
|
+
import { Channel } from "./Channel";
|
|
7
|
+
|
|
8
|
+
type ProgressPayload = Parameters<AudioClipEventMap["progress"]>[0];
|
|
9
|
+
|
|
10
|
+
export class AudioClip {
|
|
11
|
+
|
|
12
|
+
public id: string = v4();
|
|
13
|
+
public hasAttachedToChannel: boolean = false;
|
|
14
|
+
|
|
15
|
+
public loop: boolean = false;
|
|
16
|
+
public isPlaying: boolean = false;
|
|
17
|
+
public startTime: number = 0;
|
|
18
|
+
public offsetAtStart: number = 0;
|
|
19
|
+
|
|
20
|
+
public progressUpdateSpeed: number = 20;
|
|
21
|
+
|
|
22
|
+
public gainNode: GainNode | null = null;
|
|
23
|
+
public stereoPannerNode: StereoPannerNode | null = null;
|
|
24
|
+
|
|
25
|
+
public parentialAudioContext: AudioContext | null = null;
|
|
26
|
+
public parentialChannel: Channel | null = null;
|
|
27
|
+
|
|
28
|
+
public preAnalyser: AnalyserNode | null = null;
|
|
29
|
+
public postAnalyser: AnalyserNode | null = null;
|
|
30
|
+
|
|
31
|
+
public preAnalyserEnabled: boolean = false;
|
|
32
|
+
public postAnalyserEnabled: boolean = false;
|
|
33
|
+
|
|
34
|
+
public preAnalyserOptions: AnalyserOptions = {};
|
|
35
|
+
public postAnalyserOptions: AnalyserOptions = {};
|
|
36
|
+
|
|
37
|
+
public preAnalyserFloatArrayBuffer = new Float32Array();
|
|
38
|
+
public postAnalyserFloatArrayBuffer = new Float32Array();
|
|
39
|
+
|
|
40
|
+
public preAnalyserByteArrayBuffer = new Uint8Array();
|
|
41
|
+
public postAnalyserByteArrayBuffer = new Uint8Array();
|
|
42
|
+
|
|
43
|
+
private audioBufferSourceNodes: AudioBufferSourceNode[] = [];
|
|
44
|
+
private maxAudioBufferSourceNodes: number = 1;
|
|
45
|
+
|
|
46
|
+
private progressInterval: number | null = 0;
|
|
47
|
+
|
|
48
|
+
private events: AudioClipEvents = {
|
|
49
|
+
"progress": []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
constructor(public data: AudioSourceData) { }
|
|
53
|
+
|
|
54
|
+
// Private methods.
|
|
55
|
+
|
|
56
|
+
private createBufferSource(): AudioBufferSourceNode | null {
|
|
57
|
+
|
|
58
|
+
if (!this.parentialAudioContext) return null;
|
|
59
|
+
|
|
60
|
+
const context = this.parentialAudioContext;
|
|
61
|
+
|
|
62
|
+
const bufferSource = context.createBufferSource();
|
|
63
|
+
bufferSource.buffer = this.data.audioBuffer;
|
|
64
|
+
bufferSource.loop = this.loop;
|
|
65
|
+
|
|
66
|
+
return bufferSource;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private connectSourcesTo(target: AudioNode) {
|
|
70
|
+
for (const src of this.audioBufferSourceNodes) {
|
|
71
|
+
src.disconnect();
|
|
72
|
+
src.connect(target);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private safeDisconnect(node?: AudioNode | null) {
|
|
77
|
+
node?.disconnect();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private rebuildNodeChain() {
|
|
81
|
+
|
|
82
|
+
if (!this.parentialAudioContext || !this.gainNode || !this.stereoPannerNode || !this.parentialChannel || !this.parentialChannel.audioClipsInputGainNode) {
|
|
83
|
+
|
|
84
|
+
Debug.Error("rebuildNodeChain: missing context or core nodes (gain/panner).");
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const destination = this.parentialChannel.audioClipsInputGainNode;
|
|
89
|
+
|
|
90
|
+
this.safeDisconnect(this.gainNode);
|
|
91
|
+
this.safeDisconnect(this.stereoPannerNode);
|
|
92
|
+
this.safeDisconnect(this.preAnalyser);
|
|
93
|
+
this.safeDisconnect(this.postAnalyser);
|
|
94
|
+
|
|
95
|
+
let entry: AudioNode = this.gainNode;
|
|
96
|
+
|
|
97
|
+
if (this.preAnalyserEnabled && this.preAnalyser) {
|
|
98
|
+
|
|
99
|
+
entry = this.preAnalyser;
|
|
100
|
+
this.preAnalyser.connect(this.gainNode);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.gainNode.connect(this.stereoPannerNode);
|
|
104
|
+
|
|
105
|
+
if (this.postAnalyserEnabled && this.postAnalyser) {
|
|
106
|
+
this.stereoPannerNode.connect(this.postAnalyser);
|
|
107
|
+
this.postAnalyser.connect(destination);
|
|
108
|
+
} else {
|
|
109
|
+
this.stereoPannerNode.connect(destination);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.connectSourcesTo(entry);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Public methods
|
|
117
|
+
|
|
118
|
+
public InitializeAudioClipOnAttaching(channel: Channel): AudioClip | null {
|
|
119
|
+
|
|
120
|
+
if (!channel.parentialContext || !channel.audioClipsInputGainNode) {
|
|
121
|
+
|
|
122
|
+
Debug.Error("Could not initialize audio clip on channel attachment, because channel it's master channel has not been defined.", [
|
|
123
|
+
`Call .AttachChannel([channel<"${channel.id}"> Channel]) on the master channel.`
|
|
124
|
+
]);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.gainNode = new GainNode(channel.parentialContext);
|
|
129
|
+
this.stereoPannerNode = new StereoPannerNode(channel.parentialContext);
|
|
130
|
+
|
|
131
|
+
this.parentialAudioContext = channel.parentialContext;
|
|
132
|
+
this.parentialChannel = channel;
|
|
133
|
+
this.hasAttachedToChannel = true;
|
|
134
|
+
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public Play(timestamp?: number, offset?: number): AudioClip | null {
|
|
139
|
+
|
|
140
|
+
if (!this.hasAttachedToChannel || !this.parentialAudioContext || !this.parentialChannel) {
|
|
141
|
+
Debug.Error("Could not play the audio node because it is not attached to a channel", [
|
|
142
|
+
"Call 'AttachAudioClip([clip AudioClip])' on a channel, before playing this audio node."
|
|
143
|
+
]);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const context = this.parentialAudioContext;
|
|
148
|
+
const self = this;
|
|
149
|
+
|
|
150
|
+
if (this.audioBufferSourceNodes.length > this.maxAudioBufferSourceNodes - 1) return null;
|
|
151
|
+
|
|
152
|
+
const bufferSource: AudioBufferSourceNode | null = this.createBufferSource();
|
|
153
|
+
|
|
154
|
+
if (!bufferSource) {
|
|
155
|
+
Debug.Error("Something went wrong.");
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const actualOffset = offset ?? this.offsetAtStart ?? 0;
|
|
160
|
+
|
|
161
|
+
this.startTime = context.currentTime;
|
|
162
|
+
this.offsetAtStart = actualOffset;
|
|
163
|
+
this.isPlaying = true;
|
|
164
|
+
|
|
165
|
+
if (this.progressInterval) clearInterval(this.progressInterval);
|
|
166
|
+
|
|
167
|
+
if (this.isPlaying) this.progressInterval = setInterval(function () {
|
|
168
|
+
|
|
169
|
+
if (!self.isPlaying) return;
|
|
170
|
+
|
|
171
|
+
const current = self.offsetAtStart + (context.currentTime - self.startTime);
|
|
172
|
+
const date: Date = new Date(current * 1000);
|
|
173
|
+
|
|
174
|
+
const formattedTime = format(date, "mm:ss");
|
|
175
|
+
|
|
176
|
+
const progressPayload: ProgressPayload = {
|
|
177
|
+
current: parseFloat(current.toFixed(2)),
|
|
178
|
+
startTime: self.startTime,
|
|
179
|
+
offset: self.offsetAtStart,
|
|
180
|
+
contextTimestamp: context.currentTime,
|
|
181
|
+
formatted: formattedTime
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
self.events.progress.forEach(function (cb: (event: ProgressPayload) => void) {
|
|
185
|
+
cb(progressPayload);
|
|
186
|
+
});
|
|
187
|
+
}, this.progressUpdateSpeed);
|
|
188
|
+
|
|
189
|
+
bufferSource.addEventListener("ended", function () {
|
|
190
|
+
|
|
191
|
+
const i = self.audioBufferSourceNodes.indexOf(bufferSource);
|
|
192
|
+
|
|
193
|
+
bufferSource.disconnect();
|
|
194
|
+
|
|
195
|
+
if (i === 0) self.isPlaying = false;
|
|
196
|
+
|
|
197
|
+
if (i >= 0)
|
|
198
|
+
return self.audioBufferSourceNodes.splice(i, 1);
|
|
199
|
+
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
this.audioBufferSourceNodes.push(bufferSource);
|
|
203
|
+
this.rebuildNodeChain();
|
|
204
|
+
|
|
205
|
+
bufferSource.start(timestamp ?? this.startTime, actualOffset);
|
|
206
|
+
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public Seek(seconds: number): AudioClip | null {
|
|
211
|
+
|
|
212
|
+
if (!this.parentialAudioContext || !this.hasAttachedToChannel) {
|
|
213
|
+
Debug.Error("Could not seek because the clip is not attached to a channel.", [
|
|
214
|
+
`Clip ID: ${this.id}`
|
|
215
|
+
]);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const clamped = Math.max(0, Math.min(seconds, this.duration));
|
|
220
|
+
|
|
221
|
+
if (!this.isPlaying) {
|
|
222
|
+
this.offsetAtStart = clamped;
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.Stop();
|
|
227
|
+
this.Play(undefined, clamped);
|
|
228
|
+
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
public Stop(): AudioClip | null {
|
|
234
|
+
|
|
235
|
+
if (!this.hasAttachedToChannel || !this.parentialAudioContext) {
|
|
236
|
+
|
|
237
|
+
Debug.Error("Could not stop the audio node because it is not attached to a channel", [
|
|
238
|
+
"Call 'AttachAudioClip([node AudioNode])' on a channel, before stopping this audio node."
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.audioBufferSourceNodes.forEach(function (node: AudioBufferSourceNode) {
|
|
245
|
+
|
|
246
|
+
node.stop();
|
|
247
|
+
node.disconnect();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
this.audioBufferSourceNodes.length = 0;
|
|
251
|
+
this.isPlaying = false;
|
|
252
|
+
|
|
253
|
+
if (this.progressInterval) {
|
|
254
|
+
|
|
255
|
+
clearInterval(this.progressInterval);
|
|
256
|
+
this.progressInterval = null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return this;
|
|
260
|
+
}
|
|
261
|
+
public SetVolume(volume: number): AudioClip | void {
|
|
262
|
+
|
|
263
|
+
if (!this.gainNode || !this.parentialAudioContext) return Debug.Error("Something went wrong while setting the volume.", [
|
|
264
|
+
`Gain node on audio clip '${this.id}' is undefined.`
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
this.gainNode.gain.setValueAtTime(volume, this.parentialAudioContext.currentTime);
|
|
268
|
+
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public SetPanLevel(panLevel: number): AudioClip | void {
|
|
273
|
+
|
|
274
|
+
if (!this.stereoPannerNode || !this.parentialAudioContext) return Debug.Error("Something went wrong while setting the pan level", [
|
|
275
|
+
`Stereo panner node on audio clip '${this.id}' is undefined`
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
if (panLevel < -1 || panLevel > 1) return Debug.Error("Could not set the pan level because it is not between -1 and 1.", [
|
|
279
|
+
"Provide this method with a value between -1 and 1"
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
this.stereoPannerNode.pan.setValueAtTime(panLevel, this.parentialAudioContext.currentTime);
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public Loop(loop?: boolean): AudioClip {
|
|
287
|
+
|
|
288
|
+
this.audioBufferSourceNodes.forEach(function (node: AudioBufferSourceNode) {
|
|
289
|
+
node.loop = loop ?? true;
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
this.loop = loop ?? true;
|
|
293
|
+
return this;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public SetMaxAudioBufferSourceNodes(value: number): AudioClip {
|
|
297
|
+
|
|
298
|
+
Debug.Warn("Changing the amount of buffer source nodes may cause some properties of this class instance to work inproperly.", [
|
|
299
|
+
"The default value is 1."
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
this.maxAudioBufferSourceNodes = value;
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public DisconnectAllAudioBufferSourceNodes(): boolean {
|
|
307
|
+
|
|
308
|
+
if (!this.parentialAudioContext) {
|
|
309
|
+
Debug.Error("Could not disconnect audio buffer source nodes, because the parential audio contex has not been found", [
|
|
310
|
+
`Clip ID: ${this.id}`,
|
|
311
|
+
`Parential channel id: ${this.parentialChannel ? this.parentialChannel.id : "none"}`
|
|
312
|
+
])
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const contextCurrentTime: number = this.parentialAudioContext?.currentTime;
|
|
317
|
+
|
|
318
|
+
this.audioBufferSourceNodes.forEach(function (node: AudioBufferSourceNode) {
|
|
319
|
+
node.stop(contextCurrentTime);
|
|
320
|
+
node.disconnect();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public AddEventListener<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): () => void {
|
|
327
|
+
|
|
328
|
+
this.events[event].push(cb);
|
|
329
|
+
|
|
330
|
+
return () => this.RemoveEventListener(event, cb);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
public Once<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): () => void {
|
|
334
|
+
|
|
335
|
+
const wrapper = ((...args: unknown[]) => {
|
|
336
|
+
|
|
337
|
+
// @ts-ignore
|
|
338
|
+
cb(...args);
|
|
339
|
+
|
|
340
|
+
this.RemoveEventListener(event, wrapper as unknown as AudioClipEventMap[K]);
|
|
341
|
+
}) as unknown as AudioClipEventMap[K];
|
|
342
|
+
|
|
343
|
+
return this.AddEventListener(event, wrapper);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
public RemoveEventListener<K extends keyof AudioClipEventMap>(event: K, cb: AudioClipEventMap[K]): AudioClip {
|
|
347
|
+
|
|
348
|
+
const arr = this.events[event];
|
|
349
|
+
|
|
350
|
+
for (let i = 0; i < arr.length; i++) {
|
|
351
|
+
if (arr[i] === cb) {
|
|
352
|
+
arr.splice(i, 1);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return this;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
public ClearEventListeners(event?: keyof AudioClipEventMap): AudioClip {
|
|
360
|
+
|
|
361
|
+
if (event) {
|
|
362
|
+
this.events[event].length = 0;
|
|
363
|
+
} else {
|
|
364
|
+
(Object.keys(this.events) as (keyof AudioClipEventMap)[]).forEach((k) => (this.events[k].length = 0));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return this;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
public GetChannelData(channel: number = 0): Float32Array {
|
|
371
|
+
|
|
372
|
+
return this.data.audioBuffer.getChannelData(channel);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public EnablePreAnalyser(): boolean {
|
|
376
|
+
|
|
377
|
+
if (!this.parentialAudioContext || !this.parentialChannel || !this.hasAttachedToChannel) {
|
|
378
|
+
Debug.Error("EnablePreAnalyser: clip niet aan channel gekoppeld.");
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!this.preAnalyser)
|
|
383
|
+
this.preAnalyser = new AnalyserNode(this.parentialAudioContext, this.preAnalyserOptions);
|
|
384
|
+
|
|
385
|
+
this.preAnalyserFloatArrayBuffer = new Float32Array(this.preAnalyser.fftSize);
|
|
386
|
+
this.preAnalyserByteArrayBuffer = new Uint8Array(this.preAnalyser.fftSize);
|
|
387
|
+
|
|
388
|
+
this.preAnalyserEnabled = true;
|
|
389
|
+
return this.rebuildNodeChain();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
public DisablePreAnalyser() {
|
|
393
|
+
|
|
394
|
+
this.preAnalyserEnabled = false;
|
|
395
|
+
return this.rebuildNodeChain();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
public EnablePostAnalyser() {
|
|
399
|
+
|
|
400
|
+
if (!this.parentialAudioContext || !this.parentialChannel || !this.hasAttachedToChannel) {
|
|
401
|
+
|
|
402
|
+
Debug.Error("EnablePostAnalyser: clip niet aan channel gekoppeld.");
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!this.postAnalyser)
|
|
407
|
+
this.postAnalyser = new AnalyserNode(this.parentialAudioContext, this.postAnalyserOptions);
|
|
408
|
+
|
|
409
|
+
this.postAnalyserFloatArrayBuffer = new Float32Array(this.postAnalyser.fftSize);
|
|
410
|
+
this.postAnalyserByteArrayBuffer = new Uint8Array(this.postAnalyser.fftSize);
|
|
411
|
+
|
|
412
|
+
this.postAnalyserEnabled = true;
|
|
413
|
+
return this.rebuildNodeChain();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
public DisablePostAnalyser() {
|
|
417
|
+
|
|
418
|
+
this.postAnalyserEnabled = false;
|
|
419
|
+
return this.rebuildNodeChain();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
public SetPreAnalyserOptions(options: AnalyserOptions) {
|
|
423
|
+
|
|
424
|
+
this.preAnalyserOptions = { ...options };
|
|
425
|
+
|
|
426
|
+
if (!this.preAnalyser) return;
|
|
427
|
+
|
|
428
|
+
this.preAnalyser.fftSize = options.fftSize ?? this.preAnalyser.fftSize;
|
|
429
|
+
this.preAnalyser.minDecibels = options.minDecibels ?? this.preAnalyser.minDecibels;
|
|
430
|
+
this.preAnalyser.maxDecibels = options.maxDecibels ?? this.preAnalyser.maxDecibels;
|
|
431
|
+
this.preAnalyser.smoothingTimeConstant = options.smoothingTimeConstant ?? this.preAnalyser.smoothingTimeConstant;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
public SetPostAnalyserOptions(options: AnalyserOptions) {
|
|
435
|
+
|
|
436
|
+
this.postAnalyserOptions = { ...options };
|
|
437
|
+
|
|
438
|
+
if (!this.postAnalyser) return;
|
|
439
|
+
|
|
440
|
+
this.postAnalyser.fftSize = options.fftSize ?? this.postAnalyser.fftSize;
|
|
441
|
+
this.postAnalyser.minDecibels = options.minDecibels ?? this.postAnalyser.minDecibels;
|
|
442
|
+
this.postAnalyser.maxDecibels = options.maxDecibels ?? this.postAnalyser.maxDecibels;
|
|
443
|
+
this.postAnalyser.smoothingTimeConstant = options.smoothingTimeConstant ?? this.postAnalyser.smoothingTimeConstant;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
public SetAnalyserOption(analyserType: AudioClipAnalyserType, property: AudioClipAnalyserProperty, value: number) {
|
|
447
|
+
|
|
448
|
+
const node = analyserType === "pre" ? this.preAnalyser : this.postAnalyser;
|
|
449
|
+
|
|
450
|
+
if (node) switch (property) {
|
|
451
|
+
case "fftSize": node.fftSize = value as AnalyserNode["fftSize"]; break;
|
|
452
|
+
case "minDecibels": node.minDecibels = value; break;
|
|
453
|
+
case "maxDecibels": node.maxDecibels = value; break;
|
|
454
|
+
case "smoothingTimeConstant": node.smoothingTimeConstant = value; break;
|
|
455
|
+
default: return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const opts = analyserType === "pre" ? this.preAnalyserOptions : this.postAnalyserOptions;
|
|
459
|
+
|
|
460
|
+
switch (property) {
|
|
461
|
+
case "fftSize": opts.fftSize = value as AnalyserNode["fftSize"]; break;
|
|
462
|
+
case "minDecibels": opts.minDecibels = value; break;
|
|
463
|
+
case "maxDecibels": opts.maxDecibels = value; break;
|
|
464
|
+
case "smoothingTimeConstant": opts.smoothingTimeConstant = value; break;
|
|
465
|
+
default: return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
public GetWaveformFloatData(analyserType: AudioClipAnalyserType): Float32Array | null {
|
|
472
|
+
|
|
473
|
+
if (analyserType === "pre" && this.preAnalyser) {
|
|
474
|
+
this.preAnalyser.getFloatTimeDomainData(this.preAnalyserFloatArrayBuffer);
|
|
475
|
+
return this.preAnalyserFloatArrayBuffer;
|
|
476
|
+
} else if (analyserType === "post" && this.postAnalyser) {
|
|
477
|
+
this.postAnalyser.getFloatTimeDomainData(this.postAnalyserFloatArrayBuffer);
|
|
478
|
+
return this.postAnalyserFloatArrayBuffer;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
public GetWaveformByteData(analyserType: AudioClipAnalyserType): Uint8Array | null {
|
|
485
|
+
|
|
486
|
+
if (!this.preAnalyser || !this.postAnalyser) {
|
|
487
|
+
|
|
488
|
+
Debug.Error("Could not get byte waveform data because the pre analyser or post analyser has not been enabled.", [
|
|
489
|
+
"Call .EnablePreAnalyser() or .EnablePostAnalyser() before getting waveform data."
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
switch (analyserType) {
|
|
496
|
+
case "pre":
|
|
497
|
+
this.preAnalyser.getByteTimeDomainData(this.preAnalyserByteArrayBuffer);
|
|
498
|
+
return this.preAnalyserByteArrayBuffer;
|
|
499
|
+
case "post":
|
|
500
|
+
this.postAnalyser.getByteTimeDomainData(this.postAnalyserByteArrayBuffer);
|
|
501
|
+
return this.postAnalyserByteArrayBuffer;
|
|
502
|
+
default:
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Public getters and setters
|
|
508
|
+
|
|
509
|
+
public get currentPlaybackTime(): number {
|
|
510
|
+
return (!this.isPlaying || !this.parentialAudioContext)
|
|
511
|
+
? 0
|
|
512
|
+
: this.offsetAtStart + (this.parentialAudioContext.currentTime - this.startTime);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
public get duration(): number {
|
|
516
|
+
return this.data.audioBuffer.duration;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
public get volume(): number {
|
|
520
|
+
return this.gainNode?.gain.value ?? 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
public get stereoPanning(): number {
|
|
524
|
+
return this.stereoPannerNode?.pan.value ?? 1;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
public get formattedDuration(): string {
|
|
528
|
+
|
|
529
|
+
const date = new Date(this.duration * 1000);
|
|
530
|
+
|
|
531
|
+
return format(date, "mm:ss");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
public get sampleRate(): number {
|
|
535
|
+
return this.data.audioBuffer.sampleRate;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
public get numberOfChannels(): number {
|
|
539
|
+
return this.data.audioBuffer.numberOfChannels;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
public get byteLength(): number {
|
|
543
|
+
return this.data.arrayBuffer.byteLength;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
|
|
3
|
+
import { Master } from "./Master";
|
|
4
|
+
|
|
5
|
+
import { ErrorCodes } from "../../console-codes";
|
|
6
|
+
import { Debug } from "../../utilities/debugger";
|
|
7
|
+
|
|
8
|
+
export class AudioDevice {
|
|
9
|
+
|
|
10
|
+
public id: string = v4();
|
|
11
|
+
public timestamp: number = Date.now();
|
|
12
|
+
|
|
13
|
+
public masterChannel: Master = new Master();
|
|
14
|
+
public masterChannels: Master[] = [];
|
|
15
|
+
|
|
16
|
+
constructor(public deviceInfo: MediaDeviceInfo) {}
|
|
17
|
+
|
|
18
|
+
public GetMasterChannel(): Master {
|
|
19
|
+
return this.masterChannel;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public SetMasterChannel(channel: Master): void {
|
|
23
|
+
|
|
24
|
+
if(channel.id === this.masterChannel.id) return Debug.Error("The provided master channel is the same as the current channel.", [
|
|
25
|
+
"Provide this method with a different master channel.",
|
|
26
|
+
`Received master channel ID ${channel.id}.`
|
|
27
|
+
], ErrorCodes.SAME_MASTER_CHANNEL);
|
|
28
|
+
|
|
29
|
+
this.masterChannel = channel;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public CreateMasterChannel(): Master {
|
|
33
|
+
|
|
34
|
+
const master = new Master();
|
|
35
|
+
|
|
36
|
+
this.masterChannels.push(master);
|
|
37
|
+
return master;
|
|
38
|
+
}
|
|
39
|
+
}
|