howler.spatial.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. /*!
  2. * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported.
  3. *
  4. * howler.js v2.2.4
  5. * howlerjs.com
  6. *
  7. * (c) 2013-2020, James Simpson of GoldFire Studios
  8. * goldfirestudios.com
  9. *
  10. * MIT License
  11. */
  12. (function() {
  13. 'use strict';
  14. // Setup default properties.
  15. HowlerGlobal.prototype._pos = [0, 0, 0];
  16. HowlerGlobal.prototype._orientation = [0, 0, -1, 0, 1, 0];
  17. /** Global Methods **/
  18. /***************************************************************************/
  19. /**
  20. * Helper method to update the stereo panning position of all current Howls.
  21. * Future Howls will not use this value unless explicitly set.
  22. * @param {Number} pan A value of -1.0 is all the way left and 1.0 is all the way right.
  23. * @return {Howler/Number} Self or current stereo panning value.
  24. */
  25. HowlerGlobal.prototype.stereo = function(pan) {
  26. var self = this;
  27. // Stop right here if not using Web Audio.
  28. if (!self.ctx || !self.ctx.listener) {
  29. return self;
  30. }
  31. // Loop through all Howls and update their stereo panning.
  32. for (var i=self._howls.length-1; i>=0; i--) {
  33. self._howls[i].stereo(pan);
  34. }
  35. return self;
  36. };
  37. /**
  38. * Get/set the position of the listener in 3D cartesian space. Sounds using
  39. * 3D position will be relative to the listener's position.
  40. * @param {Number} x The x-position of the listener.
  41. * @param {Number} y The y-position of the listener.
  42. * @param {Number} z The z-position of the listener.
  43. * @return {Howler/Array} Self or current listener position.
  44. */
  45. HowlerGlobal.prototype.pos = function(x, y, z) {
  46. var self = this;
  47. // Stop right here if not using Web Audio.
  48. if (!self.ctx || !self.ctx.listener) {
  49. return self;
  50. }
  51. // Set the defaults for optional 'y' & 'z'.
  52. y = (typeof y !== 'number') ? self._pos[1] : y;
  53. z = (typeof z !== 'number') ? self._pos[2] : z;
  54. if (typeof x === 'number') {
  55. self._pos = [x, y, z];
  56. if (typeof self.ctx.listener.positionX !== 'undefined') {
  57. self.ctx.listener.positionX.setTargetAtTime(self._pos[0], Howler.ctx.currentTime, 0.1);
  58. self.ctx.listener.positionY.setTargetAtTime(self._pos[1], Howler.ctx.currentTime, 0.1);
  59. self.ctx.listener.positionZ.setTargetAtTime(self._pos[2], Howler.ctx.currentTime, 0.1);
  60. } else {
  61. self.ctx.listener.setPosition(self._pos[0], self._pos[1], self._pos[2]);
  62. }
  63. } else {
  64. return self._pos;
  65. }
  66. return self;
  67. };
  68. /**
  69. * Get/set the direction the listener is pointing in the 3D cartesian space.
  70. * A front and up vector must be provided. The front is the direction the
  71. * face of the listener is pointing, and up is the direction the top of the
  72. * listener is pointing. Thus, these values are expected to be at right angles
  73. * from each other.
  74. * @param {Number} x The x-orientation of the listener.
  75. * @param {Number} y The y-orientation of the listener.
  76. * @param {Number} z The z-orientation of the listener.
  77. * @param {Number} xUp The x-orientation of the top of the listener.
  78. * @param {Number} yUp The y-orientation of the top of the listener.
  79. * @param {Number} zUp The z-orientation of the top of the listener.
  80. * @return {Howler/Array} Returns self or the current orientation vectors.
  81. */
  82. HowlerGlobal.prototype.orientation = function(x, y, z, xUp, yUp, zUp) {
  83. var self = this;
  84. // Stop right here if not using Web Audio.
  85. if (!self.ctx || !self.ctx.listener) {
  86. return self;
  87. }
  88. // Set the defaults for optional 'y' & 'z'.
  89. var or = self._orientation;
  90. y = (typeof y !== 'number') ? or[1] : y;
  91. z = (typeof z !== 'number') ? or[2] : z;
  92. xUp = (typeof xUp !== 'number') ? or[3] : xUp;
  93. yUp = (typeof yUp !== 'number') ? or[4] : yUp;
  94. zUp = (typeof zUp !== 'number') ? or[5] : zUp;
  95. if (typeof x === 'number') {
  96. self._orientation = [x, y, z, xUp, yUp, zUp];
  97. if (typeof self.ctx.listener.forwardX !== 'undefined') {
  98. self.ctx.listener.forwardX.setTargetAtTime(x, Howler.ctx.currentTime, 0.1);
  99. self.ctx.listener.forwardY.setTargetAtTime(y, Howler.ctx.currentTime, 0.1);
  100. self.ctx.listener.forwardZ.setTargetAtTime(z, Howler.ctx.currentTime, 0.1);
  101. self.ctx.listener.upX.setTargetAtTime(xUp, Howler.ctx.currentTime, 0.1);
  102. self.ctx.listener.upY.setTargetAtTime(yUp, Howler.ctx.currentTime, 0.1);
  103. self.ctx.listener.upZ.setTargetAtTime(zUp, Howler.ctx.currentTime, 0.1);
  104. } else {
  105. self.ctx.listener.setOrientation(x, y, z, xUp, yUp, zUp);
  106. }
  107. } else {
  108. return or;
  109. }
  110. return self;
  111. };
  112. /** Group Methods **/
  113. /***************************************************************************/
  114. /**
  115. * Add new properties to the core init.
  116. * @param {Function} _super Core init method.
  117. * @return {Howl}
  118. */
  119. Howl.prototype.init = (function(_super) {
  120. return function(o) {
  121. var self = this;
  122. // Setup user-defined default properties.
  123. self._orientation = o.orientation || [1, 0, 0];
  124. self._stereo = o.stereo || null;
  125. self._pos = o.pos || null;
  126. self._pannerAttr = {
  127. coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : 360,
  128. coneOuterAngle: typeof o.coneOuterAngle !== 'undefined' ? o.coneOuterAngle : 360,
  129. coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : 0,
  130. distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : 'inverse',
  131. maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : 10000,
  132. panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : 'HRTF',
  133. refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : 1,
  134. rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : 1
  135. };
  136. // Setup event listeners.
  137. self._onstereo = o.onstereo ? [{fn: o.onstereo}] : [];
  138. self._onpos = o.onpos ? [{fn: o.onpos}] : [];
  139. self._onorientation = o.onorientation ? [{fn: o.onorientation}] : [];
  140. // Complete initilization with howler.js core's init function.
  141. return _super.call(this, o);
  142. };
  143. })(Howl.prototype.init);
  144. /**
  145. * Get/set the stereo panning of the audio source for this sound or all in the group.
  146. * @param {Number} pan A value of -1.0 is all the way left and 1.0 is all the way right.
  147. * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated.
  148. * @return {Howl/Number} Returns self or the current stereo panning value.
  149. */
  150. Howl.prototype.stereo = function(pan, id) {
  151. var self = this;
  152. // Stop right here if not using Web Audio.
  153. if (!self._webAudio) {
  154. return self;
  155. }
  156. // If the sound hasn't loaded, add it to the load queue to change stereo pan when capable.
  157. if (self._state !== 'loaded') {
  158. self._queue.push({
  159. event: 'stereo',
  160. action: function() {
  161. self.stereo(pan, id);
  162. }
  163. });
  164. return self;
  165. }
  166. // Check for PannerStereoNode support and fallback to PannerNode if it doesn't exist.
  167. var pannerType = (typeof Howler.ctx.createStereoPanner === 'undefined') ? 'spatial' : 'stereo';
  168. // Setup the group's stereo panning if no ID is passed.
  169. if (typeof id === 'undefined') {
  170. // Return the group's stereo panning if no parameters are passed.
  171. if (typeof pan === 'number') {
  172. self._stereo = pan;
  173. self._pos = [pan, 0, 0];
  174. } else {
  175. return self._stereo;
  176. }
  177. }
  178. // Change the streo panning of one or all sounds in group.
  179. var ids = self._getSoundIds(id);
  180. for (var i=0; i<ids.length; i++) {
  181. // Get the sound.
  182. var sound = self._soundById(ids[i]);
  183. if (sound) {
  184. if (typeof pan === 'number') {
  185. sound._stereo = pan;
  186. sound._pos = [pan, 0, 0];
  187. if (sound._node) {
  188. // If we are falling back, make sure the panningModel is equalpower.
  189. sound._pannerAttr.panningModel = 'equalpower';
  190. // Check if there is a panner setup and create a new one if not.
  191. if (!sound._panner || !sound._panner.pan) {
  192. setupPanner(sound, pannerType);
  193. }
  194. if (pannerType === 'spatial') {
  195. if (typeof sound._panner.positionX !== 'undefined') {
  196. sound._panner.positionX.setValueAtTime(pan, Howler.ctx.currentTime);
  197. sound._panner.positionY.setValueAtTime(0, Howler.ctx.currentTime);
  198. sound._panner.positionZ.setValueAtTime(0, Howler.ctx.currentTime);
  199. } else {
  200. sound._panner.setPosition(pan, 0, 0);
  201. }
  202. } else {
  203. sound._panner.pan.setValueAtTime(pan, Howler.ctx.currentTime);
  204. }
  205. }
  206. self._emit('stereo', sound._id);
  207. } else {
  208. return sound._stereo;
  209. }
  210. }
  211. }
  212. return self;
  213. };
  214. /**
  215. * Get/set the 3D spatial position of the audio source for this sound or group relative to the global listener.
  216. * @param {Number} x The x-position of the audio source.
  217. * @param {Number} y The y-position of the audio source.
  218. * @param {Number} z The z-position of the audio source.
  219. * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated.
  220. * @return {Howl/Array} Returns self or the current 3D spatial position: [x, y, z].
  221. */
  222. Howl.prototype.pos = function(x, y, z, id) {
  223. var self = this;
  224. // Stop right here if not using Web Audio.
  225. if (!self._webAudio) {
  226. return self;
  227. }
  228. // If the sound hasn't loaded, add it to the load queue to change position when capable.
  229. if (self._state !== 'loaded') {
  230. self._queue.push({
  231. event: 'pos',
  232. action: function() {
  233. self.pos(x, y, z, id);
  234. }
  235. });
  236. return self;
  237. }
  238. // Set the defaults for optional 'y' & 'z'.
  239. y = (typeof y !== 'number') ? 0 : y;
  240. z = (typeof z !== 'number') ? -0.5 : z;
  241. // Setup the group's spatial position if no ID is passed.
  242. if (typeof id === 'undefined') {
  243. // Return the group's spatial position if no parameters are passed.
  244. if (typeof x === 'number') {
  245. self._pos = [x, y, z];
  246. } else {
  247. return self._pos;
  248. }
  249. }
  250. // Change the spatial position of one or all sounds in group.
  251. var ids = self._getSoundIds(id);
  252. for (var i=0; i<ids.length; i++) {
  253. // Get the sound.
  254. var sound = self._soundById(ids[i]);
  255. if (sound) {
  256. if (typeof x === 'number') {
  257. sound._pos = [x, y, z];
  258. if (sound._node) {
  259. // Check if there is a panner setup and create a new one if not.
  260. if (!sound._panner || sound._panner.pan) {
  261. setupPanner(sound, 'spatial');
  262. }
  263. if (typeof sound._panner.positionX !== 'undefined') {
  264. sound._panner.positionX.setValueAtTime(x, Howler.ctx.currentTime);
  265. sound._panner.positionY.setValueAtTime(y, Howler.ctx.currentTime);
  266. sound._panner.positionZ.setValueAtTime(z, Howler.ctx.currentTime);
  267. } else {
  268. sound._panner.setPosition(x, y, z);
  269. }
  270. }
  271. self._emit('pos', sound._id);
  272. } else {
  273. return sound._pos;
  274. }
  275. }
  276. }
  277. return self;
  278. };
  279. /**
  280. * Get/set the direction the audio source is pointing in the 3D cartesian coordinate
  281. * space. Depending on how direction the sound is, based on the `cone` attributes,
  282. * a sound pointing away from the listener can be quiet or silent.
  283. * @param {Number} x The x-orientation of the source.
  284. * @param {Number} y The y-orientation of the source.
  285. * @param {Number} z The z-orientation of the source.
  286. * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated.
  287. * @return {Howl/Array} Returns self or the current 3D spatial orientation: [x, y, z].
  288. */
  289. Howl.prototype.orientation = function(x, y, z, id) {
  290. var self = this;
  291. // Stop right here if not using Web Audio.
  292. if (!self._webAudio) {
  293. return self;
  294. }
  295. // If the sound hasn't loaded, add it to the load queue to change orientation when capable.
  296. if (self._state !== 'loaded') {
  297. self._queue.push({
  298. event: 'orientation',
  299. action: function() {
  300. self.orientation(x, y, z, id);
  301. }
  302. });
  303. return self;
  304. }
  305. // Set the defaults for optional 'y' & 'z'.
  306. y = (typeof y !== 'number') ? self._orientation[1] : y;
  307. z = (typeof z !== 'number') ? self._orientation[2] : z;
  308. // Setup the group's spatial orientation if no ID is passed.
  309. if (typeof id === 'undefined') {
  310. // Return the group's spatial orientation if no parameters are passed.
  311. if (typeof x === 'number') {
  312. self._orientation = [x, y, z];
  313. } else {
  314. return self._orientation;
  315. }
  316. }
  317. // Change the spatial orientation of one or all sounds in group.
  318. var ids = self._getSoundIds(id);
  319. for (var i=0; i<ids.length; i++) {
  320. // Get the sound.
  321. var sound = self._soundById(ids[i]);
  322. if (sound) {
  323. if (typeof x === 'number') {
  324. sound._orientation = [x, y, z];
  325. if (sound._node) {
  326. // Check if there is a panner setup and create a new one if not.
  327. if (!sound._panner) {
  328. // Make sure we have a position to setup the node with.
  329. if (!sound._pos) {
  330. sound._pos = self._pos || [0, 0, -0.5];
  331. }
  332. setupPanner(sound, 'spatial');
  333. }
  334. if (typeof sound._panner.orientationX !== 'undefined') {
  335. sound._panner.orientationX.setValueAtTime(x, Howler.ctx.currentTime);
  336. sound._panner.orientationY.setValueAtTime(y, Howler.ctx.currentTime);
  337. sound._panner.orientationZ.setValueAtTime(z, Howler.ctx.currentTime);
  338. } else {
  339. sound._panner.setOrientation(x, y, z);
  340. }
  341. }
  342. self._emit('orientation', sound._id);
  343. } else {
  344. return sound._orientation;
  345. }
  346. }
  347. }
  348. return self;
  349. };
  350. /**
  351. * Get/set the panner node's attributes for a sound or group of sounds.
  352. * This method can optionall take 0, 1 or 2 arguments.
  353. * pannerAttr() -> Returns the group's values.
  354. * pannerAttr(id) -> Returns the sound id's values.
  355. * pannerAttr(o) -> Set's the values of all sounds in this Howl group.
  356. * pannerAttr(o, id) -> Set's the values of passed sound id.
  357. *
  358. * Attributes:
  359. * coneInnerAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees,
  360. * inside of which there will be no volume reduction.
  361. * coneOuterAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees,
  362. * outside of which the volume will be reduced to a constant value of `coneOuterGain`.
  363. * coneOuterGain - (0 by default) A parameter for directional audio sources, this is the gain outside of the
  364. * `coneOuterAngle`. It is a linear value in the range `[0, 1]`.
  365. * distanceModel - ('inverse' by default) Determines algorithm used to reduce volume as audio moves away from
  366. * listener. Can be `linear`, `inverse` or `exponential.
  367. * maxDistance - (10000 by default) The maximum distance between source and listener, after which the volume
  368. * will not be reduced any further.
  369. * refDistance - (1 by default) A reference distance for reducing volume as source moves further from the listener.
  370. * This is simply a variable of the distance model and has a different effect depending on which model
  371. * is used and the scale of your coordinates. Generally, volume will be equal to 1 at this distance.
  372. * rolloffFactor - (1 by default) How quickly the volume reduces as source moves from listener. This is simply a
  373. * variable of the distance model and can be in the range of `[0, 1]` with `linear` and `[0, ∞]`
  374. * with `inverse` and `exponential`.
  375. * panningModel - ('HRTF' by default) Determines which spatialization algorithm is used to position audio.
  376. * Can be `HRTF` or `equalpower`.
  377. *
  378. * @return {Howl/Object} Returns self or current panner attributes.
  379. */
  380. Howl.prototype.pannerAttr = function() {
  381. var self = this;
  382. var args = arguments;
  383. var o, id, sound;
  384. // Stop right here if not using Web Audio.
  385. if (!self._webAudio) {
  386. return self;
  387. }
  388. // Determine the values based on arguments.
  389. if (args.length === 0) {
  390. // Return the group's panner attribute values.
  391. return self._pannerAttr;
  392. } else if (args.length === 1) {
  393. if (typeof args[0] === 'object') {
  394. o = args[0];
  395. // Set the grou's panner attribute values.
  396. if (typeof id === 'undefined') {
  397. if (!o.pannerAttr) {
  398. o.pannerAttr = {
  399. coneInnerAngle: o.coneInnerAngle,
  400. coneOuterAngle: o.coneOuterAngle,
  401. coneOuterGain: o.coneOuterGain,
  402. distanceModel: o.distanceModel,
  403. maxDistance: o.maxDistance,
  404. refDistance: o.refDistance,
  405. rolloffFactor: o.rolloffFactor,
  406. panningModel: o.panningModel
  407. };
  408. }
  409. self._pannerAttr = {
  410. coneInnerAngle: typeof o.pannerAttr.coneInnerAngle !== 'undefined' ? o.pannerAttr.coneInnerAngle : self._coneInnerAngle,
  411. coneOuterAngle: typeof o.pannerAttr.coneOuterAngle !== 'undefined' ? o.pannerAttr.coneOuterAngle : self._coneOuterAngle,
  412. coneOuterGain: typeof o.pannerAttr.coneOuterGain !== 'undefined' ? o.pannerAttr.coneOuterGain : self._coneOuterGain,
  413. distanceModel: typeof o.pannerAttr.distanceModel !== 'undefined' ? o.pannerAttr.distanceModel : self._distanceModel,
  414. maxDistance: typeof o.pannerAttr.maxDistance !== 'undefined' ? o.pannerAttr.maxDistance : self._maxDistance,
  415. refDistance: typeof o.pannerAttr.refDistance !== 'undefined' ? o.pannerAttr.refDistance : self._refDistance,
  416. rolloffFactor: typeof o.pannerAttr.rolloffFactor !== 'undefined' ? o.pannerAttr.rolloffFactor : self._rolloffFactor,
  417. panningModel: typeof o.pannerAttr.panningModel !== 'undefined' ? o.pannerAttr.panningModel : self._panningModel
  418. };
  419. }
  420. } else {
  421. // Return this sound's panner attribute values.
  422. sound = self._soundById(parseInt(args[0], 10));
  423. return sound ? sound._pannerAttr : self._pannerAttr;
  424. }
  425. } else if (args.length === 2) {
  426. o = args[0];
  427. id = parseInt(args[1], 10);
  428. }
  429. // Update the values of the specified sounds.
  430. var ids = self._getSoundIds(id);
  431. for (var i=0; i<ids.length; i++) {
  432. sound = self._soundById(ids[i]);
  433. if (sound) {
  434. // Merge the new values into the sound.
  435. var pa = sound._pannerAttr;
  436. pa = {
  437. coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : pa.coneInnerAngle,
  438. coneOuterAngle: typeof o.coneOuterAngle !== 'undefined' ? o.coneOuterAngle : pa.coneOuterAngle,
  439. coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : pa.coneOuterGain,
  440. distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : pa.distanceModel,
  441. maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : pa.maxDistance,
  442. refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : pa.refDistance,
  443. rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : pa.rolloffFactor,
  444. panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : pa.panningModel
  445. };
  446. // Create a new panner node if one doesn't already exist.
  447. var panner = sound._panner;
  448. if (!panner) {
  449. // Make sure we have a position to setup the node with.
  450. if (!sound._pos) {
  451. sound._pos = self._pos || [0, 0, -0.5];
  452. }
  453. // Create a new panner node.
  454. setupPanner(sound, 'spatial');
  455. panner = sound._panner
  456. }
  457. // Update the panner values or create a new panner if none exists.
  458. panner.coneInnerAngle = pa.coneInnerAngle;
  459. panner.coneOuterAngle = pa.coneOuterAngle;
  460. panner.coneOuterGain = pa.coneOuterGain;
  461. panner.distanceModel = pa.distanceModel;
  462. panner.maxDistance = pa.maxDistance;
  463. panner.refDistance = pa.refDistance;
  464. panner.rolloffFactor = pa.rolloffFactor;
  465. panner.panningModel = pa.panningModel;
  466. }
  467. }
  468. return self;
  469. };
  470. /** Single Sound Methods **/
  471. /***************************************************************************/
  472. /**
  473. * Add new properties to the core Sound init.
  474. * @param {Function} _super Core Sound init method.
  475. * @return {Sound}
  476. */
  477. Sound.prototype.init = (function(_super) {
  478. return function() {
  479. var self = this;
  480. var parent = self._parent;
  481. // Setup user-defined default properties.
  482. self._orientation = parent._orientation;
  483. self._stereo = parent._stereo;
  484. self._pos = parent._pos;
  485. self._pannerAttr = parent._pannerAttr;
  486. // Complete initilization with howler.js core Sound's init function.
  487. _super.call(this);
  488. // If a stereo or position was specified, set it up.
  489. if (self._stereo) {
  490. parent.stereo(self._stereo);
  491. } else if (self._pos) {
  492. parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
  493. }
  494. };
  495. })(Sound.prototype.init);
  496. /**
  497. * Override the Sound.reset method to clean up properties from the spatial plugin.
  498. * @param {Function} _super Sound reset method.
  499. * @return {Sound}
  500. */
  501. Sound.prototype.reset = (function(_super) {
  502. return function() {
  503. var self = this;
  504. var parent = self._parent;
  505. // Reset all spatial plugin properties on this sound.
  506. self._orientation = parent._orientation;
  507. self._stereo = parent._stereo;
  508. self._pos = parent._pos;
  509. self._pannerAttr = parent._pannerAttr;
  510. // If a stereo or position was specified, set it up.
  511. if (self._stereo) {
  512. parent.stereo(self._stereo);
  513. } else if (self._pos) {
  514. parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
  515. } else if (self._panner) {
  516. // Disconnect the panner.
  517. self._panner.disconnect(0);
  518. self._panner = undefined;
  519. parent._refreshBuffer(self);
  520. }
  521. // Complete resetting of the sound.
  522. return _super.call(this);
  523. };
  524. })(Sound.prototype.reset);
  525. /** Helper Methods **/
  526. /***************************************************************************/
  527. /**
  528. * Create a new panner node and save it on the sound.
  529. * @param {Sound} sound Specific sound to setup panning on.
  530. * @param {String} type Type of panner to create: 'stereo' or 'spatial'.
  531. */
  532. var setupPanner = function(sound, type) {
  533. type = type || 'spatial';
  534. // Create the new panner node.
  535. if (type === 'spatial') {
  536. sound._panner = Howler.ctx.createPanner();
  537. sound._panner.coneInnerAngle = sound._pannerAttr.coneInnerAngle;
  538. sound._panner.coneOuterAngle = sound._pannerAttr.coneOuterAngle;
  539. sound._panner.coneOuterGain = sound._pannerAttr.coneOuterGain;
  540. sound._panner.distanceModel = sound._pannerAttr.distanceModel;
  541. sound._panner.maxDistance = sound._pannerAttr.maxDistance;
  542. sound._panner.refDistance = sound._pannerAttr.refDistance;
  543. sound._panner.rolloffFactor = sound._pannerAttr.rolloffFactor;
  544. sound._panner.panningModel = sound._pannerAttr.panningModel;
  545. if (typeof sound._panner.positionX !== 'undefined') {
  546. sound._panner.positionX.setValueAtTime(sound._pos[0], Howler.ctx.currentTime);
  547. sound._panner.positionY.setValueAtTime(sound._pos[1], Howler.ctx.currentTime);
  548. sound._panner.positionZ.setValueAtTime(sound._pos[2], Howler.ctx.currentTime);
  549. } else {
  550. sound._panner.setPosition(sound._pos[0], sound._pos[1], sound._pos[2]);
  551. }
  552. if (typeof sound._panner.orientationX !== 'undefined') {
  553. sound._panner.orientationX.setValueAtTime(sound._orientation[0], Howler.ctx.currentTime);
  554. sound._panner.orientationY.setValueAtTime(sound._orientation[1], Howler.ctx.currentTime);
  555. sound._panner.orientationZ.setValueAtTime(sound._orientation[2], Howler.ctx.currentTime);
  556. } else {
  557. sound._panner.setOrientation(sound._orientation[0], sound._orientation[1], sound._orientation[2]);
  558. }
  559. } else {
  560. sound._panner = Howler.ctx.createStereoPanner();
  561. sound._panner.pan.setValueAtTime(sound._stereo, Howler.ctx.currentTime);
  562. }
  563. sound._panner.connect(sound._node);
  564. // Update the connections.
  565. if (!sound._paused) {
  566. sound._parent.pause(sound._id, true).play(sound._id, true);
  567. }
  568. };
  569. })();