audioSceneComponent.ts 15 KB

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