Sfoglia il codice sorgente

Webaudio preliminary rework

David Catuhe 4 anni fa
parent
commit
d717c9937b

+ 1 - 0
dist/preview release/what's new.md

@@ -363,3 +363,4 @@
 - Rendering of transparent meshes: stencil state is now set to the value registered in the engine (when calling `engine.setStencilBuffer(value)`) instead of being set to `false` unconditionally. This change may affect the highlight layer when using transparent meshes. If you are impacted, you may need to exclude the transparent mesh(es) from the layer ([Popov72](https://github.com/Popov72))
 - Fix width/height GUI container computation to take into account paddings when `adaptWithToChildren = true` ([Popov72](https://github.com/Popov72))
 - `smoothstep` in NME is now taking any type of parameters for its `value` input. If you use generated code from the NME ("Generate code" button), you may have to move the smoothstep output connection AFTER the input connections ([Popov72](https://github.com/Popov72))
+- `SoundTrack.RemoveSound` and `SoundTrack.AddSound` were renamed to `SoundTrack.removeSound` and `SoundTrack.addSound` ([Deltakosh](https://github.com/deltakosh))

+ 95 - 0
src/Audio/Interfaces/IAudioEngine.ts

@@ -0,0 +1,95 @@
+import { Observable } from '../../Misc/observable';
+import { IDisposable } from '../../scene';
+import { Nullable } from '../../types';
+import { Analyser } from '../analyser';
+
+/**
+ * This represents an audio engine and it is responsible
+ * to play, synchronize and analyse sounds throughout the application.
+ * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music
+ */
+export interface IAudioEngine extends IDisposable {
+    /**
+     * Gets whether the current host supports Web Audio and thus could create AudioContexts.
+     */
+    readonly canUseWebAudio: boolean;
+
+    /**
+     * Gets the current AudioContext if available.
+     */
+    readonly audioContext: Nullable<AudioContext>;
+
+    /**
+     * The master gain node defines the global audio volume of your audio engine.
+     */
+    readonly masterGain: GainNode;
+
+    /**
+     * Gets whether or not mp3 are supported by your browser.
+     */
+    readonly isMP3supported: boolean;
+
+    /**
+     * Gets whether or not ogg are supported by your browser.
+     */
+    readonly isOGGsupported: boolean;
+
+    /**
+     * Defines if Babylon should emit a warning if WebAudio is not supported.
+     * @ignoreNaming
+     */
+    WarnedWebAudioUnsupported: boolean;
+
+    /**
+     * Defines if the audio engine relies on a custom unlocked button.
+     * In this case, the embedded button will not be displayed.
+     */
+    useCustomUnlockedButton: boolean;
+
+    /**
+     * Gets whether or not the audio engine is unlocked (require first a user gesture on some browser).
+     */
+    readonly unlocked: boolean;
+
+    /**
+     * Event raised when audio has been unlocked on the browser.
+     */
+    onAudioUnlockedObservable: Observable<IAudioEngine>;
+
+    /**
+     * Event raised when audio has been locked on the browser.
+     */
+    onAudioLockedObservable: Observable<IAudioEngine>;
+
+    /**
+     * Flags the audio engine in Locked state.
+     * This happens due to new browser policies preventing audio to autoplay.
+     */
+    lock(): void;
+
+    /**
+     * Unlocks the audio engine once a user action has been done on the dom.
+     * This is helpful to resume play once browser policies have been satisfied.
+     */
+    unlock(): void;
+
+    /**
+     * Gets the global volume sets on the master gain.
+     * @returns the global volume if set or -1 otherwise
+     */
+    getGlobalVolume(): number;
+
+    /**
+     * Sets the global volume of your experience (sets on the master gain).
+     * @param newVolume Defines the new global volume of the application
+     */
+    setGlobalVolume(newVolume: number): void;
+
+    /**
+     * Connect the audio engine to an audio analyser allowing some amazing
+     * synchornization between the sounds/music and your visualization (VuMeter for instance).
+     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#using-the-analyser
+     * @param analyser The analyser to connect to the engine
+     */
+    connectToAnalyser(analyser: Analyser): void;
+}

+ 65 - 0
src/Audio/Interfaces/ISoundOptions.ts

@@ -0,0 +1,65 @@
+
+/**
+ * Interface used to define options for Sound class
+ */
+export interface ISoundOptions {
+    /**
+     * Does the sound autoplay once loaded.
+     */
+    autoplay?: boolean;
+    /**
+     * Does the sound loop after it finishes playing once.
+     */
+    loop?: boolean;
+    /**
+     * Sound's volume
+     */
+    volume?: number;
+    /**
+     * Is it a spatial sound?
+     */
+    spatialSound?: boolean;
+    /**
+     * Maximum distance to hear that sound
+     */
+    maxDistance?: number;
+    /**
+     * Uses user defined attenuation function
+     */
+    useCustomAttenuation?: boolean;
+    /**
+     * Define the roll off factor of spatial sounds.
+     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#creating-a-spatial-3d-sound
+     */
+    rolloffFactor?: number;
+    /**
+     * Define the reference distance the sound should be heard perfectly.
+     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#creating-a-spatial-3d-sound
+     */
+    refDistance?: number;
+    /**
+     * Define the distance attenuation model the sound will follow.
+     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#creating-a-spatial-3d-sound
+     */
+    distanceModel?: string;
+    /**
+     * Defines the playback speed (1 by default)
+     */
+    playbackRate?: number;
+    /**
+     * Defines if the sound is from a streaming source
+     */
+    streaming?: boolean;
+    /**
+     * Defines an optional length (in seconds) inside the sound file
+     */
+    length?: number;
+    /**
+     * Defines an optional offset (in seconds) inside the sound file
+     */
+    offset?: number;
+    /**
+     * If true, URLs will not be required to state the audio file codec to use.
+     */
+    skipCodecCheck?: boolean;
+}

+ 1 - 1
src/Audio/analyser.ts

@@ -1,7 +1,7 @@
 import { Nullable } from "../types";
 import { Scene } from "../scene";
-import { IAudioEngine } from "../Audio/audioEngine";
 import { Engine } from "../Engines/engine";
+import { IAudioEngine } from './Interfaces/IAudioEngine';
 
 /**
  * Class used to work with sound analyzer using fast fourier transform (FFT)

+ 3 - 94
src/Audio/audioEngine.ts

@@ -1,101 +1,10 @@
-import { IDisposable } from "../scene";
 import { Analyser } from "./analyser";
 
 import { Nullable } from "../types";
 import { Observable } from "../Misc/observable";
 import { Logger } from "../Misc/logger";
 import { Engine } from "../Engines/engine";
-
-/**
- * This represents an audio engine and it is responsible
- * to play, synchronize and analyse sounds throughout the application.
- * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music
- */
-export interface IAudioEngine extends IDisposable {
-    /**
-     * Gets whether the current host supports Web Audio and thus could create AudioContexts.
-     */
-    readonly canUseWebAudio: boolean;
-
-    /**
-     * Gets the current AudioContext if available.
-     */
-    readonly audioContext: Nullable<AudioContext>;
-
-    /**
-     * The master gain node defines the global audio volume of your audio engine.
-     */
-    readonly masterGain: GainNode;
-
-    /**
-     * Gets whether or not mp3 are supported by your browser.
-     */
-    readonly isMP3supported: boolean;
-
-    /**
-     * Gets whether or not ogg are supported by your browser.
-     */
-    readonly isOGGsupported: boolean;
-
-    /**
-     * Defines if Babylon should emit a warning if WebAudio is not supported.
-     * @ignoreNaming
-     */
-    WarnedWebAudioUnsupported: boolean;
-
-    /**
-     * Defines if the audio engine relies on a custom unlocked button.
-     * In this case, the embedded button will not be displayed.
-     */
-    useCustomUnlockedButton: boolean;
-
-    /**
-     * Gets whether or not the audio engine is unlocked (require first a user gesture on some browser).
-     */
-    readonly unlocked: boolean;
-
-    /**
-     * Event raised when audio has been unlocked on the browser.
-     */
-    onAudioUnlockedObservable: Observable<AudioEngine>;
-
-    /**
-     * Event raised when audio has been locked on the browser.
-     */
-    onAudioLockedObservable: Observable<AudioEngine>;
-
-    /**
-     * Flags the audio engine in Locked state.
-     * This happens due to new browser policies preventing audio to autoplay.
-     */
-    lock(): void;
-
-    /**
-     * Unlocks the audio engine once a user action has been done on the dom.
-     * This is helpful to resume play once browser policies have been satisfied.
-     */
-    unlock(): void;
-
-    /**
-     * Gets the global volume sets on the master gain.
-     * @returns the global volume if set or -1 otherwise
-     */
-    getGlobalVolume(): number;
-
-    /**
-     * Sets the global volume of your experience (sets on the master gain).
-     * @param newVolume Defines the new global volume of the application
-     */
-    setGlobalVolume(newVolume: number): void;
-
-    /**
-     * Connect the audio engine to an audio analyser allowing some amazing
-     * synchornization between the sounds/music and your visualization (VuMeter for instance).
-     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#using-the-analyser
-     * @param analyser The analyser to connect to the engine
-     */
-    connectToAnalyser(analyser: Analyser): void;
-}
+import { IAudioEngine } from './Interfaces/IAudioEngine';
 
 // Sets the default audio engine to Babylon.js
 Engine.AudioEngineFactory = (hostElement: Nullable<HTMLElement>) => { return new AudioEngine(hostElement); };
@@ -153,12 +62,12 @@ export class AudioEngine implements IAudioEngine {
     /**
      * Event raised when audio has been unlocked on the browser.
      */
-    public onAudioUnlockedObservable = new Observable<AudioEngine>();
+    public onAudioUnlockedObservable = new Observable<IAudioEngine>();
 
     /**
      * Event raised when audio has been locked on the browser.
      */
-    public onAudioLockedObservable = new Observable<AudioEngine>();
+    public onAudioLockedObservable = new Observable<IAudioEngine>();
 
     /**
      * Gets the current AudioContext if available.

+ 2 - 2
src/Audio/audioSceneComponent.ts

@@ -355,7 +355,7 @@ export class AudioSceneComponent implements ISceneSerializableComponent {
         container.sounds.forEach((sound) => {
             sound.play();
             sound.autoplay = true;
-            this.scene.mainSoundTrack.AddSound(sound);
+            this.scene.mainSoundTrack.addSound(sound);
         });
     }
 
@@ -371,7 +371,7 @@ export class AudioSceneComponent implements ISceneSerializableComponent {
         container.sounds.forEach((sound) => {
             sound.stop();
             sound.autoplay = false;
-            this.scene.mainSoundTrack.RemoveSound(sound);
+            this.scene.mainSoundTrack.removeSound(sound);
             if (dispose) {
                 sound.dispose();
             }

+ 2 - 0
src/Audio/index.ts

@@ -1,3 +1,5 @@
+export * from "./Interfaces/IAudioEngine";
+export * from "./Interfaces/ISoundOptions";
 export * from "./analyser";
 export * from "./audioEngine";
 export * from "./audioSceneComponent";

+ 36 - 94
src/Audio/sound.ts

@@ -8,71 +8,7 @@ import { AbstractMesh } from "../Meshes/abstractMesh";
 import { TransformNode } from "../Meshes/transformNode";
 import { Logger } from "../Misc/logger";
 import { _DevTools } from "../Misc/devTools";
-
-/**
- * Interface used to define options for Sound class
- */
-export interface ISoundOptions {
-    /**
-     * Does the sound autoplay once loaded.
-     */
-    autoplay?: boolean;
-    /**
-     * Does the sound loop after it finishes playing once.
-     */
-    loop?: boolean;
-    /**
-     * Sound's volume
-     */
-    volume?: number;
-    /**
-     * Is it a spatial sound?
-     */
-    spatialSound?: boolean;
-    /**
-     * Maximum distance to hear that sound
-     */
-    maxDistance?: number;
-    /**
-     * Uses user defined attenuation function
-     */
-    useCustomAttenuation?: boolean;
-    /**
-     * Define the roll off factor of spatial sounds.
-     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#creating-a-spatial-3d-sound
-     */
-    rolloffFactor?: number;
-    /**
-     * Define the reference distance the sound should be heard perfectly.
-     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#creating-a-spatial-3d-sound
-     */
-    refDistance?: number;
-    /**
-     * Define the distance attenuation model the sound will follow.
-     * @see https://doc.babylonjs.com/how_to/playing_sounds_and_music#creating-a-spatial-3d-sound
-     */
-    distanceModel?: string;
-    /**
-     * Defines the playback speed (1 by default)
-     */
-    playbackRate?: number;
-    /**
-     * Defines if the sound is from a streaming source
-     */
-    streaming?: boolean;
-    /**
-     * Defines an optional length (in seconds) inside the sound file
-     */
-    length?: number;
-    /**
-     * Defines an optional offset (in seconds) inside the sound file
-     */
-    offset?: number;
-    /**
-     * If true, URLs will not be required to state the audio file codec to use.
-     */
-    skipCodecCheck?: boolean;
-}
+import { ISoundOptions } from './Interfaces/ISoundOptions';
 
 /**
  * Defines a sound that can be played in the application.
@@ -183,8 +119,8 @@ export class Sound {
     private _streamingSource: AudioNode;
     private _soundPanner: Nullable<PannerNode>;
     private _soundGain: Nullable<GainNode>;
-    private _inputAudioNode: AudioNode;
-    private _outputAudioNode: AudioNode;
+    private _inputAudioNode: Nullable<AudioNode>;
+    private _outputAudioNode: Nullable<AudioNode>;
     // Used if you'd like to create a directional sound.
     // If not set, the sound will be omnidirectional
     private _coneInnerAngle: number = 360;
@@ -248,13 +184,13 @@ export class Sound {
 
         if (Engine.audioEngine.canUseWebAudio && Engine.audioEngine.audioContext) {
             this._soundGain = Engine.audioEngine.audioContext.createGain();
-            this._soundGain.gain.value = this._volume;
+            this._soundGain!.gain.value = this._volume;
             this._inputAudioNode = this._soundGain;
             this._outputAudioNode = this._soundGain;
             if (this.spatialSound) {
                 this._createSpatialParameters();
             }
-            this._scene.mainSoundTrack.AddSound(this);
+            this._scene.mainSoundTrack.addSound(this);
             var validParameter = true;
 
             // if no parameter is passed, you need to call setAudioBuffer yourself to prepare the sound
@@ -324,7 +260,7 @@ export class Sound {
                                                     Logger.Error("XHR " + exception.status + " error on: " + url + ".");
                                                 }
                                                 Logger.Error("Sound creation aborted.");
-                                                this._scene.mainSoundTrack.RemoveSound(this);
+                                                this._scene.mainSoundTrack.removeSound(this);
                                             }
                                         );
                                     }
@@ -373,12 +309,12 @@ export class Sound {
                     }
                 } catch (ex) {
                     Logger.Error("Unexpected error. Sound creation aborted.");
-                    this._scene.mainSoundTrack.RemoveSound(this);
+                    this._scene.mainSoundTrack.removeSound(this);
                 }
             }
         } else {
             // Adding an empty sound to avoid breaking audio calls for non Web Audio browsers
-            this._scene.mainSoundTrack.AddSound(this);
+            this._scene.mainSoundTrack.addSound(this);
             if (!Engine.audioEngine.WarnedWebAudioUnsupported) {
                 Logger.Error("Web Audio is not supported by your browser.");
                 Engine.audioEngine.WarnedWebAudioUnsupported = true;
@@ -404,9 +340,9 @@ export class Sound {
             }
             this._isReadyToPlay = false;
             if (this.soundTrackId === -1) {
-                this._scene.mainSoundTrack.RemoveSound(this);
+                this._scene.mainSoundTrack.removeSound(this);
             } else if (this._scene.soundTracks) {
-                this._scene.soundTracks[this.soundTrackId].RemoveSound(this);
+                this._scene.soundTracks[this.soundTrackId].removeSound(this);
             }
             if (this._soundGain) {
                 this._soundGain.disconnect();
@@ -526,9 +462,11 @@ export class Sound {
                 this._panningModel = "HRTF";
             }
             this._soundPanner = Engine.audioEngine.audioContext.createPanner();
-            this._updateSpatialParameters();
-            this._soundPanner.connect(this._outputAudioNode);
-            this._inputAudioNode = this._soundPanner;
+            if (this._soundPanner && this._outputAudioNode) {
+                this._updateSpatialParameters();
+                this._soundPanner.connect(this._outputAudioNode);
+                this._inputAudioNode = this._soundPanner;
+            }
         }
     }
 
@@ -582,7 +520,7 @@ export class Sound {
      * @param soundTrackAudioNode the sound track audio node to connect to
      */
     public connectToSoundTrackAudioNode(soundTrackAudioNode: AudioNode): void {
-        if (Engine.audioEngine.canUseWebAudio) {
+        if (Engine.audioEngine.canUseWebAudio && this._outputAudioNode) {
             if (this._isOutputConnected) {
                 this._outputAudioNode.disconnect();
             }
@@ -753,7 +691,9 @@ export class Sound {
                         this._htmlAudioElement.playbackRate = this._playbackRate;
                     }
                     this._streamingSource.disconnect();
-                    this._streamingSource.connect(this._inputAudioNode);
+                    if (this._inputAudioNode) {
+                        this._streamingSource.connect(this._inputAudioNode);
+                    }
                     if (this._htmlAudioElement) {
                         // required to manage properly the new suspended default state of Chrome
                         // When the option 'streaming: true' is used, we need first to wait for
@@ -800,22 +740,24 @@ export class Sound {
                                 };
                             }
                             this._soundSource = Engine.audioEngine.audioContext.createBufferSource();
-                            this._soundSource.buffer = this._audioBuffer;
-                            this._soundSource.connect(this._inputAudioNode);
-                            this._soundSource.loop = this.loop;
-                            if (offset !== undefined) {
-                                this._soundSource.loopStart = offset;
-                            }
-                            if (length !== undefined) {
-                                this._soundSource.loopEnd = (offset! | 0) + length!;
+                            if (this._soundSource && this._inputAudioNode) {
+                                this._soundSource.buffer = this._audioBuffer;
+                                this._soundSource.connect(this._inputAudioNode);
+                                this._soundSource.loop = this.loop;
+                                if (offset !== undefined) {
+                                    this._soundSource.loopStart = offset;
+                                }
+                                if (length !== undefined) {
+                                    this._soundSource.loopEnd = (offset! | 0) + length!;
+                                }
+                                this._soundSource.playbackRate.value = this._playbackRate;
+                                this._soundSource.onended = () => {
+                                    this._onended();
+                                };
+                                startTime = time ? Engine.audioEngine.audioContext!.currentTime + time : Engine.audioEngine.audioContext!.currentTime;
+                                const actualOffset = this.isPaused ? this._startOffset % this._soundSource!.buffer!.duration : offset ? offset : 0;
+                                this._soundSource!.start(startTime, actualOffset, this.loop ? undefined : length);
                             }
-                            this._soundSource.playbackRate.value = this._playbackRate;
-                            this._soundSource.onended = () => {
-                                this._onended();
-                            };
-                            startTime = time ? Engine.audioEngine.audioContext!.currentTime + time : Engine.audioEngine.audioContext!.currentTime;
-                            const actualOffset = this.isPaused ? this._startOffset % this._soundSource!.buffer!.duration : offset ? offset : 0;
-                            this._soundSource!.start(startTime, actualOffset, this.loop ? undefined : length);
                         }
                     };
 

+ 4 - 4
src/Audio/soundTrack.ts

@@ -92,7 +92,7 @@ export class SoundTrack {
      * @param sound define the cound to add
      * @ignoreNaming
      */
-    public AddSound(sound: Sound): void {
+    public addSound(sound: Sound): void {
         if (!this._isInitialized) {
             this._initializeSoundTrackAudioGraph();
         }
@@ -101,10 +101,10 @@ export class SoundTrack {
         }
         if (sound.soundTrackId) {
             if (sound.soundTrackId === -1) {
-                this._scene.mainSoundTrack.RemoveSound(sound);
+                this._scene.mainSoundTrack.removeSound(sound);
             }
             else if (this._scene.soundTracks) {
-                this._scene.soundTracks[sound.soundTrackId].RemoveSound(sound);
+                this._scene.soundTracks[sound.soundTrackId].removeSound(sound);
             }
         }
 
@@ -117,7 +117,7 @@ export class SoundTrack {
      * @param sound define the cound to remove
      * @ignoreNaming
      */
-    public RemoveSound(sound: Sound): void {
+    public removeSound(sound: Sound): void {
         var index = this.soundCollection.indexOf(sound);
         if (index !== -1) {
             this.soundCollection.splice(index, 1);

+ 1 - 1
src/Engines/engine.ts

@@ -2,7 +2,6 @@ import { Observable } from "../Misc/observable";
 import { Nullable } from "../types";
 import { Scene } from "../scene";
 import { InternalTexture } from "../Materials/Textures/internalTexture";
-import { IAudioEngine } from "../Audio/audioEngine";
 import { IOfflineProvider } from "../Offline/IOfflineProvider";
 import { ILoadingScreen } from "../Loading/loadingScreen";
 import { DomManagement } from "../Misc/domManagement";
@@ -24,6 +23,7 @@ import { Logger } from '../Misc/logger';
 import "./Extensions/engine.alpha";
 import "./Extensions/engine.readTexture";
 import "./Extensions/engine.dynamicBuffer";
+import { IAudioEngine } from '../Audio/Interfaces/IAudioEngine';
 
 declare type Material = import("../Materials/material").Material;
 declare type PostProcess = import("../PostProcesses/postProcess").PostProcess;