audioSceneComponent.js 18 KB

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