summaryrefslogtreecommitdiffstats
path: root/platform/web/js
diff options
context:
space:
mode:
authorAdam Scott <ascott.ca@gmail.com>2024-04-18 10:50:34 -0400
committerAdam Scott <ascott.ca@gmail.com>2024-06-18 11:06:31 -0400
commit52fa4f05f3945fdf511c249adede9b6d07c51beb (patch)
tree2ac2aca86c09dd757fd4a8b5defab932f2b8eb5d /platform/web/js
parenteb20a68b323c1fcb75492f8132e1bd6d321713ec (diff)
downloadredot-engine-52fa4f05f3945fdf511c249adede9b6d07c51beb.tar.gz
Add samples playback support
Diffstat (limited to 'platform/web/js')
-rw-r--r--platform/web/js/engine/features.js3
-rw-r--r--platform/web/js/libs/library_godot_audio.js1614
2 files changed, 1586 insertions, 31 deletions
diff --git a/platform/web/js/engine/features.js b/platform/web/js/engine/features.js
index 601fa4a117..c366683dfa 100644
--- a/platform/web/js/engine/features.js
+++ b/platform/web/js/engine/features.js
@@ -76,7 +76,8 @@ const Features = {
*/
getMissingFeatures: function (supportedFeatures = {}) {
const {
- threads: supportsThreads = true,
+ // Quotes are needed for the Closure compiler.
+ 'threads': supportsThreads = true,
} = supportedFeatures;
const missing = [];
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js
index b54c5cac85..d7baece781 100644
--- a/platform/web/js/libs/library_godot_audio.js
+++ b/platform/web/js/libs/library_godot_audio.js
@@ -28,18 +28,1048 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-const GodotAudio = {
+/**
+ * @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
+ * @param {SampleOptions} [options={}] Optional params
+ * @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
+ * @param {SampleOptions} [options={}] Optional params
+ * @constructor
+ */
+ 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() {
+ this.audioBuffer = null;
+ 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');
+ }
+ /** @type {Float32Array[]} */
+ 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
+ */
+ 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
+ * }} 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
+ * @param {SampleNodeOptions} [options={}] Optional params
+ * @constructor
+ */
+ constructor(params, options = {}) {
+ /** @type {string} */
+ this.id = params.id;
+ /** @type {string} */
+ this.streamObjectId = params.streamObjectId;
+ /** @type {number} */
+ this.offset = options.offset ?? 0;
+ /** @type {LoopMode} */
+ this.startTime = options.startTime ?? 0;
+ /** @type {number} */
+ this.pauseTime = 0;
+ /** @type {number} */
+ this._playbackRate = 44100;
+ /** @type {LoopMode} */
+ this._loopMode = 'disabled';
+ /** @type {number} */
+ this._pitchScale = 1;
+ /** @type {Map<Bus, SampleNodeBus>} */
+ this._sampleNodeBuses = new Map();
+ /** @type {AudioBufferSourceNode} */
+ this._source = GodotAudio.ctx.createBufferSource();
+
+ this.setPlaybackRate(options.playbackRate ?? 44100);
+ this.setLoopMode(options.loopMode ?? this.getSample().loopMode ?? 'disabled');
+ this._source.buffer = this.getSample().getAudioBuffer();
+
+ /** @type {SampleNode} */
+ // eslint-disable-next-line consistent-this
+ const self = this;
+ this._source.addEventListener('ended', (_) => {
+ switch (self.getSample().loopMode) {
+ case 'disabled':
+ GodotAudio.SampleNode.stopSampleNode(self.id);
+ break;
+ default:
+ // do nothing
+ }
+ });
+
+ const bus = GodotAudio.Bus.getBus(params.busIndex);
+ const sampleNodeBus = this.getSampleNodeBus(bus);
+ sampleNodeBus.setVolume(options.volume);
+ }
+
+ /**
+ * Gets the loop mode of the current instance.
+ * @returns {LoopMode}
+ */
+ getLoopMode() {
+ return this._loopMode;
+ }
+
+ /**
+ * Sets the loop mode of the current instance.
+ * @param {LoopMode} val Value to set.
+ * @returns {void}
+ */
+ setLoopMode(val) {
+ this._loopMode = val;
+ switch (val) {
+ case 'forward':
+ case 'backward':
+ this._source.loop = true;
+ break;
+ default:
+ this._source.loop = false;
+ }
+ }
+
+ /**
+ * Gets the playback rate.
+ * @returns {number}
+ */
+ getPlaybackRate() {
+ return this._playbackRate;
+ }
+
+ /**
+ * 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() {
+ this._source.start(this.offset);
+ }
+
+ /**
+ * Stops the `SampleNode`.
+ * @returns {void}
+ */
+ stop() {
+ this._source.stop();
+ this.clear();
+ }
+
+ /**
+ * Pauses the `SampleNode`.
+ * @param {boolean} [enable=true] State of the pause.
+ * @returns {void}
+ */
+ pause(enable = true) {
+ if (enable) {
+ this.pauseTime = (GodotAudio.ctx.currentTime - this.startTime) / this.playbackRate;
+ this._source.stop();
+ return;
+ }
+
+ if (this.pauseTime === 0) {
+ return;
+ }
+
+ this._source.disconnect();
+ this._source = GodotAudio.ctx.createBufferSource();
+
+ this._source.buffer = this.getSample().getAudioBuffer();
+ this._source.connect(this._gain);
+ this._source.start(this.offset + this.pauseTime);
+ }
+
+ /**
+ * 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.
+ * @param {Bus[]} buses
+ * @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);
+ }
+
+ /**
+ * Clears the `SampleNode`.
+ * @returns {void}
+ */
+ clear() {
+ this._source.stop();
+ this._source.disconnect();
+ this._source = null;
+
+ for (const sampleNodeBus of this._sampleNodeBuses.values()) {
+ sampleNodeBus.clear();
+ }
+ this._sampleNodeBuses.clear();
+ this._sampleNodeBuses = null;
+
+ GodotAudio.SampleNode.delete(this.id);
+ }
+
+ /**
+ * 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();
+ }
+}
+
+/**
+ * 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);
+ let buses = GodotAudio.buses;
+ buses = buses.filter((_, i) => i !== fromIndex);
+ // 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
+ */
+ 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) {
+ this._gainNode.gain.value = GodotAudio.db_to_linear(val);
+ }
+
+ /**
+ * 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);
+ }
+
+ /** @type {Bus["prototype"]["_syncSampleNodes"]} */
+ _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 = {
$GodotAudio__deps: ['$GodotRuntime', '$GodotOS'],
$GodotAudio: {
+ /**
+ * 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.
+ * @type {Bus[]}
+ */
+ buses: null,
+ /**
+ * Reference to the current bus in solo mode.
+ * @type {Bus | null}
+ */
+ busSolo: null,
+ Bus,
+
+ /** @type {AudioContext} */
ctx: null,
input: null,
driver: null,
interval: 0,
+ /**
+ * 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);
+ },
+
init: function (mix_rate, latency, onstatechange, onlatencyupdate) {
+ // Initialize classes static values.
+ GodotAudio.samples = new Map();
+ GodotAudio.sampleNodes = new Map();
+ GodotAudio.buses = [];
+ GodotAudio.busSolo = null;
+
const opts = {};
// If mix_rate is 0, let the browser choose.
if (mix_rate) {
+ GodotAudio.sampleRate = mix_rate;
opts['sampleRate'] = mix_rate;
}
// Do not specify, leave 'interactive' for good performance.
@@ -58,8 +1088,8 @@ const GodotAudio = {
case 'closed':
state = 2;
break;
-
- // no default
+ default:
+ // Do nothing.
}
onstatechange(state);
};
@@ -148,6 +1178,163 @@ const GodotAudio = {
resolve();
});
},
+
+ /**
+ * 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);
+ const sampleNode = GodotAudio.SampleNode.create(
+ {
+ busIndex,
+ id: playbackObjectId,
+ streamObjectId,
+ },
+ startOptions
+ );
+ sampleNode.start();
+ },
+
+ /**
+ * 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
+ * @param {number[]} busIndexes Indexes of the buses that need to be updated
+ * @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);
+ bus.volumeDb = volumeDb;
+ },
+
+ /**
+ * 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);
+ },
},
godot_audio_is_available__sig: 'i',
@@ -162,22 +1349,32 @@ const GodotAudio = {
godot_audio_has_worklet__proxy: 'sync',
godot_audio_has_worklet__sig: 'i',
godot_audio_has_worklet: function () {
- return (GodotAudio.ctx && GodotAudio.ctx.audioWorklet) ? 1 : 0;
+ return GodotAudio.ctx && GodotAudio.ctx.audioWorklet ? 1 : 0;
},
godot_audio_has_script_processor__proxy: 'sync',
godot_audio_has_script_processor__sig: 'i',
godot_audio_has_script_processor: function () {
- return (GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor) ? 1 : 0;
+ return GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor ? 1 : 0;
},
godot_audio_init__proxy: 'sync',
godot_audio_init__sig: 'iiiii',
- godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) {
+ godot_audio_init: function (
+ p_mix_rate,
+ p_latency,
+ p_state_change,
+ p_latency_update
+ ) {
const statechange = GodotRuntime.get_func(p_state_change);
const latencyupdate = GodotRuntime.get_func(p_latency_update);
const mix_rate = GodotRuntime.getHeapValue(p_mix_rate, 'i32');
- const channels = GodotAudio.init(mix_rate, p_latency, statechange, latencyupdate);
+ const channels = GodotAudio.init(
+ mix_rate,
+ p_latency,
+ statechange,
+ latencyupdate
+ );
GodotRuntime.setHeapValue(p_mix_rate, GodotAudio.ctx.sampleRate, 'i32');
return channels;
},
@@ -210,10 +1407,311 @@ const GodotAudio = {
GodotAudio.input = null;
}
},
+
+ 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,
+ };
+ 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));
+ },
+
+ 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));
+ },
};
-autoAddDeps(GodotAudio, '$GodotAudio');
-mergeInto(LibraryManager.library, GodotAudio);
+autoAddDeps(_GodotAudio, '$GodotAudio');
+mergeInto(LibraryManager.library, _GodotAudio);
/**
* The AudioWorklet API driver, used when threads are available.
@@ -227,16 +1725,18 @@ const GodotAudioWorklet = {
create: function (channels) {
const path = GodotConfig.locate_file('godot.audio.worklet.js');
- GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function () {
- GodotAudioWorklet.worklet = new AudioWorkletNode(
- GodotAudio.ctx,
- 'godot-processor',
- {
- 'outputChannelCount': [channels],
- }
- );
- return Promise.resolve();
- });
+ GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet
+ .addModule(path)
+ .then(function () {
+ GodotAudioWorklet.worklet = new AudioWorkletNode(
+ GodotAudio.ctx,
+ 'godot-processor',
+ {
+ outputChannelCount: [channels],
+ }
+ );
+ return Promise.resolve();
+ });
GodotAudio.driver = GodotAudioWorklet;
},
@@ -254,7 +1754,14 @@ const GodotAudioWorklet = {
});
},
- start_no_threads: function (p_out_buf, p_out_size, out_callback, p_in_buf, p_in_size, in_callback) {
+ start_no_threads: function (
+ p_out_buf,
+ p_out_size,
+ out_callback,
+ p_in_buf,
+ p_in_size,
+ in_callback
+ ) {
function RingBuffer() {
let wpos = 0;
let rpos = 0;
@@ -276,7 +1783,10 @@ const GodotAudioWorklet = {
wpos = 0;
}
if (pending_samples > 0) {
- wbuf.set(buffer.subarray(wpos, wpos + pending_samples), tot_sent - pending_samples);
+ wbuf.set(
+ buffer.subarray(wpos, wpos + pending_samples),
+ tot_sent - pending_samples
+ );
}
port.postMessage({ 'cmd': 'chunk', 'data': wbuf.subarray(0, tot_sent) });
wpos += pending_samples;
@@ -319,7 +1829,10 @@ const GodotAudioWorklet = {
}
if (event.data['cmd'] === 'read') {
const read = event.data['data'];
- GodotAudioWorklet.ring_buffer.consumed(read, GodotAudioWorklet.worklet.port);
+ GodotAudioWorklet.ring_buffer.consumed(
+ read,
+ GodotAudioWorklet.worklet.port
+ );
} else if (event.data['cmd'] === 'input') {
const buf = event.data['data'];
if (buf.length > p_in_size) {
@@ -376,7 +1889,13 @@ const GodotAudioWorklet = {
godot_audio_worklet_start__proxy: 'sync',
godot_audio_worklet_start__sig: 'viiiii',
- godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
+ godot_audio_worklet_start: function (
+ p_in_buf,
+ p_in_size,
+ p_out_buf,
+ p_out_size,
+ p_state
+ ) {
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);
@@ -385,14 +1904,33 @@ const GodotAudioWorklet = {
godot_audio_worklet_start_no_threads__proxy: 'sync',
godot_audio_worklet_start_no_threads__sig: 'viiiiii',
- 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) {
+ 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
+ ) {
const out_callback = GodotRuntime.get_func(p_out_callback);
const in_callback = GodotRuntime.get_func(p_in_callback);
- GodotAudioWorklet.start_no_threads(p_out_buf, p_out_size, out_callback, p_in_buf, p_in_size, in_callback);
+ GodotAudioWorklet.start_no_threads(
+ p_out_buf,
+ p_out_size,
+ out_callback,
+ p_in_buf,
+ p_in_size,
+ in_callback
+ );
},
godot_audio_worklet_state_wait__sig: 'iiii',
- godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) {
+ godot_audio_worklet_state_wait: function (
+ p_state,
+ p_idx,
+ p_expected,
+ p_timeout
+ ) {
Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
},
@@ -412,7 +1950,7 @@ autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
mergeInto(LibraryManager.library, GodotAudioWorklet);
/*
- * The deprecated ScriptProcessorNode API, used when threads are disabled.
+ * The ScriptProcessorNode API, used when threads are disabled.
*/
const GodotAudioScript = {
$GodotAudioScript__deps: ['$GodotAudio'],
@@ -420,7 +1958,11 @@ const GodotAudioScript = {
script: null,
create: function (buffer_length, channel_count) {
- GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count);
+ GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(
+ buffer_length,
+ 2,
+ channel_count
+ );
GodotAudio.driver = GodotAudioScript;
return GodotAudioScript.script.bufferSize;
},
@@ -488,9 +2030,21 @@ const GodotAudioScript = {
godot_audio_script_start__proxy: 'sync',
godot_audio_script_start__sig: 'viiiii',
- godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
+ godot_audio_script_start: function (
+ p_in_buf,
+ p_in_size,
+ p_out_buf,
+ p_out_size,
+ p_cb
+ ) {
const onprocess = GodotRuntime.get_func(p_cb);
- GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess);
+ GodotAudioScript.start(
+ p_in_buf,
+ p_in_size,
+ p_out_buf,
+ p_out_size,
+ onprocess
+ );
},
};