UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

677 lines (665 loc) 22.5 kB
import { g as AbstractNamedAudioNode, d as _WebAudioParameterComponent } from './spatialWebAudioUpdaterComponent-jS7MKp73.esm.js'; import { y as Logger } from './index-MZPybX0H.esm.js'; /** @internal */ class _AbstractAudioSubNode extends AbstractNamedAudioNode { /** @internal */ constructor(name, engine) { super(name, engine, 3 /* AudioNodeType.HAS_INPUTS_AND_OUTPUTS */); } /** @internal */ connect(node) { if (!this._connect(node)) { throw new Error("Connect failed"); } } /** @internal */ disconnect(node) { if (!this._disconnect(node)) { throw new Error("Disconnect failed"); } } /** @internal */ disconnectAll() { if (!this._downstreamNodes) { throw new Error("Disconnect failed"); } const it = this._downstreamNodes.values(); for (let next = it.next(); !next.done; next = it.next()) { if (!this._disconnect(next.value)) { throw new Error("Disconnect failed"); } } } } /** @internal */ const _VolumeAudioDefaults = { volume: 1, }; /** @internal */ class _VolumeAudioSubNode extends _AbstractAudioSubNode { constructor(engine) { super("Volume" /* AudioSubNode.VOLUME */, engine); } /** @internal */ setOptions(options) { this.setVolume(options.volume ?? _VolumeAudioDefaults.volume, { shape: "none" /* AudioParameterRampShape.None */ }); } } /** @internal */ function _GetVolumeAudioSubNode(subGraph) { return subGraph.getSubNode("Volume" /* AudioSubNode.VOLUME */); } /** @internal */ function _GetVolumeAudioProperty(subGraph, property) { return _GetVolumeAudioSubNode(subGraph)?.[property] ?? _VolumeAudioDefaults[property]; } const _AudioAnalyzerDefaults = { fftSize: 2048, minDecibels: -100, maxDecibels: -30, smoothing: 0.8, }; let EmptyByteTimeDomainData; let EmptyFloatTimeDomainData; /** * @param options The audio analyzer options to check. * @returns `true` if audio analyzer options are defined, otherwise `false`. */ function _HasAudioAnalyzerOptions(options) { return (options.analyzerEnabled || options.analyzerFFTSize !== undefined || options.analyzerMinDecibels !== undefined || options.analyzerMaxDecibels !== undefined || options.analyzerSmoothing !== undefined); } /** * An AudioAnalyzer converts time-domain audio data into the frequency-domain. */ class AbstractAudioAnalyzer { /** * The number of data values that will be returned when calling getByteFrequencyData() or getFloatFrequencyData(). This is always half the `fftSize`. */ get frequencyBinCount() { return this.fftSize / 2; } /** * Gets the current waveform data as a byte array * @returns a Uint8Array with length `fftSize` if the analyzer is enabled, otherwise an empty array */ getByteTimeDomainData() { return (EmptyByteTimeDomainData ??= new Uint8Array()); } /** * Gets the current waveform data as a float array * @returns a Float32Array with length `fftSize` if the analyzer is enabled, otherwise an empty array */ getFloatTimeDomainData() { return (EmptyFloatTimeDomainData ??= new Float32Array()); } } /** @internal */ class _AudioAnalyzerSubNode extends _AbstractAudioSubNode { constructor(engine) { super("Analyzer" /* AudioSubNode.ANALYZER */, engine); } /** @internal */ setOptions(options) { this.fftSize = options.analyzerFFTSize ?? _AudioAnalyzerDefaults.fftSize; this.minDecibels = options.analyzerMinDecibels ?? _AudioAnalyzerDefaults.minDecibels; this.maxDecibels = options.analyzerMaxDecibels ?? _AudioAnalyzerDefaults.maxDecibels; this.smoothing = options.analyzerSmoothing ?? _AudioAnalyzerDefaults.smoothing; } } /** @internal */ function _GetAudioAnalyzerSubNode(subGraph) { return subGraph.getSubNode("Analyzer" /* AudioSubNode.ANALYZER */); } /** @internal */ function _SetAudioAnalyzerProperty(subGraph, property, value) { subGraph.callOnSubNode("Analyzer" /* AudioSubNode.ANALYZER */, (node) => { node[property] = value; }); } let EmptyByteFrequencyData = null; let EmptyFloatFrequencyData = null; /** @internal */ function _GetEmptyByteFrequencyData() { if (!EmptyByteFrequencyData) { EmptyByteFrequencyData = new Uint8Array(); } return EmptyByteFrequencyData; } /** @internal */ function _GetEmptyFloatFrequencyData() { if (!EmptyFloatFrequencyData) { EmptyFloatFrequencyData = new Float32Array(); } return EmptyFloatFrequencyData; } /** @internal */ class _AudioAnalyzer extends AbstractAudioAnalyzer { /** @internal */ constructor(subGraph) { super(); this._fftSize = _AudioAnalyzerDefaults.fftSize; this._maxDecibels = _AudioAnalyzerDefaults.maxDecibels; this._minDecibels = _AudioAnalyzerDefaults.minDecibels; this._smoothing = _AudioAnalyzerDefaults.smoothing; this._subGraph = subGraph; } /** @internal */ get fftSize() { return this._fftSize; } set fftSize(value) { this._fftSize = value; _SetAudioAnalyzerProperty(this._subGraph, "fftSize", value); } /** @internal */ get isEnabled() { return _GetAudioAnalyzerSubNode(this._subGraph) !== null; } /** @internal */ get minDecibels() { return this._minDecibels; } set minDecibels(value) { this._minDecibels = value; _SetAudioAnalyzerProperty(this._subGraph, "minDecibels", value); } /** @internal */ get maxDecibels() { return this._maxDecibels; } set maxDecibels(value) { this._maxDecibels = value; _SetAudioAnalyzerProperty(this._subGraph, "maxDecibels", value); } /** @internal */ get smoothing() { return this._smoothing; } set smoothing(value) { this._smoothing = value; _SetAudioAnalyzerProperty(this._subGraph, "smoothing", value); } /** @internal */ dispose() { const subNode = _GetAudioAnalyzerSubNode(this._subGraph); if (subNode) { // eslint-disable-next-line @typescript-eslint/no-floating-promises this._subGraph.removeSubNodeAsync(subNode); subNode.dispose(); } } /** @internal */ async enableAsync() { const subNode = _GetAudioAnalyzerSubNode(this._subGraph); if (!subNode) { await this._subGraph.createAndAddSubNodeAsync("Analyzer" /* AudioSubNode.ANALYZER */); } } /** @internal */ getByteFrequencyData() { const subNode = _GetAudioAnalyzerSubNode(this._subGraph); if (!subNode) { Logger.Warn("AudioAnalyzer not enabled"); // eslint-disable-next-line @typescript-eslint/no-floating-promises this.enableAsync(); return _GetEmptyByteFrequencyData(); } return subNode.getByteFrequencyData(); } /** @internal */ getByteTimeDomainData() { const subNode = _GetAudioAnalyzerSubNode(this._subGraph); if (!subNode) { Logger.Warn("AudioAnalyzer not enabled"); // eslint-disable-next-line @typescript-eslint/no-floating-promises this.enableAsync(); return super.getByteTimeDomainData(); } return subNode.getByteTimeDomainData(); } /** @internal */ getFloatFrequencyData() { const subNode = _GetAudioAnalyzerSubNode(this._subGraph); if (!subNode) { Logger.Warn("AudioAnalyzer not enabled"); // eslint-disable-next-line @typescript-eslint/no-floating-promises this.enableAsync(); return _GetEmptyFloatFrequencyData(); } return subNode.getFloatFrequencyData(); } /** @internal */ getFloatTimeDomainData() { const subNode = _GetAudioAnalyzerSubNode(this._subGraph); if (!subNode) { Logger.Warn("AudioAnalyzer not enabled"); // eslint-disable-next-line @typescript-eslint/no-floating-promises this.enableAsync(); return super.getFloatTimeDomainData(); } return subNode.getFloatTimeDomainData(); } } /** * Abstract class representing and audio output node with an analyzer and volume control. */ class AbstractAudioOutNode extends AbstractNamedAudioNode { constructor(name, engine, nodeType) { super(name, engine, nodeType); this._analyzer = null; } /** * The audio analyzer features. */ get analyzer() { return this._analyzer ?? (this._analyzer = new _AudioAnalyzer(this._subGraph)); } /** * The audio output volume. */ get volume() { return _GetVolumeAudioProperty(this._subGraph, "volume"); } set volume(value) { // The volume subnode is created on initialization and should always exist. const node = _GetVolumeAudioSubNode(this._subGraph); if (!node) { throw new Error("No volume subnode"); } node.volume = value; } /** * Releases associated resources. */ dispose() { super.dispose(); this._analyzer?.dispose(); this._analyzer = null; this._subGraph.dispose(); } /** * Sets the audio output volume with optional ramping. * If the duration is 0 then the volume is set immediately, otherwise it is ramped to the new value over the given duration using the given shape. * If a ramp is already in progress then the volume is not set and an error is thrown. * @param value The value to set the volume to. * @param options The options to use for ramping the volume change. */ setVolume(value, options = null) { const node = _GetVolumeAudioSubNode(this._subGraph); if (!node) { throw new Error("No volume subnode"); } node.setVolume(value, options); } } /** * Adds common sub graph functionality to an audio node. * * Audio nodes such as static sounds, streaming sounds, and buses can use audio sub graphs to process audio internally * before sending it to connected downstream audio nodes. This is useful for applying effects, spatial audio, and other * audio processing tasks common to multiple audio node classes. * * A key feature of audio sub graphs is their audio sub nodes are created asynchronously on demand so the minimum set * of sub nodes are used at all times to save memory and CPU resources. The tradeoff is a small delay when first * setting a property backed by a sub node. This delay is avoided by using the appropriate options to initialize the * sub node on creation, e.g. `spatialEnabled` and `stereoEnabled`, or by setting any creation option backed by the * sub node, e.g. `spatialPosition` and `stereoPan`. * * @internal */ class _AbstractAudioSubGraph { constructor() { this._createSubNodePromises = {}; this._isDisposed = false; this._subNodes = {}; this._onSubNodeDisposed = (node) => { const subNode = node; delete this._subNodes[subNode.name]; this._onSubNodesChanged(); }; } /** * Executes the given callback with the named sub node, creating the sub node if needed. * * @param name The name of the sub node * @param callback The function to call with the named sub node * * @internal */ callOnSubNode(name, callback) { const node = this.getSubNode(name); if (node) { callback(node); return; } // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then this._createSubNodePromisesResolvedAsync().then(() => { const node = this.getSubNode(name); if (node) { callback(node); return; } // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then this.createAndAddSubNodeAsync(name).then((node) => { callback(node); }); }); } /** * Creates the named subnode and adds it to the sub graph. * * @param name The name of the sub node. * @returns A promise that resolves to the created sub node. * * @internal */ // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax createAndAddSubNodeAsync(name) { // eslint-disable-next-line github/no-then this._createSubNodePromises[name] ||= this._createSubNode(name).then((node) => { this._addSubNode(node); return node; }); return this._createSubNodePromises[name]; } /** * Releases associated resources. * * @internal */ dispose() { this._isDisposed = true; const subNodes = Object.values(this._subNodes); for (const subNode of subNodes) { subNode.dispose(); } this._subNodes = {}; this._createSubNodePromises = {}; } /** * Gets a previously created sub node. * * @param name - The name of the sub node * @returns The named sub node, or `null` if it has not been created, yet * * @internal * */ getSubNode(name) { return this._subNodes[name] ?? null; } /** * Removes a sub node from the sub graph. * * @param subNode - The sub node to remove * @returns A promise that resolves when the sub node is removed * * @internal */ async removeSubNodeAsync(subNode) { if (!subNode) { return; } await this._createSubNodePromisesResolvedAsync(); const name = subNode.name; if (this._subNodes[name]) { delete this._subNodes[name]; } delete this._createSubNodePromises[name]; this._onSubNodesChanged(); } async _createSubNodePromisesResolvedAsync() { return await Promise.all(Object.values(this._createSubNodePromises)); } _addSubNode(node) { if (this._isDisposed) { node.dispose(); return; } this._subNodes[node.name] = node; node.onDisposeObservable.addOnce(this._onSubNodeDisposed); this._onSubNodesChanged(); } } /** @internal */ // eslint-disable-next-line @typescript-eslint/require-await async function _CreateVolumeAudioSubNodeAsync(engine) { return new _VolumeWebAudioSubNode(engine); } /** @internal */ class _VolumeWebAudioSubNode extends _VolumeAudioSubNode { /** @internal */ constructor(engine) { super(engine); const gainNode = (this.node = new GainNode(engine._audioContext)); this._volume = new _WebAudioParameterComponent(engine, gainNode.gain); } /** @internal */ dispose() { super.dispose(); this._volume.dispose(); } /** @internal */ get volume() { return this._volume.value; } /** @internal */ set volume(value) { this.setVolume(value); } /** @internal */ get _inNode() { return this.node; } /** @internal */ get _outNode() { return this.node; } /** @internal */ setVolume(value, options = null) { this._volume.setTargetValue(value, 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.node.connect(node._inNode); } return true; } _disconnect(node) { const disconnected = super._disconnect(node); if (!disconnected) { return false; } if (node._inNode) { this.node.disconnect(node._inNode); } return true; } /** @internal */ getClassName() { return "_VolumeWebAudioSubNode"; } } /** @internal */ // eslint-disable-next-line @typescript-eslint/require-await async function _CreateAudioAnalyzerSubNodeAsync(engine) { return new _WebAudioAnalyzerSubNode(engine); } /** @internal */ class _WebAudioAnalyzerSubNode extends _AudioAnalyzerSubNode { /** @internal */ constructor(engine) { super(engine); this._byteFrequencyData = null; this._byteTimeDomainData = null; this._floatFrequencyData = null; this._floatTimeDomainData = null; this._analyzerNode = new AnalyserNode(engine._audioContext); } /** @internal */ get fftSize() { return this._analyzerNode.fftSize; } set fftSize(value) { if (value === this._analyzerNode.fftSize) { return; } this._analyzerNode.fftSize = value; this._clearArrays(); } /** @internal */ get _inNode() { return this._analyzerNode; } /** @internal */ get minDecibels() { return this._analyzerNode.minDecibels; } set minDecibels(value) { this._analyzerNode.minDecibels = value; } /** @internal */ get maxDecibels() { return this._analyzerNode.maxDecibels; } set maxDecibels(value) { this._analyzerNode.maxDecibels = value; } /** @internal */ get smoothing() { return this._analyzerNode.smoothingTimeConstant; } set smoothing(value) { this._analyzerNode.smoothingTimeConstant = value; } /** @internal */ dispose() { super.dispose(); this._clearArrays(); this._analyzerNode.disconnect(); } /** @internal */ getClassName() { return "_WebAudioAnalyzerSubNode"; } /** @internal */ getByteFrequencyData() { if (!this._byteFrequencyData || this._byteFrequencyData.length === 0) { this._byteFrequencyData = new Uint8Array(this._analyzerNode.frequencyBinCount); } this._analyzerNode.getByteFrequencyData(this._byteFrequencyData); return this._byteFrequencyData; } /** @internal */ getByteTimeDomainData() { if (!this._byteTimeDomainData || this._byteTimeDomainData.length === 0) { this._byteTimeDomainData = new Uint8Array(this._analyzerNode.fftSize); } this._analyzerNode.getByteTimeDomainData(this._byteTimeDomainData); return this._byteTimeDomainData; } /** @internal */ getFloatFrequencyData() { if (!this._floatFrequencyData || this._floatFrequencyData.length === 0) { this._floatFrequencyData = new Float32Array(this._analyzerNode.frequencyBinCount); } this._analyzerNode.getFloatFrequencyData(this._floatFrequencyData); return this._floatFrequencyData; } /** @internal */ getFloatTimeDomainData() { if (!this._floatTimeDomainData || this._floatTimeDomainData.length === 0) { this._floatTimeDomainData = new Float32Array(this._analyzerNode.fftSize); } this._analyzerNode.getFloatTimeDomainData(this._floatTimeDomainData); return this._floatTimeDomainData; } _clearArrays() { this._byteFrequencyData = null; this._byteTimeDomainData = null; this._floatFrequencyData = null; this._floatTimeDomainData = null; } } /** @internal */ class _WebAudioBaseSubGraph extends _AbstractAudioSubGraph { /** @internal */ constructor(owner) { super(); this._outputNode = null; this._owner = owner; } /** @internal */ async initAsync(options) { const hasAnalyzerOptions = _HasAudioAnalyzerOptions(options); if (hasAnalyzerOptions) { await this.createAndAddSubNodeAsync("Analyzer" /* AudioSubNode.ANALYZER */); } await this.createAndAddSubNodeAsync("Volume" /* AudioSubNode.VOLUME */); await this._createSubNodePromisesResolvedAsync(); if (hasAnalyzerOptions) { const analyzerNode = _GetAudioAnalyzerSubNode(this); if (!analyzerNode) { throw new Error("No analyzer subnode."); } analyzerNode.setOptions(options); } const volumeNode = _GetVolumeAudioSubNode(this); if (!volumeNode) { throw new Error("No volume subnode."); } volumeNode.setOptions(options); if (volumeNode.getClassName() !== "_VolumeWebAudioSubNode") { throw new Error("Not a WebAudio subnode."); } this._outputNode = volumeNode.node; // Connect the new wrapped WebAudio node to the wrapped downstream WebAudio nodes. // The wrapper nodes are unaware of this change. if (this._outputNode && this._downstreamNodes) { const it = this._downstreamNodes.values(); for (let next = it.next(); !next.done; next = it.next()) { const inNode = next.value._inNode; if (inNode) { this._outputNode.connect(inNode); } } } } /** @internal */ get _inNode() { return this._outputNode; } /** @internal */ get _outNode() { return this._outputNode; } // Function is async, but throws synchronously. Avoiding breaking changes. // eslint-disable-next-line @typescript-eslint/promise-function-async _createSubNode(name) { switch (name) { case "Analyzer" /* AudioSubNode.ANALYZER */: return _CreateAudioAnalyzerSubNodeAsync(this._owner.engine); case "Volume" /* AudioSubNode.VOLUME */: return _CreateVolumeAudioSubNodeAsync(this._owner.engine); default: throw new Error(`Unknown subnode name: ${name}`); } } _onSubNodesChanged() { const analyzerNode = _GetAudioAnalyzerSubNode(this); const volumeNode = _GetVolumeAudioSubNode(this); if (analyzerNode && volumeNode) { volumeNode.connect(analyzerNode); } } } export { AbstractAudioOutNode as A, _WebAudioBaseSubGraph as _, _AbstractAudioSubNode as a, _GetVolumeAudioSubNode as b }; //# sourceMappingURL=webAudioBaseSubGraph-BjGMAHX6.esm.js.map