| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /*  library_godot_audio.js                                                */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /*                         This file is part of:                          */ | 
					
						
							|  |  |  | /*                             GODOT ENGINE                               */ | 
					
						
							|  |  |  | /*                        https://godotengine.org                         */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | 
					
						
							|  |  |  | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */ | 
					
						
							|  |  |  | /*                                                                        */ | 
					
						
							|  |  |  | /* Permission is hereby granted, free of charge, to any person obtaining  */ | 
					
						
							|  |  |  | /* a copy of this software and associated documentation files (the        */ | 
					
						
							|  |  |  | /* "Software"), to deal in the Software without restriction, including    */ | 
					
						
							|  |  |  | /* without limitation the rights to use, copy, modify, merge, publish,    */ | 
					
						
							|  |  |  | /* distribute, sublicense, and/or sell copies of the Software, and to     */ | 
					
						
							|  |  |  | /* permit persons to whom the Software is furnished to do so, subject to  */ | 
					
						
							|  |  |  | /* the following conditions:                                              */ | 
					
						
							|  |  |  | /*                                                                        */ | 
					
						
							|  |  |  | /* The above copyright notice and this permission notice shall be         */ | 
					
						
							|  |  |  | /* included in all copies or substantial portions of the Software.        */ | 
					
						
							|  |  |  | /*                                                                        */ | 
					
						
							|  |  |  | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */ | 
					
						
							|  |  |  | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */ | 
					
						
							|  |  |  | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | 
					
						
							|  |  |  | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */ | 
					
						
							|  |  |  | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */ | 
					
						
							|  |  |  | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */ | 
					
						
							|  |  |  | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef { "disabled" | "forward" | "backward" | "pingpong" } LoopMode | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @typedef {{ | 
					
						
							|  |  |  |  *   id: string | 
					
						
							|  |  |  |  *   audioBuffer: AudioBuffer | 
					
						
							|  |  |  |  * }} SampleParams | 
					
						
							|  |  |  |  * @typedef {{ | 
					
						
							|  |  |  |  *   numberOfChannels?: number | 
					
						
							|  |  |  |  *   sampleRate?: number | 
					
						
							|  |  |  |  *   loopMode?: LoopMode | 
					
						
							|  |  |  |  *   loopBegin?: number | 
					
						
							|  |  |  |  *   loopEnd?: number | 
					
						
							|  |  |  |  * }} SampleOptions | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Represents a sample, memory-wise. | 
					
						
							|  |  |  |  * @class | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class Sample { | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns a `Sample`. | 
					
						
							|  |  |  | 	 * @param {string} id Id of the `Sample` to get. | 
					
						
							|  |  |  | 	 * @returns {Sample} | 
					
						
							|  |  |  | 	 * @throws {ReferenceError} When no `Sample` is found | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getSample(id) { | 
					
						
							|  |  |  | 		if (!GodotAudio.samples.has(id)) { | 
					
						
							|  |  |  | 			throw new ReferenceError(`Could not find sample "${id}"`); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return GodotAudio.samples.get(id); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns a `Sample` or `null`, if it doesn't exist. | 
					
						
							|  |  |  | 	 * @param {string} id Id of the `Sample` to get. | 
					
						
							|  |  |  | 	 * @returns {Sample?} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getSampleOrNull(id) { | 
					
						
							|  |  |  | 		return GodotAudio.samples.get(id) ?? null; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Creates a `Sample` based on the params. Will register it to the | 
					
						
							|  |  |  | 	 * `GodotAudio.samples` registry. | 
					
						
							|  |  |  | 	 * @param {SampleParams} params Base params | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 	 * @param {SampleOptions} [options={{}}] Optional params | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	 * @returns {Sample} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static create(params, options = {}) { | 
					
						
							|  |  |  | 		const sample = new GodotAudio.Sample(params, options); | 
					
						
							|  |  |  | 		GodotAudio.samples.set(params.id, sample); | 
					
						
							|  |  |  | 		return sample; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Deletes a `Sample` based on the id. | 
					
						
							|  |  |  | 	 * @param {string} id `Sample` id to delete | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static delete(id) { | 
					
						
							|  |  |  | 		GodotAudio.samples.delete(id); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * `Sample` constructor. | 
					
						
							|  |  |  | 	 * @param {SampleParams} params Base params | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 	 * @param {SampleOptions} [options={{}}] Optional params | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	constructor(params, options = {}) { | 
					
						
							|  |  |  | 		/** @type {string} */ | 
					
						
							|  |  |  | 		this.id = params.id; | 
					
						
							|  |  |  | 		/** @type {AudioBuffer} */ | 
					
						
							|  |  |  | 		this._audioBuffer = null; | 
					
						
							|  |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this.numberOfChannels = options.numberOfChannels ?? 2; | 
					
						
							|  |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this.sampleRate = options.sampleRate ?? 44100; | 
					
						
							|  |  |  | 		/** @type {LoopMode} */ | 
					
						
							|  |  |  | 		this.loopMode = options.loopMode ?? 'disabled'; | 
					
						
							|  |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this.loopBegin = options.loopBegin ?? 0; | 
					
						
							|  |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this.loopEnd = options.loopEnd ?? 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.setAudioBuffer(params.audioBuffer); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Gets the audio buffer of the sample. | 
					
						
							|  |  |  | 	 * @returns {AudioBuffer} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getAudioBuffer() { | 
					
						
							|  |  |  | 		return this._duplicateAudioBuffer(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the audio buffer of the sample. | 
					
						
							|  |  |  | 	 * @param {AudioBuffer} val The audio buffer to set. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	setAudioBuffer(val) { | 
					
						
							|  |  |  | 		this._audioBuffer = val; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Clears the current sample. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	clear() { | 
					
						
							| 
									
										
										
										
											2024-06-29 14:20:52 -04:00
										 |  |  | 		this.setAudioBuffer(null); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		GodotAudio.Sample.delete(this.id); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns a duplicate of the stored audio buffer. | 
					
						
							|  |  |  | 	 * @returns {AudioBuffer} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_duplicateAudioBuffer() { | 
					
						
							|  |  |  | 		if (this._audioBuffer == null) { | 
					
						
							|  |  |  | 			throw new Error('couldn\'t duplicate a null audioBuffer'); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 		/** @type {Array<Float32Array>} */ | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		const channels = new Array(this._audioBuffer.numberOfChannels); | 
					
						
							|  |  |  | 		for (let i = 0; i < this._audioBuffer.numberOfChannels; i++) { | 
					
						
							|  |  |  | 			const channel = new Float32Array(this._audioBuffer.getChannelData(i)); | 
					
						
							|  |  |  | 			channels[i] = channel; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		const buffer = GodotAudio.ctx.createBuffer( | 
					
						
							|  |  |  | 			this.numberOfChannels, | 
					
						
							|  |  |  | 			this._audioBuffer.length, | 
					
						
							|  |  |  | 			this._audioBuffer.sampleRate | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		for (let i = 0; i < channels.length; i++) { | 
					
						
							|  |  |  | 			buffer.copyToChannel(channels[i], i, 0); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return buffer; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Represents a `SampleNode` linked to a `Bus`. | 
					
						
							|  |  |  |  * @class | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class SampleNodeBus { | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Creates a new `SampleNodeBus`. | 
					
						
							|  |  |  | 	 * @param {Bus} bus The bus related to the new `SampleNodeBus`. | 
					
						
							|  |  |  | 	 * @returns {SampleNodeBus} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static create(bus) { | 
					
						
							|  |  |  | 		return new GodotAudio.SampleNodeBus(bus); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * `SampleNodeBus` constructor. | 
					
						
							|  |  |  | 	 * @param {Bus} bus The bus related to the new `SampleNodeBus`. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	constructor(bus) { | 
					
						
							|  |  |  | 		const NUMBER_OF_WEB_CHANNELS = 6; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** @type {Bus} */ | 
					
						
							|  |  |  | 		this._bus = bus; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** @type {ChannelSplitterNode} */ | 
					
						
							|  |  |  | 		this._channelSplitter = GodotAudio.ctx.createChannelSplitter(NUMBER_OF_WEB_CHANNELS); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._l = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._r = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._sl = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._sr = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._c = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._lfe = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {ChannelMergerNode} */ | 
					
						
							|  |  |  | 		this._channelMerger = GodotAudio.ctx.createChannelMerger(NUMBER_OF_WEB_CHANNELS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this._channelSplitter | 
					
						
							|  |  |  | 			.connect(this._l, GodotAudio.WebChannel.CHANNEL_L) | 
					
						
							|  |  |  | 			.connect( | 
					
						
							|  |  |  | 				this._channelMerger, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_L, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_L | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		this._channelSplitter | 
					
						
							|  |  |  | 			.connect(this._r, GodotAudio.WebChannel.CHANNEL_R) | 
					
						
							|  |  |  | 			.connect( | 
					
						
							|  |  |  | 				this._channelMerger, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_L, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_R | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		this._channelSplitter | 
					
						
							|  |  |  | 			.connect(this._sl, GodotAudio.WebChannel.CHANNEL_SL) | 
					
						
							|  |  |  | 			.connect( | 
					
						
							|  |  |  | 				this._channelMerger, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_L, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_SL | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		this._channelSplitter | 
					
						
							|  |  |  | 			.connect(this._sr, GodotAudio.WebChannel.CHANNEL_SR) | 
					
						
							|  |  |  | 			.connect( | 
					
						
							|  |  |  | 				this._channelMerger, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_L, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_SR | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		this._channelSplitter | 
					
						
							|  |  |  | 			.connect(this._c, GodotAudio.WebChannel.CHANNEL_C) | 
					
						
							|  |  |  | 			.connect( | 
					
						
							|  |  |  | 				this._channelMerger, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_L, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_C | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		this._channelSplitter | 
					
						
							|  |  |  | 			.connect(this._lfe, GodotAudio.WebChannel.CHANNEL_L) | 
					
						
							|  |  |  | 			.connect( | 
					
						
							|  |  |  | 				this._channelMerger, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_L, | 
					
						
							|  |  |  | 				GodotAudio.WebChannel.CHANNEL_LFE | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this._channelMerger.connect(this._bus.getInputNode()); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the input node. | 
					
						
							|  |  |  | 	 * @returns {AudioNode} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getInputNode() { | 
					
						
							|  |  |  | 		return this._channelSplitter; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the output node. | 
					
						
							|  |  |  | 	 * @returns {AudioNode} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getOutputNode() { | 
					
						
							|  |  |  | 		return this._channelMerger; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the volume for each (split) channel. | 
					
						
							|  |  |  | 	 * @param {Float32Array} volume Volume array from the engine for each channel. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	setVolume(volume) { | 
					
						
							|  |  |  | 		if (volume.length !== GodotAudio.MAX_VOLUME_CHANNELS) { | 
					
						
							|  |  |  | 			throw new Error( | 
					
						
							|  |  |  | 				`Volume length isn't "${GodotAudio.MAX_VOLUME_CHANNELS}", is ${volume.length} instead` | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this._l.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_L] ?? 0; | 
					
						
							|  |  |  | 		this._r.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_R] ?? 0; | 
					
						
							|  |  |  | 		this._sl.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_SL] ?? 0; | 
					
						
							|  |  |  | 		this._sr.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_SR] ?? 0; | 
					
						
							|  |  |  | 		this._c.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_C] ?? 0; | 
					
						
							|  |  |  | 		this._lfe.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_LFE] ?? 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Clears the current `SampleNodeBus` instance. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	clear() { | 
					
						
							|  |  |  | 		this._bus = null; | 
					
						
							|  |  |  | 		this._channelSplitter.disconnect(); | 
					
						
							|  |  |  | 		this._channelSplitter = null; | 
					
						
							|  |  |  | 		this._l.disconnect(); | 
					
						
							|  |  |  | 		this._l = null; | 
					
						
							|  |  |  | 		this._r.disconnect(); | 
					
						
							|  |  |  | 		this._r = null; | 
					
						
							|  |  |  | 		this._sl.disconnect(); | 
					
						
							|  |  |  | 		this._sl = null; | 
					
						
							|  |  |  | 		this._sr.disconnect(); | 
					
						
							|  |  |  | 		this._sr = null; | 
					
						
							|  |  |  | 		this._c.disconnect(); | 
					
						
							|  |  |  | 		this._c = null; | 
					
						
							|  |  |  | 		this._lfe.disconnect(); | 
					
						
							|  |  |  | 		this._lfe = null; | 
					
						
							|  |  |  | 		this._channelMerger.disconnect(); | 
					
						
							|  |  |  | 		this._channelMerger = null; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @typedef {{ | 
					
						
							|  |  |  |  *   id: string | 
					
						
							|  |  |  |  *   streamObjectId: string | 
					
						
							|  |  |  |  *   busIndex: number | 
					
						
							|  |  |  |  * }} SampleNodeParams | 
					
						
							|  |  |  |  * @typedef {{ | 
					
						
							|  |  |  |  *   offset?: number | 
					
						
							|  |  |  |  *   playbackRate?: number | 
					
						
							|  |  |  |  *   startTime?: number | 
					
						
							|  |  |  |  *   loopMode?: LoopMode | 
					
						
							|  |  |  |  *   volume?: Float32Array | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  |  *   start?: boolean | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  |  * }} SampleNodeOptions | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Represents an `AudioNode` of a `Sample`. | 
					
						
							|  |  |  |  * @class | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class SampleNode { | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns a `SampleNode`. | 
					
						
							|  |  |  | 	 * @param {string} id Id of the `SampleNode`. | 
					
						
							|  |  |  | 	 * @returns {SampleNode} | 
					
						
							|  |  |  | 	 * @throws {ReferenceError} When no `SampleNode` is not found | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getSampleNode(id) { | 
					
						
							|  |  |  | 		if (!GodotAudio.sampleNodes.has(id)) { | 
					
						
							|  |  |  | 			throw new ReferenceError(`Could not find sample node "${id}"`); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return GodotAudio.sampleNodes.get(id); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns a `SampleNode`, returns null if not found. | 
					
						
							|  |  |  | 	 * @param {string} id Id of the SampleNode. | 
					
						
							|  |  |  | 	 * @returns {SampleNode?} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getSampleNodeOrNull(id) { | 
					
						
							|  |  |  | 		return GodotAudio.sampleNodes.get(id) ?? null; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Stops a `SampleNode` by id. | 
					
						
							|  |  |  | 	 * @param {string} id Id of the `SampleNode` to stop. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static stopSampleNode(id) { | 
					
						
							|  |  |  | 		const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(id); | 
					
						
							|  |  |  | 		if (sampleNode == null) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		sampleNode.stop(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Pauses the `SampleNode` by id. | 
					
						
							|  |  |  | 	 * @param {string} id Id of the `SampleNode` to pause. | 
					
						
							|  |  |  | 	 * @param {boolean} enable State of the pause | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static pauseSampleNode(id, enable) { | 
					
						
							|  |  |  | 		const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(id); | 
					
						
							|  |  |  | 		if (sampleNode == null) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		sampleNode.pause(enable); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Creates a `SampleNode` based on the params. Will register the `SampleNode` to | 
					
						
							|  |  |  | 	 * the `GodotAudio.sampleNodes` regisery. | 
					
						
							|  |  |  | 	 * @param {SampleNodeParams} params Base params. | 
					
						
							|  |  |  | 	 * @param {SampleNodeOptions} options Optional params. | 
					
						
							|  |  |  | 	 * @returns {SampleNode} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static create(params, options = {}) { | 
					
						
							|  |  |  | 		const sampleNode = new GodotAudio.SampleNode(params, options); | 
					
						
							|  |  |  | 		GodotAudio.sampleNodes.set(params.id, sampleNode); | 
					
						
							|  |  |  | 		return sampleNode; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Deletes a `SampleNode` based on the id. | 
					
						
							|  |  |  | 	 * @param {string} id Id of the `SampleNode` to delete. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static delete(id) { | 
					
						
							|  |  |  | 		GodotAudio.sampleNodes.delete(id); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {SampleNodeParams} params Base params | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 	 * @param {SampleNodeOptions} [options={{}}] Optional params | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	constructor(params, options = {}) { | 
					
						
							|  |  |  | 		/** @type {string} */ | 
					
						
							|  |  |  | 		this.id = params.id; | 
					
						
							|  |  |  | 		/** @type {string} */ | 
					
						
							|  |  |  | 		this.streamObjectId = params.streamObjectId; | 
					
						
							|  |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this.offset = options.offset ?? 0; | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		/** @type {number} */ | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		this._playbackPosition = options.offset; | 
					
						
							|  |  |  | 		/** @type {number} */ | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		this.startTime = options.startTime ?? 0; | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		/** @type {boolean} */ | 
					
						
							|  |  |  | 		this.isPaused = false; | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		/** @type {boolean} */ | 
					
						
							|  |  |  | 		this.isStarted = false; | 
					
						
							|  |  |  | 		/** @type {boolean} */ | 
					
						
							|  |  |  | 		this.isCanceled = false; | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this.pauseTime = 0; | 
					
						
							|  |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this._playbackRate = 44100; | 
					
						
							|  |  |  | 		/** @type {LoopMode} */ | 
					
						
							| 
									
										
										
										
											2024-06-29 14:20:52 -04:00
										 |  |  | 		this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled'; | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this._pitchScale = 1; | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		/** @type {number} */ | 
					
						
							|  |  |  | 		this._sourceStartTime = 0; | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		/** @type {Map<Bus, SampleNodeBus>} */ | 
					
						
							|  |  |  | 		this._sampleNodeBuses = new Map(); | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		/** @type {AudioBufferSourceNode | null} */ | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		this._source = GodotAudio.ctx.createBufferSource(); | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		this._onended = null; | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		/** @type {AudioWorkletNode | null} */ | 
					
						
							|  |  |  | 		this._positionWorklet = null; | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		this.setPlaybackRate(options.playbackRate ?? 44100); | 
					
						
							|  |  |  | 		this._source.buffer = this.getSample().getAudioBuffer(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		this._addEndedListener(); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		const bus = GodotAudio.Bus.getBus(params.busIndex); | 
					
						
							|  |  |  | 		const sampleNodeBus = this.getSampleNodeBus(bus); | 
					
						
							|  |  |  | 		sampleNodeBus.setVolume(options.volume); | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		this.connectPositionWorklet(options.start); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Gets the playback rate. | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getPlaybackRate() { | 
					
						
							|  |  |  | 		return this._playbackRate; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Gets the playback position. | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getPlaybackPosition() { | 
					
						
							|  |  |  | 		return this._playbackPosition; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the playback rate. | 
					
						
							|  |  |  | 	 * @param {number} val Value to set. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	setPlaybackRate(val) { | 
					
						
							|  |  |  | 		this._playbackRate = val; | 
					
						
							|  |  |  | 		this._syncPlaybackRate(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Gets the pitch scale. | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getPitchScale() { | 
					
						
							|  |  |  | 		return this._pitchScale; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the pitch scale. | 
					
						
							|  |  |  | 	 * @param {number} val Value to set. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	setPitchScale(val) { | 
					
						
							|  |  |  | 		this._pitchScale = val; | 
					
						
							|  |  |  | 		this._syncPlaybackRate(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the linked `Sample`. | 
					
						
							|  |  |  | 	 * @returns {Sample} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getSample() { | 
					
						
							|  |  |  | 		return GodotAudio.Sample.getSample(this.streamObjectId); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the output node. | 
					
						
							|  |  |  | 	 * @returns {AudioNode} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getOutputNode() { | 
					
						
							|  |  |  | 		return this._source; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Starts the `SampleNode`. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	start() { | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		if (this.isStarted) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		this._resetSourceStartTime(); | 
					
						
							|  |  |  | 		this._source.start(this.startTime, this.offset); | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		this.isStarted = true; | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Stops the `SampleNode`. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	stop() { | 
					
						
							|  |  |  | 		this.clear(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Restarts the `SampleNode`. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	restart() { | 
					
						
							|  |  |  | 		this.isPaused = false; | 
					
						
							|  |  |  | 		this.pauseTime = 0; | 
					
						
							|  |  |  | 		this._resetSourceStartTime(); | 
					
						
							|  |  |  | 		this._restart(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Pauses the `SampleNode`. | 
					
						
							|  |  |  | 	 * @param {boolean} [enable=true] State of the pause. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	pause(enable = true) { | 
					
						
							|  |  |  | 		if (enable) { | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 			this._pause(); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		this._unpause(); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Connects an AudioNode to the output node of this `SampleNode`. | 
					
						
							|  |  |  | 	 * @param {AudioNode} node AudioNode to connect. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	connect(node) { | 
					
						
							|  |  |  | 		return this.getOutputNode().connect(node); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the volumes of the `SampleNode` for each buses passed in parameters. | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 	 * @param {Array<Bus>} buses | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	 * @param {Float32Array} volumes | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	setVolumes(buses, volumes) { | 
					
						
							|  |  |  | 		for (let busIdx = 0; busIdx < buses.length; busIdx++) { | 
					
						
							|  |  |  | 			const sampleNodeBus = this.getSampleNodeBus(buses[busIdx]); | 
					
						
							|  |  |  | 			sampleNodeBus.setVolume( | 
					
						
							|  |  |  | 				volumes.slice( | 
					
						
							|  |  |  | 					busIdx * GodotAudio.MAX_VOLUME_CHANNELS, | 
					
						
							|  |  |  | 					(busIdx * GodotAudio.MAX_VOLUME_CHANNELS) + GodotAudio.MAX_VOLUME_CHANNELS | 
					
						
							|  |  |  | 				) | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the SampleNodeBus based on the bus in parameters. | 
					
						
							|  |  |  | 	 * @param {Bus} bus Bus to get the SampleNodeBus from. | 
					
						
							|  |  |  | 	 * @returns {SampleNodeBus} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getSampleNodeBus(bus) { | 
					
						
							|  |  |  | 		if (!this._sampleNodeBuses.has(bus)) { | 
					
						
							|  |  |  | 			const sampleNodeBus = GodotAudio.SampleNodeBus.create(bus); | 
					
						
							|  |  |  | 			this._sampleNodeBuses.set(bus, sampleNodeBus); | 
					
						
							|  |  |  | 			this._source.connect(sampleNodeBus.getInputNode()); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this._sampleNodeBuses.get(bus); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * 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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Clears the `SampleNode`. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	clear() { | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		this.isCanceled = true; | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		this.isPaused = false; | 
					
						
							|  |  |  | 		this.pauseTime = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (this._source != null) { | 
					
						
							|  |  |  | 			this._source.removeEventListener('ended', this._onended); | 
					
						
							|  |  |  | 			this._onended = null; | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 			if (this.isStarted) { | 
					
						
							|  |  |  | 				this._source.stop(); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 			this._source.disconnect(); | 
					
						
							|  |  |  | 			this._source = null; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		for (const sampleNodeBus of this._sampleNodeBuses.values()) { | 
					
						
							|  |  |  | 			sampleNodeBus.clear(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this._sampleNodeBuses.clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		if (this._positionWorklet) { | 
					
						
							|  |  |  | 			this._positionWorklet.disconnect(); | 
					
						
							|  |  |  | 			this._positionWorklet.port.onmessage = null; | 
					
						
							|  |  |  | 			this._positionWorklet = null; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		GodotAudio.SampleNode.delete(this.id); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Resets the source start time | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_resetSourceStartTime() { | 
					
						
							|  |  |  | 		this._sourceStartTime = GodotAudio.ctx.currentTime; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * 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(); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Restarts the `SampleNode`. | 
					
						
							|  |  |  | 	 * Honors `isPaused` and `pauseTime`. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_restart() { | 
					
						
							| 
									
										
										
										
											2024-07-30 15:00:58 -04:00
										 |  |  | 		if (this._source != null) { | 
					
						
							|  |  |  | 			this._source.disconnect(); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		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; | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		this.connectPositionWorklet(); | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 		this._source.start(this.startTime, this.offset + pauseTime); | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 		this.isStarted = true; | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * 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) { | 
					
						
							| 
									
										
										
										
											2024-07-07 13:31:56 -04:00
										 |  |  | 			case 'disabled': { | 
					
						
							|  |  |  | 				const id = this.id; | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 				self.stop(); | 
					
						
							| 
									
										
										
										
											2024-07-07 13:31:56 -04:00
										 |  |  | 				if (GodotAudio.sampleFinishedCallback != null) { | 
					
						
							|  |  |  | 					const idCharPtr = GodotRuntime.allocString(id); | 
					
						
							|  |  |  | 					GodotAudio.sampleFinishedCallback(idCharPtr); | 
					
						
							|  |  |  | 					GodotRuntime.free(idCharPtr); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} break; | 
					
						
							| 
									
										
										
										
											2024-06-19 13:42:58 -04:00
										 |  |  | 			case 'forward': | 
					
						
							|  |  |  | 			case 'backward': | 
					
						
							|  |  |  | 				self.restart(); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				// do nothing
 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		this._source.addEventListener('ended', this._onended); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Collection of nodes to represents a Godot Engine audio bus. | 
					
						
							|  |  |  |  * @class | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class Bus { | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the number of registered buses. | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getCount() { | 
					
						
							|  |  |  | 		return GodotAudio.buses.length; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the number of registered buses. | 
					
						
							|  |  |  | 	 * Will delete buses if lower than the current number. | 
					
						
							|  |  |  | 	 * @param {number} val Count of registered buses. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static setCount(val) { | 
					
						
							|  |  |  | 		const buses = GodotAudio.buses; | 
					
						
							|  |  |  | 		if (val === buses.length) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (val < buses.length) { | 
					
						
							|  |  |  | 			// TODO: what to do with nodes connected to the deleted buses?
 | 
					
						
							|  |  |  | 			const deletedBuses = buses.slice(val); | 
					
						
							|  |  |  | 			for (let i = 0; i < deletedBuses.length; i++) { | 
					
						
							|  |  |  | 				const deletedBus = deletedBuses[i]; | 
					
						
							|  |  |  | 				deletedBus.clear(); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			GodotAudio.buses = buses.slice(0, val); | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (let i = GodotAudio.buses.length; i < val; i++) { | 
					
						
							|  |  |  | 			GodotAudio.Bus.create(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns a `Bus` based on it's index number. | 
					
						
							|  |  |  | 	 * @param {number} index | 
					
						
							|  |  |  | 	 * @returns {Bus} | 
					
						
							|  |  |  | 	 * @throws {ReferenceError} If the index value is outside the registry. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getBus(index) { | 
					
						
							|  |  |  | 		if (index < 0 || index >= GodotAudio.buses.length) { | 
					
						
							|  |  |  | 			throw new ReferenceError(`invalid bus index "${index}"`); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return GodotAudio.buses[index]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns a `Bus` based on it's index number. Returns null if it doesn't exist. | 
					
						
							|  |  |  | 	 * @param {number} index | 
					
						
							|  |  |  | 	 * @returns {Bus?} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getBusOrNull(index) { | 
					
						
							|  |  |  | 		if (index < 0 || index >= GodotAudio.buses.length) { | 
					
						
							|  |  |  | 			return null; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return GodotAudio.buses[index]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Move a bus from an index to another. | 
					
						
							|  |  |  | 	 * @param {number} fromIndex From index | 
					
						
							|  |  |  | 	 * @param {number} toIndex To index | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static move(fromIndex, toIndex) { | 
					
						
							|  |  |  | 		const movedBus = GodotAudio.Bus.getBus(fromIndex); | 
					
						
							| 
									
										
										
										
											2024-06-29 14:20:52 -04:00
										 |  |  | 		const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		// Inserts at index.
 | 
					
						
							|  |  |  | 		buses.splice(toIndex - 1, 0, movedBus); | 
					
						
							|  |  |  | 		GodotAudio.buses = buses; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Adds a new bus at the specified index. | 
					
						
							|  |  |  | 	 * @param {number} index Index to add a new bus. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static addAt(index) { | 
					
						
							|  |  |  | 		const newBus = GodotAudio.Bus.create(); | 
					
						
							|  |  |  | 		if (index !== newBus.getId()) { | 
					
						
							|  |  |  | 			GodotAudio.Bus.move(newBus.getId(), index); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Creates a `Bus` and registers it. | 
					
						
							|  |  |  | 	 * @returns {Bus} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static create() { | 
					
						
							|  |  |  | 		const newBus = new GodotAudio.Bus(); | 
					
						
							|  |  |  | 		const isFirstBus = GodotAudio.buses.length === 0; | 
					
						
							|  |  |  | 		GodotAudio.buses.push(newBus); | 
					
						
							|  |  |  | 		if (isFirstBus) { | 
					
						
							|  |  |  | 			newBus.setSend(null); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			newBus.setSend(GodotAudio.Bus.getBus(0)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return newBus; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * `Bus` constructor. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	constructor() { | 
					
						
							|  |  |  | 		/** @type {Set<SampleNode>} */ | 
					
						
							|  |  |  | 		this._sampleNodes = new Set(); | 
					
						
							|  |  |  | 		/** @type {boolean} */ | 
					
						
							|  |  |  | 		this.isSolo = false; | 
					
						
							|  |  |  | 		/** @type {Bus?} */ | 
					
						
							|  |  |  | 		this._send = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._gainNode = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._soloNode = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 		/** @type {GainNode} */ | 
					
						
							|  |  |  | 		this._muteNode = GodotAudio.ctx.createGain(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this._gainNode | 
					
						
							|  |  |  | 			.connect(this._soloNode) | 
					
						
							|  |  |  | 			.connect(this._muteNode); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the current id of the bus (its index). | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getId() { | 
					
						
							|  |  |  | 		return GodotAudio.buses.indexOf(this); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the bus volume db value. | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getVolumeDb() { | 
					
						
							|  |  |  | 		return GodotAudio.linear_to_db(this._gainNode.gain.value); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the bus volume db value. | 
					
						
							|  |  |  | 	 * @param {number} val Value to set | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	setVolumeDb(val) { | 
					
						
							| 
									
										
										
										
											2024-07-28 11:47:25 +02:00
										 |  |  | 		const linear = GodotAudio.db_to_linear(val); | 
					
						
							|  |  |  | 		if (isFinite(linear)) { | 
					
						
							|  |  |  | 			this._gainNode.gain.value = linear; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the "send" bus. | 
					
						
							|  |  |  | 	 * If null, this bus sends its contents directly to the output. | 
					
						
							|  |  |  | 	 * If not null, this bus sends its contents to another bus. | 
					
						
							|  |  |  | 	 * @returns {Bus?} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getSend() { | 
					
						
							|  |  |  | 		return this._send; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the "send" bus. | 
					
						
							|  |  |  | 	 * If null, this bus sends its contents directly to the output. | 
					
						
							|  |  |  | 	 * If not null, this bus sends its contents to another bus. | 
					
						
							|  |  |  | 	 * | 
					
						
							|  |  |  | 	 * **Note:** if null, `getId()` must be equal to 0. Otherwise, it will throw. | 
					
						
							|  |  |  | 	 * @param {Bus?} val | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 * @throws {Error} When val is `null` and `getId()` isn't equal to 0 | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	setSend(val) { | 
					
						
							|  |  |  | 		this._send = val; | 
					
						
							|  |  |  | 		if (val == null) { | 
					
						
							|  |  |  | 			if (this.getId() == 0) { | 
					
						
							|  |  |  | 				this.getOutputNode().connect(GodotAudio.ctx.destination); | 
					
						
							|  |  |  | 				return; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			throw new Error( | 
					
						
							|  |  |  | 				`Cannot send to "${val}" without the bus being at index 0 (current index: ${this.getId()})` | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this.connect(val); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the input node of the bus. | 
					
						
							|  |  |  | 	 * @returns {AudioNode} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getInputNode() { | 
					
						
							|  |  |  | 		return this._gainNode; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns the output node of the bus. | 
					
						
							|  |  |  | 	 * @returns {AudioNode} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	getOutputNode() { | 
					
						
							|  |  |  | 		return this._muteNode; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the mute status of the bus. | 
					
						
							|  |  |  | 	 * @param {boolean} enable | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	mute(enable) { | 
					
						
							|  |  |  | 		this._muteNode.gain.value = enable ? 0 : 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the solo status of the bus. | 
					
						
							|  |  |  | 	 * @param {boolean} enable | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	solo(enable) { | 
					
						
							|  |  |  | 		if (this.isSolo === enable) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (enable) { | 
					
						
							|  |  |  | 			if (GodotAudio.busSolo != null && GodotAudio.busSolo !== this) { | 
					
						
							|  |  |  | 				GodotAudio.busSolo._disableSolo(); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			this._enableSolo(); | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this._disableSolo(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Wrapper to simply add a sample node to the bus. | 
					
						
							|  |  |  | 	 * @param {SampleNode} sampleNode `SampleNode` to remove | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	addSampleNode(sampleNode) { | 
					
						
							|  |  |  | 		this._sampleNodes.add(sampleNode); | 
					
						
							|  |  |  | 		sampleNode.getOutputNode().connect(this.getInputNode()); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Wrapper to simply remove a sample node from the bus. | 
					
						
							|  |  |  | 	 * @param {SampleNode} sampleNode `SampleNode` to remove | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	removeSampleNode(sampleNode) { | 
					
						
							|  |  |  | 		this._sampleNodes.delete(sampleNode); | 
					
						
							|  |  |  | 		sampleNode.getOutputNode().disconnect(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Wrapper to simply connect to another bus. | 
					
						
							|  |  |  | 	 * @param {Bus} bus | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	connect(bus) { | 
					
						
							|  |  |  | 		if (bus == null) { | 
					
						
							|  |  |  | 			throw new Error('cannot connect to null bus'); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this.getOutputNode().disconnect(); | 
					
						
							|  |  |  | 		this.getOutputNode().connect(bus.getInputNode()); | 
					
						
							|  |  |  | 		return bus; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Clears the current bus. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	clear() { | 
					
						
							|  |  |  | 		GodotAudio.buses = GodotAudio.buses.filter((v) => v !== this); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_syncSampleNodes() { | 
					
						
							|  |  |  | 		const sampleNodes = Array.from(this._sampleNodes); | 
					
						
							|  |  |  | 		for (let i = 0; i < sampleNodes.length; i++) { | 
					
						
							|  |  |  | 			const sampleNode = sampleNodes[i]; | 
					
						
							|  |  |  | 			sampleNode.getOutputNode().disconnect(); | 
					
						
							|  |  |  | 			sampleNode.getOutputNode().connect(this.getInputNode()); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Process to enable solo. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_enableSolo() { | 
					
						
							|  |  |  | 		this.isSolo = true; | 
					
						
							|  |  |  | 		GodotAudio.busSolo = this; | 
					
						
							|  |  |  | 		this._soloNode.gain.value = 1; | 
					
						
							|  |  |  | 		const otherBuses = GodotAudio.buses.filter( | 
					
						
							|  |  |  | 			(otherBus) => otherBus !== this | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		for (let i = 0; i < otherBuses.length; i++) { | 
					
						
							|  |  |  | 			const otherBus = otherBuses[i]; | 
					
						
							|  |  |  | 			otherBus._soloNode.gain.value = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Process to disable solo. | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	_disableSolo() { | 
					
						
							|  |  |  | 		this.isSolo = false; | 
					
						
							|  |  |  | 		GodotAudio.busSolo = null; | 
					
						
							|  |  |  | 		this._soloNode.gain.value = 1; | 
					
						
							|  |  |  | 		const otherBuses = GodotAudio.buses.filter( | 
					
						
							|  |  |  | 			(otherBus) => otherBus !== this | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		for (let i = 0; i < otherBuses.length; i++) { | 
					
						
							|  |  |  | 			const otherBus = otherBuses[i]; | 
					
						
							|  |  |  | 			otherBus._soloNode.gain.value = 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const _GodotAudio = { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 	$GodotAudio__deps: ['$GodotRuntime', '$GodotOS'], | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 	$GodotAudio: { | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		/** | 
					
						
							|  |  |  | 		 * Max number of volume channels. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		MAX_VOLUME_CHANNELS: 8, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Represents the index of each sound channel relative to the engine. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		GodotChannel: Object.freeze({ | 
					
						
							|  |  |  | 			CHANNEL_L: 0, | 
					
						
							|  |  |  | 			CHANNEL_R: 1, | 
					
						
							|  |  |  | 			CHANNEL_C: 3, | 
					
						
							|  |  |  | 			CHANNEL_LFE: 4, | 
					
						
							|  |  |  | 			CHANNEL_RL: 5, | 
					
						
							|  |  |  | 			CHANNEL_RR: 6, | 
					
						
							|  |  |  | 			CHANNEL_SL: 7, | 
					
						
							|  |  |  | 			CHANNEL_SR: 8, | 
					
						
							|  |  |  | 		}), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Represents the index of each sound channel relative to the Web Audio API. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		WebChannel: Object.freeze({ | 
					
						
							|  |  |  | 			CHANNEL_L: 0, | 
					
						
							|  |  |  | 			CHANNEL_R: 1, | 
					
						
							|  |  |  | 			CHANNEL_SL: 2, | 
					
						
							|  |  |  | 			CHANNEL_SR: 3, | 
					
						
							|  |  |  | 			CHANNEL_C: 4, | 
					
						
							|  |  |  | 			CHANNEL_LFE: 5, | 
					
						
							|  |  |  | 		}), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// `Sample` class
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Registry of `Sample`s. | 
					
						
							|  |  |  | 		 * @type {Map<string, Sample>} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		samples: null, | 
					
						
							|  |  |  | 		Sample, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// `SampleNodeBus` class
 | 
					
						
							|  |  |  | 		SampleNodeBus, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// `SampleNode` class
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Registry of `SampleNode`s. | 
					
						
							|  |  |  | 		 * @type {Map<string, SampleNode>} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		sampleNodes: null, | 
					
						
							|  |  |  | 		SampleNode, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// `Bus` class
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Registry of `Bus`es. | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 		 * @type {Array<Bus>} | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		 */ | 
					
						
							|  |  |  | 		buses: null, | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Reference to the current bus in solo mode. | 
					
						
							|  |  |  | 		 * @type {Bus | null} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		busSolo: null, | 
					
						
							|  |  |  | 		Bus, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-07 13:31:56 -04:00
										 |  |  | 		/** | 
					
						
							|  |  |  | 		 * Callback to signal that a sample has finished. | 
					
						
							|  |  |  | 		 * @type {(playbackObjectIdPtr: number) => void | null} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		sampleFinishedCallback: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		/** @type {AudioContext} */ | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 		ctx: null, | 
					
						
							|  |  |  | 		input: null, | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 		driver: null, | 
					
						
							|  |  |  | 		interval: 0, | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		/** | 
					
						
							|  |  |  | 		 * Converts linear volume to Db. | 
					
						
							|  |  |  | 		 * @param {number} linear Linear value to convert. | 
					
						
							|  |  |  | 		 * @returns {number} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		linear_to_db: function (linear) { | 
					
						
							|  |  |  | 			// eslint-disable-next-line no-loss-of-precision
 | 
					
						
							|  |  |  | 			return Math.log(linear) * 8.6858896380650365530225783783321; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Converts Db volume to linear. | 
					
						
							|  |  |  | 		 * @param {number} db Db value to convert. | 
					
						
							|  |  |  | 		 * @returns {number} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		db_to_linear: function (db) { | 
					
						
							|  |  |  | 			// eslint-disable-next-line no-loss-of-precision
 | 
					
						
							|  |  |  | 			return Math.exp(db * 0.11512925464970228420089957273422); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		init: function (mix_rate, latency, onstatechange, onlatencyupdate) { | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 			// Initialize classes static values.
 | 
					
						
							|  |  |  | 			GodotAudio.samples = new Map(); | 
					
						
							|  |  |  | 			GodotAudio.sampleNodes = new Map(); | 
					
						
							|  |  |  | 			GodotAudio.buses = []; | 
					
						
							|  |  |  | 			GodotAudio.busSolo = null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-13 03:06:34 +02:00
										 |  |  | 			const opts = {}; | 
					
						
							|  |  |  | 			// If mix_rate is 0, let the browser choose.
 | 
					
						
							|  |  |  | 			if (mix_rate) { | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 				GodotAudio.sampleRate = mix_rate; | 
					
						
							| 
									
										
										
										
											2021-09-13 03:06:34 +02:00
										 |  |  | 				opts['sampleRate'] = mix_rate; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Do not specify, leave 'interactive' for good performance.
 | 
					
						
							|  |  |  | 			// opts['latencyHint'] = latency / 1000;
 | 
					
						
							|  |  |  | 			const ctx = new (window.AudioContext || window.webkitAudioContext)(opts); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			GodotAudio.ctx = ctx; | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 			ctx.onstatechange = function () { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				let state = 0; | 
					
						
							|  |  |  | 				switch (ctx.state) { | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 				case 'suspended': | 
					
						
							|  |  |  | 					state = 0; | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				case 'running': | 
					
						
							|  |  |  | 					state = 1; | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				case 'closed': | 
					
						
							|  |  |  | 					state = 2; | 
					
						
							|  |  |  | 					break; | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 				default: | 
					
						
							|  |  |  | 					// Do nothing.
 | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				onstatechange(state); | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 			}; | 
					
						
							| 
									
										
										
										
											2021-05-20 12:07:26 +02:00
										 |  |  | 			ctx.onstatechange(); // Immediately notify state.
 | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			// Update computed latency
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 			GodotAudio.interval = setInterval(function () { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 				let computed_latency = 0; | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				if (ctx.baseLatency) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 					computed_latency += GodotAudio.ctx.baseLatency; | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				if (ctx.outputLatency) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 					computed_latency += GodotAudio.ctx.outputLatency; | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 				onlatencyupdate(computed_latency); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			}, 1000); | 
					
						
							|  |  |  | 			GodotOS.atexit(GodotAudio.close_async); | 
					
						
							|  |  |  | 			return ctx.destination.channelCount; | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		create_input: function (callback) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			if (GodotAudio.input) { | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 				return 0; // Already started.
 | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			function gotMediaInput(stream) { | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 				try { | 
					
						
							|  |  |  | 					GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); | 
					
						
							|  |  |  | 					callback(GodotAudio.input); | 
					
						
							|  |  |  | 				} catch (e) { | 
					
						
							| 
									
										
										
										
											2023-08-07 12:59:23 +02:00
										 |  |  | 					GodotRuntime.error('Failed creating input.', e); | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 			if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				navigator.mediaDevices.getUserMedia({ | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 					'audio': true, | 
					
						
							|  |  |  | 				}).then(gotMediaInput, function (e) { | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 					GodotRuntime.error('Error getting user media.', e); | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				if (!navigator.getUserMedia) { | 
					
						
							|  |  |  | 					navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 				if (!navigator.getUserMedia) { | 
					
						
							|  |  |  | 					GodotRuntime.error('getUserMedia not available.'); | 
					
						
							|  |  |  | 					return 1; | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				navigator.getUserMedia({ | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 					'audio': true, | 
					
						
							|  |  |  | 				}, gotMediaInput, function (e) { | 
					
						
							|  |  |  | 					GodotRuntime.print(e); | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 			return 0; | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		close_async: function (resolve, reject) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			const ctx = GodotAudio.ctx; | 
					
						
							|  |  |  | 			GodotAudio.ctx = null; | 
					
						
							|  |  |  | 			// Audio was not initialized.
 | 
					
						
							|  |  |  | 			if (!ctx) { | 
					
						
							|  |  |  | 				resolve(); | 
					
						
							| 
									
										
										
										
											2020-10-23 18:33:20 +02:00
										 |  |  | 				return; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			// Remove latency callback
 | 
					
						
							|  |  |  | 			if (GodotAudio.interval) { | 
					
						
							|  |  |  | 				clearInterval(GodotAudio.interval); | 
					
						
							|  |  |  | 				GodotAudio.interval = 0; | 
					
						
							| 
									
										
										
										
											2020-10-23 18:33:20 +02:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			// Disconnect input, if it was started.
 | 
					
						
							| 
									
										
										
										
											2020-10-23 18:33:20 +02:00
										 |  |  | 			if (GodotAudio.input) { | 
					
						
							|  |  |  | 				GodotAudio.input.disconnect(); | 
					
						
							|  |  |  | 				GodotAudio.input = null; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			// Disconnect output
 | 
					
						
							|  |  |  | 			let closed = Promise.resolve(); | 
					
						
							|  |  |  | 			if (GodotAudio.driver) { | 
					
						
							|  |  |  | 				closed = GodotAudio.driver.close(); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 			closed.then(function () { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				return ctx.close(); | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 			}).then(function () { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				ctx.onstatechange = null; | 
					
						
							|  |  |  | 				resolve(); | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 			}).catch(function (e) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				ctx.onstatechange = null; | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 				GodotRuntime.error('Error closing AudioContext', e); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				resolve(); | 
					
						
							| 
									
										
										
										
											2020-10-23 18:33:20 +02:00
										 |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a sample node needs to start. | 
					
						
							|  |  |  | 		 * @param {string} playbackObjectId The unique id of the sample playback | 
					
						
							|  |  |  | 		 * @param {string} streamObjectId The unique id of the stream | 
					
						
							|  |  |  | 		 * @param {number} busIndex Index of the bus currently binded to the sample playback | 
					
						
							|  |  |  | 		 * @param {SampleNodeOptions} startOptions Optional params | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		start_sample: function ( | 
					
						
							|  |  |  | 			playbackObjectId, | 
					
						
							|  |  |  | 			streamObjectId, | 
					
						
							|  |  |  | 			busIndex, | 
					
						
							|  |  |  | 			startOptions | 
					
						
							|  |  |  | 		) { | 
					
						
							|  |  |  | 			GodotAudio.SampleNode.stopSampleNode(playbackObjectId); | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 			GodotAudio.SampleNode.create( | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 				{ | 
					
						
							|  |  |  | 					busIndex, | 
					
						
							|  |  |  | 					id: playbackObjectId, | 
					
						
							|  |  |  | 					streamObjectId, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				startOptions | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a sample node needs to be stopped. | 
					
						
							|  |  |  | 		 * @param {string} playbackObjectId Id of the sample playback | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		stop_sample: function (playbackObjectId) { | 
					
						
							|  |  |  | 			GodotAudio.SampleNode.stopSampleNode(playbackObjectId); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a sample node needs to be paused or unpaused. | 
					
						
							|  |  |  | 		 * @param {string} playbackObjectId Id of the sample playback | 
					
						
							|  |  |  | 		 * @param {boolean} pause State of the pause | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		sample_set_pause: function (playbackObjectId, pause) { | 
					
						
							|  |  |  | 			GodotAudio.SampleNode.pauseSampleNode(playbackObjectId, pause); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a sample node needs its pitch scale to be updated. | 
					
						
							|  |  |  | 		 * @param {string} playbackObjectId Id of the sample playback | 
					
						
							|  |  |  | 		 * @param {number} pitchScale Pitch scale of the sample playback | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		update_sample_pitch_scale: function (playbackObjectId, pitchScale) { | 
					
						
							|  |  |  | 			const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId); | 
					
						
							|  |  |  | 			if (sampleNode == null) { | 
					
						
							|  |  |  | 				return; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			sampleNode.setPitchScale(pitchScale); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a sample node volumes need to be updated. | 
					
						
							|  |  |  | 		 * @param {string} playbackObjectId Id of the sample playback | 
					
						
							| 
									
										
										
										
											2024-07-26 11:52:36 +02:00
										 |  |  | 		 * @param {Array<number>} busIndexes Indexes of the buses that need to be updated | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		 * @param {Float32Array} volumes Array of the volumes | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		sample_set_volumes_linear: function (playbackObjectId, busIndexes, volumes) { | 
					
						
							|  |  |  | 			const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId); | 
					
						
							|  |  |  | 			if (sampleNode == null) { | 
					
						
							|  |  |  | 				return; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const buses = busIndexes.map((busIndex) => GodotAudio.Bus.getBus(busIndex)); | 
					
						
							|  |  |  | 			sampleNode.setVolumes(buses, volumes); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when the bus count changes. | 
					
						
							|  |  |  | 		 * @param {number} count Number of buses | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		set_sample_bus_count: function (count) { | 
					
						
							|  |  |  | 			GodotAudio.Bus.setCount(count); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a bus needs to be removed. | 
					
						
							|  |  |  | 		 * @param {number} index Bus index | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		remove_sample_bus: function (index) { | 
					
						
							|  |  |  | 			const bus = GodotAudio.Bus.getBus(index); | 
					
						
							|  |  |  | 			bus.clear(); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a bus needs to be at the desired position. | 
					
						
							|  |  |  | 		 * @param {number} atPos Position to add the bus | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		add_sample_bus: function (atPos) { | 
					
						
							|  |  |  | 			GodotAudio.Bus.addAt(atPos); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a bus needs to be moved. | 
					
						
							|  |  |  | 		 * @param {number} busIndex Index of the bus to move | 
					
						
							|  |  |  | 		 * @param {number} toPos Index of the new position of the bus | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		move_sample_bus: function (busIndex, toPos) { | 
					
						
							|  |  |  | 			GodotAudio.Bus.move(busIndex, toPos); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when the "send" value of a bus changes. | 
					
						
							|  |  |  | 		 * @param {number} busIndex Index of the bus to update the "send" value | 
					
						
							|  |  |  | 		 * @param {number} sendIndex Index of the bus that is the new "send" | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		set_sample_bus_send: function (busIndex, sendIndex) { | 
					
						
							|  |  |  | 			const bus = GodotAudio.Bus.getBus(busIndex); | 
					
						
							|  |  |  | 			bus.setSend(GodotAudio.Bus.getBus(sendIndex)); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a bus needs its volume db to be updated. | 
					
						
							|  |  |  | 		 * @param {number} busIndex Index of the bus to update its volume db | 
					
						
							|  |  |  | 		 * @param {number} volumeDb Volume of the bus | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		set_sample_bus_volume_db: function (busIndex, volumeDb) { | 
					
						
							|  |  |  | 			const bus = GodotAudio.Bus.getBus(busIndex); | 
					
						
							| 
									
										
										
										
											2024-06-29 14:20:52 -04:00
										 |  |  | 			bus.setVolumeDb(volumeDb); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a bus needs to update its solo status | 
					
						
							|  |  |  | 		 * @param {number} busIndex Index of the bus to update its solo status | 
					
						
							|  |  |  | 		 * @param {boolean} enable Status of the solo | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		set_sample_bus_solo: function (busIndex, enable) { | 
					
						
							|  |  |  | 			const bus = GodotAudio.Bus.getBus(busIndex); | 
					
						
							|  |  |  | 			bus.solo(enable); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * Triggered when a bus needs to update its mute status | 
					
						
							|  |  |  | 		 * @param {number} busIndex Index of the bus to update its mute status | 
					
						
							|  |  |  | 		 * @param {boolean} enable Status of the mute | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		set_sample_bus_mute: function (busIndex, enable) { | 
					
						
							|  |  |  | 			const bus = GodotAudio.Bus.getBus(busIndex); | 
					
						
							|  |  |  | 			bus.mute(enable); | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_is_available__sig: 'i', | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 	godot_audio_is_available__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_is_available: function () { | 
					
						
							|  |  |  | 		if (!(window.AudioContext || window.webkitAudioContext)) { | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return 1; | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_has_worklet__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	godot_audio_has_worklet__sig: 'i', | 
					
						
							|  |  |  | 	godot_audio_has_worklet: function () { | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		return GodotAudio.ctx && GodotAudio.ctx.audioWorklet ? 1 : 0; | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_has_script_processor__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	godot_audio_has_script_processor__sig: 'i', | 
					
						
							|  |  |  | 	godot_audio_has_script_processor: function () { | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		return GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor ? 1 : 0; | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_init__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_init__sig: 'iiiii', | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	godot_audio_init: function ( | 
					
						
							|  |  |  | 		p_mix_rate, | 
					
						
							|  |  |  | 		p_latency, | 
					
						
							|  |  |  | 		p_state_change, | 
					
						
							|  |  |  | 		p_latency_update | 
					
						
							|  |  |  | 	) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 		const statechange = GodotRuntime.get_func(p_state_change); | 
					
						
							|  |  |  | 		const latencyupdate = GodotRuntime.get_func(p_latency_update); | 
					
						
							| 
									
										
										
										
											2021-09-13 03:06:34 +02:00
										 |  |  | 		const mix_rate = GodotRuntime.getHeapValue(p_mix_rate, 'i32'); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		const channels = GodotAudio.init( | 
					
						
							|  |  |  | 			mix_rate, | 
					
						
							|  |  |  | 			p_latency, | 
					
						
							|  |  |  | 			statechange, | 
					
						
							|  |  |  | 			latencyupdate | 
					
						
							|  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2021-09-13 03:06:34 +02:00
										 |  |  | 		GodotRuntime.setHeapValue(p_mix_rate, GodotAudio.ctx.sampleRate, 'i32'); | 
					
						
							|  |  |  | 		return channels; | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_resume__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_resume__sig: 'v', | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 	godot_audio_resume: function () { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 		if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') { | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 			GodotAudio.ctx.resume(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 17:40:15 +02:00
										 |  |  | 	godot_audio_input_start__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_input_start__sig: 'i', | 
					
						
							|  |  |  | 	godot_audio_input_start: function () { | 
					
						
							| 
									
										
										
										
											2020-12-08 11:18:03 +01:00
										 |  |  | 		return GodotAudio.create_input(function (input) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			input.connect(GodotAudio.driver.get_node()); | 
					
						
							|  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 17:40:15 +02:00
										 |  |  | 	godot_audio_input_stop__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_input_stop__sig: 'v', | 
					
						
							|  |  |  | 	godot_audio_input_stop: function () { | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 		if (GodotAudio.input) { | 
					
						
							| 
									
										
										
										
											2020-10-23 18:33:20 +02:00
										 |  |  | 			const tracks = GodotAudio.input['mediaStream']['getTracks'](); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			for (let i = 0; i < tracks.length; i++) { | 
					
						
							| 
									
										
										
										
											2020-10-23 18:33:20 +02:00
										 |  |  | 				tracks[i]['stop'](); | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			GodotAudio.input.disconnect(); | 
					
						
							|  |  |  | 			GodotAudio.input = null; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_stream_is_registered__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_stream_is_registered__sig: 'ii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns if the sample stream is registered | 
					
						
							|  |  |  | 	 * @param {number} streamObjectIdStrPtr Pointer of the streamObjectId | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_stream_is_registered: function (streamObjectIdStrPtr) { | 
					
						
							|  |  |  | 		const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr); | 
					
						
							|  |  |  | 		return Number(GodotAudio.Sample.getSampleOrNull(streamObjectId) != null); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_register_stream__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_register_stream__sig: 'viiiiiii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Registers a stream. | 
					
						
							|  |  |  | 	 * @param {number} streamObjectIdStrPtr StreamObjectId pointer | 
					
						
							|  |  |  | 	 * @param {number} framesPtr Frames pointer | 
					
						
							|  |  |  | 	 * @param {number} framesTotal Frames total value | 
					
						
							|  |  |  | 	 * @param {number} loopModeStrPtr Loop mode pointer | 
					
						
							|  |  |  | 	 * @param {number} loopBegin Loop begin value | 
					
						
							|  |  |  | 	 * @param {number} loopEnd Loop end value | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_register_stream: function ( | 
					
						
							|  |  |  | 		streamObjectIdStrPtr, | 
					
						
							|  |  |  | 		framesPtr, | 
					
						
							|  |  |  | 		framesTotal, | 
					
						
							|  |  |  | 		loopModeStrPtr, | 
					
						
							|  |  |  | 		loopBegin, | 
					
						
							|  |  |  | 		loopEnd | 
					
						
							|  |  |  | 	) { | 
					
						
							|  |  |  | 		const BYTES_PER_FLOAT32 = 4; | 
					
						
							|  |  |  | 		const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr); | 
					
						
							|  |  |  | 		const loopMode = GodotRuntime.parseString(loopModeStrPtr); | 
					
						
							|  |  |  | 		const numberOfChannels = 2; | 
					
						
							|  |  |  | 		const sampleRate = GodotAudio.ctx.sampleRate; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** @type {Float32Array} */ | 
					
						
							|  |  |  | 		const subLeft = GodotRuntime.heapSub(HEAPF32, framesPtr, framesTotal); | 
					
						
							|  |  |  | 		/** @type {Float32Array} */ | 
					
						
							|  |  |  | 		const subRight = GodotRuntime.heapSub( | 
					
						
							|  |  |  | 			HEAPF32, | 
					
						
							|  |  |  | 			framesPtr + framesTotal * BYTES_PER_FLOAT32, | 
					
						
							|  |  |  | 			framesTotal | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const audioBuffer = GodotAudio.ctx.createBuffer( | 
					
						
							|  |  |  | 			numberOfChannels, | 
					
						
							|  |  |  | 			framesTotal, | 
					
						
							|  |  |  | 			sampleRate | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		audioBuffer.copyToChannel(new Float32Array(subLeft), 0, 0); | 
					
						
							|  |  |  | 		audioBuffer.copyToChannel(new Float32Array(subRight), 1, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		GodotAudio.Sample.create( | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				id: streamObjectId, | 
					
						
							|  |  |  | 				audioBuffer, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				loopBegin, | 
					
						
							|  |  |  | 				loopEnd, | 
					
						
							|  |  |  | 				loopMode, | 
					
						
							|  |  |  | 				numberOfChannels, | 
					
						
							|  |  |  | 				sampleRate, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_unregister_stream__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_unregister_stream__sig: 'vi', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Unregisters a stream. | 
					
						
							|  |  |  | 	 * @param {number} streamObjectIdStrPtr StreamObjectId pointer | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_unregister_stream: function (streamObjectIdStrPtr) { | 
					
						
							|  |  |  | 		const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr); | 
					
						
							|  |  |  | 		const sample = GodotAudio.Sample.getSampleOrNull(streamObjectId); | 
					
						
							|  |  |  | 		if (sample != null) { | 
					
						
							|  |  |  | 			sample.clear(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_start__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_start__sig: 'viiiii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * 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} volumePtr Volume pointer | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_start: function ( | 
					
						
							|  |  |  | 		playbackObjectIdStrPtr, | 
					
						
							|  |  |  | 		streamObjectIdStrPtr, | 
					
						
							|  |  |  | 		busIndex, | 
					
						
							|  |  |  | 		offset, | 
					
						
							|  |  |  | 		volumePtr | 
					
						
							|  |  |  | 	) { | 
					
						
							|  |  |  | 		/** @type {string} */ | 
					
						
							|  |  |  | 		const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr); | 
					
						
							|  |  |  | 		/** @type {string} */ | 
					
						
							|  |  |  | 		const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr); | 
					
						
							|  |  |  | 		/** @type {Float32Array} */ | 
					
						
							|  |  |  | 		const volume = GodotRuntime.heapSub(HEAPF32, volumePtr, 8); | 
					
						
							|  |  |  | 		/** @type {SampleNodeConstructorOptions} */ | 
					
						
							|  |  |  | 		const startOptions = { | 
					
						
							|  |  |  | 			offset, | 
					
						
							|  |  |  | 			volume, | 
					
						
							|  |  |  | 			playbackRate: 1, | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 			start: true, | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		}; | 
					
						
							|  |  |  | 		GodotAudio.start_sample( | 
					
						
							|  |  |  | 			playbackObjectId, | 
					
						
							|  |  |  | 			streamObjectId, | 
					
						
							|  |  |  | 			busIndex, | 
					
						
							|  |  |  | 			startOptions | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_stop__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_stop__sig: 'vi', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Stops a sample from playing. | 
					
						
							|  |  |  | 	 * @param {number} playbackObjectIdStrPtr Playback object id pointer | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_stop: function (playbackObjectIdStrPtr) { | 
					
						
							|  |  |  | 		const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr); | 
					
						
							|  |  |  | 		GodotAudio.stop_sample(playbackObjectId); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_set_pause__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_set_pause__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the pause state of a sample. | 
					
						
							|  |  |  | 	 * @param {number} playbackObjectIdStrPtr Playback object id pointer | 
					
						
							|  |  |  | 	 * @param {number} pause Pause state | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_set_pause: function (playbackObjectIdStrPtr, pause) { | 
					
						
							|  |  |  | 		const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr); | 
					
						
							|  |  |  | 		GodotAudio.sample_set_pause(playbackObjectId, Boolean(pause)); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_is_active__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_is_active__sig: 'ii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Returns if the sample is active. | 
					
						
							|  |  |  | 	 * @param {number} playbackObjectIdStrPtr Playback object id pointer | 
					
						
							|  |  |  | 	 * @returns {number} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_is_active: function (playbackObjectIdStrPtr) { | 
					
						
							|  |  |  | 		const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr); | 
					
						
							|  |  |  | 		return Number(GodotAudio.sampleNodes.has(playbackObjectId)); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-06 09:46:37 -04:00
										 |  |  | 	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(); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	godot_audio_sample_update_pitch_scale__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_update_pitch_scale__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Updates the pitch scale of a sample. | 
					
						
							|  |  |  | 	 * @param {number} playbackObjectIdStrPtr Playback object id pointer | 
					
						
							|  |  |  | 	 * @param {number} pitchScale Pitch scale value | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_update_pitch_scale: function ( | 
					
						
							|  |  |  | 		playbackObjectIdStrPtr, | 
					
						
							|  |  |  | 		pitchScale | 
					
						
							|  |  |  | 	) { | 
					
						
							|  |  |  | 		const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr); | 
					
						
							|  |  |  | 		GodotAudio.update_sample_pitch_scale(playbackObjectId, pitchScale); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_set_volumes_linear__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_set_volumes_linear__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the volumes linear of each mentioned bus for the sample. | 
					
						
							|  |  |  | 	 * @param {number} playbackObjectIdStrPtr Playback object id pointer | 
					
						
							|  |  |  | 	 * @param {number} busesPtr Buses array pointer | 
					
						
							|  |  |  | 	 * @param {number} busesSize Buses array size | 
					
						
							|  |  |  | 	 * @param {number} volumesPtr Volumes array pointer | 
					
						
							|  |  |  | 	 * @param {number} volumesSize Volumes array size | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_set_volumes_linear: function ( | 
					
						
							|  |  |  | 		playbackObjectIdStrPtr, | 
					
						
							|  |  |  | 		busesPtr, | 
					
						
							|  |  |  | 		busesSize, | 
					
						
							|  |  |  | 		volumesPtr, | 
					
						
							|  |  |  | 		volumesSize | 
					
						
							|  |  |  | 	) { | 
					
						
							|  |  |  | 		/** @type {string} */ | 
					
						
							|  |  |  | 		const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** @type {Uint32Array} */ | 
					
						
							|  |  |  | 		const buses = GodotRuntime.heapSub(HEAP32, busesPtr, busesSize); | 
					
						
							|  |  |  | 		/** @type {Float32Array} */ | 
					
						
							|  |  |  | 		const volumes = GodotRuntime.heapSub(HEAPF32, volumesPtr, volumesSize); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		GodotAudio.sample_set_volumes_linear( | 
					
						
							|  |  |  | 			playbackObjectId, | 
					
						
							|  |  |  | 			Array.from(buses), | 
					
						
							|  |  |  | 			volumes | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_count__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_count__sig: 'vi', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the bus count. | 
					
						
							|  |  |  | 	 * @param {number} count Bus count | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_count: function (count) { | 
					
						
							|  |  |  | 		GodotAudio.set_sample_bus_count(count); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_remove__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_remove__sig: 'vi', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Removes a bus. | 
					
						
							|  |  |  | 	 * @param {number} index Index of the bus to remove | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_remove: function (index) { | 
					
						
							|  |  |  | 		GodotAudio.remove_sample_bus(index); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_add__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_add__sig: 'vi', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Adds a bus at the defined position. | 
					
						
							|  |  |  | 	 * @param {number} atPos Position to add the bus | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_add: function (atPos) { | 
					
						
							|  |  |  | 		GodotAudio.add_sample_bus(atPos); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_move__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_move__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Moves the bus from a position to another. | 
					
						
							|  |  |  | 	 * @param {number} fromPos Position of the bus to move | 
					
						
							|  |  |  | 	 * @param {number} toPos Final position of the bus | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_move: function (fromPos, toPos) { | 
					
						
							|  |  |  | 		GodotAudio.move_sample_bus(fromPos, toPos); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_send__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_send__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the "send" of a bus. | 
					
						
							|  |  |  | 	 * @param {number} bus Position of the bus to set the send | 
					
						
							|  |  |  | 	 * @param {number} sendIndex Position of the "send" bus | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_send: function (bus, sendIndex) { | 
					
						
							|  |  |  | 		GodotAudio.set_sample_bus_send(bus, sendIndex); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_volume_db__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_volume_db__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the volume db of a bus. | 
					
						
							|  |  |  | 	 * @param {number} bus Position of the bus to set the volume db | 
					
						
							|  |  |  | 	 * @param {number} volumeDb Volume db to set | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_volume_db: function (bus, volumeDb) { | 
					
						
							|  |  |  | 		GodotAudio.set_sample_bus_volume_db(bus, volumeDb); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_solo__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_solo__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the state of solo for a bus | 
					
						
							|  |  |  | 	 * @param {number} bus Position of the bus to set the solo state | 
					
						
							|  |  |  | 	 * @param {number} enable State of the solo | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_solo: function (bus, enable) { | 
					
						
							|  |  |  | 		GodotAudio.set_sample_bus_solo(bus, Boolean(enable)); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_mute__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_mute__sig: 'vii', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the state of mute for a bus | 
					
						
							|  |  |  | 	 * @param {number} bus Position of the bus to set the mute state | 
					
						
							|  |  |  | 	 * @param {number} enable State of the mute | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_bus_set_mute: function (bus, enable) { | 
					
						
							|  |  |  | 		GodotAudio.set_sample_bus_mute(bus, Boolean(enable)); | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2024-07-07 13:31:56 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	godot_audio_sample_set_finished_callback__proxy: 'sync', | 
					
						
							|  |  |  | 	godot_audio_sample_set_finished_callback__sig: 'vi', | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Sets the finished callback | 
					
						
							|  |  |  | 	 * @param {Number} callbackPtr Finished callback pointer | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	godot_audio_sample_set_finished_callback: function (callbackPtr) { | 
					
						
							|  |  |  | 		GodotAudio.sampleFinishedCallback = GodotRuntime.get_func(callbackPtr); | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2020-10-02 13:49:00 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | autoAddDeps(_GodotAudio, '$GodotAudio'); | 
					
						
							|  |  |  | mergeInto(LibraryManager.library, _GodotAudio); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * The AudioWorklet API driver, used when threads are available. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const GodotAudioWorklet = { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 	$GodotAudioWorklet__deps: ['$GodotAudio', '$GodotConfig'], | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 	$GodotAudioWorklet: { | 
					
						
							|  |  |  | 		promise: null, | 
					
						
							|  |  |  | 		worklet: null, | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		ring_buffer: null, | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		create: function (channels) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 			const path = GodotConfig.locate_file('godot.audio.worklet.js'); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 			GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet | 
					
						
							|  |  |  | 				.addModule(path) | 
					
						
							|  |  |  | 				.then(function () { | 
					
						
							|  |  |  | 					GodotAudioWorklet.worklet = new AudioWorkletNode( | 
					
						
							|  |  |  | 						GodotAudio.ctx, | 
					
						
							|  |  |  | 						'godot-processor', | 
					
						
							|  |  |  | 						{ | 
					
						
							|  |  |  | 							outputChannelCount: [channels], | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 					return Promise.resolve(); | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			GodotAudio.driver = GodotAudioWorklet; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		start: function (in_buf, out_buf, state) { | 
					
						
							|  |  |  | 			GodotAudioWorklet.promise.then(function () { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				const node = GodotAudioWorklet.worklet; | 
					
						
							|  |  |  | 				node.connect(GodotAudio.ctx.destination); | 
					
						
							|  |  |  | 				node.port.postMessage({ | 
					
						
							|  |  |  | 					'cmd': 'start', | 
					
						
							|  |  |  | 					'data': [state, in_buf, out_buf], | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 				node.port.onmessage = function (event) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 					GodotRuntime.error(event.data); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		start_no_threads: function ( | 
					
						
							|  |  |  | 			p_out_buf, | 
					
						
							|  |  |  | 			p_out_size, | 
					
						
							|  |  |  | 			out_callback, | 
					
						
							|  |  |  | 			p_in_buf, | 
					
						
							|  |  |  | 			p_in_size, | 
					
						
							|  |  |  | 			in_callback | 
					
						
							|  |  |  | 		) { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 			function RingBuffer() { | 
					
						
							|  |  |  | 				let wpos = 0; | 
					
						
							|  |  |  | 				let rpos = 0; | 
					
						
							|  |  |  | 				let pending_samples = 0; | 
					
						
							|  |  |  | 				const wbuf = new Float32Array(p_out_size); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				function send(port) { | 
					
						
							|  |  |  | 					if (pending_samples === 0) { | 
					
						
							|  |  |  | 						return; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					const buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); | 
					
						
							|  |  |  | 					const size = buffer.length; | 
					
						
							|  |  |  | 					const tot_sent = pending_samples; | 
					
						
							|  |  |  | 					out_callback(wpos, pending_samples); | 
					
						
							|  |  |  | 					if (wpos + pending_samples >= size) { | 
					
						
							|  |  |  | 						const high = size - wpos; | 
					
						
							|  |  |  | 						wbuf.set(buffer.subarray(wpos, size)); | 
					
						
							|  |  |  | 						pending_samples -= high; | 
					
						
							|  |  |  | 						wpos = 0; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if (pending_samples > 0) { | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 						wbuf.set( | 
					
						
							|  |  |  | 							buffer.subarray(wpos, wpos + pending_samples), | 
					
						
							|  |  |  | 							tot_sent - pending_samples | 
					
						
							|  |  |  | 						); | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 					} | 
					
						
							|  |  |  | 					port.postMessage({ 'cmd': 'chunk', 'data': wbuf.subarray(0, tot_sent) }); | 
					
						
							|  |  |  | 					wpos += pending_samples; | 
					
						
							|  |  |  | 					pending_samples = 0; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				this.receive = function (recv_buf) { | 
					
						
							|  |  |  | 					const buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); | 
					
						
							|  |  |  | 					const from = rpos; | 
					
						
							|  |  |  | 					let to_write = recv_buf.length; | 
					
						
							|  |  |  | 					let high = 0; | 
					
						
							|  |  |  | 					if (rpos + to_write >= p_in_size) { | 
					
						
							|  |  |  | 						high = p_in_size - rpos; | 
					
						
							|  |  |  | 						buffer.set(recv_buf.subarray(0, high), rpos); | 
					
						
							|  |  |  | 						to_write -= high; | 
					
						
							|  |  |  | 						rpos = 0; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if (to_write) { | 
					
						
							|  |  |  | 						buffer.set(recv_buf.subarray(high, to_write), rpos); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					in_callback(from, recv_buf.length); | 
					
						
							|  |  |  | 					rpos += to_write; | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 				this.consumed = function (size, port) { | 
					
						
							|  |  |  | 					pending_samples += size; | 
					
						
							|  |  |  | 					send(port); | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			GodotAudioWorklet.ring_buffer = new RingBuffer(); | 
					
						
							|  |  |  | 			GodotAudioWorklet.promise.then(function () { | 
					
						
							|  |  |  | 				const node = GodotAudioWorklet.worklet; | 
					
						
							|  |  |  | 				const buffer = GodotRuntime.heapSlice(HEAPF32, p_out_buf, p_out_size); | 
					
						
							|  |  |  | 				node.connect(GodotAudio.ctx.destination); | 
					
						
							|  |  |  | 				node.port.postMessage({ | 
					
						
							|  |  |  | 					'cmd': 'start_nothreads', | 
					
						
							|  |  |  | 					'data': [buffer, p_in_size], | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 				node.port.onmessage = function (event) { | 
					
						
							|  |  |  | 					if (!GodotAudioWorklet.worklet) { | 
					
						
							|  |  |  | 						return; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if (event.data['cmd'] === 'read') { | 
					
						
							|  |  |  | 						const read = event.data['data']; | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 						GodotAudioWorklet.ring_buffer.consumed( | 
					
						
							|  |  |  | 							read, | 
					
						
							|  |  |  | 							GodotAudioWorklet.worklet.port | 
					
						
							|  |  |  | 						); | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 					} else if (event.data['cmd'] === 'input') { | 
					
						
							|  |  |  | 						const buf = event.data['data']; | 
					
						
							|  |  |  | 						if (buf.length > p_in_size) { | 
					
						
							|  |  |  | 							GodotRuntime.error('Input chunk is too big'); | 
					
						
							|  |  |  | 							return; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						GodotAudioWorklet.ring_buffer.receive(buf); | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						GodotRuntime.error(event.data); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		get_node: function () { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			return GodotAudioWorklet.worklet; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		close: function () { | 
					
						
							|  |  |  | 			return new Promise(function (resolve, reject) { | 
					
						
							| 
									
										
										
										
											2021-03-08 14:07:19 +01:00
										 |  |  | 				if (GodotAudioWorklet.promise === null) { | 
					
						
							|  |  |  | 					return; | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-09-08 09:44:14 +02:00
										 |  |  | 				const p = GodotAudioWorklet.promise; | 
					
						
							|  |  |  | 				p.then(function () { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 					GodotAudioWorklet.worklet.port.postMessage({ | 
					
						
							|  |  |  | 						'cmd': 'stop', | 
					
						
							|  |  |  | 						'data': null, | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 					GodotAudioWorklet.worklet.disconnect(); | 
					
						
							| 
									
										
										
										
											2022-09-08 09:44:14 +02:00
										 |  |  | 					GodotAudioWorklet.worklet.port.onmessage = null; | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 					GodotAudioWorklet.worklet = null; | 
					
						
							|  |  |  | 					GodotAudioWorklet.promise = null; | 
					
						
							|  |  |  | 					resolve(); | 
					
						
							| 
									
										
										
										
											2022-09-08 09:44:14 +02:00
										 |  |  | 				}).catch(function (err) { | 
					
						
							|  |  |  | 					// Aborted?
 | 
					
						
							|  |  |  | 					GodotRuntime.error(err); | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_worklet_create__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	godot_audio_worklet_create__sig: 'ii', | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 	godot_audio_worklet_create: function (channels) { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		try { | 
					
						
							|  |  |  | 			GodotAudioWorklet.create(channels); | 
					
						
							|  |  |  | 		} catch (e) { | 
					
						
							|  |  |  | 			GodotRuntime.error('Error starting AudioDriverWorklet', e); | 
					
						
							|  |  |  | 			return 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return 0; | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_worklet_start__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_worklet_start__sig: 'viiiii', | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	godot_audio_worklet_start: function ( | 
					
						
							|  |  |  | 		p_in_buf, | 
					
						
							|  |  |  | 		p_in_size, | 
					
						
							|  |  |  | 		p_out_buf, | 
					
						
							|  |  |  | 		p_out_size, | 
					
						
							|  |  |  | 		p_state | 
					
						
							|  |  |  | 	) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 		const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); | 
					
						
							|  |  |  | 		const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); | 
					
						
							|  |  |  | 		const state = GodotRuntime.heapSub(HEAP32, p_state, 4); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 		GodotAudioWorklet.start(in_buffer, out_buffer, state); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_worklet_start_no_threads__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	godot_audio_worklet_start_no_threads__sig: 'viiiiii', | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	godot_audio_worklet_start_no_threads: function ( | 
					
						
							|  |  |  | 		p_out_buf, | 
					
						
							|  |  |  | 		p_out_size, | 
					
						
							|  |  |  | 		p_out_callback, | 
					
						
							|  |  |  | 		p_in_buf, | 
					
						
							|  |  |  | 		p_in_size, | 
					
						
							|  |  |  | 		p_in_callback | 
					
						
							|  |  |  | 	) { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		const out_callback = GodotRuntime.get_func(p_out_callback); | 
					
						
							|  |  |  | 		const in_callback = GodotRuntime.get_func(p_in_callback); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		GodotAudioWorklet.start_no_threads( | 
					
						
							|  |  |  | 			p_out_buf, | 
					
						
							|  |  |  | 			p_out_size, | 
					
						
							|  |  |  | 			out_callback, | 
					
						
							|  |  |  | 			p_in_buf, | 
					
						
							|  |  |  | 			p_in_size, | 
					
						
							|  |  |  | 			in_callback | 
					
						
							|  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_worklet_state_wait__sig: 'iiii', | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	godot_audio_worklet_state_wait: function ( | 
					
						
							|  |  |  | 		p_state, | 
					
						
							|  |  |  | 		p_idx, | 
					
						
							|  |  |  | 		p_expected, | 
					
						
							|  |  |  | 		p_timeout | 
					
						
							|  |  |  | 	) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 		Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout); | 
					
						
							|  |  |  | 		return Atomics.load(HEAP32, (p_state >> 2) + p_idx); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_worklet_state_add__sig: 'iiii', | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 	godot_audio_worklet_state_add: function (p_state, p_idx, p_value) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 		return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_worklet_state_get__sig: 'iii', | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 	godot_audio_worklet_state_get: function (p_state, p_idx) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 		return Atomics.load(HEAP32, (p_state >> 2) + p_idx); | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet'); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | mergeInto(LibraryManager.library, GodotAudioWorklet); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  |  * The ScriptProcessorNode API, used when threads are disabled. | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  |  */ | 
					
						
							|  |  |  | const GodotAudioScript = { | 
					
						
							|  |  |  | 	$GodotAudioScript__deps: ['$GodotAudio'], | 
					
						
							|  |  |  | 	$GodotAudioScript: { | 
					
						
							|  |  |  | 		script: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		create: function (buffer_length, channel_count) { | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 			GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor( | 
					
						
							|  |  |  | 				buffer_length, | 
					
						
							|  |  |  | 				2, | 
					
						
							|  |  |  | 				channel_count | 
					
						
							|  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			GodotAudio.driver = GodotAudioScript; | 
					
						
							|  |  |  | 			return GodotAudioScript.script.bufferSize; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) { | 
					
						
							|  |  |  | 			GodotAudioScript.script.onaudioprocess = function (event) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				// Read input
 | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 				const inb = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				const input = event.inputBuffer; | 
					
						
							|  |  |  | 				if (GodotAudio.input) { | 
					
						
							|  |  |  | 					const inlen = input.getChannelData(0).length; | 
					
						
							|  |  |  | 					for (let ch = 0; ch < 2; ch++) { | 
					
						
							|  |  |  | 						const data = input.getChannelData(ch); | 
					
						
							|  |  |  | 						for (let s = 0; s < inlen; s++) { | 
					
						
							|  |  |  | 							inb[s * 2 + ch] = data[s]; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Let Godot process the input/output.
 | 
					
						
							|  |  |  | 				onprocess(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Write the output.
 | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 				const outb = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				const output = event.outputBuffer; | 
					
						
							|  |  |  | 				const channels = output.numberOfChannels; | 
					
						
							|  |  |  | 				for (let ch = 0; ch < channels; ch++) { | 
					
						
							|  |  |  | 					const data = output.getChannelData(ch); | 
					
						
							|  |  |  | 					// Loop through samples and assign computed values.
 | 
					
						
							|  |  |  | 					for (let sample = 0; sample < data.length; sample++) { | 
					
						
							|  |  |  | 						data[sample] = outb[sample * channels + ch]; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			GodotAudioScript.script.connect(GodotAudio.ctx.destination); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		get_node: function () { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 			return GodotAudioScript.script; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 		close: function () { | 
					
						
							|  |  |  | 			return new Promise(function (resolve, reject) { | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 				GodotAudioScript.script.disconnect(); | 
					
						
							|  |  |  | 				GodotAudioScript.script.onaudioprocess = null; | 
					
						
							|  |  |  | 				GodotAudioScript.script = null; | 
					
						
							|  |  |  | 				resolve(); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_script_create__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_script_create__sig: 'iii', | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | 	godot_audio_script_create: function (buffer_length, channel_count) { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		const buf_len = GodotRuntime.getHeapValue(buffer_length, 'i32'); | 
					
						
							|  |  |  | 		try { | 
					
						
							|  |  |  | 			const out_len = GodotAudioScript.create(buf_len, channel_count); | 
					
						
							|  |  |  | 			GodotRuntime.setHeapValue(buffer_length, out_len, 'i32'); | 
					
						
							|  |  |  | 		} catch (e) { | 
					
						
							|  |  |  | 			GodotRuntime.error('Error starting AudioDriverScriptProcessor', e); | 
					
						
							|  |  |  | 			return 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return 0; | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 09:42:03 -04:00
										 |  |  | 	godot_audio_script_start__proxy: 'sync', | 
					
						
							| 
									
										
										
										
											2020-12-03 12:51:17 +01:00
										 |  |  | 	godot_audio_script_start__sig: 'viiiii', | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 	godot_audio_script_start: function ( | 
					
						
							|  |  |  | 		p_in_buf, | 
					
						
							|  |  |  | 		p_in_size, | 
					
						
							|  |  |  | 		p_out_buf, | 
					
						
							|  |  |  | 		p_out_size, | 
					
						
							|  |  |  | 		p_cb | 
					
						
							|  |  |  | 	) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 		const onprocess = GodotRuntime.get_func(p_cb); | 
					
						
							| 
									
										
										
										
											2024-04-18 10:50:34 -04:00
										 |  |  | 		GodotAudioScript.start( | 
					
						
							|  |  |  | 			p_in_buf, | 
					
						
							|  |  |  | 			p_in_size, | 
					
						
							|  |  |  | 			p_out_buf, | 
					
						
							|  |  |  | 			p_out_size, | 
					
						
							|  |  |  | 			onprocess | 
					
						
							|  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 12:13:52 +01:00
										 |  |  | autoAddDeps(GodotAudioScript, '$GodotAudioScript'); | 
					
						
							| 
									
										
										
										
											2020-11-10 11:05:22 +01:00
										 |  |  | mergeInto(LibraryManager.library, GodotAudioScript); |