weightedsound.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { Logger } from "../Misc/logger.js";
  2. /**
  3. * Wraps one or more Sound objects and selects one with random weight for playback.
  4. */
  5. export class WeightedSound {
  6. /**
  7. * Creates a new WeightedSound from the list of sounds given.
  8. * @param loop When true a Sound will be selected and played when the current playing Sound completes.
  9. * @param sounds Array of Sounds that will be selected from.
  10. * @param weights Array of number values for selection weights; length must equal sounds, values will be normalized to 1
  11. */
  12. constructor(loop, sounds, weights) {
  13. /** When true a Sound will be selected and played when the current playing Sound completes. */
  14. this.loop = false;
  15. this._coneInnerAngle = 360;
  16. this._coneOuterAngle = 360;
  17. this._volume = 1;
  18. /** A Sound is currently playing. */
  19. this.isPlaying = false;
  20. /** A Sound is currently paused. */
  21. this.isPaused = false;
  22. this._sounds = [];
  23. this._weights = [];
  24. if (sounds.length !== weights.length) {
  25. throw new Error("Sounds length does not equal weights length");
  26. }
  27. this.loop = loop;
  28. this._weights = weights;
  29. // Normalize the weights
  30. let weightSum = 0;
  31. for (const weight of weights) {
  32. weightSum += weight;
  33. }
  34. const invWeightSum = weightSum > 0 ? 1 / weightSum : 0;
  35. for (let i = 0; i < this._weights.length; i++) {
  36. this._weights[i] *= invWeightSum;
  37. }
  38. this._sounds = sounds;
  39. for (const sound of this._sounds) {
  40. sound.onEndedObservable.add(() => {
  41. this._onended();
  42. });
  43. }
  44. }
  45. /**
  46. * The size of cone in degrees for a directional sound in which there will be no attenuation.
  47. */
  48. get directionalConeInnerAngle() {
  49. return this._coneInnerAngle;
  50. }
  51. /**
  52. * The size of cone in degrees for a directional sound in which there will be no attenuation.
  53. */
  54. set directionalConeInnerAngle(value) {
  55. if (value !== this._coneInnerAngle) {
  56. if (this._coneOuterAngle < value) {
  57. Logger.Error("directionalConeInnerAngle: outer angle of the cone must be superior or equal to the inner angle.");
  58. return;
  59. }
  60. this._coneInnerAngle = value;
  61. for (const sound of this._sounds) {
  62. sound.directionalConeInnerAngle = value;
  63. }
  64. }
  65. }
  66. /**
  67. * Size of cone in degrees for a directional sound outside of which there will be no sound.
  68. * Listener angles between innerAngle and outerAngle will falloff linearly.
  69. */
  70. get directionalConeOuterAngle() {
  71. return this._coneOuterAngle;
  72. }
  73. /**
  74. * Size of cone in degrees for a directional sound outside of which there will be no sound.
  75. * Listener angles between innerAngle and outerAngle will falloff linearly.
  76. */
  77. set directionalConeOuterAngle(value) {
  78. if (value !== this._coneOuterAngle) {
  79. if (value < this._coneInnerAngle) {
  80. Logger.Error("directionalConeOuterAngle: outer angle of the cone must be superior or equal to the inner angle.");
  81. return;
  82. }
  83. this._coneOuterAngle = value;
  84. for (const sound of this._sounds) {
  85. sound.directionalConeOuterAngle = value;
  86. }
  87. }
  88. }
  89. /**
  90. * Playback volume.
  91. */
  92. get volume() {
  93. return this._volume;
  94. }
  95. /**
  96. * Playback volume.
  97. */
  98. set volume(value) {
  99. if (value !== this._volume) {
  100. for (const sound of this._sounds) {
  101. sound.setVolume(value);
  102. }
  103. }
  104. }
  105. _onended() {
  106. if (this._currentIndex !== undefined) {
  107. this._sounds[this._currentIndex].autoplay = false;
  108. }
  109. if (this.loop && this.isPlaying) {
  110. this.play();
  111. }
  112. else {
  113. this.isPlaying = false;
  114. }
  115. }
  116. /**
  117. * Suspend playback
  118. */
  119. pause() {
  120. this.isPaused = true;
  121. if (this._currentIndex !== undefined) {
  122. this._sounds[this._currentIndex].pause();
  123. }
  124. }
  125. /**
  126. * Stop playback
  127. */
  128. stop() {
  129. this.isPlaying = false;
  130. if (this._currentIndex !== undefined) {
  131. this._sounds[this._currentIndex].stop();
  132. }
  133. }
  134. /**
  135. * Start playback.
  136. * @param startOffset Position the clip head at a specific time in seconds.
  137. */
  138. play(startOffset) {
  139. if (!this.isPaused) {
  140. this.stop();
  141. const randomValue = Math.random();
  142. let total = 0;
  143. for (let i = 0; i < this._weights.length; i++) {
  144. total += this._weights[i];
  145. if (randomValue <= total) {
  146. this._currentIndex = i;
  147. break;
  148. }
  149. }
  150. }
  151. const sound = this._sounds[this._currentIndex];
  152. if (sound.isReady()) {
  153. sound.play(0, this.isPaused ? undefined : startOffset);
  154. }
  155. else {
  156. sound.autoplay = true;
  157. }
  158. this.isPlaying = true;
  159. this.isPaused = false;
  160. }
  161. }
  162. //# sourceMappingURL=weightedsound.js.map