Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/platform/web/js/libs/library_godot_audio.js
10279 views
1
/**************************************************************************/
2
/* library_godot_audio.js */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
/**
32
* @typedef { "disabled" | "forward" | "backward" | "pingpong" } LoopMode
33
*/
34
35
/**
36
* @typedef {{
37
* id: string
38
* audioBuffer: AudioBuffer
39
* }} SampleParams
40
* @typedef {{
41
* numberOfChannels?: number
42
* sampleRate?: number
43
* loopMode?: LoopMode
44
* loopBegin?: number
45
* loopEnd?: number
46
* }} SampleOptions
47
*/
48
49
/**
50
* Represents a sample, memory-wise.
51
* @class
52
*/
53
class Sample {
54
/**
55
* Returns a `Sample`.
56
* @param {string} id Id of the `Sample` to get.
57
* @returns {Sample}
58
* @throws {ReferenceError} When no `Sample` is found
59
*/
60
static getSample(id) {
61
if (!GodotAudio.samples.has(id)) {
62
throw new ReferenceError(`Could not find sample "${id}"`);
63
}
64
return GodotAudio.samples.get(id);
65
}
66
67
/**
68
* Returns a `Sample` or `null`, if it doesn't exist.
69
* @param {string} id Id of the `Sample` to get.
70
* @returns {Sample?}
71
*/
72
static getSampleOrNull(id) {
73
return GodotAudio.samples.get(id) ?? null;
74
}
75
76
/**
77
* Creates a `Sample` based on the params. Will register it to the
78
* `GodotAudio.samples` registry.
79
* @param {SampleParams} params Base params
80
* @param {SampleOptions | undefined} options Optional params.
81
* @returns {Sample}
82
*/
83
static create(params, options = {}) {
84
const sample = new GodotAudio.Sample(params, options);
85
GodotAudio.samples.set(params.id, sample);
86
return sample;
87
}
88
89
/**
90
* Deletes a `Sample` based on the id.
91
* @param {string} id `Sample` id to delete
92
* @returns {void}
93
*/
94
static delete(id) {
95
GodotAudio.samples.delete(id);
96
}
97
98
/**
99
* `Sample` constructor.
100
* @param {SampleParams} params Base params
101
* @param {SampleOptions | undefined} options Optional params.
102
*/
103
constructor(params, options = {}) {
104
/** @type {string} */
105
this.id = params.id;
106
/** @type {AudioBuffer} */
107
this._audioBuffer = null;
108
/** @type {number} */
109
this.numberOfChannels = options.numberOfChannels ?? 2;
110
/** @type {number} */
111
this.sampleRate = options.sampleRate ?? 44100;
112
/** @type {LoopMode} */
113
this.loopMode = options.loopMode ?? 'disabled';
114
/** @type {number} */
115
this.loopBegin = options.loopBegin ?? 0;
116
/** @type {number} */
117
this.loopEnd = options.loopEnd ?? 0;
118
119
this.setAudioBuffer(params.audioBuffer);
120
}
121
122
/**
123
* Gets the audio buffer of the sample.
124
* @returns {AudioBuffer}
125
*/
126
getAudioBuffer() {
127
return this._duplicateAudioBuffer();
128
}
129
130
/**
131
* Sets the audio buffer of the sample.
132
* @param {AudioBuffer} val The audio buffer to set.
133
* @returns {void}
134
*/
135
setAudioBuffer(val) {
136
this._audioBuffer = val;
137
}
138
139
/**
140
* Clears the current sample.
141
* @returns {void}
142
*/
143
clear() {
144
this.setAudioBuffer(null);
145
GodotAudio.Sample.delete(this.id);
146
}
147
148
/**
149
* Returns a duplicate of the stored audio buffer.
150
* @returns {AudioBuffer}
151
*/
152
_duplicateAudioBuffer() {
153
if (this._audioBuffer == null) {
154
throw new Error('couldn\'t duplicate a null audioBuffer');
155
}
156
/** @type {Array<Float32Array>} */
157
const channels = new Array(this._audioBuffer.numberOfChannels);
158
for (let i = 0; i < this._audioBuffer.numberOfChannels; i++) {
159
const channel = new Float32Array(this._audioBuffer.getChannelData(i));
160
channels[i] = channel;
161
}
162
const buffer = GodotAudio.ctx.createBuffer(
163
this.numberOfChannels,
164
this._audioBuffer.length,
165
this._audioBuffer.sampleRate
166
);
167
for (let i = 0; i < channels.length; i++) {
168
buffer.copyToChannel(channels[i], i, 0);
169
}
170
return buffer;
171
}
172
}
173
174
/**
175
* Represents a `SampleNode` linked to a `Bus`.
176
* @class
177
*/
178
class SampleNodeBus {
179
/**
180
* Creates a new `SampleNodeBus`.
181
* @param {Bus} bus The bus related to the new `SampleNodeBus`.
182
* @returns {SampleNodeBus}
183
*/
184
static create(bus) {
185
return new GodotAudio.SampleNodeBus(bus);
186
}
187
188
/**
189
* `SampleNodeBus` constructor.
190
* @param {Bus} bus The bus related to the new `SampleNodeBus`.
191
*/
192
constructor(bus) {
193
const NUMBER_OF_WEB_CHANNELS = 6;
194
195
/** @type {Bus} */
196
this._bus = bus;
197
198
/** @type {ChannelSplitterNode} */
199
this._channelSplitter = GodotAudio.ctx.createChannelSplitter(NUMBER_OF_WEB_CHANNELS);
200
/** @type {GainNode} */
201
this._l = GodotAudio.ctx.createGain();
202
/** @type {GainNode} */
203
this._r = GodotAudio.ctx.createGain();
204
/** @type {GainNode} */
205
this._sl = GodotAudio.ctx.createGain();
206
/** @type {GainNode} */
207
this._sr = GodotAudio.ctx.createGain();
208
/** @type {GainNode} */
209
this._c = GodotAudio.ctx.createGain();
210
/** @type {GainNode} */
211
this._lfe = GodotAudio.ctx.createGain();
212
/** @type {ChannelMergerNode} */
213
this._channelMerger = GodotAudio.ctx.createChannelMerger(NUMBER_OF_WEB_CHANNELS);
214
215
this._channelSplitter
216
.connect(this._l, GodotAudio.WebChannel.CHANNEL_L)
217
.connect(
218
this._channelMerger,
219
GodotAudio.WebChannel.CHANNEL_L,
220
GodotAudio.WebChannel.CHANNEL_L
221
);
222
this._channelSplitter
223
.connect(this._r, GodotAudio.WebChannel.CHANNEL_R)
224
.connect(
225
this._channelMerger,
226
GodotAudio.WebChannel.CHANNEL_L,
227
GodotAudio.WebChannel.CHANNEL_R
228
);
229
this._channelSplitter
230
.connect(this._sl, GodotAudio.WebChannel.CHANNEL_SL)
231
.connect(
232
this._channelMerger,
233
GodotAudio.WebChannel.CHANNEL_L,
234
GodotAudio.WebChannel.CHANNEL_SL
235
);
236
this._channelSplitter
237
.connect(this._sr, GodotAudio.WebChannel.CHANNEL_SR)
238
.connect(
239
this._channelMerger,
240
GodotAudio.WebChannel.CHANNEL_L,
241
GodotAudio.WebChannel.CHANNEL_SR
242
);
243
this._channelSplitter
244
.connect(this._c, GodotAudio.WebChannel.CHANNEL_C)
245
.connect(
246
this._channelMerger,
247
GodotAudio.WebChannel.CHANNEL_L,
248
GodotAudio.WebChannel.CHANNEL_C
249
);
250
this._channelSplitter
251
.connect(this._lfe, GodotAudio.WebChannel.CHANNEL_L)
252
.connect(
253
this._channelMerger,
254
GodotAudio.WebChannel.CHANNEL_L,
255
GodotAudio.WebChannel.CHANNEL_LFE
256
);
257
258
this._channelMerger.connect(this._bus.getInputNode());
259
}
260
261
/**
262
* Returns the input node.
263
* @returns {AudioNode}
264
*/
265
getInputNode() {
266
return this._channelSplitter;
267
}
268
269
/**
270
* Returns the output node.
271
* @returns {AudioNode}
272
*/
273
getOutputNode() {
274
return this._channelMerger;
275
}
276
277
/**
278
* Sets the volume for each (split) channel.
279
* @param {Float32Array} volume Volume array from the engine for each channel.
280
* @returns {void}
281
*/
282
setVolume(volume) {
283
if (volume.length !== GodotAudio.MAX_VOLUME_CHANNELS) {
284
throw new Error(
285
`Volume length isn't "${GodotAudio.MAX_VOLUME_CHANNELS}", is ${volume.length} instead`
286
);
287
}
288
this._l.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_L] ?? 0;
289
this._r.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_R] ?? 0;
290
this._sl.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_SL] ?? 0;
291
this._sr.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_SR] ?? 0;
292
this._c.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_C] ?? 0;
293
this._lfe.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_LFE] ?? 0;
294
}
295
296
/**
297
* Clears the current `SampleNodeBus` instance.
298
* @returns {void}
299
*/
300
clear() {
301
this._bus = null;
302
this._channelSplitter.disconnect();
303
this._channelSplitter = null;
304
this._l.disconnect();
305
this._l = null;
306
this._r.disconnect();
307
this._r = null;
308
this._sl.disconnect();
309
this._sl = null;
310
this._sr.disconnect();
311
this._sr = null;
312
this._c.disconnect();
313
this._c = null;
314
this._lfe.disconnect();
315
this._lfe = null;
316
this._channelMerger.disconnect();
317
this._channelMerger = null;
318
}
319
}
320
321
/**
322
* @typedef {{
323
* id: string
324
* streamObjectId: string
325
* busIndex: number
326
* }} SampleNodeParams
327
* @typedef {{
328
* offset?: number
329
* playbackRate?: number
330
* startTime?: number
331
* pitchScale?: number
332
* loopMode?: LoopMode
333
* volume?: Float32Array
334
* start?: boolean
335
* }} SampleNodeOptions
336
*/
337
338
/**
339
* Represents an `AudioNode` of a `Sample`.
340
* @class
341
*/
342
class SampleNode {
343
/**
344
* Returns a `SampleNode`.
345
* @param {string} id Id of the `SampleNode`.
346
* @returns {SampleNode}
347
* @throws {ReferenceError} When no `SampleNode` is not found
348
*/
349
static getSampleNode(id) {
350
if (!GodotAudio.sampleNodes.has(id)) {
351
throw new ReferenceError(`Could not find sample node "${id}"`);
352
}
353
return GodotAudio.sampleNodes.get(id);
354
}
355
356
/**
357
* Returns a `SampleNode`, returns null if not found.
358
* @param {string} id Id of the SampleNode.
359
* @returns {SampleNode?}
360
*/
361
static getSampleNodeOrNull(id) {
362
return GodotAudio.sampleNodes.get(id) ?? null;
363
}
364
365
/**
366
* Stops a `SampleNode` by id.
367
* @param {string} id Id of the `SampleNode` to stop.
368
* @returns {void}
369
*/
370
static stopSampleNode(id) {
371
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(id);
372
if (sampleNode == null) {
373
return;
374
}
375
sampleNode.stop();
376
}
377
378
/**
379
* Pauses the `SampleNode` by id.
380
* @param {string} id Id of the `SampleNode` to pause.
381
* @param {boolean} enable State of the pause
382
* @returns {void}
383
*/
384
static pauseSampleNode(id, enable) {
385
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(id);
386
if (sampleNode == null) {
387
return;
388
}
389
sampleNode.pause(enable);
390
}
391
392
/**
393
* Creates a `SampleNode` based on the params. Will register the `SampleNode` to
394
* the `GodotAudio.sampleNodes` regisery.
395
* @param {SampleNodeParams} params Base params.
396
* @param {SampleNodeOptions | undefined} options Optional params.
397
* @returns {SampleNode}
398
*/
399
static create(params, options = {}) {
400
const sampleNode = new GodotAudio.SampleNode(params, options);
401
GodotAudio.sampleNodes.set(params.id, sampleNode);
402
return sampleNode;
403
}
404
405
/**
406
* Deletes a `SampleNode` based on the id.
407
* @param {string} id Id of the `SampleNode` to delete.
408
* @returns {void}
409
*/
410
static delete(id) {
411
GodotAudio.deleteSampleNode(id);
412
}
413
414
/**
415
* @param {SampleNodeParams} params Base params
416
* @param {SampleNodeOptions | undefined} options Optional params.
417
*/
418
constructor(params, options = {}) {
419
/** @type {string} */
420
this.id = params.id;
421
/** @type {string} */
422
this.streamObjectId = params.streamObjectId;
423
/** @type {number} */
424
this.offset = options.offset ?? 0;
425
/** @type {number} */
426
this._playbackPosition = options.offset;
427
/** @type {number} */
428
this.startTime = options.startTime ?? 0;
429
/** @type {boolean} */
430
this.isPaused = false;
431
/** @type {boolean} */
432
this.isStarted = false;
433
/** @type {boolean} */
434
this.isCanceled = false;
435
/** @type {number} */
436
this.pauseTime = 0;
437
/** @type {number} */
438
this._playbackRate = 44100;
439
/** @type {LoopMode} */
440
this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
441
/** @type {number} */
442
this._pitchScale = options.pitchScale ?? 1;
443
/** @type {number} */
444
this._sourceStartTime = 0;
445
/** @type {Map<Bus, SampleNodeBus>} */
446
this._sampleNodeBuses = new Map();
447
/** @type {AudioBufferSourceNode | null} */
448
this._source = GodotAudio.ctx.createBufferSource();
449
450
this._onended = null;
451
/** @type {AudioWorkletNode | null} */
452
this._positionWorklet = null;
453
454
this.setPlaybackRate(options.playbackRate ?? 44100);
455
this._source.buffer = this.getSample().getAudioBuffer();
456
457
this._addEndedListener();
458
459
const bus = GodotAudio.Bus.getBus(params.busIndex);
460
const sampleNodeBus = this.getSampleNodeBus(bus);
461
sampleNodeBus.setVolume(options.volume);
462
463
this.connectPositionWorklet(options.start).catch((err) => {
464
const newErr = new Error('Failed to create PositionWorklet.');
465
newErr.cause = err;
466
GodotRuntime.error(newErr);
467
});
468
}
469
470
/**
471
* Gets the playback rate.
472
* @returns {number}
473
*/
474
getPlaybackRate() {
475
return this._playbackRate;
476
}
477
478
/**
479
* Gets the playback position.
480
* @returns {number}
481
*/
482
getPlaybackPosition() {
483
return this._playbackPosition;
484
}
485
486
/**
487
* Sets the playback rate.
488
* @param {number} val Value to set.
489
* @returns {void}
490
*/
491
setPlaybackRate(val) {
492
this._playbackRate = val;
493
this._syncPlaybackRate();
494
}
495
496
/**
497
* Gets the pitch scale.
498
* @returns {number}
499
*/
500
getPitchScale() {
501
return this._pitchScale;
502
}
503
504
/**
505
* Sets the pitch scale.
506
* @param {number} val Value to set.
507
* @returns {void}
508
*/
509
setPitchScale(val) {
510
this._pitchScale = val;
511
this._syncPlaybackRate();
512
}
513
514
/**
515
* Returns the linked `Sample`.
516
* @returns {Sample}
517
*/
518
getSample() {
519
return GodotAudio.Sample.getSample(this.streamObjectId);
520
}
521
522
/**
523
* Returns the output node.
524
* @returns {AudioNode}
525
*/
526
getOutputNode() {
527
return this._source;
528
}
529
530
/**
531
* Starts the `SampleNode`.
532
* @returns {void}
533
*/
534
start() {
535
if (this.isStarted) {
536
return;
537
}
538
this._resetSourceStartTime();
539
this._source.start(this.startTime, this.offset);
540
this.isStarted = true;
541
}
542
543
/**
544
* Stops the `SampleNode`.
545
* @returns {void}
546
*/
547
stop() {
548
this.clear();
549
}
550
551
/**
552
* Restarts the `SampleNode`.
553
*/
554
restart() {
555
this.isPaused = false;
556
this.pauseTime = 0;
557
this._resetSourceStartTime();
558
this._restart();
559
}
560
561
/**
562
* Pauses the `SampleNode`.
563
* @param {boolean} [enable=true] State of the pause.
564
* @returns {void}
565
*/
566
pause(enable = true) {
567
if (enable) {
568
this._pause();
569
return;
570
}
571
572
this._unpause();
573
}
574
575
/**
576
* Connects an AudioNode to the output node of this `SampleNode`.
577
* @param {AudioNode} node AudioNode to connect.
578
* @returns {void}
579
*/
580
connect(node) {
581
return this.getOutputNode().connect(node);
582
}
583
584
/**
585
* Sets the volumes of the `SampleNode` for each buses passed in parameters.
586
* @param {Array<Bus>} buses
587
* @param {Float32Array} volumes
588
*/
589
setVolumes(buses, volumes) {
590
for (let busIdx = 0; busIdx < buses.length; busIdx++) {
591
const sampleNodeBus = this.getSampleNodeBus(buses[busIdx]);
592
sampleNodeBus.setVolume(
593
volumes.slice(
594
busIdx * GodotAudio.MAX_VOLUME_CHANNELS,
595
(busIdx * GodotAudio.MAX_VOLUME_CHANNELS) + GodotAudio.MAX_VOLUME_CHANNELS
596
)
597
);
598
}
599
}
600
601
/**
602
* Returns the SampleNodeBus based on the bus in parameters.
603
* @param {Bus} bus Bus to get the SampleNodeBus from.
604
* @returns {SampleNodeBus}
605
*/
606
getSampleNodeBus(bus) {
607
if (!this._sampleNodeBuses.has(bus)) {
608
const sampleNodeBus = GodotAudio.SampleNodeBus.create(bus);
609
this._sampleNodeBuses.set(bus, sampleNodeBus);
610
this._source.connect(sampleNodeBus.getInputNode());
611
}
612
return this._sampleNodeBuses.get(bus);
613
}
614
615
/**
616
* Sets up and connects the source to the GodotPositionReportingProcessor
617
* If the worklet module is not loaded in, it will be added
618
*/
619
async connectPositionWorklet(start) {
620
await GodotAudio.audioPositionWorkletPromise;
621
if (this.isCanceled) {
622
return;
623
}
624
this._source.connect(this.getPositionWorklet());
625
if (start) {
626
this.start();
627
}
628
}
629
630
/**
631
* Get a AudioWorkletProcessor
632
* @returns {AudioWorkletNode}
633
*/
634
getPositionWorklet() {
635
if (this._positionWorklet != null) {
636
return this._positionWorklet;
637
}
638
if (GodotAudio.audioPositionWorkletNodes.length > 0) {
639
this._positionWorklet = GodotAudio.audioPositionWorkletNodes.pop();
640
} else {
641
this._positionWorklet = new AudioWorkletNode(
642
GodotAudio.ctx,
643
'godot-position-reporting-processor'
644
);
645
}
646
this._playbackPosition = this.offset;
647
this._positionWorklet.port.onmessage = (event) => {
648
switch (event.data['type']) {
649
case 'position':
650
this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset;
651
break;
652
default:
653
// Do nothing.
654
}
655
};
656
657
const resetParameter = this._positionWorklet.parameters.get('reset');
658
resetParameter.setValueAtTime(1, GodotAudio.ctx.currentTime);
659
resetParameter.setValueAtTime(0, GodotAudio.ctx.currentTime + 1);
660
661
return this._positionWorklet;
662
}
663
664
/**
665
* Clears the `SampleNode`.
666
* @returns {void}
667
*/
668
clear() {
669
this.isCanceled = true;
670
this.isPaused = false;
671
this.pauseTime = 0;
672
673
if (this._source != null) {
674
this._source.removeEventListener('ended', this._onended);
675
this._onended = null;
676
if (this.isStarted) {
677
this._source.stop();
678
}
679
this._source.disconnect();
680
this._source = null;
681
}
682
683
for (const sampleNodeBus of this._sampleNodeBuses.values()) {
684
sampleNodeBus.clear();
685
}
686
this._sampleNodeBuses.clear();
687
688
if (this._positionWorklet) {
689
this._positionWorklet.disconnect();
690
this._positionWorklet.port.onmessage = null;
691
GodotAudio.audioPositionWorkletNodes.push(this._positionWorklet);
692
this._positionWorklet = null;
693
}
694
695
GodotAudio.SampleNode.delete(this.id);
696
}
697
698
/**
699
* Resets the source start time
700
* @returns {void}
701
*/
702
_resetSourceStartTime() {
703
this._sourceStartTime = GodotAudio.ctx.currentTime;
704
}
705
706
/**
707
* Syncs the `AudioNode` playback rate based on the `SampleNode` playback rate and pitch scale.
708
* @returns {void}
709
*/
710
_syncPlaybackRate() {
711
this._source.playbackRate.value = this.getPlaybackRate() * this.getPitchScale();
712
}
713
714
/**
715
* Restarts the `SampleNode`.
716
* Honors `isPaused` and `pauseTime`.
717
* @returns {void}
718
*/
719
_restart() {
720
if (this._source != null) {
721
this._source.disconnect();
722
}
723
this._source = GodotAudio.ctx.createBufferSource();
724
this._source.buffer = this.getSample().getAudioBuffer();
725
726
// Make sure that we connect the new source to the sample node bus.
727
for (const sampleNodeBus of this._sampleNodeBuses.values()) {
728
this.connect(sampleNodeBus.getInputNode());
729
}
730
731
this._addEndedListener();
732
const pauseTime = this.isPaused
733
? this.pauseTime
734
: 0;
735
if (this._positionWorklet != null) {
736
this._positionWorklet.port.postMessage({ type: 'clear' });
737
this._source.connect(this._positionWorklet);
738
}
739
this._source.start(this.startTime, this.offset + pauseTime);
740
this.isStarted = true;
741
}
742
743
/**
744
* Pauses the `SampleNode`.
745
* @returns {void}
746
*/
747
_pause() {
748
if (!this.isStarted) {
749
return;
750
}
751
this.isPaused = true;
752
this.pauseTime = (GodotAudio.ctx.currentTime - this._sourceStartTime) / this.getPlaybackRate();
753
this._source.stop();
754
}
755
756
/**
757
* Unpauses the `SampleNode`.
758
* @returns {void}
759
*/
760
_unpause() {
761
this._restart();
762
this.isPaused = false;
763
this.pauseTime = 0;
764
}
765
766
/**
767
* Adds an "ended" listener to the source node to repeat it if necessary.
768
* @returns {void}
769
*/
770
_addEndedListener() {
771
if (this._onended != null) {
772
this._source.removeEventListener('ended', this._onended);
773
}
774
775
/** @type {SampleNode} */
776
// eslint-disable-next-line consistent-this
777
const self = this;
778
this._onended = (_) => {
779
if (self.isPaused) {
780
return;
781
}
782
783
switch (self.getSample().loopMode) {
784
case 'disabled':
785
self.stop();
786
break;
787
case 'forward':
788
case 'backward':
789
self.restart();
790
break;
791
default:
792
// do nothing
793
}
794
};
795
this._source.addEventListener('ended', this._onended);
796
}
797
}
798
799
/**
800
* Collection of nodes to represents a Godot Engine audio bus.
801
* @class
802
*/
803
class Bus {
804
/**
805
* Returns the number of registered buses.
806
* @returns {number}
807
*/
808
static getCount() {
809
return GodotAudio.buses.length;
810
}
811
812
/**
813
* Sets the number of registered buses.
814
* Will delete buses if lower than the current number.
815
* @param {number} val Count of registered buses.
816
* @returns {void}
817
*/
818
static setCount(val) {
819
const buses = GodotAudio.buses;
820
if (val === buses.length) {
821
return;
822
}
823
824
if (val < buses.length) {
825
// TODO: what to do with nodes connected to the deleted buses?
826
const deletedBuses = buses.slice(val);
827
for (let i = 0; i < deletedBuses.length; i++) {
828
const deletedBus = deletedBuses[i];
829
deletedBus.clear();
830
}
831
GodotAudio.buses = buses.slice(0, val);
832
return;
833
}
834
835
for (let i = GodotAudio.buses.length; i < val; i++) {
836
GodotAudio.Bus.create();
837
}
838
}
839
840
/**
841
* Returns a `Bus` based on it's index number.
842
* @param {number} index
843
* @returns {Bus}
844
* @throws {ReferenceError} If the index value is outside the registry.
845
*/
846
static getBus(index) {
847
if (index < 0 || index >= GodotAudio.buses.length) {
848
throw new ReferenceError(`invalid bus index "${index}"`);
849
}
850
return GodotAudio.buses[index];
851
}
852
853
/**
854
* Returns a `Bus` based on it's index number. Returns null if it doesn't exist.
855
* @param {number} index
856
* @returns {Bus?}
857
*/
858
static getBusOrNull(index) {
859
if (index < 0 || index >= GodotAudio.buses.length) {
860
return null;
861
}
862
return GodotAudio.buses[index];
863
}
864
865
/**
866
* Move a bus from an index to another.
867
* @param {number} fromIndex From index
868
* @param {number} toIndex To index
869
* @returns {void}
870
*/
871
static move(fromIndex, toIndex) {
872
const movedBus = GodotAudio.Bus.getBusOrNull(fromIndex);
873
if (movedBus == null) {
874
return;
875
}
876
const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex);
877
// Inserts at index.
878
buses.splice(toIndex - 1, 0, movedBus);
879
GodotAudio.buses = buses;
880
}
881
882
/**
883
* Adds a new bus at the specified index.
884
* @param {number} index Index to add a new bus.
885
* @returns {void}
886
*/
887
static addAt(index) {
888
const newBus = GodotAudio.Bus.create();
889
if (index !== newBus.getId()) {
890
GodotAudio.Bus.move(newBus.getId(), index);
891
}
892
}
893
894
/**
895
* Creates a `Bus` and registers it.
896
* @returns {Bus}
897
*/
898
static create() {
899
const newBus = new GodotAudio.Bus();
900
const isFirstBus = GodotAudio.buses.length === 0;
901
GodotAudio.buses.push(newBus);
902
if (isFirstBus) {
903
newBus.setSend(null);
904
} else {
905
newBus.setSend(GodotAudio.Bus.getBus(0));
906
}
907
return newBus;
908
}
909
910
/**
911
* `Bus` constructor.
912
*/
913
constructor() {
914
/** @type {Set<SampleNode>} */
915
this._sampleNodes = new Set();
916
/** @type {boolean} */
917
this.isSolo = false;
918
/** @type {Bus?} */
919
this._send = null;
920
921
/** @type {GainNode} */
922
this._gainNode = GodotAudio.ctx.createGain();
923
/** @type {GainNode} */
924
this._soloNode = GodotAudio.ctx.createGain();
925
/** @type {GainNode} */
926
this._muteNode = GodotAudio.ctx.createGain();
927
928
this._gainNode
929
.connect(this._soloNode)
930
.connect(this._muteNode);
931
}
932
933
/**
934
* Returns the current id of the bus (its index).
935
* @returns {number}
936
*/
937
getId() {
938
return GodotAudio.buses.indexOf(this);
939
}
940
941
/**
942
* Returns the bus volume db value.
943
* @returns {number}
944
*/
945
getVolumeDb() {
946
return GodotAudio.linear_to_db(this._gainNode.gain.value);
947
}
948
949
/**
950
* Sets the bus volume db value.
951
* @param {number} val Value to set
952
* @returns {void}
953
*/
954
setVolumeDb(val) {
955
const linear = GodotAudio.db_to_linear(val);
956
if (isFinite(linear)) {
957
this._gainNode.gain.value = linear;
958
}
959
}
960
961
/**
962
* Returns the "send" bus.
963
* If null, this bus sends its contents directly to the output.
964
* If not null, this bus sends its contents to another bus.
965
* @returns {Bus?}
966
*/
967
getSend() {
968
return this._send;
969
}
970
971
/**
972
* Sets the "send" bus.
973
* If null, this bus sends its contents directly to the output.
974
* If not null, this bus sends its contents to another bus.
975
*
976
* **Note:** if null, `getId()` must be equal to 0. Otherwise, it will throw.
977
* @param {Bus?} val
978
* @returns {void}
979
* @throws {Error} When val is `null` and `getId()` isn't equal to 0
980
*/
981
setSend(val) {
982
this._send = val;
983
if (val == null) {
984
if (this.getId() == 0) {
985
this.getOutputNode().connect(GodotAudio.ctx.destination);
986
return;
987
}
988
throw new Error(
989
`Cannot send to "${val}" without the bus being at index 0 (current index: ${this.getId()})`
990
);
991
}
992
this.connect(val);
993
}
994
995
/**
996
* Returns the input node of the bus.
997
* @returns {AudioNode}
998
*/
999
getInputNode() {
1000
return this._gainNode;
1001
}
1002
1003
/**
1004
* Returns the output node of the bus.
1005
* @returns {AudioNode}
1006
*/
1007
getOutputNode() {
1008
return this._muteNode;
1009
}
1010
1011
/**
1012
* Sets the mute status of the bus.
1013
* @param {boolean} enable
1014
*/
1015
mute(enable) {
1016
this._muteNode.gain.value = enable ? 0 : 1;
1017
}
1018
1019
/**
1020
* Sets the solo status of the bus.
1021
* @param {boolean} enable
1022
*/
1023
solo(enable) {
1024
if (this.isSolo === enable) {
1025
return;
1026
}
1027
1028
if (enable) {
1029
if (GodotAudio.busSolo != null && GodotAudio.busSolo !== this) {
1030
GodotAudio.busSolo._disableSolo();
1031
}
1032
this._enableSolo();
1033
return;
1034
}
1035
1036
this._disableSolo();
1037
}
1038
1039
/**
1040
* Wrapper to simply add a sample node to the bus.
1041
* @param {SampleNode} sampleNode `SampleNode` to remove
1042
* @returns {void}
1043
*/
1044
addSampleNode(sampleNode) {
1045
this._sampleNodes.add(sampleNode);
1046
sampleNode.getOutputNode().connect(this.getInputNode());
1047
}
1048
1049
/**
1050
* Wrapper to simply remove a sample node from the bus.
1051
* @param {SampleNode} sampleNode `SampleNode` to remove
1052
* @returns {void}
1053
*/
1054
removeSampleNode(sampleNode) {
1055
this._sampleNodes.delete(sampleNode);
1056
sampleNode.getOutputNode().disconnect();
1057
}
1058
1059
/**
1060
* Wrapper to simply connect to another bus.
1061
* @param {Bus} bus
1062
* @returns {void}
1063
*/
1064
connect(bus) {
1065
if (bus == null) {
1066
throw new Error('cannot connect to null bus');
1067
}
1068
this.getOutputNode().disconnect();
1069
this.getOutputNode().connect(bus.getInputNode());
1070
return bus;
1071
}
1072
1073
/**
1074
* Clears the current bus.
1075
* @returns {void}
1076
*/
1077
clear() {
1078
GodotAudio.buses = GodotAudio.buses.filter((v) => v !== this);
1079
}
1080
1081
_syncSampleNodes() {
1082
const sampleNodes = Array.from(this._sampleNodes);
1083
for (let i = 0; i < sampleNodes.length; i++) {
1084
const sampleNode = sampleNodes[i];
1085
sampleNode.getOutputNode().disconnect();
1086
sampleNode.getOutputNode().connect(this.getInputNode());
1087
}
1088
}
1089
1090
/**
1091
* Process to enable solo.
1092
* @returns {void}
1093
*/
1094
_enableSolo() {
1095
this.isSolo = true;
1096
GodotAudio.busSolo = this;
1097
this._soloNode.gain.value = 1;
1098
const otherBuses = GodotAudio.buses.filter(
1099
(otherBus) => otherBus !== this
1100
);
1101
for (let i = 0; i < otherBuses.length; i++) {
1102
const otherBus = otherBuses[i];
1103
otherBus._soloNode.gain.value = 0;
1104
}
1105
}
1106
1107
/**
1108
* Process to disable solo.
1109
* @returns {void}
1110
*/
1111
_disableSolo() {
1112
this.isSolo = false;
1113
GodotAudio.busSolo = null;
1114
this._soloNode.gain.value = 1;
1115
const otherBuses = GodotAudio.buses.filter(
1116
(otherBus) => otherBus !== this
1117
);
1118
for (let i = 0; i < otherBuses.length; i++) {
1119
const otherBus = otherBuses[i];
1120
otherBus._soloNode.gain.value = 1;
1121
}
1122
}
1123
}
1124
1125
const _GodotAudio = {
1126
$GodotAudio__deps: ['$GodotRuntime', '$GodotOS'],
1127
$GodotAudio: {
1128
/**
1129
* Max number of volume channels.
1130
*/
1131
MAX_VOLUME_CHANNELS: 8,
1132
1133
/**
1134
* Represents the index of each sound channel relative to the engine.
1135
*/
1136
GodotChannel: Object.freeze({
1137
CHANNEL_L: 0,
1138
CHANNEL_R: 1,
1139
CHANNEL_C: 3,
1140
CHANNEL_LFE: 4,
1141
CHANNEL_RL: 5,
1142
CHANNEL_RR: 6,
1143
CHANNEL_SL: 7,
1144
CHANNEL_SR: 8,
1145
}),
1146
1147
/**
1148
* Represents the index of each sound channel relative to the Web Audio API.
1149
*/
1150
WebChannel: Object.freeze({
1151
CHANNEL_L: 0,
1152
CHANNEL_R: 1,
1153
CHANNEL_SL: 2,
1154
CHANNEL_SR: 3,
1155
CHANNEL_C: 4,
1156
CHANNEL_LFE: 5,
1157
}),
1158
1159
// `Sample` class
1160
/**
1161
* Registry of `Sample`s.
1162
* @type {Map<string, Sample>}
1163
*/
1164
samples: null,
1165
Sample,
1166
1167
// `SampleNodeBus` class
1168
SampleNodeBus,
1169
1170
// `SampleNode` class
1171
/**
1172
* Registry of `SampleNode`s.
1173
* @type {Map<string, SampleNode>}
1174
*/
1175
sampleNodes: null,
1176
SampleNode,
1177
deleteSampleNode: (pSampleNodeId) => {
1178
GodotAudio.sampleNodes.delete(pSampleNodeId);
1179
if (GodotAudio.sampleFinishedCallback == null) {
1180
return;
1181
}
1182
const sampleNodeIdPtr = GodotRuntime.allocString(pSampleNodeId);
1183
GodotAudio.sampleFinishedCallback(sampleNodeIdPtr);
1184
GodotRuntime.free(sampleNodeIdPtr);
1185
},
1186
1187
// `Bus` class
1188
/**
1189
* Registry of `Bus`es.
1190
* @type {Array<Bus>}
1191
*/
1192
buses: null,
1193
/**
1194
* Reference to the current bus in solo mode.
1195
* @type {Bus | null}
1196
*/
1197
busSolo: null,
1198
Bus,
1199
1200
/**
1201
* Callback to signal that a sample has finished.
1202
* @type {(playbackObjectIdPtr: number) => void | null}
1203
*/
1204
sampleFinishedCallback: null,
1205
1206
/** @type {AudioContext} */
1207
ctx: null,
1208
input: null,
1209
driver: null,
1210
interval: 0,
1211
1212
/** @type {Promise} */
1213
audioPositionWorkletPromise: null,
1214
/** @type {Array<AudioWorkletNode>} */
1215
audioPositionWorkletNodes: null,
1216
1217
/**
1218
* Converts linear volume to Db.
1219
* @param {number} linear Linear value to convert.
1220
* @returns {number}
1221
*/
1222
linear_to_db: function (linear) {
1223
// eslint-disable-next-line no-loss-of-precision
1224
return Math.log(linear) * 8.6858896380650365530225783783321;
1225
},
1226
/**
1227
* Converts Db volume to linear.
1228
* @param {number} db Db value to convert.
1229
* @returns {number}
1230
*/
1231
db_to_linear: function (db) {
1232
// eslint-disable-next-line no-loss-of-precision
1233
return Math.exp(db * 0.11512925464970228420089957273422);
1234
},
1235
1236
init: function (mix_rate, latency, onstatechange, onlatencyupdate) {
1237
// Initialize classes static values.
1238
GodotAudio.samples = new Map();
1239
GodotAudio.sampleNodes = new Map();
1240
GodotAudio.buses = [];
1241
GodotAudio.busSolo = null;
1242
GodotAudio.audioPositionWorkletNodes = [];
1243
1244
const opts = {};
1245
// If mix_rate is 0, let the browser choose.
1246
if (mix_rate) {
1247
GodotAudio.sampleRate = mix_rate;
1248
opts['sampleRate'] = mix_rate;
1249
}
1250
// Do not specify, leave 'interactive' for good performance.
1251
// opts['latencyHint'] = latency / 1000;
1252
const ctx = new (window.AudioContext || window.webkitAudioContext)(opts);
1253
GodotAudio.ctx = ctx;
1254
ctx.onstatechange = function () {
1255
let state = 0;
1256
switch (ctx.state) {
1257
case 'suspended':
1258
state = 0;
1259
break;
1260
case 'running':
1261
state = 1;
1262
break;
1263
case 'closed':
1264
state = 2;
1265
break;
1266
default:
1267
// Do nothing.
1268
}
1269
onstatechange(state);
1270
};
1271
ctx.onstatechange(); // Immediately notify state.
1272
// Update computed latency
1273
GodotAudio.interval = setInterval(function () {
1274
let computed_latency = 0;
1275
if (ctx.baseLatency) {
1276
computed_latency += GodotAudio.ctx.baseLatency;
1277
}
1278
if (ctx.outputLatency) {
1279
computed_latency += GodotAudio.ctx.outputLatency;
1280
}
1281
onlatencyupdate(computed_latency);
1282
}, 1000);
1283
GodotOS.atexit(GodotAudio.close_async);
1284
1285
const path = GodotConfig.locate_file('godot.audio.position.worklet.js');
1286
GodotAudio.audioPositionWorkletPromise = ctx.audioWorklet.addModule(path);
1287
1288
return ctx.destination.channelCount;
1289
},
1290
1291
create_input: function (callback) {
1292
if (GodotAudio.input) {
1293
return 0; // Already started.
1294
}
1295
function gotMediaInput(stream) {
1296
try {
1297
GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream);
1298
callback(GodotAudio.input);
1299
} catch (e) {
1300
GodotRuntime.error('Failed creating input.', e);
1301
}
1302
}
1303
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
1304
navigator.mediaDevices.getUserMedia({
1305
'audio': true,
1306
}).then(gotMediaInput, function (e) {
1307
GodotRuntime.error('Error getting user media.', e);
1308
});
1309
} else {
1310
if (!navigator.getUserMedia) {
1311
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
1312
}
1313
if (!navigator.getUserMedia) {
1314
GodotRuntime.error('getUserMedia not available.');
1315
return 1;
1316
}
1317
navigator.getUserMedia({
1318
'audio': true,
1319
}, gotMediaInput, function (e) {
1320
GodotRuntime.print(e);
1321
});
1322
}
1323
return 0;
1324
},
1325
1326
close_async: function (resolve, reject) {
1327
const ctx = GodotAudio.ctx;
1328
GodotAudio.ctx = null;
1329
// Audio was not initialized.
1330
if (!ctx) {
1331
resolve();
1332
return;
1333
}
1334
// Remove latency callback
1335
if (GodotAudio.interval) {
1336
clearInterval(GodotAudio.interval);
1337
GodotAudio.interval = 0;
1338
}
1339
// Disconnect input, if it was started.
1340
if (GodotAudio.input) {
1341
GodotAudio.input.disconnect();
1342
GodotAudio.input = null;
1343
}
1344
// Disconnect output
1345
let closed = Promise.resolve();
1346
if (GodotAudio.driver) {
1347
closed = GodotAudio.driver.close();
1348
}
1349
closed.then(function () {
1350
return ctx.close();
1351
}).then(function () {
1352
ctx.onstatechange = null;
1353
resolve();
1354
}).catch(function (e) {
1355
ctx.onstatechange = null;
1356
GodotRuntime.error('Error closing AudioContext', e);
1357
resolve();
1358
});
1359
},
1360
1361
/**
1362
* Triggered when a sample node needs to start.
1363
* @param {string} playbackObjectId The unique id of the sample playback
1364
* @param {string} streamObjectId The unique id of the stream
1365
* @param {number} busIndex Index of the bus currently binded to the sample playback
1366
* @param {SampleNodeOptions | undefined} startOptions Optional params.
1367
* @returns {void}
1368
*/
1369
start_sample: function (
1370
playbackObjectId,
1371
streamObjectId,
1372
busIndex,
1373
startOptions
1374
) {
1375
GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
1376
GodotAudio.SampleNode.create(
1377
{
1378
busIndex,
1379
id: playbackObjectId,
1380
streamObjectId,
1381
},
1382
startOptions
1383
);
1384
},
1385
1386
/**
1387
* Triggered when a sample node needs to be stopped.
1388
* @param {string} playbackObjectId Id of the sample playback
1389
* @returns {void}
1390
*/
1391
stop_sample: function (playbackObjectId) {
1392
GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
1393
},
1394
1395
/**
1396
* Triggered when a sample node needs to be paused or unpaused.
1397
* @param {string} playbackObjectId Id of the sample playback
1398
* @param {boolean} pause State of the pause
1399
* @returns {void}
1400
*/
1401
sample_set_pause: function (playbackObjectId, pause) {
1402
GodotAudio.SampleNode.pauseSampleNode(playbackObjectId, pause);
1403
},
1404
1405
/**
1406
* Triggered when a sample node needs its pitch scale to be updated.
1407
* @param {string} playbackObjectId Id of the sample playback
1408
* @param {number} pitchScale Pitch scale of the sample playback
1409
* @returns {void}
1410
*/
1411
update_sample_pitch_scale: function (playbackObjectId, pitchScale) {
1412
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
1413
if (sampleNode == null) {
1414
return;
1415
}
1416
sampleNode.setPitchScale(pitchScale);
1417
},
1418
1419
/**
1420
* Triggered when a sample node volumes need to be updated.
1421
* @param {string} playbackObjectId Id of the sample playback
1422
* @param {Array<number>} busIndexes Indexes of the buses that need to be updated
1423
* @param {Float32Array} volumes Array of the volumes
1424
* @returns {void}
1425
*/
1426
sample_set_volumes_linear: function (playbackObjectId, busIndexes, volumes) {
1427
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
1428
if (sampleNode == null) {
1429
return;
1430
}
1431
const buses = busIndexes.map((busIndex) => GodotAudio.Bus.getBus(busIndex));
1432
sampleNode.setVolumes(buses, volumes);
1433
},
1434
1435
/**
1436
* Triggered when the bus count changes.
1437
* @param {number} count Number of buses
1438
* @returns {void}
1439
*/
1440
set_sample_bus_count: function (count) {
1441
GodotAudio.Bus.setCount(count);
1442
},
1443
1444
/**
1445
* Triggered when a bus needs to be removed.
1446
* @param {number} index Bus index
1447
* @returns {void}
1448
*/
1449
remove_sample_bus: function (index) {
1450
const bus = GodotAudio.Bus.getBusOrNull(index);
1451
if (bus == null) {
1452
return;
1453
}
1454
bus.clear();
1455
},
1456
1457
/**
1458
* Triggered when a bus needs to be at the desired position.
1459
* @param {number} atPos Position to add the bus
1460
* @returns {void}
1461
*/
1462
add_sample_bus: function (atPos) {
1463
GodotAudio.Bus.addAt(atPos);
1464
},
1465
1466
/**
1467
* Triggered when a bus needs to be moved.
1468
* @param {number} busIndex Index of the bus to move
1469
* @param {number} toPos Index of the new position of the bus
1470
* @returns {void}
1471
*/
1472
move_sample_bus: function (busIndex, toPos) {
1473
GodotAudio.Bus.move(busIndex, toPos);
1474
},
1475
1476
/**
1477
* Triggered when the "send" value of a bus changes.
1478
* @param {number} busIndex Index of the bus to update the "send" value
1479
* @param {number} sendIndex Index of the bus that is the new "send"
1480
* @returns {void}
1481
*/
1482
set_sample_bus_send: function (busIndex, sendIndex) {
1483
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
1484
if (bus == null) {
1485
// Cannot send from an invalid bus.
1486
return;
1487
}
1488
let targetBus = GodotAudio.Bus.getBusOrNull(sendIndex);
1489
if (targetBus == null) {
1490
// Send to master.
1491
targetBus = GodotAudio.Bus.getBus(0);
1492
}
1493
bus.setSend(targetBus);
1494
},
1495
1496
/**
1497
* Triggered when a bus needs its volume db to be updated.
1498
* @param {number} busIndex Index of the bus to update its volume db
1499
* @param {number} volumeDb Volume of the bus
1500
* @returns {void}
1501
*/
1502
set_sample_bus_volume_db: function (busIndex, volumeDb) {
1503
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
1504
if (bus == null) {
1505
return;
1506
}
1507
bus.setVolumeDb(volumeDb);
1508
},
1509
1510
/**
1511
* Triggered when a bus needs to update its solo status
1512
* @param {number} busIndex Index of the bus to update its solo status
1513
* @param {boolean} enable Status of the solo
1514
* @returns {void}
1515
*/
1516
set_sample_bus_solo: function (busIndex, enable) {
1517
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
1518
if (bus == null) {
1519
return;
1520
}
1521
bus.solo(enable);
1522
},
1523
1524
/**
1525
* Triggered when a bus needs to update its mute status
1526
* @param {number} busIndex Index of the bus to update its mute status
1527
* @param {boolean} enable Status of the mute
1528
* @returns {void}
1529
*/
1530
set_sample_bus_mute: function (busIndex, enable) {
1531
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
1532
if (bus == null) {
1533
return;
1534
}
1535
bus.mute(enable);
1536
},
1537
},
1538
1539
godot_audio_is_available__sig: 'i',
1540
godot_audio_is_available__proxy: 'sync',
1541
godot_audio_is_available: function () {
1542
if (!(window.AudioContext || window.webkitAudioContext)) {
1543
return 0;
1544
}
1545
return 1;
1546
},
1547
1548
godot_audio_has_worklet__proxy: 'sync',
1549
godot_audio_has_worklet__sig: 'i',
1550
godot_audio_has_worklet: function () {
1551
return GodotAudio.ctx && GodotAudio.ctx.audioWorklet ? 1 : 0;
1552
},
1553
1554
godot_audio_has_script_processor__proxy: 'sync',
1555
godot_audio_has_script_processor__sig: 'i',
1556
godot_audio_has_script_processor: function () {
1557
return GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor ? 1 : 0;
1558
},
1559
1560
godot_audio_init__proxy: 'sync',
1561
godot_audio_init__sig: 'iiiii',
1562
godot_audio_init: function (
1563
p_mix_rate,
1564
p_latency,
1565
p_state_change,
1566
p_latency_update
1567
) {
1568
const statechange = GodotRuntime.get_func(p_state_change);
1569
const latencyupdate = GodotRuntime.get_func(p_latency_update);
1570
const mix_rate = GodotRuntime.getHeapValue(p_mix_rate, 'i32');
1571
const channels = GodotAudio.init(
1572
mix_rate,
1573
p_latency,
1574
statechange,
1575
latencyupdate
1576
);
1577
GodotRuntime.setHeapValue(p_mix_rate, GodotAudio.ctx.sampleRate, 'i32');
1578
return channels;
1579
},
1580
1581
godot_audio_resume__proxy: 'sync',
1582
godot_audio_resume__sig: 'v',
1583
godot_audio_resume: function () {
1584
if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
1585
GodotAudio.ctx.resume();
1586
}
1587
},
1588
1589
godot_audio_input_start__proxy: 'sync',
1590
godot_audio_input_start__sig: 'i',
1591
godot_audio_input_start: function () {
1592
return GodotAudio.create_input(function (input) {
1593
input.connect(GodotAudio.driver.get_node());
1594
});
1595
},
1596
1597
godot_audio_input_stop__proxy: 'sync',
1598
godot_audio_input_stop__sig: 'v',
1599
godot_audio_input_stop: function () {
1600
if (GodotAudio.input) {
1601
const tracks = GodotAudio.input['mediaStream']['getTracks']();
1602
for (let i = 0; i < tracks.length; i++) {
1603
tracks[i]['stop']();
1604
}
1605
GodotAudio.input.disconnect();
1606
GodotAudio.input = null;
1607
}
1608
},
1609
1610
godot_audio_sample_stream_is_registered__proxy: 'sync',
1611
godot_audio_sample_stream_is_registered__sig: 'ii',
1612
/**
1613
* Returns if the sample stream is registered
1614
* @param {number} streamObjectIdStrPtr Pointer of the streamObjectId
1615
* @returns {number}
1616
*/
1617
godot_audio_sample_stream_is_registered: function (streamObjectIdStrPtr) {
1618
const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
1619
return Number(GodotAudio.Sample.getSampleOrNull(streamObjectId) != null);
1620
},
1621
1622
godot_audio_sample_register_stream__proxy: 'sync',
1623
godot_audio_sample_register_stream__sig: 'viiiiiii',
1624
/**
1625
* Registers a stream.
1626
* @param {number} streamObjectIdStrPtr StreamObjectId pointer
1627
* @param {number} framesPtr Frames pointer
1628
* @param {number} framesTotal Frames total value
1629
* @param {number} loopModeStrPtr Loop mode pointer
1630
* @param {number} loopBegin Loop begin value
1631
* @param {number} loopEnd Loop end value
1632
* @returns {void}
1633
*/
1634
godot_audio_sample_register_stream: function (
1635
streamObjectIdStrPtr,
1636
framesPtr,
1637
framesTotal,
1638
loopModeStrPtr,
1639
loopBegin,
1640
loopEnd
1641
) {
1642
const BYTES_PER_FLOAT32 = 4;
1643
const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
1644
const loopMode = GodotRuntime.parseString(loopModeStrPtr);
1645
const numberOfChannels = 2;
1646
const sampleRate = GodotAudio.ctx.sampleRate;
1647
1648
/** @type {Float32Array} */
1649
const subLeft = GodotRuntime.heapSub(HEAPF32, framesPtr, framesTotal);
1650
/** @type {Float32Array} */
1651
const subRight = GodotRuntime.heapSub(
1652
HEAPF32,
1653
framesPtr + framesTotal * BYTES_PER_FLOAT32,
1654
framesTotal
1655
);
1656
1657
const audioBuffer = GodotAudio.ctx.createBuffer(
1658
numberOfChannels,
1659
framesTotal,
1660
sampleRate
1661
);
1662
audioBuffer.copyToChannel(new Float32Array(subLeft), 0, 0);
1663
audioBuffer.copyToChannel(new Float32Array(subRight), 1, 0);
1664
1665
GodotAudio.Sample.create(
1666
{
1667
id: streamObjectId,
1668
audioBuffer,
1669
},
1670
{
1671
loopBegin,
1672
loopEnd,
1673
loopMode,
1674
numberOfChannels,
1675
sampleRate,
1676
}
1677
);
1678
},
1679
1680
godot_audio_sample_unregister_stream__proxy: 'sync',
1681
godot_audio_sample_unregister_stream__sig: 'vi',
1682
/**
1683
* Unregisters a stream.
1684
* @param {number} streamObjectIdStrPtr StreamObjectId pointer
1685
* @returns {void}
1686
*/
1687
godot_audio_sample_unregister_stream: function (streamObjectIdStrPtr) {
1688
const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
1689
const sample = GodotAudio.Sample.getSampleOrNull(streamObjectId);
1690
if (sample != null) {
1691
sample.clear();
1692
}
1693
},
1694
1695
godot_audio_sample_start__proxy: 'sync',
1696
godot_audio_sample_start__sig: 'viiiifi',
1697
/**
1698
* Starts a sample.
1699
* @param {number} playbackObjectIdStrPtr Playback object id pointer
1700
* @param {number} streamObjectIdStrPtr Stream object id pointer
1701
* @param {number} busIndex Bus index
1702
* @param {number} offset Sample offset
1703
* @param {number} pitchScale Pitch scale
1704
* @param {number} volumePtr Volume pointer
1705
* @returns {void}
1706
*/
1707
godot_audio_sample_start: function (
1708
playbackObjectIdStrPtr,
1709
streamObjectIdStrPtr,
1710
busIndex,
1711
offset,
1712
pitchScale,
1713
volumePtr
1714
) {
1715
/** @type {string} */
1716
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
1717
/** @type {string} */
1718
const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
1719
/** @type {Float32Array} */
1720
const volume = GodotRuntime.heapSub(HEAPF32, volumePtr, 8);
1721
/** @type {SampleNodeOptions} */
1722
const startOptions = {
1723
offset,
1724
volume,
1725
playbackRate: 1,
1726
pitchScale,
1727
start: true,
1728
};
1729
GodotAudio.start_sample(
1730
playbackObjectId,
1731
streamObjectId,
1732
busIndex,
1733
startOptions
1734
);
1735
},
1736
1737
godot_audio_sample_stop__proxy: 'sync',
1738
godot_audio_sample_stop__sig: 'vi',
1739
/**
1740
* Stops a sample from playing.
1741
* @param {number} playbackObjectIdStrPtr Playback object id pointer
1742
* @returns {void}
1743
*/
1744
godot_audio_sample_stop: function (playbackObjectIdStrPtr) {
1745
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
1746
GodotAudio.stop_sample(playbackObjectId);
1747
},
1748
1749
godot_audio_sample_set_pause__proxy: 'sync',
1750
godot_audio_sample_set_pause__sig: 'vii',
1751
/**
1752
* Sets the pause state of a sample.
1753
* @param {number} playbackObjectIdStrPtr Playback object id pointer
1754
* @param {number} pause Pause state
1755
*/
1756
godot_audio_sample_set_pause: function (playbackObjectIdStrPtr, pause) {
1757
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
1758
GodotAudio.sample_set_pause(playbackObjectId, Boolean(pause));
1759
},
1760
1761
godot_audio_sample_is_active__proxy: 'sync',
1762
godot_audio_sample_is_active__sig: 'ii',
1763
/**
1764
* Returns if the sample is active.
1765
* @param {number} playbackObjectIdStrPtr Playback object id pointer
1766
* @returns {number}
1767
*/
1768
godot_audio_sample_is_active: function (playbackObjectIdStrPtr) {
1769
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
1770
return Number(GodotAudio.sampleNodes.has(playbackObjectId));
1771
},
1772
1773
godot_audio_get_sample_playback_position__proxy: 'sync',
1774
godot_audio_get_sample_playback_position__sig: 'di',
1775
/**
1776
* Returns the position of the playback position.
1777
* @param {number} playbackObjectIdStrPtr Playback object id pointer
1778
* @returns {number}
1779
*/
1780
godot_audio_get_sample_playback_position: function (playbackObjectIdStrPtr) {
1781
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
1782
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
1783
if (sampleNode == null) {
1784
return 0;
1785
}
1786
return sampleNode.getPlaybackPosition();
1787
},
1788
1789
godot_audio_sample_update_pitch_scale__proxy: 'sync',
1790
godot_audio_sample_update_pitch_scale__sig: 'vii',
1791
/**
1792
* Updates the pitch scale of a sample.
1793
* @param {number} playbackObjectIdStrPtr Playback object id pointer
1794
* @param {number} pitchScale Pitch scale value
1795
* @returns {void}
1796
*/
1797
godot_audio_sample_update_pitch_scale: function (
1798
playbackObjectIdStrPtr,
1799
pitchScale
1800
) {
1801
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
1802
GodotAudio.update_sample_pitch_scale(playbackObjectId, pitchScale);
1803
},
1804
1805
godot_audio_sample_set_volumes_linear__proxy: 'sync',
1806
godot_audio_sample_set_volumes_linear__sig: 'vii',
1807
/**
1808
* Sets the volumes linear of each mentioned bus for the sample.
1809
* @param {number} playbackObjectIdStrPtr Playback object id pointer
1810
* @param {number} busesPtr Buses array pointer
1811
* @param {number} busesSize Buses array size
1812
* @param {number} volumesPtr Volumes array pointer
1813
* @param {number} volumesSize Volumes array size
1814
* @returns {void}
1815
*/
1816
godot_audio_sample_set_volumes_linear: function (
1817
playbackObjectIdStrPtr,
1818
busesPtr,
1819
busesSize,
1820
volumesPtr,
1821
volumesSize
1822
) {
1823
/** @type {string} */
1824
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
1825
1826
/** @type {Uint32Array} */
1827
const buses = GodotRuntime.heapSub(HEAP32, busesPtr, busesSize);
1828
/** @type {Float32Array} */
1829
const volumes = GodotRuntime.heapSub(HEAPF32, volumesPtr, volumesSize);
1830
1831
GodotAudio.sample_set_volumes_linear(
1832
playbackObjectId,
1833
Array.from(buses),
1834
volumes
1835
);
1836
},
1837
1838
godot_audio_sample_bus_set_count__proxy: 'sync',
1839
godot_audio_sample_bus_set_count__sig: 'vi',
1840
/**
1841
* Sets the bus count.
1842
* @param {number} count Bus count
1843
* @returns {void}
1844
*/
1845
godot_audio_sample_bus_set_count: function (count) {
1846
GodotAudio.set_sample_bus_count(count);
1847
},
1848
1849
godot_audio_sample_bus_remove__proxy: 'sync',
1850
godot_audio_sample_bus_remove__sig: 'vi',
1851
/**
1852
* Removes a bus.
1853
* @param {number} index Index of the bus to remove
1854
* @returns {void}
1855
*/
1856
godot_audio_sample_bus_remove: function (index) {
1857
GodotAudio.remove_sample_bus(index);
1858
},
1859
1860
godot_audio_sample_bus_add__proxy: 'sync',
1861
godot_audio_sample_bus_add__sig: 'vi',
1862
/**
1863
* Adds a bus at the defined position.
1864
* @param {number} atPos Position to add the bus
1865
* @returns {void}
1866
*/
1867
godot_audio_sample_bus_add: function (atPos) {
1868
GodotAudio.add_sample_bus(atPos);
1869
},
1870
1871
godot_audio_sample_bus_move__proxy: 'sync',
1872
godot_audio_sample_bus_move__sig: 'vii',
1873
/**
1874
* Moves the bus from a position to another.
1875
* @param {number} fromPos Position of the bus to move
1876
* @param {number} toPos Final position of the bus
1877
* @returns {void}
1878
*/
1879
godot_audio_sample_bus_move: function (fromPos, toPos) {
1880
GodotAudio.move_sample_bus(fromPos, toPos);
1881
},
1882
1883
godot_audio_sample_bus_set_send__proxy: 'sync',
1884
godot_audio_sample_bus_set_send__sig: 'vii',
1885
/**
1886
* Sets the "send" of a bus.
1887
* @param {number} bus Position of the bus to set the send
1888
* @param {number} sendIndex Position of the "send" bus
1889
* @returns {void}
1890
*/
1891
godot_audio_sample_bus_set_send: function (bus, sendIndex) {
1892
GodotAudio.set_sample_bus_send(bus, sendIndex);
1893
},
1894
1895
godot_audio_sample_bus_set_volume_db__proxy: 'sync',
1896
godot_audio_sample_bus_set_volume_db__sig: 'vii',
1897
/**
1898
* Sets the volume db of a bus.
1899
* @param {number} bus Position of the bus to set the volume db
1900
* @param {number} volumeDb Volume db to set
1901
* @returns {void}
1902
*/
1903
godot_audio_sample_bus_set_volume_db: function (bus, volumeDb) {
1904
GodotAudio.set_sample_bus_volume_db(bus, volumeDb);
1905
},
1906
1907
godot_audio_sample_bus_set_solo__proxy: 'sync',
1908
godot_audio_sample_bus_set_solo__sig: 'vii',
1909
/**
1910
* Sets the state of solo for a bus
1911
* @param {number} bus Position of the bus to set the solo state
1912
* @param {number} enable State of the solo
1913
* @returns {void}
1914
*/
1915
godot_audio_sample_bus_set_solo: function (bus, enable) {
1916
GodotAudio.set_sample_bus_solo(bus, Boolean(enable));
1917
},
1918
1919
godot_audio_sample_bus_set_mute__proxy: 'sync',
1920
godot_audio_sample_bus_set_mute__sig: 'vii',
1921
/**
1922
* Sets the state of mute for a bus
1923
* @param {number} bus Position of the bus to set the mute state
1924
* @param {number} enable State of the mute
1925
* @returns {void}
1926
*/
1927
godot_audio_sample_bus_set_mute: function (bus, enable) {
1928
GodotAudio.set_sample_bus_mute(bus, Boolean(enable));
1929
},
1930
1931
godot_audio_sample_set_finished_callback__proxy: 'sync',
1932
godot_audio_sample_set_finished_callback__sig: 'vi',
1933
/**
1934
* Sets the finished callback
1935
* @param {Number} callbackPtr Finished callback pointer
1936
* @returns {void}
1937
*/
1938
godot_audio_sample_set_finished_callback: function (callbackPtr) {
1939
GodotAudio.sampleFinishedCallback = GodotRuntime.get_func(callbackPtr);
1940
},
1941
};
1942
1943
autoAddDeps(_GodotAudio, '$GodotAudio');
1944
mergeInto(LibraryManager.library, _GodotAudio);
1945
1946
/**
1947
* The AudioWorklet API driver, used when threads are available.
1948
*/
1949
const GodotAudioWorklet = {
1950
$GodotAudioWorklet__deps: ['$GodotAudio', '$GodotConfig'],
1951
$GodotAudioWorklet: {
1952
promise: null,
1953
worklet: null,
1954
ring_buffer: null,
1955
1956
create: function (channels) {
1957
const path = GodotConfig.locate_file('godot.audio.worklet.js');
1958
GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet
1959
.addModule(path)
1960
.then(function () {
1961
GodotAudioWorklet.worklet = new AudioWorkletNode(
1962
GodotAudio.ctx,
1963
'godot-processor',
1964
{
1965
outputChannelCount: [channels],
1966
}
1967
);
1968
return Promise.resolve();
1969
});
1970
GodotAudio.driver = GodotAudioWorklet;
1971
},
1972
1973
start: function (in_buf, out_buf, state) {
1974
GodotAudioWorklet.promise.then(function () {
1975
const node = GodotAudioWorklet.worklet;
1976
node.connect(GodotAudio.ctx.destination);
1977
node.port.postMessage({
1978
'cmd': 'start',
1979
'data': [state, in_buf, out_buf],
1980
});
1981
node.port.onmessage = function (event) {
1982
GodotRuntime.error(event.data);
1983
};
1984
});
1985
},
1986
1987
start_no_threads: function (
1988
p_out_buf,
1989
p_out_size,
1990
out_callback,
1991
p_in_buf,
1992
p_in_size,
1993
in_callback
1994
) {
1995
function RingBuffer() {
1996
let wpos = 0;
1997
let rpos = 0;
1998
let pending_samples = 0;
1999
const wbuf = new Float32Array(p_out_size);
2000
2001
function send(port) {
2002
if (pending_samples === 0) {
2003
return;
2004
}
2005
const buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
2006
const size = buffer.length;
2007
const tot_sent = pending_samples;
2008
out_callback(wpos, pending_samples);
2009
if (wpos + pending_samples >= size) {
2010
const high = size - wpos;
2011
wbuf.set(buffer.subarray(wpos, size));
2012
pending_samples -= high;
2013
wpos = 0;
2014
}
2015
if (pending_samples > 0) {
2016
wbuf.set(
2017
buffer.subarray(wpos, wpos + pending_samples),
2018
tot_sent - pending_samples
2019
);
2020
}
2021
port.postMessage({ 'cmd': 'chunk', 'data': wbuf.subarray(0, tot_sent) });
2022
wpos += pending_samples;
2023
pending_samples = 0;
2024
}
2025
this.receive = function (recv_buf) {
2026
const buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
2027
const from = rpos;
2028
let to_write = recv_buf.length;
2029
let high = 0;
2030
if (rpos + to_write >= p_in_size) {
2031
high = p_in_size - rpos;
2032
buffer.set(recv_buf.subarray(0, high), rpos);
2033
to_write -= high;
2034
rpos = 0;
2035
}
2036
if (to_write) {
2037
buffer.set(recv_buf.subarray(high, to_write), rpos);
2038
}
2039
in_callback(from, recv_buf.length);
2040
rpos += to_write;
2041
};
2042
this.consumed = function (size, port) {
2043
pending_samples += size;
2044
send(port);
2045
};
2046
}
2047
GodotAudioWorklet.ring_buffer = new RingBuffer();
2048
GodotAudioWorklet.promise.then(function () {
2049
const node = GodotAudioWorklet.worklet;
2050
const buffer = GodotRuntime.heapSlice(HEAPF32, p_out_buf, p_out_size);
2051
node.connect(GodotAudio.ctx.destination);
2052
node.port.postMessage({
2053
'cmd': 'start_nothreads',
2054
'data': [buffer, p_in_size],
2055
});
2056
node.port.onmessage = function (event) {
2057
if (!GodotAudioWorklet.worklet) {
2058
return;
2059
}
2060
if (event.data['cmd'] === 'read') {
2061
const read = event.data['data'];
2062
GodotAudioWorklet.ring_buffer.consumed(
2063
read,
2064
GodotAudioWorklet.worklet.port
2065
);
2066
} else if (event.data['cmd'] === 'input') {
2067
const buf = event.data['data'];
2068
if (buf.length > p_in_size) {
2069
GodotRuntime.error('Input chunk is too big');
2070
return;
2071
}
2072
GodotAudioWorklet.ring_buffer.receive(buf);
2073
} else {
2074
GodotRuntime.error(event.data);
2075
}
2076
};
2077
});
2078
},
2079
2080
get_node: function () {
2081
return GodotAudioWorklet.worklet;
2082
},
2083
2084
close: function () {
2085
return new Promise(function (resolve, reject) {
2086
if (GodotAudioWorklet.promise === null) {
2087
return;
2088
}
2089
const p = GodotAudioWorklet.promise;
2090
p.then(function () {
2091
GodotAudioWorklet.worklet.port.postMessage({
2092
'cmd': 'stop',
2093
'data': null,
2094
});
2095
GodotAudioWorklet.worklet.disconnect();
2096
GodotAudioWorklet.worklet.port.onmessage = null;
2097
GodotAudioWorklet.worklet = null;
2098
GodotAudioWorklet.promise = null;
2099
resolve();
2100
}).catch(function (err) {
2101
// Aborted?
2102
GodotRuntime.error(err);
2103
});
2104
});
2105
},
2106
},
2107
2108
godot_audio_worklet_create__proxy: 'sync',
2109
godot_audio_worklet_create__sig: 'ii',
2110
godot_audio_worklet_create: function (channels) {
2111
try {
2112
GodotAudioWorklet.create(channels);
2113
} catch (e) {
2114
GodotRuntime.error('Error starting AudioDriverWorklet', e);
2115
return 1;
2116
}
2117
return 0;
2118
},
2119
2120
godot_audio_worklet_start__proxy: 'sync',
2121
godot_audio_worklet_start__sig: 'viiiii',
2122
godot_audio_worklet_start: function (
2123
p_in_buf,
2124
p_in_size,
2125
p_out_buf,
2126
p_out_size,
2127
p_state
2128
) {
2129
const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
2130
const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
2131
const state = GodotRuntime.heapSub(HEAP32, p_state, 4);
2132
GodotAudioWorklet.start(in_buffer, out_buffer, state);
2133
},
2134
2135
godot_audio_worklet_start_no_threads__proxy: 'sync',
2136
godot_audio_worklet_start_no_threads__sig: 'viiiiii',
2137
godot_audio_worklet_start_no_threads: function (
2138
p_out_buf,
2139
p_out_size,
2140
p_out_callback,
2141
p_in_buf,
2142
p_in_size,
2143
p_in_callback
2144
) {
2145
const out_callback = GodotRuntime.get_func(p_out_callback);
2146
const in_callback = GodotRuntime.get_func(p_in_callback);
2147
GodotAudioWorklet.start_no_threads(
2148
p_out_buf,
2149
p_out_size,
2150
out_callback,
2151
p_in_buf,
2152
p_in_size,
2153
in_callback
2154
);
2155
},
2156
2157
godot_audio_worklet_state_wait__sig: 'iiii',
2158
godot_audio_worklet_state_wait: function (
2159
p_state,
2160
p_idx,
2161
p_expected,
2162
p_timeout
2163
) {
2164
Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
2165
return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
2166
},
2167
2168
godot_audio_worklet_state_add__sig: 'iiii',
2169
godot_audio_worklet_state_add: function (p_state, p_idx, p_value) {
2170
return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value);
2171
},
2172
2173
godot_audio_worklet_state_get__sig: 'iii',
2174
godot_audio_worklet_state_get: function (p_state, p_idx) {
2175
return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
2176
},
2177
};
2178
2179
autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
2180
mergeInto(LibraryManager.library, GodotAudioWorklet);
2181
2182
/*
2183
* The ScriptProcessorNode API, used as a fallback if AudioWorklet is not available.
2184
*/
2185
const GodotAudioScript = {
2186
$GodotAudioScript__deps: ['$GodotAudio'],
2187
$GodotAudioScript: {
2188
script: null,
2189
2190
create: function (buffer_length, channel_count) {
2191
GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(
2192
buffer_length,
2193
2,
2194
channel_count
2195
);
2196
GodotAudio.driver = GodotAudioScript;
2197
return GodotAudioScript.script.bufferSize;
2198
},
2199
2200
start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) {
2201
GodotAudioScript.script.onaudioprocess = function (event) {
2202
// Read input
2203
const inb = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
2204
const input = event.inputBuffer;
2205
if (GodotAudio.input) {
2206
const inlen = input.getChannelData(0).length;
2207
for (let ch = 0; ch < 2; ch++) {
2208
const data = input.getChannelData(ch);
2209
for (let s = 0; s < inlen; s++) {
2210
inb[s * 2 + ch] = data[s];
2211
}
2212
}
2213
}
2214
2215
// Let Godot process the input/output.
2216
onprocess();
2217
2218
// Write the output.
2219
const outb = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
2220
const output = event.outputBuffer;
2221
const channels = output.numberOfChannels;
2222
for (let ch = 0; ch < channels; ch++) {
2223
const data = output.getChannelData(ch);
2224
// Loop through samples and assign computed values.
2225
for (let sample = 0; sample < data.length; sample++) {
2226
data[sample] = outb[sample * channels + ch];
2227
}
2228
}
2229
};
2230
GodotAudioScript.script.connect(GodotAudio.ctx.destination);
2231
},
2232
2233
get_node: function () {
2234
return GodotAudioScript.script;
2235
},
2236
2237
close: function () {
2238
return new Promise(function (resolve, reject) {
2239
GodotAudioScript.script.disconnect();
2240
GodotAudioScript.script.onaudioprocess = null;
2241
GodotAudioScript.script = null;
2242
resolve();
2243
});
2244
},
2245
},
2246
2247
godot_audio_script_create__proxy: 'sync',
2248
godot_audio_script_create__sig: 'iii',
2249
godot_audio_script_create: function (buffer_length, channel_count) {
2250
const buf_len = GodotRuntime.getHeapValue(buffer_length, 'i32');
2251
try {
2252
const out_len = GodotAudioScript.create(buf_len, channel_count);
2253
GodotRuntime.setHeapValue(buffer_length, out_len, 'i32');
2254
} catch (e) {
2255
GodotRuntime.error('Error starting AudioDriverScriptProcessor', e);
2256
return 1;
2257
}
2258
return 0;
2259
},
2260
2261
godot_audio_script_start__proxy: 'sync',
2262
godot_audio_script_start__sig: 'viiiii',
2263
godot_audio_script_start: function (
2264
p_in_buf,
2265
p_in_size,
2266
p_out_buf,
2267
p_out_size,
2268
p_cb
2269
) {
2270
const onprocess = GodotRuntime.get_func(p_cb);
2271
GodotAudioScript.start(
2272
p_in_buf,
2273
p_in_size,
2274
p_out_buf,
2275
p_out_size,
2276
onprocess
2277
);
2278
},
2279
};
2280
2281
autoAddDeps(GodotAudioScript, '$GodotAudioScript');
2282
mergeInto(LibraryManager.library, GodotAudioScript);
2283
2284