babylon.sound.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. var BABYLON;
  2. (function (BABYLON) {
  3. var Sound = (function () {
  4. /**
  5. * Create a sound and attach it to a scene
  6. * @param name Name of your sound
  7. * @param urlOrArrayBuffer Url to the sound to load async or ArrayBuffer
  8. * @param readyToPlayCallback Provide a callback function if you'd like to load your code once the sound is ready to be played
  9. * @param options Objects to provide with the current available options: autoplay, loop, volume, spatialSound, maxDistance, rolloffFactor, refDistance, distanceModel, panningModel
  10. */
  11. function Sound(name, urlOrArrayBuffer, scene, readyToPlayCallback, options) {
  12. var _this = this;
  13. this.autoplay = false;
  14. this.loop = false;
  15. this.useCustomAttenuation = false;
  16. this.spatialSound = false;
  17. this.refDistance = 1;
  18. this.rolloffFactor = 1;
  19. this.maxDistance = 100;
  20. this.distanceModel = "linear";
  21. this.panningModel = "HRTF";
  22. this.startTime = 0;
  23. this.startOffset = 0;
  24. this._position = BABYLON.Vector3.Zero();
  25. this._localDirection = new BABYLON.Vector3(1, 0, 0);
  26. this._volume = 1;
  27. this._isLoaded = false;
  28. this._isReadyToPlay = false;
  29. this._isPlaying = false;
  30. this._isDirectional = false;
  31. // Used if you'd like to create a directional sound.
  32. // If not set, the sound will be omnidirectional
  33. this._coneInnerAngle = 360;
  34. this._coneOuterAngle = 360;
  35. this._coneOuterGain = 0;
  36. this._name = name;
  37. this._scene = scene;
  38. this._audioEngine = this._scene.getEngine().getAudioEngine();
  39. this._readyToPlayCallback = readyToPlayCallback;
  40. // Default custom attenuation function is a linear attenuation
  41. this._customAttenuationFunction = function (currentVolume, currentDistance, maxDistance, refDistance, rolloffFactor) {
  42. if (currentDistance < maxDistance) {
  43. return currentVolume * (1 - currentDistance / maxDistance);
  44. } else {
  45. return 0;
  46. }
  47. };
  48. if (options) {
  49. this.autoplay = options.autoplay || false;
  50. this.loop = options.loop || false;
  51. this._volume = options.volume || 1;
  52. this.spatialSound = options.spatialSound || false;
  53. this.maxDistance = options.maxDistance || 100;
  54. this.useCustomAttenuation = options.useCustomAttenation || false;
  55. this.rolloffFactor = options.rolloffFactor || 1;
  56. this.refDistance = options.refDistance || 1;
  57. this.distanceModel = options.distanceModel || "linear";
  58. this.panningModel = options.panningModel || "HRTF";
  59. }
  60. if (this._audioEngine.canUseWebAudio) {
  61. this._soundGain = this._audioEngine.audioContext.createGain();
  62. this._soundGain.gain.value = this._volume;
  63. if (this.spatialSound) {
  64. this._createSpatialParameters();
  65. } else {
  66. this._audioNode = this._soundGain;
  67. }
  68. this._scene.mainSoundTrack.AddSound(this);
  69. if (typeof (urlOrArrayBuffer) === "string") {
  70. BABYLON.Tools.LoadFile(urlOrArrayBuffer, function (data) {
  71. _this._soundLoaded(data);
  72. }, null, null, true);
  73. } else {
  74. if (urlOrArrayBuffer instanceof ArrayBuffer) {
  75. this._soundLoaded(urlOrArrayBuffer);
  76. } else {
  77. BABYLON.Tools.Error("Parameter must be a URL to the sound or an ArrayBuffer of the sound.");
  78. }
  79. }
  80. }
  81. }
  82. Sound.prototype.updateOptions = function (options) {
  83. if (options) {
  84. this.loop = options.loop || this.loop;
  85. this.maxDistance = options.maxDistance || this.maxDistance;
  86. this.useCustomAttenuation = options.useCustomAttenation || this.useCustomAttenuation;
  87. this.rolloffFactor = options.rolloffFactor || this.rolloffFactor;
  88. this.refDistance = options.refDistance || this.refDistance;
  89. this.distanceModel = options.distanceModel || this.distanceModel;
  90. this.panningModel = options.panningModel || this.panningModel;
  91. }
  92. };
  93. Sound.prototype._createSpatialParameters = function () {
  94. this._soundPanner = this._audioEngine.audioContext.createPanner();
  95. if (this.useCustomAttenuation) {
  96. // Tricks to disable in a way embedded Web Audio attenuation
  97. this._soundPanner.distanceModel = "linear";
  98. this._soundPanner.maxDistance = Number.MAX_VALUE;
  99. this._soundPanner.refDistance = 1;
  100. this._soundPanner.rolloffFactor = 1;
  101. this._soundPanner.panningModel = "HRTF";
  102. } else {
  103. this._soundPanner.distanceModel = this.distanceModel;
  104. this._soundPanner.maxDistance = this.maxDistance;
  105. this._soundPanner.refDistance = this.refDistance;
  106. this._soundPanner.rolloffFactor = this.rolloffFactor;
  107. this._soundPanner.panningModel = this.panningModel;
  108. }
  109. this._soundPanner.connect(this._soundGain);
  110. this._audioNode = this._soundPanner;
  111. };
  112. Sound.prototype.connectToSoundTrackAudioNode = function (soundTrackAudioNode) {
  113. if (this._audioEngine.canUseWebAudio) {
  114. this._audioNode.disconnect();
  115. this._audioNode.connect(soundTrackAudioNode);
  116. }
  117. };
  118. /**
  119. * Transform this sound into a directional source
  120. * @param coneInnerAngle Size of the inner cone in degree
  121. * @param coneOuterAngle Size of the outer cone in degree
  122. * @param coneOuterGain Volume of the sound outside the outer cone (between 0.0 and 1.0)
  123. */
  124. Sound.prototype.setDirectionalCone = function (coneInnerAngle, coneOuterAngle, coneOuterGain) {
  125. if (coneOuterAngle < coneInnerAngle) {
  126. BABYLON.Tools.Error("setDirectionalCone(): outer angle of the cone must be superior or equal to the inner angle.");
  127. return;
  128. }
  129. this._coneInnerAngle = coneInnerAngle;
  130. this._coneOuterAngle = coneOuterAngle;
  131. this._coneOuterGain = coneOuterGain;
  132. this._isDirectional = true;
  133. if (this._isPlaying && this.loop) {
  134. this.stop();
  135. this.play();
  136. }
  137. };
  138. Sound.prototype.setPosition = function (newPosition) {
  139. this._position = newPosition;
  140. if (this._isPlaying && this.spatialSound) {
  141. this._soundPanner.setPosition(this._position.x, this._position.y, this._position.z);
  142. }
  143. };
  144. Sound.prototype.setLocalDirectionToMesh = function (newLocalDirection) {
  145. this._localDirection = newLocalDirection;
  146. if (this._connectedMesh && this._isPlaying) {
  147. this._updateDirection();
  148. }
  149. };
  150. Sound.prototype._updateDirection = function () {
  151. var mat = this._connectedMesh.getWorldMatrix();
  152. var direction = BABYLON.Vector3.TransformNormal(this._localDirection, mat);
  153. direction.normalize();
  154. this._soundPanner.setOrientation(direction.x, direction.y, direction.z);
  155. };
  156. Sound.prototype.updateDistanceFromListener = function () {
  157. if (this._connectedMesh && this.useCustomAttenuation) {
  158. var distance = this._connectedMesh.getDistanceToCamera(this._scene.activeCamera);
  159. this._soundGain.gain.value = this._customAttenuationFunction(this._volume, distance, this.maxDistance, this.refDistance, this.rolloffFactor);
  160. }
  161. };
  162. Sound.prototype.setAttenuationFunction = function (callback) {
  163. this._customAttenuationFunction = callback;
  164. };
  165. /**
  166. * Play the sound
  167. * @param time (optional) Start the sound after X seconds. Start immediately (0) by default.
  168. */
  169. Sound.prototype.play = function (time) {
  170. if (this._isReadyToPlay) {
  171. try {
  172. var startTime = time ? this._audioEngine.audioContext.currentTime + time : 0;
  173. this._soundSource = this._audioEngine.audioContext.createBufferSource();
  174. this._soundSource.buffer = this._audioBuffer;
  175. if (this.spatialSound) {
  176. this._soundPanner.setPosition(this._position.x, this._position.y, this._position.z);
  177. if (this._isDirectional) {
  178. this._soundPanner.coneInnerAngle = this._coneInnerAngle;
  179. this._soundPanner.coneOuterAngle = this._coneOuterAngle;
  180. this._soundPanner.coneOuterGain = this._coneOuterGain;
  181. if (this._connectedMesh) {
  182. this._updateDirection();
  183. } else {
  184. this._soundPanner.setOrientation(this._localDirection.x, this._localDirection.y, this._localDirection.z);
  185. }
  186. }
  187. }
  188. this._soundSource.connect(this._audioNode);
  189. this._soundSource.loop = this.loop;
  190. this.startTime = startTime;
  191. this._soundSource.start(startTime, this.startOffset % this._soundSource.buffer.duration);
  192. this._isPlaying = true;
  193. } catch (ex) {
  194. BABYLON.Tools.Error("Error while trying to play audio: " + this._name + ", " + ex.message);
  195. }
  196. }
  197. };
  198. /**
  199. * Stop the sound
  200. * @param time (optional) Stop the sound after X seconds. Stop immediately (0) by default.
  201. */
  202. Sound.prototype.stop = function (time) {
  203. var stopTime = time ? this._audioEngine.audioContext.currentTime + time : 0;
  204. this._soundSource.stop(stopTime);
  205. this._isPlaying = false;
  206. };
  207. Sound.prototype.pause = function () {
  208. this._soundSource.stop(0);
  209. this.startOffset += this._audioEngine.audioContext.currentTime - this.startTime;
  210. };
  211. Sound.prototype.setVolume = function (newVolume) {
  212. this._volume = newVolume;
  213. this._soundGain.gain.value = newVolume;
  214. };
  215. Sound.prototype.getVolume = function () {
  216. return this._volume;
  217. };
  218. Sound.prototype.attachToMesh = function (meshToConnectTo) {
  219. var _this = this;
  220. this._connectedMesh = meshToConnectTo;
  221. if (!this.spatialSound) {
  222. this._createSpatialParameters();
  223. this.spatialSound = true;
  224. if (this._isPlaying && this.loop) {
  225. this.stop();
  226. this.play();
  227. }
  228. }
  229. meshToConnectTo.registerAfterWorldMatrixUpdate(function (connectedMesh) {
  230. return _this._onRegisterAfterWorldMatrixUpdate(connectedMesh);
  231. });
  232. };
  233. Sound.prototype._onRegisterAfterWorldMatrixUpdate = function (connectedMesh) {
  234. this.setPosition(connectedMesh.position);
  235. if (this._isDirectional && this._isPlaying) {
  236. this._updateDirection();
  237. }
  238. };
  239. Sound.prototype._soundLoaded = function (audioData) {
  240. var _this = this;
  241. this._isLoaded = true;
  242. this._audioEngine.audioContext.decodeAudioData(audioData, function (buffer) {
  243. _this._audioBuffer = buffer;
  244. _this._isReadyToPlay = true;
  245. if (_this.autoplay) {
  246. _this.play();
  247. }
  248. if (_this._readyToPlayCallback) {
  249. _this._readyToPlayCallback();
  250. }
  251. }, function (error) {
  252. BABYLON.Tools.Error("Error while decoding audio data: " + error.err);
  253. });
  254. };
  255. return Sound;
  256. })();
  257. BABYLON.Sound = Sound;
  258. })(BABYLON || (BABYLON = {}));
  259. //# sourceMappingURL=babylon.sound.js.map