UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

395 lines 13.6 kB
import { StaticSound } from "../abstractAudio/staticSound.js"; import { StaticSoundBuffer } from "../abstractAudio/staticSoundBuffer.js"; import { _StaticSoundInstance } from "../abstractAudio/staticSoundInstance.js"; import { _HasSpatialAudioOptions } from "../abstractAudio/subProperties/abstractSpatialAudio.js"; import { _StereoAudio } from "../abstractAudio/subProperties/stereoAudio.js"; import { _CleanUrl, _FileExtensionRegex } from "../audioUtils.js"; import { _WebAudioBusAndSoundSubGraph } from "./subNodes/webAudioBusAndSoundSubGraph.js"; import { _SpatialWebAudio } from "./subProperties/spatialWebAudio.js"; /** @internal */ export class _WebAudioStaticSound extends StaticSound { /** @internal */ constructor(name, engine, options) { super(name, engine); this._spatial = null; this._spatialAutoUpdate = true; this._spatialMinUpdateTime = 0; this._stereo = null; if (typeof options.spatialAutoUpdate === "boolean") { this._spatialAutoUpdate = options.spatialAutoUpdate; } if (typeof options.spatialMinUpdateTime === "number") { this._spatialMinUpdateTime = options.spatialMinUpdateTime; } this._options = { autoplay: options.autoplay ?? false, duration: options.duration ?? 0, loop: options.loop ?? false, loopEnd: options.loopEnd ?? 0, loopStart: options.loopStart ?? 0, maxInstances: options.maxInstances ?? Infinity, pitch: options.pitch ?? 0, playbackRate: options.playbackRate ?? 1, startOffset: options.startOffset ?? 0, }; this._subGraph = new _WebAudioStaticSound._SubGraph(this); } /** @internal */ async _init(source, options) { this._audioContext = this.engine._audioContext; if (source instanceof _WebAudioStaticSoundBuffer) { this._buffer = source; } else if (typeof source === "string" || Array.isArray(source) || source instanceof ArrayBuffer || source instanceof AudioBuffer) { this._buffer = (await this.engine.createSoundBufferAsync(source, options)); } if (options.outBus) { this.outBus = options.outBus; } else { await this.engine.isReadyPromise; this.outBus = this.engine.defaultMainBus; } await this._subGraph.init(options); if (_HasSpatialAudioOptions(options)) { this._initSpatialProperty(); } if (options.autoplay) { this.play(); } this.engine._addNode(this); } /** @internal */ get buffer() { return this._buffer; } /** @internal */ get _inNode() { return this._subGraph._inNode; } /** @internal */ get _outNode() { return this._subGraph._outNode; } /** @internal */ get spatial() { if (this._spatial) { return this._spatial; } return this._initSpatialProperty(); } /** @internal */ get stereo() { return this._stereo ?? (this._stereo = new _StereoAudio(this._subGraph)); } /** @internal */ dispose() { super.dispose(); this._spatial?.dispose(); this._spatial = null; this._stereo = null; this._subGraph.dispose(); this.engine._removeNode(this); } /** @internal */ getClassName() { return "_WebAudioStaticSound"; } _createInstance() { return new _WebAudioStaticSoundInstance(this, this._options); } _connect(node) { const connected = super._connect(node); if (!connected) { return false; } // If the wrapped node is not available now, it will be connected later by the subgraph. if (node._inNode) { this._outNode?.connect(node._inNode); } return true; } _disconnect(node) { const disconnected = super._disconnect(node); if (!disconnected) { return false; } if (node._inNode) { this._outNode?.disconnect(node._inNode); } return true; } _initSpatialProperty() { if (!this._spatial) { this._spatial = new _SpatialWebAudio(this._subGraph, this._spatialAutoUpdate, this._spatialMinUpdateTime); } return this._spatial; } } _WebAudioStaticSound._SubGraph = class extends _WebAudioBusAndSoundSubGraph { get _downstreamNodes() { return this._owner._downstreamNodes ?? null; } get _upstreamNodes() { return this._owner._upstreamNodes ?? null; } }; /** @internal */ export class _WebAudioStaticSoundBuffer extends StaticSoundBuffer { /** @internal */ constructor(engine) { super(engine); } async _init(source, options) { if (source instanceof AudioBuffer) { this._audioBuffer = source; } else if (typeof source === "string") { await this._initFromUrl(source); } else if (Array.isArray(source)) { await this._initFromUrls(source, options.skipCodecCheck ?? false); } else if (source instanceof ArrayBuffer) { await this._initFromArrayBuffer(source); } } /** @internal */ get channelCount() { return this._audioBuffer.numberOfChannels; } /** @internal */ get duration() { return this._audioBuffer.duration; } /** @internal */ get length() { return this._audioBuffer.length; } /** @internal */ get sampleRate() { return this._audioBuffer.sampleRate; } async _initFromArrayBuffer(arrayBuffer) { this._audioBuffer = await this.engine._audioContext.decodeAudioData(arrayBuffer); } async _initFromUrl(url) { url = _CleanUrl(url); await this._initFromArrayBuffer(await (await fetch(url)).arrayBuffer()); } async _initFromUrls(urls, skipCodecCheck) { for (const url of urls) { if (skipCodecCheck) { await this._initFromUrl(url); } else { const matches = url.match(_FileExtensionRegex); const format = matches?.at(1); if (format && this.engine.isFormatValid(format)) { try { await this._initFromUrl(url); } catch (e) { if (format && 0 < format.length) { this.engine.flagInvalidFormat(format); } } } } if (this._audioBuffer) { break; } } } } /** @internal */ class _WebAudioStaticSoundInstance extends _StaticSoundInstance { constructor(sound, options) { super(sound); this._enginePlayTime = 0; this._enginePauseTime = 0; this._sourceNode = null; this._onEnded = () => { this._enginePlayTime = 0; this.onEndedObservable.notifyObservers(this); this._deinitSourceNode(); }; this._onEngineStateChanged = () => { if (this.engine.state !== "running") { return; } if (this._options.loop && this.state === 2 /* SoundState.Starting */) { this.play(); } this.engine.stateChangedObservable.removeCallback(this._onEngineStateChanged); }; this._options = options; this._volumeNode = new GainNode(sound._audioContext); this._initSourceNode(); } /** @internal */ get currentTime() { if (this._state === 1 /* SoundState.Stopped */) { return 0; } const timeSinceLastStart = this._state === 5 /* SoundState.Paused */ ? 0 : this.engine.currentTime - this._enginePlayTime; return this._enginePauseTime + timeSinceLastStart + this._options.startOffset; } set currentTime(value) { const restart = this._state === 2 /* SoundState.Starting */ || this._state === 3 /* SoundState.Started */; if (restart) { this.stop(); this._deinitSourceNode(); } this._options.startOffset = value; if (restart) { this.play(); } } get _outNode() { return this._volumeNode; } /** @internal */ set pitch(value) { if (this._sourceNode) { this.engine._setAudioParam(this._sourceNode.detune, value); } } /** @internal */ set playbackRate(value) { if (this._sourceNode) { this.engine._setAudioParam(this._sourceNode.playbackRate, value); } } /** @internal */ get startTime() { if (this._state === 1 /* SoundState.Stopped */) { return 0; } return this._enginePlayTime; } /** @internal */ dispose() { super.dispose(); this._sourceNode = null; this.stop(); this._deinitSourceNode(); this.engine.stateChangedObservable.removeCallback(this._onEngineStateChanged); } /** @internal */ getClassName() { return "_WebAudioStaticSoundInstance"; } /** @internal */ play(options = {}) { if (this._state === 3 /* SoundState.Started */) { return; } if (options.duration !== undefined) { this._options.duration = options.duration; } if (options.loop !== undefined) { this._options.loop = options.loop; } if (options.loopStart !== undefined) { this._options.loopStart = options.loopStart; } if (options.loopEnd !== undefined) { this._options.loopEnd = options.loopEnd; } if (options.startOffset !== undefined) { this._options.startOffset = options.startOffset; } let startOffset = this._options.startOffset; if (this._state === 5 /* SoundState.Paused */) { startOffset += this.currentTime; startOffset %= this._sound.buffer.duration; } this._enginePlayTime = this.engine.currentTime + (options.waitTime ?? 0); this._volumeNode.gain.value = options.volume ?? 1; this._initSourceNode(); if (this.engine.state === "running") { this._setState(3 /* SoundState.Started */); this._sourceNode?.start(this._enginePlayTime, startOffset, this._options.duration > 0 ? this._options.duration : undefined); } else if (this._options.loop) { this._setState(2 /* SoundState.Starting */); this.engine.stateChangedObservable.add(this._onEngineStateChanged); } } /** @internal */ pause() { if (this._state === 5 /* SoundState.Paused */) { return; } this._setState(5 /* SoundState.Paused */); this._enginePauseTime += this.engine.currentTime - this._enginePlayTime; this._sourceNode?.stop(); this._deinitSourceNode(); } /** @internal */ resume() { if (this._state === 5 /* SoundState.Paused */) { this.play(); } } /** @internal */ stop(options = {}) { if (this._state === 1 /* SoundState.Stopped */) { return; } this._setState(1 /* SoundState.Stopped */); const engineStopTime = this.engine.currentTime + (options.waitTime ?? 0); this._sourceNode?.stop(engineStopTime); this.engine.stateChangedObservable.removeCallback(this._onEngineStateChanged); } _connect(node) { const connected = super._connect(node); if (!connected) { return false; } // If the wrapped node is not available now, it will be connected later by the sound's subgraph. if (node instanceof _WebAudioStaticSound && node._inNode) { this._outNode?.connect(node._inNode); } return true; } _disconnect(node) { const disconnected = super._disconnect(node); if (!disconnected) { return false; } if (node instanceof _WebAudioStaticSound && node._inNode) { this._outNode?.disconnect(node._inNode); } return true; } _deinitSourceNode() { if (!this._sourceNode) { return; } if (!this._disconnect(this._sound)) { throw new Error("Disconnect failed"); } this._sourceNode.disconnect(this._volumeNode); this._sourceNode.removeEventListener("ended", this._onEnded); this._sourceNode = null; } _initSourceNode() { if (!this._sourceNode) { this._sourceNode = new AudioBufferSourceNode(this._sound._audioContext, { buffer: this._sound.buffer._audioBuffer }); this._sourceNode.addEventListener("ended", this._onEnded, { once: true }); this._sourceNode.connect(this._volumeNode); if (!this._connect(this._sound)) { throw new Error("Connect failed"); } } const node = this._sourceNode; node.detune.value = this._sound.pitch; node.loop = this._options.loop; node.loopEnd = this._options.loopEnd; node.loopStart = this._options.loopStart; node.playbackRate.value = this._sound.playbackRate; } } //# sourceMappingURL=webAudioStaticSound.js.map