babylon.sound.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. module BABYLON {
  2. export class Sound {
  3. public maxDistance: number = 20;
  4. public autoplay: boolean = false;
  5. public loop: boolean = false;
  6. public useBabylonJSAttenuation: boolean = true;
  7. public soundTrackId: number;
  8. private _position: Vector3 = Vector3.Zero();
  9. private _localDirection: Vector3 = new Vector3(1,0,0);
  10. private _volume: number = 1;
  11. private _isLoaded: boolean = false;
  12. private _isReadyToPlay: boolean = false;
  13. private _isPlaying: boolean = false;
  14. private _isDirectional: boolean = false;
  15. private _audioEngine: BABYLON.AudioEngine;
  16. private _readyToPlayCallback;
  17. private _audioBuffer;
  18. private _soundSource: AudioBufferSourceNode;
  19. private _soundPanner: PannerNode;
  20. private _soundGain: GainNode;
  21. // Used if you'd like to create a directional sound.
  22. // If not set, the sound will be omnidirectional
  23. private _coneInnerAngle: number = null;
  24. private _coneOuterAngle: number = null;
  25. private _coneOuterGain: number = null;
  26. private _scene: BABYLON.Scene;
  27. private _name: string;
  28. private _connectedMesh: BABYLON.AbstractMesh;
  29. /**
  30. * Create a sound and attach it to a scene
  31. * @param name Name of your sound
  32. * @param url Url to the sound to load async
  33. * @param readyToPlayCallback Provide a callback function if you'd like to load your code once the sound is ready to be played
  34. * @param options Objects to provide with the current available options: autoplay, loop, distanceMax
  35. */
  36. constructor(name: string, url: string, scene: BABYLON.Scene, readyToPlayCallback?: () => void, options?) {
  37. this._name = name;
  38. this._scene = scene;
  39. this._audioEngine = this._scene.getEngine().getAudioEngine();
  40. this._readyToPlayCallback = readyToPlayCallback;
  41. if (options) {
  42. if (options.maxDistance) { this.maxDistance = options.maxDistance; }
  43. if (options.autoplay) { this.autoplay = options.autoplay; }
  44. if (options.loop) { this.loop = options.loop; }
  45. if (options.volume) { this._volume = options.volume; }
  46. if (options.useBabylonJSAttenuation) { this.useBabylonJSAttenuation = options.useBabylonJSAttenuation; }
  47. }
  48. if (this._audioEngine.canUseWebAudio) {
  49. this._soundGain = this._audioEngine.audioContext.createGain();
  50. this._soundGain.gain.value = this._volume;
  51. //this._soundGain.connect(this._audioEngine.masterGain);
  52. this._soundPanner = this._audioEngine.audioContext.createPanner();
  53. this._soundPanner.connect(this._soundGain);
  54. this._scene.mainSoundTrack.AddSound(this);
  55. BABYLON.Tools.LoadFile(url, (data) => { this._soundLoaded(data); }, null, null, true);
  56. }
  57. }
  58. public connectToSoundTrackAudioNode(soundTrackAudioNode: AudioNode) {
  59. if (this._audioEngine.canUseWebAudio) {
  60. this._soundGain.disconnect();
  61. this._soundGain.connect(soundTrackAudioNode);
  62. }
  63. }
  64. /**
  65. * Transform this sound into a directional source
  66. * @param coneInnerAngle Size of the inner cone in degree
  67. * @param coneOuterAngle Size of the outer cone in degree
  68. * @param coneOuterGain Volume of the sound outside the outer cone (between 0.0 and 1.0)
  69. */
  70. public setDirectionalCone(coneInnerAngle: number, coneOuterAngle: number, coneOuterGain: number) {
  71. if (coneOuterAngle < coneInnerAngle) {
  72. BABYLON.Tools.Error("setDirectionalCone(): outer angle of the cone must be superior or equal to the inner angle.");
  73. return;
  74. }
  75. this._coneInnerAngle = coneInnerAngle;
  76. this._coneOuterAngle = coneOuterAngle;
  77. this._coneOuterGain = coneOuterGain;
  78. this._isDirectional = true;
  79. if (this._isPlaying && this.loop) {
  80. this.stop();
  81. this.play();
  82. }
  83. }
  84. public setPosition(newPosition: Vector3) {
  85. this._position = newPosition;
  86. if (this._isPlaying) {
  87. this._soundPanner.setPosition(this._position.x, this._position.y, this._position.z);
  88. }
  89. }
  90. public setLocalDirectionToMesh(newLocalDirection: Vector3) {
  91. this._localDirection = newLocalDirection;
  92. if (this._connectedMesh && this._isPlaying) {
  93. this._updateDirection();
  94. }
  95. }
  96. private _updateDirection() {
  97. var mat = this._connectedMesh.getWorldMatrix();
  98. var direction = BABYLON.Vector3.TransformNormal(this._localDirection, mat);
  99. direction.normalize();
  100. this._soundPanner.setOrientation(direction.x, direction.y, direction.z);
  101. }
  102. public updateDistanceFromListener() {
  103. if (this._connectedMesh) {
  104. var distance = this._connectedMesh.getDistanceToCamera(this._scene.activeCamera);
  105. if (distance < 1) distance = 1;
  106. if (this.useBabylonJSAttenuation) {
  107. if (distance < this.maxDistance) {
  108. this._soundGain.gain.value = this._volume / distance;
  109. }
  110. else {
  111. this._soundGain.gain.value = 0;
  112. }
  113. }
  114. }
  115. }
  116. /**
  117. * Play the sound
  118. * @param time (optional) Start the sound after X seconds. Start immediately (0) by default.
  119. */
  120. public play(time?: number) {
  121. if (this._isReadyToPlay) {
  122. try {
  123. var startTime = time ? this._audioEngine.audioContext.currentTime + time : 0;
  124. this._soundSource = this._audioEngine.audioContext.createBufferSource();
  125. this._soundSource.buffer = this._audioBuffer;
  126. this._soundPanner.setPosition(this._position.x, this._position.y, this._position.z);
  127. if (this._isDirectional) {
  128. this._soundPanner.coneInnerAngle = this._coneInnerAngle;
  129. this._soundPanner.coneOuterAngle = this._coneOuterAngle;
  130. this._soundPanner.coneOuterGain = this._coneOuterGain;
  131. this._soundPanner.setOrientation(this._localDirection.x, this._localDirection.y, this._localDirection.z);
  132. }
  133. this._soundSource.connect(this._soundPanner);
  134. this._soundSource.loop = this.loop;
  135. this._soundSource.start(startTime);
  136. this._isPlaying = true;
  137. }
  138. catch (ex) {
  139. BABYLON.Tools.Error("Error while trying to play audio: " + this._name + ", " + ex.message);
  140. }
  141. }
  142. }
  143. /**
  144. * Stop the sound
  145. * @param time (optional) Stop the sound after X seconds. Stop immediately (0) by default.
  146. */
  147. public stop(time?: number) {
  148. var stopTime = time ? this._audioEngine.audioContext.currentTime + time : 0;
  149. this._soundSource.stop(stopTime);
  150. this._isPlaying = false;
  151. }
  152. public pause() {
  153. //this._soundSource.p
  154. }
  155. public setVolume(newVolume: number) {
  156. this._volume = newVolume;
  157. }
  158. public getVolume(): number {
  159. return this._volume;
  160. }
  161. public attachToMesh(meshToConnectTo: BABYLON.AbstractMesh) {
  162. this._connectedMesh = meshToConnectTo;
  163. meshToConnectTo.registerAfterWorldMatrixUpdate((connectedMesh: BABYLON.AbstractMesh) => this._onRegisterAfterWorldMatrixUpdate(connectedMesh));
  164. }
  165. private _onRegisterAfterWorldMatrixUpdate(connectedMesh: BABYLON.AbstractMesh) {
  166. this.setPosition(connectedMesh.position);
  167. if (this._isDirectional && this._isPlaying) {
  168. this._updateDirection();
  169. }
  170. }
  171. private _soundLoaded(audioData: ArrayBuffer) {
  172. this._isLoaded = true;
  173. this._audioEngine.audioContext.decodeAudioData(audioData, (buffer) => {
  174. this._audioBuffer = buffer;
  175. this._isReadyToPlay = true;
  176. if (this.autoplay) { this.play(); }
  177. if (this._readyToPlayCallback) { this._readyToPlayCallback(); }
  178. }, function (error) {
  179. BABYLON.Tools.Error("Error while decoding audio data: " + error.err);
  180. });
  181. }
  182. }
  183. }