@fluex/fluexgl-dsp 0.4.7 → 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 +26 -17
- package/lib/dist/core/classes/AudioClip.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioClip.js +124 -51
- 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 +6 -6
- package/lib/dist/effects/classes/Analyser.d.ts.map +1 -1
- package/lib/dist/effects/classes/Analyser.js +7 -7
- 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 +5 -5
- package/lib/dist/effects/classes/filters/HighPassFilter.d.ts.map +1 -1
- package/lib/dist/effects/classes/filters/HighPassFilter.js +16 -16
- 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 +5 -5
- package/lib/dist/effects/classes/filters/NotchFilter.d.ts.map +1 -1
- package/lib/dist/effects/classes/filters/NotchFilter.js +16 -16
- package/lib/dist/effects/exports.d.ts +1 -5
- package/lib/dist/effects/exports.d.ts.map +1 -1
- package/lib/dist/effects/exports.js +1 -5
- package/lib/dist/index.d.ts +2 -2
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +2 -2
- package/lib/dist/typings.d.ts +11 -0
- package/lib/dist/typings.d.ts.map +1 -1
- 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 +154 -60
- 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 +7 -7
- 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 +16 -16
- package/lib/src/effects/classes/filters/LowPassFilter.ts +17 -17
- package/lib/src/effects/classes/filters/NotchFilter.ts +16 -16
- package/lib/src/effects/exports.ts +1 -6
- package/lib/src/index.ts +11 -15
- package/lib/src/typings.ts +13 -0
- 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
|
@@ -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
|
}
|
|
@@ -44,13 +44,13 @@ export class Channel {
|
|
|
44
44
|
this.analyserNode.connect(this.gainNode);
|
|
45
45
|
this.gainNode.connect(this.output);
|
|
46
46
|
|
|
47
|
-
this.audioClipPlayer.
|
|
47
|
+
this.audioClipPlayer.send(this);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
private
|
|
50
|
+
private rebuildEffectChainInternal(): void {
|
|
51
51
|
|
|
52
52
|
if (!this.input || !this.stereoPannerNode) {
|
|
53
|
-
Debug.
|
|
53
|
+
Debug.error("Could not rebuild effect chain because one or more required audio nodes are undefined.", [
|
|
54
54
|
`Channel id: ${this.id}.`,
|
|
55
55
|
`Input defined: ${!!this.input}.`,
|
|
56
56
|
`StereoPannerNode defined: ${!!this.stereoPannerNode}.`
|
|
@@ -140,11 +140,11 @@ export class Channel {
|
|
|
140
140
|
* work properly.
|
|
141
141
|
* @returns
|
|
142
142
|
*/
|
|
143
|
-
public
|
|
144
|
-
return this.
|
|
143
|
+
public rebuildEffectChain() {
|
|
144
|
+
return this.rebuildEffectChainInternal();
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
public
|
|
147
|
+
public addEffect(effect: Effector): Channel {
|
|
148
148
|
|
|
149
149
|
if (!this.context)
|
|
150
150
|
throw new Error(`Could not add effect (${effect.id}), to channel (${this.id}), because the channel's AudioContext is undefined.`);
|
|
@@ -153,20 +153,20 @@ export class Channel {
|
|
|
153
153
|
throw new Error(`Could not add effect (${effect.id}), to channel (${this.id}), because it has already been added to the channel.`);
|
|
154
154
|
|
|
155
155
|
this.effects.push(effect);
|
|
156
|
-
effect.
|
|
157
|
-
this.
|
|
156
|
+
effect.initializeOnAttachment(this.context);
|
|
157
|
+
this.rebuildEffectChainInternal();
|
|
158
158
|
return this;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
public
|
|
162
|
-
return this.
|
|
161
|
+
public attachEffect(effect: Effector): Channel {
|
|
162
|
+
return this.addEffect(effect);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
public
|
|
165
|
+
public removeEffect(effect: Effector): void {
|
|
166
166
|
|
|
167
167
|
if (!this.effects.includes(effect)) {
|
|
168
|
-
Debug.
|
|
169
|
-
"Call .
|
|
168
|
+
Debug.error("Could not remove effect, because it is not part of this channel.", [
|
|
169
|
+
"Call .addEffect([effect Effector]) before removing effect."
|
|
170
170
|
], ErrorCodes.EFFECT_NOT_FOUND);
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
@@ -180,48 +180,48 @@ export class Channel {
|
|
|
180
180
|
|
|
181
181
|
effect.audioWorkletNode?.disconnect();
|
|
182
182
|
|
|
183
|
-
this.
|
|
183
|
+
this.rebuildEffectChainInternal();
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
public
|
|
186
|
+
public removeAllEffects() {
|
|
187
187
|
for (const effect of this.effects) {
|
|
188
|
-
this.
|
|
188
|
+
this.removeEffect(effect);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
public
|
|
193
|
-
return this.
|
|
192
|
+
public detachEffect(effect: Effector): void {
|
|
193
|
+
return this.removeEffect(effect);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
public
|
|
197
|
-
return this.
|
|
196
|
+
public detachAllEffects() {
|
|
197
|
+
return this.removeAllEffects();
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
public
|
|
200
|
+
public send(channel: Channel | Master) {
|
|
201
201
|
|
|
202
202
|
if (channel instanceof Master)
|
|
203
|
-
return (channel as Master).
|
|
203
|
+
return (channel as Master).attachChannel(this);
|
|
204
204
|
|
|
205
|
-
if (channel.id === this.id) return Debug.
|
|
205
|
+
if (channel.id === this.id) return Debug.error("Could not link channel to itself.", [
|
|
206
206
|
`This channel id: ${this.id}`
|
|
207
207
|
], ErrorCodes.CHANNEL_CANNOT_LINK_TO_ITSELF);
|
|
208
208
|
|
|
209
|
-
if (!this.isInitialized() || !channel.isInitialized()) return Debug.
|
|
209
|
+
if (!this.isInitialized() || !channel.isInitialized()) return Debug.error("Could not link channels because one (or both) channels are not initialized.", [
|
|
210
210
|
`This channel id: ${this.id} initialized: ${this.isInitialized()}`,
|
|
211
211
|
`Target channel id: ${channel.id} initialized: ${channel.isInitialized()}`
|
|
212
212
|
], ErrorCodes.CHANNEL_NOT_INITIALIZED);
|
|
213
213
|
|
|
214
|
-
if (this.context !== channel.context) return Debug.
|
|
214
|
+
if (this.context !== channel.context) return Debug.error("Could not link channels because they do not share the same AudioContext.", [
|
|
215
215
|
`This channel context: ${this.context ? "set" : "null"}`,
|
|
216
216
|
`Target channel context: ${channel.context ? "set" : "null"}`
|
|
217
217
|
], ErrorCodes.CHANNEL_NOT_SAME_AUDIO_CONTEXT);
|
|
218
218
|
|
|
219
|
-
if (this.sends.includes(channel)) return Debug.
|
|
219
|
+
if (this.sends.includes(channel)) return Debug.error("Could not link channels, because the given channel is already linked with this one.", [
|
|
220
220
|
`This channel id: ${this.id}`,
|
|
221
221
|
`Target channel id: ${channel.id}`
|
|
222
222
|
], ErrorCodes.CHANNEL_ALREADY_LINKED);
|
|
223
223
|
|
|
224
|
-
if (channel.isReachable(this)) return Debug.
|
|
224
|
+
if (channel.isReachable(this)) return Debug.error("Could not link channels because it would create a feedback loop.", [
|
|
225
225
|
`This channel id: ${this.id}`,
|
|
226
226
|
`Target channel id: ${channel.id}`
|
|
227
227
|
], ErrorCodes.CHANNEL_POSSIBLE_FEEDBACK_LOOP);
|
|
@@ -231,10 +231,10 @@ export class Channel {
|
|
|
231
231
|
this.sends.push(channel);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
public
|
|
234
|
+
public unsend(channel: Channel | Master) {
|
|
235
235
|
|
|
236
236
|
if (channel instanceof Master)
|
|
237
|
-
return (channel as Master).
|
|
237
|
+
return (channel as Master).detachChannel(this);
|
|
238
238
|
|
|
239
239
|
const idx: number = this.sends.indexOf(channel);
|
|
240
240
|
|
|
@@ -246,26 +246,26 @@ export class Channel {
|
|
|
246
246
|
this.sends.splice(idx, 1);
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
public
|
|
249
|
+
public hasAudioClipPlayer(): boolean {
|
|
250
250
|
return !!this.audioClipPlayer;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
public
|
|
253
|
+
public unsendToAllChannels() {
|
|
254
254
|
for (var i: number = 0; i < this.sends.length; i++) {
|
|
255
|
-
this.
|
|
255
|
+
this.unsend(this.sends[i]);
|
|
256
256
|
i--;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
public
|
|
260
|
+
public attachAudioClip(audioClip: AudioClip): Channel {
|
|
261
261
|
|
|
262
262
|
if (!this.audioClipPlayer) throw Error("Cannot not link AudioClip to this channel because this channel's AudioClipPlayer is undefined.");
|
|
263
263
|
|
|
264
|
-
this.audioClipPlayer.
|
|
264
|
+
this.audioClipPlayer.attachAudioClip(audioClip);
|
|
265
265
|
return this;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
public
|
|
268
|
+
public volume(volume?: number): number {
|
|
269
269
|
|
|
270
270
|
if (!this.context) throw new Error("Could not set volume on channel, because it's context is undefined.");
|
|
271
271
|
if (!this.gainNode) throw new Error("Could not set volume on channel, because it's GainNode is undefined.")
|
|
@@ -274,7 +274,7 @@ export class Channel {
|
|
|
274
274
|
return volume ?? this.gainNode.gain.value;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
public
|
|
277
|
+
public pan(pan?: number): number {
|
|
278
278
|
|
|
279
279
|
if (!this.context) throw new Error("Could not set pan on channel, because it's context is undefined.");
|
|
280
280
|
if (!this.stereoPannerNode) throw new Error("Cannot set pan on channel, because it's StereoPannerNode is undefined.");
|
|
@@ -283,27 +283,27 @@ export class Channel {
|
|
|
283
283
|
return pan ?? this.stereoPannerNode.pan.value;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
public
|
|
286
|
+
public getEffectsByLabel(label: string): Effector[] {
|
|
287
287
|
return this.effects.filter(effect => effect.label === label);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
public
|
|
290
|
+
public getFirstEffectByLabel(label: string): Effector | null {
|
|
291
291
|
|
|
292
292
|
const filteredEffects: Effector[] = this.effects.filter(effect => effect.label === label);
|
|
293
293
|
return filteredEffects.length !== 0 ? filteredEffects[0] : null;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
public
|
|
296
|
+
public getEffectById(id: string): Effector[] {
|
|
297
297
|
return this.effects.filter(effect => effect.id === id);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
public
|
|
300
|
+
public getFirstEffectById(id: string): Effector | null {
|
|
301
301
|
|
|
302
302
|
const filteredEffects: Effector[] = this.effects.filter(effect => effect.id === id);
|
|
303
303
|
return filteredEffects.length !== 0 ? filteredEffects[0] : null;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
public
|
|
306
|
+
public moveEffectToIndex(effect: Effector, index: number | ArrayPosition): void {
|
|
307
307
|
|
|
308
308
|
let fromIndex: number = -1,
|
|
309
309
|
matches: number = 0,
|
|
@@ -368,6 +368,6 @@ export class Channel {
|
|
|
368
368
|
|
|
369
369
|
// Initializing the effect is unnessecary because it should have been intialized already
|
|
370
370
|
// otherwise it could not been found. Rebuilding the effect chain is nessecary though.
|
|
371
|
-
this.
|
|
371
|
+
this.rebuildEffectChainInternal();
|
|
372
372
|
}
|
|
373
373
|
}
|
|
@@ -3,7 +3,7 @@ import { v4 } from "uuid";
|
|
|
3
3
|
import { AudioDevice } from "./AudioDevice";
|
|
4
4
|
import { Debug } from "../../utilities/debugger";
|
|
5
5
|
import { LoadWebAssemblyModule } from "../../utilities/web-assembly";
|
|
6
|
-
import {
|
|
6
|
+
import { constructProcessorWorklet, loadWorkletOnAudioDevice } from "../../utilities/helpers";
|
|
7
7
|
|
|
8
8
|
import { ErrorCodes, WarningCodes } from "../../console-codes";
|
|
9
9
|
import { DspPipelineInitializationOptions } from "../../typings";
|
|
@@ -56,12 +56,12 @@ export class DspPipeline {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Initializes the DSP pipeline.
|
|
59
|
-
* This is a convenience wrapper around {@link
|
|
59
|
+
* This is a convenience wrapper around {@link initializeDpsPipeline}.
|
|
60
60
|
*
|
|
61
61
|
* @returns A promise resolving to `true` if the initialization succeeded, otherwise `false`.
|
|
62
62
|
*/
|
|
63
|
-
public async
|
|
64
|
-
return await this.
|
|
63
|
+
public async init(): Promise<boolean> {
|
|
64
|
+
return await this.initializeDpsPipeline();
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -71,26 +71,26 @@ export class DspPipeline {
|
|
|
71
71
|
*
|
|
72
72
|
* @returns A promise resolving to `true` when initialization completes successfully, otherwise `false`.
|
|
73
73
|
*/
|
|
74
|
-
public async
|
|
75
|
-
Debug.
|
|
74
|
+
public async initializeDpsPipeline(): Promise<boolean> {
|
|
75
|
+
Debug.log("Attempting to initialize DSP pipeline...");
|
|
76
76
|
|
|
77
77
|
const start: number = Date.now();
|
|
78
78
|
|
|
79
79
|
if (!this.pathToWasm || !this.pathToWorklet) {
|
|
80
|
-
Debug.
|
|
80
|
+
Debug.error("Could not initialize DSP pipeline, because the path to the WASM, or the worklet file has not been defined.");
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
try {
|
|
85
85
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
86
|
-
Debug.
|
|
86
|
+
Debug.log(`Found ${stream.getTracks().length} media stream tracks.`);
|
|
87
87
|
|
|
88
88
|
// Stop all tracks immediately after verification
|
|
89
89
|
stream.getTracks().forEach((track: MediaStreamTrack) => {
|
|
90
90
|
track.stop();
|
|
91
91
|
});
|
|
92
92
|
} catch (err) {
|
|
93
|
-
Debug.
|
|
93
|
+
Debug.error(
|
|
94
94
|
"Permission to access media devices has not been granted.",
|
|
95
95
|
["Make sure the user has granted FluentGL permission to access media devices."],
|
|
96
96
|
ErrorCodes.NO_CONTEXT_PERMISSION
|
|
@@ -103,30 +103,30 @@ export class DspPipeline {
|
|
|
103
103
|
const workletFileRequest = await fetch(this.pathToWorklet);
|
|
104
104
|
const textContent: string = await workletFileRequest.text();
|
|
105
105
|
|
|
106
|
-
this.blobUrl =
|
|
106
|
+
this.blobUrl = constructProcessorWorklet(textContent);
|
|
107
107
|
|
|
108
108
|
const end: number = Date.now();
|
|
109
109
|
const difference: number = end - start;
|
|
110
110
|
|
|
111
|
-
Debug.
|
|
111
|
+
Debug.success(`Successfully initialized DSP pipeline within ${difference}ms.`);
|
|
112
112
|
|
|
113
113
|
return (this.hasInitialized = true);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* Resolves and initializes the system
|
|
117
|
+
* Resolves and initializes the system’s default audio output device.
|
|
118
118
|
* This method can only be called after the DSP pipeline has been initialized.
|
|
119
119
|
*
|
|
120
120
|
* @returns A promise that resolves with the default {@link AudioDevice} instance,
|
|
121
121
|
* or `null` if no default device was found or initialization failed.
|
|
122
122
|
*/
|
|
123
|
-
public async
|
|
124
|
-
Debug.
|
|
123
|
+
public async resolveDefaultAudioOutputDevice() {
|
|
124
|
+
Debug.log("Attempting to resolve default audio output device...");
|
|
125
125
|
|
|
126
126
|
if (!this.hasInitialized || !this.blobUrl) {
|
|
127
|
-
Debug.
|
|
127
|
+
Debug.error(
|
|
128
128
|
"Could not resolve default audio output device, because the required blob URL has not been created.",
|
|
129
|
-
["Call await .
|
|
129
|
+
["Call await .initializeDpsPipeline() before resolving default audio output device."]
|
|
130
130
|
);
|
|
131
131
|
return null;
|
|
132
132
|
}
|
|
@@ -139,19 +139,19 @@ export class DspPipeline {
|
|
|
139
139
|
audioDeviceInfos.push(device);
|
|
140
140
|
|
|
141
141
|
devices.length === 0 &&
|
|
142
|
-
Debug.
|
|
142
|
+
Debug.warn("No default audio device found.", [], WarningCodes.NO_DEFAULT_AUDIO_DEVICE_FOUND);
|
|
143
143
|
|
|
144
144
|
const defaultAudioDevice =
|
|
145
145
|
devices.length === 0 ? null : new AudioDevice(audioDeviceInfos[0]);
|
|
146
146
|
|
|
147
147
|
if (!defaultAudioDevice) return null;
|
|
148
148
|
|
|
149
|
-
await
|
|
149
|
+
await loadWorkletOnAudioDevice(defaultAudioDevice, this.blobUrl);
|
|
150
150
|
|
|
151
151
|
return defaultAudioDevice;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
public
|
|
154
|
+
public tellMeWhatTheFuckThisWholeLibraryActuallyDoes() {
|
|
155
155
|
return `I wanted to create a web based game and implement advanced audio, but javascript is slow so that's why this library uses Web Assembly. You dont have to use it, unless you know what the fuck you're doing. If you do, god bless you.`
|
|
156
156
|
}
|
|
157
157
|
}
|
|
@@ -43,30 +43,30 @@ export abstract class Effector {
|
|
|
43
43
|
|
|
44
44
|
this.hasRegisteredMessageEventListener = true;
|
|
45
45
|
node.port.onmessage = this.handleIncomingMessages.bind(this);
|
|
46
|
-
Debug.
|
|
46
|
+
Debug.log(`Succesfully registered event listener on effector ${this.name} (${this.label}) [${this.id}]`);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
public
|
|
49
|
+
public addEventListener<K extends keyof EffectorEventMap>(event: K, cb: EffectorEventMap[K]): () => void {
|
|
50
50
|
|
|
51
51
|
this.events[event].push(cb);
|
|
52
52
|
|
|
53
|
-
return () => this.
|
|
53
|
+
return () => this.removeEventListener(event, cb);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
public
|
|
56
|
+
public once<K extends keyof EffectorEventMap>(event: K, cb: EffectorEventMap[K]): () => void {
|
|
57
57
|
|
|
58
58
|
const wrapper = ((...args: unknown[]) => {
|
|
59
59
|
|
|
60
60
|
// @ts-ignore
|
|
61
61
|
cb(...args);
|
|
62
62
|
|
|
63
|
-
this.
|
|
63
|
+
this.removeEventListener(event, wrapper as unknown as EffectorEventMap[K]);
|
|
64
64
|
}) as unknown as EffectorEventMap[K];
|
|
65
65
|
|
|
66
|
-
return this.
|
|
66
|
+
return this.addEventListener(event, wrapper);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
public
|
|
69
|
+
public removeEventListener<K extends keyof EffectorEventMap>(event: K, cb: EffectorEventMap[K]): Effector {
|
|
70
70
|
|
|
71
71
|
const arr = this.events[event];
|
|
72
72
|
|
|
@@ -79,7 +79,7 @@ export abstract class Effector {
|
|
|
79
79
|
return this;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
public
|
|
82
|
+
public clearEventListeners(event?: keyof EffectorEventMap): Effector {
|
|
83
83
|
|
|
84
84
|
if (event) {
|
|
85
85
|
this.events[event].length = 0;
|
|
@@ -90,5 +90,5 @@ export abstract class Effector {
|
|
|
90
90
|
return this;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
public abstract
|
|
93
|
+
public abstract initializeOnAttachment(context: AudioContext): Promise<void>;
|
|
94
94
|
}
|
|
@@ -36,7 +36,7 @@ export class Master {
|
|
|
36
36
|
this.gainNode.connect(this.analyserNode);
|
|
37
37
|
this.analyserNode.connect(this.context.destination);
|
|
38
38
|
|
|
39
|
-
this.audioClipPlayer.
|
|
39
|
+
this.audioClipPlayer.send(this);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
private disconnectAudioNodes(): void {
|
|
@@ -74,23 +74,23 @@ export class Master {
|
|
|
74
74
|
this.analyserNode.connect(this.context.destination);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
public
|
|
77
|
+
public attachEffect(effect: Effector): void {
|
|
78
78
|
|
|
79
|
-
if (this.effects.includes(effect)) return Debug.
|
|
80
|
-
"Call .
|
|
79
|
+
if (this.effects.includes(effect)) return Debug.error("Could not attach the effect because it is already part of this master channel.", [
|
|
80
|
+
"Call .detachEffect([effect Effector]) before attaching the effect."
|
|
81
81
|
], ErrorCodes.EFFECT_ALREADY_ATTACHED);
|
|
82
82
|
|
|
83
83
|
if(!this.context) return this.rebuildEffectChain();
|
|
84
84
|
|
|
85
85
|
this.effects.push(effect);
|
|
86
|
-
effect.
|
|
86
|
+
effect.initializeOnAttachment(this.context);
|
|
87
87
|
this.rebuildEffectChain();
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
public
|
|
90
|
+
public detachEffect(effect: Effector): void {
|
|
91
91
|
|
|
92
|
-
if (!this.effects.includes(effect)) return Debug.
|
|
93
|
-
"Call .
|
|
92
|
+
if (!this.effects.includes(effect)) return Debug.error("Could not detach the effect because it is not part of this master channel.", [
|
|
93
|
+
"Call .attachEffect([effect Effector]) before detaching the effect."
|
|
94
94
|
], ErrorCodes.EFFECT_NOT_FOUND);
|
|
95
95
|
|
|
96
96
|
const self = this;
|
|
@@ -105,10 +105,10 @@ export class Master {
|
|
|
105
105
|
this.rebuildEffectChain();
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
public
|
|
108
|
+
public attachChannel(channel: Channel): void {
|
|
109
109
|
|
|
110
|
-
if (this.channels.includes(channel)) return Debug.
|
|
111
|
-
"Call .
|
|
110
|
+
if (this.channels.includes(channel)) return Debug.error("Could not attach the channel because it is already part of this master channel.", [
|
|
111
|
+
"Call .detachChannel([channel Channel]) before attaching the channel."
|
|
112
112
|
], ErrorCodes.CHANNEL_ALREADY_ATTACHED);
|
|
113
113
|
|
|
114
114
|
this.channels.push(channel);
|
|
@@ -119,10 +119,10 @@ export class Master {
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
public
|
|
122
|
+
public detachChannel(channel: Channel): void {
|
|
123
123
|
|
|
124
|
-
if (!this.channels.includes(channel)) return Debug.
|
|
125
|
-
"Call .
|
|
124
|
+
if (!this.channels.includes(channel)) return Debug.error("Could not detach the channel because it is not part of this master channel.", [
|
|
125
|
+
"Call .attachChannel([channel Channel]) before detaching the channel."
|
|
126
126
|
], ErrorCodes.CHANNEL_NOT_FOUND);
|
|
127
127
|
|
|
128
128
|
if (channel.output && this.input)
|
|
@@ -137,14 +137,14 @@ export class Master {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
public
|
|
140
|
+
public hasAudioClipPlayer(): boolean {
|
|
141
141
|
return !!this.audioClipPlayer;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
public
|
|
144
|
+
public attachAudioClip(audioClip: AudioClip) {
|
|
145
145
|
|
|
146
|
-
if (!this.audioClipPlayer) return Debug.
|
|
146
|
+
if (!this.audioClipPlayer) return Debug.error("Cannot not link AudioClip to this channel because this channel's AudioClipPlayer is undefined.");
|
|
147
147
|
|
|
148
|
-
this.audioClipPlayer.
|
|
148
|
+
this.audioClipPlayer.attachAudioClip(audioClip);
|
|
149
149
|
}
|
|
150
150
|
}
|