diff options
Diffstat (limited to 'platform/web/js/libs/library_godot_audio.js')
-rw-r--r-- | platform/web/js/libs/library_godot_audio.js | 128 |
1 files changed, 120 insertions, 8 deletions
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js index 66d0d3d79d..40fb0c356c 100644 --- a/platform/web/js/libs/library_godot_audio.js +++ b/platform/web/js/libs/library_godot_audio.js @@ -328,8 +328,10 @@ class SampleNodeBus { * offset?: number * playbackRate?: number * startTime?: number + * pitchScale?: number * loopMode?: LoopMode * volume?: Float32Array + * start?: boolean * }} SampleNodeOptions */ @@ -421,9 +423,15 @@ class SampleNode { /** @type {number} */ this.offset = options.offset ?? 0; /** @type {number} */ + this._playbackPosition = options.offset; + /** @type {number} */ this.startTime = options.startTime ?? 0; /** @type {boolean} */ this.isPaused = false; + /** @type {boolean} */ + this.isStarted = false; + /** @type {boolean} */ + this.isCanceled = false; /** @type {number} */ this.pauseTime = 0; /** @type {number} */ @@ -431,7 +439,7 @@ class SampleNode { /** @type {LoopMode} */ this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled'; /** @type {number} */ - this._pitchScale = 1; + this._pitchScale = options.pitchScale ?? 1; /** @type {number} */ this._sourceStartTime = 0; /** @type {Map<Bus, SampleNodeBus>} */ @@ -440,6 +448,8 @@ class SampleNode { this._source = GodotAudio.ctx.createBufferSource(); this._onended = null; + /** @type {AudioWorkletNode | null} */ + this._positionWorklet = null; this.setPlaybackRate(options.playbackRate ?? 44100); this._source.buffer = this.getSample().getAudioBuffer(); @@ -449,6 +459,8 @@ class SampleNode { const bus = GodotAudio.Bus.getBus(params.busIndex); const sampleNodeBus = this.getSampleNodeBus(bus); sampleNodeBus.setVolume(options.volume); + + this.connectPositionWorklet(options.start); } /** @@ -460,6 +472,14 @@ class SampleNode { } /** + * Gets the playback position. + * @returns {number} + */ + getPlaybackPosition() { + return this._playbackPosition; + } + + /** * Sets the playback rate. * @param {number} val Value to set. * @returns {void} @@ -508,8 +528,12 @@ class SampleNode { * @returns {void} */ start() { + if (this.isStarted) { + return; + } this._resetSourceStartTime(); this._source.start(this.startTime, this.offset); + this.isStarted = true; } /** @@ -585,17 +609,73 @@ class SampleNode { } /** + * Sets up and connects the source to the GodotPositionReportingProcessor + * If the worklet module is not loaded in, it will be added + */ + connectPositionWorklet(start) { + try { + this._positionWorklet = this.createPositionWorklet(); + this._source.connect(this._positionWorklet); + if (start) { + this.start(); + } + } catch (error) { + if (error?.name !== 'InvalidStateError') { + throw error; + } + const path = GodotConfig.locate_file('godot.audio.position.worklet.js'); + GodotAudio.ctx.audioWorklet + .addModule(path) + .then(() => { + if (!this.isCanceled) { + this._positionWorklet = this.createPositionWorklet(); + this._source.connect(this._positionWorklet); + if (start) { + this.start(); + } + } + }).catch((addModuleError) => { + GodotRuntime.error('Failed to create PositionWorklet.', addModuleError); + }); + } + } + + /** + * Creates the AudioWorkletProcessor used to track playback position. + * @returns {AudioWorkletNode} + */ + createPositionWorklet() { + const worklet = new AudioWorkletNode( + GodotAudio.ctx, + 'godot-position-reporting-processor' + ); + worklet.port.onmessage = (event) => { + switch (event.data['type']) { + case 'position': + this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset; + break; + default: + // Do nothing. + } + }; + return worklet; + } + + /** * Clears the `SampleNode`. * @returns {void} */ clear() { + this.isCanceled = true; this.isPaused = false; this.pauseTime = 0; if (this._source != null) { this._source.removeEventListener('ended', this._onended); this._onended = null; - this._source.stop(); + if (this.isStarted) { + this._source.stop(); + } this._source.disconnect(); this._source = null; } @@ -605,6 +685,12 @@ class SampleNode { } this._sampleNodeBuses.clear(); + if (this._positionWorklet) { + this._positionWorklet.disconnect(); + this._positionWorklet.port.onmessage = null; + this._positionWorklet = null; + } + GodotAudio.SampleNode.delete(this.id); } @@ -630,7 +716,9 @@ class SampleNode { * @returns {void} */ _restart() { - this._source.disconnect(); + if (this._source != null) { + this._source.disconnect(); + } this._source = GodotAudio.ctx.createBufferSource(); this._source.buffer = this.getSample().getAudioBuffer(); @@ -643,7 +731,9 @@ class SampleNode { const pauseTime = this.isPaused ? this.pauseTime : 0; + this.connectPositionWorklet(); this._source.start(this.startTime, this.offset + pauseTime); + this.isStarted = true; } /** @@ -858,7 +948,10 @@ class Bus { * @returns {void} */ setVolumeDb(val) { - this._gainNode.gain.value = GodotAudio.db_to_linear(val); + const linear = GodotAudio.db_to_linear(val); + if (isFinite(linear)) { + this._gainNode.gain.value = linear; + } } /** @@ -1257,7 +1350,7 @@ const _GodotAudio = { startOptions ) { GodotAudio.SampleNode.stopSampleNode(playbackObjectId); - const sampleNode = GodotAudio.SampleNode.create( + GodotAudio.SampleNode.create( { busIndex, id: playbackObjectId, @@ -1265,7 +1358,6 @@ const _GodotAudio = { }, startOptions ); - sampleNode.start(); }, /** @@ -1557,13 +1649,14 @@ const _GodotAudio = { }, godot_audio_sample_start__proxy: 'sync', - godot_audio_sample_start__sig: 'viiiii', + godot_audio_sample_start__sig: 'viiiifi', /** * Starts a sample. * @param {number} playbackObjectIdStrPtr Playback object id pointer * @param {number} streamObjectIdStrPtr Stream object id pointer * @param {number} busIndex Bus index * @param {number} offset Sample offset + * @param {number} pitchScale Pitch scale * @param {number} volumePtr Volume pointer * @returns {void} */ @@ -1572,6 +1665,7 @@ const _GodotAudio = { streamObjectIdStrPtr, busIndex, offset, + pitchScale, volumePtr ) { /** @type {string} */ @@ -1580,11 +1674,13 @@ const _GodotAudio = { const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr); /** @type {Float32Array} */ const volume = GodotRuntime.heapSub(HEAPF32, volumePtr, 8); - /** @type {SampleNodeConstructorOptions} */ + /** @type {SampleNodeOptions} */ const startOptions = { offset, volume, playbackRate: 1, + pitchScale, + start: true, }; GodotAudio.start_sample( playbackObjectId, @@ -1630,6 +1726,22 @@ const _GodotAudio = { return Number(GodotAudio.sampleNodes.has(playbackObjectId)); }, + godot_audio_get_sample_playback_position__proxy: 'sync', + godot_audio_get_sample_playback_position__sig: 'di', + /** + * Returns the position of the playback position. + * @param {number} playbackObjectIdStrPtr Playback object id pointer + * @returns {number} + */ + godot_audio_get_sample_playback_position: function (playbackObjectIdStrPtr) { + const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr); + const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId); + if (sampleNode == null) { + return 0; + } + return sampleNode.getPlaybackPosition(); + }, + godot_audio_sample_update_pitch_scale__proxy: 'sync', godot_audio_sample_update_pitch_scale__sig: 'vii', /** |