Skip to content

Commit

Permalink
Merge pull request #93362 from adamscott/fix-web-audio-pause
Browse files Browse the repository at this point in the history
Fix pausing issues when using Web Audio samples
  • Loading branch information
akien-mga committed Jun 21, 2024
2 parents 4a9dc72 + 57db018 commit ee3b31d
Showing 1 changed file with 117 additions and 59 deletions.
176 changes: 117 additions & 59 deletions platform/web/js/libs/library_godot_audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,68 +423,38 @@ class SampleNode {
this.streamObjectId = params.streamObjectId;
/** @type {number} */
this.offset = options.offset ?? 0;
/** @type {LoopMode} */
/** @type {number} */
this.startTime = options.startTime ?? 0;
/** @type {boolean} */
this.isPaused = false;
/** @type {number} */
this.pauseTime = 0;
/** @type {number} */
this._playbackRate = 44100;
/** @type {LoopMode} */
this._loopMode = 'disabled';
this.loopMode = 'disabled';
/** @type {number} */
this._pitchScale = 1;
/** @type {number} */
this._sourceStartTime = 0;
/** @type {Map<Bus, SampleNodeBus>} */
this._sampleNodeBuses = new Map();
/** @type {AudioBufferSourceNode} */
/** @type {AudioBufferSourceNode | null} */
this._source = GodotAudio.ctx.createBufferSource();
/** @type {AudioBufferSourceNode["onended"]} */
this._onended = null;

this.setPlaybackRate(options.playbackRate ?? 44100);
this.setLoopMode(options.loopMode ?? this.getSample().loopMode ?? 'disabled');
this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
this._source.buffer = this.getSample().getAudioBuffer();

/** @type {SampleNode} */
// eslint-disable-next-line consistent-this
const self = this;
this._source.addEventListener('ended', (_) => {
switch (self.getSample().loopMode) {
case 'disabled':
GodotAudio.SampleNode.stopSampleNode(self.id);
break;
default:
// do nothing
}
});
this._addEndedListener();

const bus = GodotAudio.Bus.getBus(params.busIndex);
const sampleNodeBus = this.getSampleNodeBus(bus);
sampleNodeBus.setVolume(options.volume);
}

/**
* Gets the loop mode of the current instance.
* @returns {LoopMode}
*/
getLoopMode() {
return this._loopMode;
}

/**
* Sets the loop mode of the current instance.
* @param {LoopMode} val Value to set.
* @returns {void}
*/
setLoopMode(val) {
this._loopMode = val;
switch (val) {
case 'forward':
case 'backward':
this._source.loop = true;
break;
default:
this._source.loop = false;
}
}

/**
* Gets the playback rate.
* @returns {number}
Expand Down Expand Up @@ -542,40 +512,40 @@ class SampleNode {
* @returns {void}
*/
start() {
this._source.start(this.offset);
this._resetSourceStartTime();
this._source.start(this.startTime, this.offset);
}

/**
* Stops the `SampleNode`.
* @returns {void}
*/
stop() {
this._source.stop();
this.clear();
}

/**
* Restarts the `SampleNode`.
*/
restart() {
this.isPaused = false;
this.pauseTime = 0;
this._resetSourceStartTime();
this._restart();
}

/**
* Pauses the `SampleNode`.
* @param {boolean} [enable=true] State of the pause.
* @returns {void}
*/
pause(enable = true) {
if (enable) {
this.pauseTime = (GodotAudio.ctx.currentTime - this.startTime) / this.playbackRate;
this._source.stop();
return;
}

if (this.pauseTime === 0) {
this._pause();
return;
}

this._source.disconnect();
this._source = GodotAudio.ctx.createBufferSource();

this._source.buffer = this.getSample().getAudioBuffer();
this._source.connect(this._gain);
this._source.start(this.offset + this.pauseTime);
this._unpause();
}

/**
Expand Down Expand Up @@ -623,26 +593,114 @@ class SampleNode {
* @returns {void}
*/
clear() {
this._source.stop();
this._source.disconnect();
this._source = null;
this.isPaused = false;
this.pauseTime = 0;

if (this._source != null) {
this._source.removeEventListener('ended', this._onended);
this._onended = null;
this._source.stop();
this._source.disconnect();
this._source = null;
}

for (const sampleNodeBus of this._sampleNodeBuses.values()) {
sampleNodeBus.clear();
}
this._sampleNodeBuses.clear();
this._sampleNodeBuses = null;

GodotAudio.SampleNode.delete(this.id);
}

/**
* Resets the source start time
* @returns {void}
*/
_resetSourceStartTime() {
this._sourceStartTime = GodotAudio.ctx.currentTime;
}

/**
* Syncs the `AudioNode` playback rate based on the `SampleNode` playback rate and pitch scale.
* @returns {void}
*/
_syncPlaybackRate() {
this._source.playbackRate.value = this.getPlaybackRate() * this.getPitchScale();
}

/**
* Restarts the `SampleNode`.
* Honors `isPaused` and `pauseTime`.
* @returns {void}
*/
_restart() {
this._source.disconnect();
this._source = GodotAudio.ctx.createBufferSource();
this._source.buffer = this.getSample().getAudioBuffer();

// Make sure that we connect the new source to the sample node bus.
for (const sampleNodeBus of this._sampleNodeBuses.values()) {
this.connect(sampleNodeBus.getInputNode());
}

this._addEndedListener();
const pauseTime = this.isPaused
? this.pauseTime
: 0;
this._source.start(this.startTime, this.offset + pauseTime);
}

/**
* Pauses the `SampleNode`.
* @returns {void}
*/
_pause() {
this.isPaused = true;
this.pauseTime = (GodotAudio.ctx.currentTime - this._sourceStartTime) / this.getPlaybackRate();
this._source.stop();
}

/**
* Unpauses the `SampleNode`.
* @returns {void}
*/
_unpause() {
this._restart();
this.isPaused = false;
this.pauseTime = 0;
}

/**
* Adds an "ended" listener to the source node to repeat it if necessary.
* @returns {void}
*/
_addEndedListener() {
if (this._onended != null) {
this._source.removeEventListener('ended', this._onended);
}

/** @type {SampleNode} */
// eslint-disable-next-line consistent-this
const self = this;
this._onended = (_) => {
if (self.isPaused) {
return;
}

switch (self.getSample().loopMode) {
case 'disabled':
self.stop();
break;
case 'forward':
case 'backward':
self.restart();
break;
default:
// do nothing
}
};
this._source.addEventListener('ended', this._onended);
}
}

/**
Expand Down

0 comments on commit ee3b31d

Please sign in to comment.