audioSceneComponent.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. import { Sound } from "./sound";
  2. import { SoundTrack } from "./soundTrack";
  3. import { Engine } from "../Engines/engine";
  4. import { Camera } from "../Cameras/camera";
  5. import { Nullable } from "../types";
  6. import { Matrix, Vector3 } from "../Maths/math.vector";
  7. import { SceneComponentConstants, ISceneSerializableComponent } from "../sceneComponent";
  8. import { Scene } from "../scene";
  9. import { AbstractScene } from "../abstractScene";
  10. import { AssetContainer } from "../assetContainer";
  11. import "./audioEngine";
  12. // Adds the parser to the scene parsers.
  13. AbstractScene.AddParser(SceneComponentConstants.NAME_AUDIO, (parsedData: any, scene: Scene, container: AssetContainer, rootUrl: string) => {
  14. // TODO: add sound
  15. var loadedSounds: Sound[] = [];
  16. var loadedSound: Sound;
  17. container.sounds = container.sounds || [];
  18. if (parsedData.sounds !== undefined && parsedData.sounds !== null) {
  19. for (let index = 0, cache = parsedData.sounds.length; index < cache; index++) {
  20. var parsedSound = parsedData.sounds[index];
  21. if (Engine.audioEngine.canUseWebAudio) {
  22. if (!parsedSound.url) { parsedSound.url = parsedSound.name; }
  23. if (!loadedSounds[parsedSound.url]) {
  24. loadedSound = Sound.Parse(parsedSound, scene, rootUrl);
  25. loadedSounds[parsedSound.url] = loadedSound;
  26. container.sounds.push(loadedSound);
  27. }
  28. else {
  29. container.sounds.push(Sound.Parse(parsedSound, scene, rootUrl, loadedSounds[parsedSound.url]));
  30. }
  31. } else {
  32. container.sounds.push(new Sound(parsedSound.name, null, scene));
  33. }
  34. }
  35. }
  36. loadedSounds = [];
  37. });
  38. declare module "../abstractScene" {
  39. export interface AbstractScene {
  40. /**
  41. * The list of sounds used in the scene.
  42. */
  43. sounds: Nullable<Array<Sound>>;
  44. }
  45. }
  46. declare module "../scene" {
  47. export interface Scene {
  48. /**
  49. * @hidden
  50. * Backing field
  51. */
  52. _mainSoundTrack: SoundTrack;
  53. /**
  54. * The main sound track played by the scene.
  55. * It cotains your primary collection of sounds.
  56. */
  57. mainSoundTrack: SoundTrack;
  58. /**
  59. * The list of sound tracks added to the scene
  60. * @see http://doc.babylonjs.com/how_to/playing_sounds_and_music
  61. */
  62. soundTracks: Nullable<Array<SoundTrack>>;
  63. /**
  64. * Gets a sound using a given name
  65. * @param name defines the name to search for
  66. * @return the found sound or null if not found at all.
  67. */
  68. getSoundByName(name: string): Nullable<Sound>;
  69. /**
  70. * Gets or sets if audio support is enabled
  71. * @see http://doc.babylonjs.com/how_to/playing_sounds_and_music
  72. */
  73. audioEnabled: boolean;
  74. /**
  75. * Gets or sets if audio will be output to headphones
  76. * @see http://doc.babylonjs.com/how_to/playing_sounds_and_music
  77. */
  78. headphone: boolean;
  79. /**
  80. * Gets or sets custom audio listener position provider
  81. * @see http://doc.babylonjs.com/how_to/playing_sounds_and_music
  82. */
  83. audioListenerPositionProvider: Nullable<() => Vector3>;
  84. }
  85. }
  86. Object.defineProperty(Scene.prototype, "mainSoundTrack", {
  87. get: function(this: Scene) {
  88. let compo = this._getComponent(SceneComponentConstants.NAME_AUDIO) as AudioSceneComponent;
  89. if (!compo) {
  90. compo = new AudioSceneComponent(this);
  91. this._addComponent(compo);
  92. }
  93. if (!this._mainSoundTrack) {
  94. this._mainSoundTrack = new SoundTrack(this, { mainTrack: true });
  95. }
  96. return this._mainSoundTrack;
  97. },
  98. enumerable: true,
  99. configurable: true
  100. });
  101. Scene.prototype.getSoundByName = function(name: string): Nullable<Sound> {
  102. var index: number;
  103. for (index = 0; index < this.mainSoundTrack.soundCollection.length; index++) {
  104. if (this.mainSoundTrack.soundCollection[index].name === name) {
  105. return this.mainSoundTrack.soundCollection[index];
  106. }
  107. }
  108. if (this.soundTracks) {
  109. for (var sdIndex = 0; sdIndex < this.soundTracks.length; sdIndex++) {
  110. for (index = 0; index < this.soundTracks[sdIndex].soundCollection.length; index++) {
  111. if (this.soundTracks[sdIndex].soundCollection[index].name === name) {
  112. return this.soundTracks[sdIndex].soundCollection[index];
  113. }
  114. }
  115. }
  116. }
  117. return null;
  118. };
  119. Object.defineProperty(Scene.prototype, "audioEnabled", {
  120. get: function(this: Scene) {
  121. let compo = this._getComponent(SceneComponentConstants.NAME_AUDIO) as AudioSceneComponent;
  122. if (!compo) {
  123. compo = new AudioSceneComponent(this);
  124. this._addComponent(compo);
  125. }
  126. return compo.audioEnabled;
  127. },
  128. set: function(this: Scene, value: boolean) {
  129. let compo = this._getComponent(SceneComponentConstants.NAME_AUDIO) as AudioSceneComponent;
  130. if (!compo) {
  131. compo = new AudioSceneComponent(this);
  132. this._addComponent(compo);
  133. }
  134. if (value) {
  135. compo.enableAudio();
  136. }
  137. else {
  138. compo.disableAudio();
  139. }
  140. },
  141. enumerable: true,
  142. configurable: true
  143. });
  144. Object.defineProperty(Scene.prototype, "headphone", {
  145. get: function(this: Scene) {
  146. let compo = this._getComponent(SceneComponentConstants.NAME_AUDIO) as AudioSceneComponent;
  147. if (!compo) {
  148. compo = new AudioSceneComponent(this);
  149. this._addComponent(compo);
  150. }
  151. return compo.headphone;
  152. },
  153. set: function(this: Scene, value: boolean) {
  154. let compo = this._getComponent(SceneComponentConstants.NAME_AUDIO) as AudioSceneComponent;
  155. if (!compo) {
  156. compo = new AudioSceneComponent(this);
  157. this._addComponent(compo);
  158. }
  159. if (value) {
  160. compo.switchAudioModeForHeadphones();
  161. }
  162. else {
  163. compo.switchAudioModeForNormalSpeakers();
  164. }
  165. },
  166. enumerable: true,
  167. configurable: true
  168. });
  169. Object.defineProperty(Scene.prototype, "audioListenerPositionProvider", {
  170. get: function(this: Scene) {
  171. let compo = this._getComponent(SceneComponentConstants.NAME_AUDIO) as AudioSceneComponent;
  172. if (!compo) {
  173. compo = new AudioSceneComponent(this);
  174. this._addComponent(compo);
  175. }
  176. return compo.audioListenerPositionProvider;
  177. },
  178. set: function(this: Scene, value: () => Vector3) {
  179. let compo = this._getComponent(SceneComponentConstants.NAME_AUDIO) as AudioSceneComponent;
  180. if (!compo) {
  181. compo = new AudioSceneComponent(this);
  182. this._addComponent(compo);
  183. }
  184. if (typeof value !== 'function') {
  185. throw new Error('The value passed to [Scene.audioListenerPositionProvider] must be a function that returns a Vector3');
  186. } else {
  187. compo.audioListenerPositionProvider = value;
  188. }
  189. },
  190. enumerable: true,
  191. configurable: true
  192. });
  193. /**
  194. * Defines the sound scene component responsible to manage any sounds
  195. * in a given scene.
  196. */
  197. export class AudioSceneComponent implements ISceneSerializableComponent {
  198. /**
  199. * The component name helpfull to identify the component in the list of scene components.
  200. */
  201. public readonly name = SceneComponentConstants.NAME_AUDIO;
  202. /**
  203. * The scene the component belongs to.
  204. */
  205. public scene: Scene;
  206. private _audioEnabled = true;
  207. /**
  208. * Gets whether audio is enabled or not.
  209. * Please use related enable/disable method to switch state.
  210. */
  211. public get audioEnabled(): boolean {
  212. return this._audioEnabled;
  213. }
  214. private _headphone = false;
  215. /**
  216. * Gets whether audio is outputing to headphone or not.
  217. * Please use the according Switch methods to change output.
  218. */
  219. public get headphone(): boolean {
  220. return this._headphone;
  221. }
  222. private _audioListenerPositionProvider: Nullable<() => Vector3> = null;
  223. /**
  224. * Gets the current audio listener position provider
  225. */
  226. public get audioListenerPositionProvider(): Nullable<() => Vector3> {
  227. return this._audioListenerPositionProvider;
  228. }
  229. /**
  230. * Sets a custom listener position for all sounds in the scene
  231. * By default, this is the position of the first active camera
  232. */
  233. public set audioListenerPositionProvider(value: Nullable<() => Vector3>) {
  234. this._audioListenerPositionProvider = value;
  235. }
  236. /**
  237. * Creates a new instance of the component for the given scene
  238. * @param scene Defines the scene to register the component in
  239. */
  240. constructor(scene: Scene) {
  241. this.scene = scene;
  242. scene.soundTracks = new Array<SoundTrack>();
  243. scene.sounds = new Array<Sound>();
  244. }
  245. /**
  246. * Registers the component in a given scene
  247. */
  248. public register(): void {
  249. this.scene._afterRenderStage.registerStep(SceneComponentConstants.STEP_AFTERRENDER_AUDIO, this, this._afterRender);
  250. }
  251. /**
  252. * Rebuilds the elements related to this component in case of
  253. * context lost for instance.
  254. */
  255. public rebuild(): void {
  256. // Nothing to do here. (Not rendering related)
  257. }
  258. /**
  259. * Serializes the component data to the specified json object
  260. * @param serializationObject The object to serialize to
  261. */
  262. public serialize(serializationObject: any): void {
  263. serializationObject.sounds = [];
  264. if (this.scene.soundTracks) {
  265. for (var index = 0; index < this.scene.soundTracks.length; index++) {
  266. var soundtrack = this.scene.soundTracks[index];
  267. for (var soundId = 0; soundId < soundtrack.soundCollection.length; soundId++) {
  268. serializationObject.sounds.push(soundtrack.soundCollection[soundId].serialize());
  269. }
  270. }
  271. }
  272. }
  273. /**
  274. * Adds all the elements from the container to the scene
  275. * @param container the container holding the elements
  276. */
  277. public addFromContainer(container: AbstractScene): void {
  278. if (!container.sounds) {
  279. return;
  280. }
  281. container.sounds.forEach((sound) => {
  282. sound.play();
  283. sound.autoplay = true;
  284. this.scene.mainSoundTrack.AddSound(sound);
  285. });
  286. }
  287. /**
  288. * Removes all the elements in the container from the scene
  289. * @param container contains the elements to remove
  290. * @param dispose if the removed element should be disposed (default: false)
  291. */
  292. public removeFromContainer(container: AbstractScene, dispose = false): void {
  293. if (!container.sounds) {
  294. return;
  295. }
  296. container.sounds.forEach((sound) => {
  297. sound.stop();
  298. sound.autoplay = false;
  299. this.scene.mainSoundTrack.RemoveSound(sound);
  300. if (dispose) {
  301. sound.dispose();
  302. }
  303. });
  304. }
  305. /**
  306. * Disposes the component and the associated ressources.
  307. */
  308. public dispose(): void {
  309. const scene = this.scene;
  310. if (scene._mainSoundTrack) {
  311. scene.mainSoundTrack.dispose();
  312. }
  313. if (scene.soundTracks) {
  314. for (var scIndex = 0; scIndex < scene.soundTracks.length; scIndex++) {
  315. scene.soundTracks[scIndex].dispose();
  316. }
  317. }
  318. }
  319. /**
  320. * Disables audio in the associated scene.
  321. */
  322. public disableAudio() {
  323. const scene = this.scene;
  324. this._audioEnabled = false;
  325. let i: number;
  326. for (i = 0; i < scene.mainSoundTrack.soundCollection.length; i++) {
  327. scene.mainSoundTrack.soundCollection[i].pause();
  328. }
  329. if (scene.soundTracks) {
  330. for (i = 0; i < scene.soundTracks.length; i++) {
  331. for (var j = 0; j < scene.soundTracks[i].soundCollection.length; j++) {
  332. scene.soundTracks[i].soundCollection[j].pause();
  333. }
  334. }
  335. }
  336. }
  337. /**
  338. * Enables audio in the associated scene.
  339. */
  340. public enableAudio() {
  341. const scene = this.scene;
  342. this._audioEnabled = true;
  343. let i: number;
  344. for (i = 0; i < scene.mainSoundTrack.soundCollection.length; i++) {
  345. if (scene.mainSoundTrack.soundCollection[i].isPaused) {
  346. scene.mainSoundTrack.soundCollection[i].play();
  347. }
  348. }
  349. if (scene.soundTracks) {
  350. for (i = 0; i < scene.soundTracks.length; i++) {
  351. for (var j = 0; j < scene.soundTracks[i].soundCollection.length; j++) {
  352. if (scene.soundTracks[i].soundCollection[j].isPaused) {
  353. scene.soundTracks[i].soundCollection[j].play();
  354. }
  355. }
  356. }
  357. }
  358. }
  359. /**
  360. * Switch audio to headphone output.
  361. */
  362. public switchAudioModeForHeadphones() {
  363. const scene = this.scene;
  364. this._headphone = true;
  365. scene.mainSoundTrack.switchPanningModelToHRTF();
  366. if (scene.soundTracks) {
  367. for (var i = 0; i < scene.soundTracks.length; i++) {
  368. scene.soundTracks[i].switchPanningModelToHRTF();
  369. }
  370. }
  371. }
  372. /**
  373. * Switch audio to normal speakers.
  374. */
  375. public switchAudioModeForNormalSpeakers() {
  376. const scene = this.scene;
  377. this._headphone = false;
  378. scene.mainSoundTrack.switchPanningModelToEqualPower();
  379. if (scene.soundTracks) {
  380. for (var i = 0; i < scene.soundTracks.length; i++) {
  381. scene.soundTracks[i].switchPanningModelToEqualPower();
  382. }
  383. }
  384. }
  385. private _afterRender() {
  386. const scene = this.scene;
  387. if (!this._audioEnabled || !scene._mainSoundTrack || !scene.soundTracks || (scene._mainSoundTrack.soundCollection.length === 0 && scene.soundTracks.length === 1)) {
  388. return;
  389. }
  390. var audioEngine = Engine.audioEngine;
  391. if (audioEngine.audioContext) {
  392. // A custom listener position provider was set
  393. // Use the users provided position instead of camera's
  394. if (this._audioListenerPositionProvider) {
  395. var position: Vector3 = this._audioListenerPositionProvider();
  396. // Make sure all coordinates were provided
  397. position.x = position.x || 0;
  398. position.y = position.y || 0;
  399. position.z = position.z || 0;
  400. // Set the listener position
  401. audioEngine.audioContext.listener.setPosition(position.x, position.y, position.z);
  402. } else {
  403. var listeningCamera: Nullable<Camera>;
  404. if (scene.activeCameras.length > 0) {
  405. listeningCamera = scene.activeCameras[0];
  406. } else {
  407. listeningCamera = scene.activeCamera;
  408. }
  409. // Check if there is a listening camera
  410. if (listeningCamera) {
  411. // Set the listener position to the listening camera global position
  412. audioEngine.audioContext.listener.setPosition(listeningCamera.globalPosition.x, listeningCamera.globalPosition.y, listeningCamera.globalPosition.z);
  413. // for VR cameras
  414. if (listeningCamera.rigCameras && listeningCamera.rigCameras.length > 0) {
  415. listeningCamera = listeningCamera.rigCameras[0];
  416. }
  417. var mat = Matrix.Invert(listeningCamera.getViewMatrix());
  418. var cameraDirection = Vector3.TransformNormal(new Vector3(0, 0, -1), mat);
  419. cameraDirection.normalize();
  420. // To avoid some errors on GearVR
  421. if (!isNaN(cameraDirection.x) && !isNaN(cameraDirection.y) && !isNaN(cameraDirection.z)) {
  422. audioEngine.audioContext.listener.setOrientation(cameraDirection.x, cameraDirection.y, cameraDirection.z, 0, 1, 0);
  423. }
  424. }
  425. // Otherwise set the listener position to 0, 0 ,0
  426. else {
  427. // Set the listener position
  428. audioEngine.audioContext.listener.setPosition(0, 0, 0);
  429. }
  430. }
  431. var i: number;
  432. for (i = 0; i < scene.mainSoundTrack.soundCollection.length; i++) {
  433. var sound = scene.mainSoundTrack.soundCollection[i];
  434. if (sound.useCustomAttenuation) {
  435. sound.updateDistanceFromListener();
  436. }
  437. }
  438. if (scene.soundTracks) {
  439. for (i = 0; i < scene.soundTracks.length; i++) {
  440. for (var j = 0; j < scene.soundTracks[i].soundCollection.length; j++) {
  441. sound = scene.soundTracks[i].soundCollection[j];
  442. if (sound.useCustomAttenuation) {
  443. sound.updateDistanceFromListener();
  444. }
  445. }
  446. }
  447. }
  448. }
  449. }
  450. }
  451. Sound._SceneComponentInitialization = (scene: Scene) => {
  452. let compo = scene._getComponent(SceneComponentConstants.NAME_AUDIO);
  453. if (!compo) {
  454. compo = new AudioSceneComponent(scene);
  455. scene._addComponent(compo);
  456. }
  457. };