|
@@ -8,6 +8,10 @@
|
|
|
public refDistance: number = 1;
|
|
|
public rolloffFactor: number = 1;
|
|
|
public maxDistance: number = 100;
|
|
|
+ public distanceModel: string = "linear";
|
|
|
+ public panningModel: string = "HRTF";
|
|
|
+ private startTime: number = 0;
|
|
|
+ private startOffset: number = 0;
|
|
|
private _position: Vector3 = Vector3.Zero();
|
|
|
private _localDirection: Vector3 = new Vector3(1,0,0);
|
|
|
private _volume: number = 1;
|
|
@@ -24,28 +28,28 @@
|
|
|
private _audioNode: AudioNode;
|
|
|
// Used if you'd like to create a directional sound.
|
|
|
// If not set, the sound will be omnidirectional
|
|
|
- private _coneInnerAngle: number = null;
|
|
|
- private _coneOuterAngle: number = null;
|
|
|
- private _coneOuterGain: number = null;
|
|
|
+ private _coneInnerAngle: number = 360;
|
|
|
+ private _coneOuterAngle: number = 360;
|
|
|
+ private _coneOuterGain: number = 0;
|
|
|
private _scene: BABYLON.Scene;
|
|
|
private _name: string;
|
|
|
private _connectedMesh: BABYLON.AbstractMesh;
|
|
|
- private _customAttenuationFunction: (currentVolume: number, currentDistance: number, maxDistance: number) => number;
|
|
|
+ private _customAttenuationFunction: (currentVolume: number, currentDistance: number, maxDistance: number, refDistance: number, rolloffFactor: number) => number;
|
|
|
|
|
|
/**
|
|
|
* Create a sound and attach it to a scene
|
|
|
* @param name Name of your sound
|
|
|
- * @param url Url to the sound to load async
|
|
|
+ * @param urlOrArrayBuffer Url to the sound to load async or ArrayBuffer
|
|
|
* @param readyToPlayCallback Provide a callback function if you'd like to load your code once the sound is ready to be played
|
|
|
- * @param options Objects to provide with the current available options: autoplay, loop, distanceMax
|
|
|
+ * @param options Objects to provide with the current available options: autoplay, loop, volume, spatialSound, maxDistance, rolloffFactor, refDistance, distanceModel, panningModel
|
|
|
*/
|
|
|
- constructor(name: string, url: string, scene: BABYLON.Scene, readyToPlayCallback?: () => void, options?) {
|
|
|
+ constructor(name: string, urlOrArrayBuffer: any, scene: BABYLON.Scene, readyToPlayCallback?: () => void, options?) {
|
|
|
this._name = name;
|
|
|
this._scene = scene;
|
|
|
this._audioEngine = this._scene.getEngine().getAudioEngine();
|
|
|
this._readyToPlayCallback = readyToPlayCallback;
|
|
|
// Default custom attenuation function is a linear attenuation
|
|
|
- this._customAttenuationFunction = (currentVolume: number, currentDistance: number, maxDistance: number) => {
|
|
|
+ this._customAttenuationFunction = (currentVolume: number, currentDistance: number, maxDistance: number, refDistance: number, rolloffFactor: number) => {
|
|
|
if (currentDistance < maxDistance) {
|
|
|
return currentVolume * (1 - currentDistance / maxDistance);
|
|
|
}
|
|
@@ -54,15 +58,16 @@
|
|
|
}
|
|
|
};
|
|
|
if (options) {
|
|
|
- if (options.maxDistance) { this.maxDistance = options.maxDistance; }
|
|
|
- if (options.autoplay) { this.autoplay = options.autoplay; }
|
|
|
- if (options.loop) { this.loop = options.loop; }
|
|
|
- if (options.volume) { this._volume = options.volume; }
|
|
|
- if (options.useCustomAttenuation) {
|
|
|
- this.maxDistance = Number.MAX_VALUE;
|
|
|
- this.useCustomAttenuation = options.useCustomAttenuation;
|
|
|
- }
|
|
|
- if (options.spatialSound) { this.spatialSound = options.spatialSound; }
|
|
|
+ this.autoplay = options.autoplay || false;
|
|
|
+ this.loop = options.loop || false;
|
|
|
+ this._volume = options.volume || 1;
|
|
|
+ this.spatialSound = options.spatialSound || false;
|
|
|
+ this.maxDistance = options.maxDistance || 100;
|
|
|
+ this.useCustomAttenuation = options.useCustomAttenation || false;
|
|
|
+ this.rolloffFactor = options.rolloffFactor || 1;
|
|
|
+ this.refDistance = options.refDistance || 1;
|
|
|
+ this.distanceModel = options.distanceModel || "linear";
|
|
|
+ this.panningModel = options.panningModel || "HRTF";
|
|
|
}
|
|
|
|
|
|
if (this._audioEngine.canUseWebAudio) {
|
|
@@ -75,15 +80,51 @@
|
|
|
this._audioNode = this._soundGain;
|
|
|
}
|
|
|
this._scene.mainSoundTrack.AddSound(this);
|
|
|
- BABYLON.Tools.LoadFile(url, (data) => { this._soundLoaded(data); }, null, null, true);
|
|
|
+ if (typeof (urlOrArrayBuffer) === "string") {
|
|
|
+ BABYLON.Tools.LoadFile(urlOrArrayBuffer, (data) => { this._soundLoaded(data); }, null, null, true);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if (urlOrArrayBuffer instanceof ArrayBuffer) {
|
|
|
+ this._soundLoaded(urlOrArrayBuffer);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ BABYLON.Tools.Error("Parameter must be a URL to the sound or an ArrayBuffer of the sound.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public updateOptions(options) {
|
|
|
+ if (options) {
|
|
|
+ this.loop = options.loop || this.loop;
|
|
|
+ this.maxDistance = options.maxDistance || this.maxDistance;
|
|
|
+ this.useCustomAttenuation = options.useCustomAttenation || this.useCustomAttenuation;
|
|
|
+ this.rolloffFactor = options.rolloffFactor || this.rolloffFactor;
|
|
|
+ this.refDistance = options.refDistance || this.refDistance;
|
|
|
+ this.distanceModel = options.distanceModel || this.distanceModel;
|
|
|
+ this.panningModel = options.panningModel || this.panningModel;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private _createSpatialParameters() {
|
|
|
this._soundPanner = this._audioEngine.audioContext.createPanner();
|
|
|
- this._soundPanner.distanceModel = "linear";
|
|
|
- this._soundPanner.maxDistance = this.maxDistance;
|
|
|
- this._soundGain.connect(this._soundPanner);
|
|
|
+
|
|
|
+ if (this.useCustomAttenuation) {
|
|
|
+ // Tricks to disable in a way embedded Web Audio attenuation
|
|
|
+ this._soundPanner.distanceModel = "linear";
|
|
|
+ this._soundPanner.maxDistance = Number.MAX_VALUE;
|
|
|
+ this._soundPanner.refDistance = 1;
|
|
|
+ this._soundPanner.rolloffFactor = 1;
|
|
|
+ this._soundPanner.panningModel = "HRTF";
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ this._soundPanner.distanceModel = this.distanceModel;
|
|
|
+ this._soundPanner.maxDistance = this.maxDistance;
|
|
|
+ this._soundPanner.refDistance = this.refDistance;
|
|
|
+ this._soundPanner.rolloffFactor = this.rolloffFactor;
|
|
|
+ this._soundPanner.panningModel = this.panningModel;
|
|
|
+ }
|
|
|
+ this._soundPanner.connect(this._soundGain);
|
|
|
this._audioNode = this._soundPanner;
|
|
|
}
|
|
|
|
|
@@ -142,11 +183,11 @@
|
|
|
public updateDistanceFromListener() {
|
|
|
if (this._connectedMesh && this.useCustomAttenuation) {
|
|
|
var distance = this._connectedMesh.getDistanceToCamera(this._scene.activeCamera);
|
|
|
- this._soundGain.gain.value = this._customAttenuationFunction(this._volume, distance, this.maxDistance);
|
|
|
+ this._soundGain.gain.value = this._customAttenuationFunction(this._volume, distance, this.maxDistance, this.refDistance, this.rolloffFactor);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public setAttenuationFunction(callback: (currentVolume: number, currentDistance: number, maxDistance: number) => number) {
|
|
|
+ public setAttenuationFunction(callback: (currentVolume: number, currentDistance: number, maxDistance: number, refDistance: number, rolloffFactor: number) => number) {
|
|
|
this._customAttenuationFunction = callback;
|
|
|
}
|
|
|
|
|
@@ -176,7 +217,8 @@
|
|
|
}
|
|
|
this._soundSource.connect(this._audioNode);
|
|
|
this._soundSource.loop = this.loop;
|
|
|
- this._soundSource.start(startTime);
|
|
|
+ this.startTime = startTime;
|
|
|
+ this._soundSource.start(startTime, this.startOffset % this._soundSource.buffer.duration);
|
|
|
this._isPlaying = true;
|
|
|
}
|
|
|
catch (ex) {
|
|
@@ -196,7 +238,8 @@
|
|
|
}
|
|
|
|
|
|
public pause() {
|
|
|
- // TODO
|
|
|
+ this._soundSource.stop(0);
|
|
|
+ this.startOffset += this._audioEngine.audioContext.currentTime - this.startTime;
|
|
|
}
|
|
|
|
|
|
public setVolume(newVolume: number) {
|
|
@@ -213,6 +256,10 @@
|
|
|
if (!this.spatialSound) {
|
|
|
this._createSpatialParameters();
|
|
|
this.spatialSound = true;
|
|
|
+ if (this._isPlaying && this.loop) {
|
|
|
+ this.stop();
|
|
|
+ this.play();
|
|
|
+ }
|
|
|
}
|
|
|
meshToConnectTo.registerAfterWorldMatrixUpdate((connectedMesh: BABYLON.AbstractMesh) => this._onRegisterAfterWorldMatrixUpdate(connectedMesh));
|
|
|
}
|