cacophony 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +74 -6
  3. package/dist/index.d.ts +1 -0
  4. package/dist/playback.cjs +1 -1
  5. package/dist/playback.cjs.map +1 -1
  6. package/dist/playback.d.ts +45 -0
  7. package/dist/playback.mjs +179 -24
  8. package/dist/playback.mjs.map +1 -1
  9. package/dist/sound.cjs +1 -1
  10. package/dist/sound.cjs.map +1 -1
  11. package/dist/sound.d.ts +2 -0
  12. package/dist/sound.mjs +6 -1
  13. package/dist/sound.mjs.map +1 -1
  14. package/docs/assets/hierarchy.js +1 -1
  15. package/docs/assets/navigation.js +1 -1
  16. package/docs/assets/search.js +1 -1
  17. package/docs/classes/AudioCache.html +4 -4
  18. package/docs/classes/AudioSprite.html +7 -7
  19. package/docs/classes/BarberpoleEffect.html +6 -6
  20. package/docs/classes/BiquadEffect.html +3 -3
  21. package/docs/classes/Bus.html +21 -21
  22. package/docs/classes/Cacophony.html +92 -92
  23. package/docs/classes/DynamicsEffect.html +6 -6
  24. package/docs/classes/FdnReverbEffect.html +6 -6
  25. package/docs/classes/FoaDecoder.html +5 -5
  26. package/docs/classes/FoaDecoderEffect.html +3 -3
  27. package/docs/classes/FrequencyShifterEffect.html +6 -6
  28. package/docs/classes/Group.html +33 -33
  29. package/docs/classes/HarmonizerEffect.html +6 -6
  30. package/docs/classes/ImpulseResponseEffect.html +3 -3
  31. package/docs/classes/KWeightingFilter.html +4 -4
  32. package/docs/classes/LoudnessMeter.html +11 -11
  33. package/docs/classes/MediaStreamPlayback.html +70 -70
  34. package/docs/classes/MediaStreamSound.html +51 -51
  35. package/docs/classes/MicrophoneStream.html +52 -52
  36. package/docs/classes/ModulatedDelayEffect.html +6 -6
  37. package/docs/classes/PcmStreamPlayback.html +70 -70
  38. package/docs/classes/PcmStreamSound.html +61 -61
  39. package/docs/classes/PhaserEffect.html +6 -6
  40. package/docs/classes/Playback.html +184 -89
  41. package/docs/classes/ReverbEffect.html +6 -6
  42. package/docs/classes/Scheduler.html +3 -3
  43. package/docs/classes/ShareEffect.html +3 -3
  44. package/docs/classes/Sound.html +85 -73
  45. package/docs/classes/SpectralFreezeEffect.html +6 -6
  46. package/docs/classes/StereoWidenerEffect.html +3 -3
  47. package/docs/classes/Synth.html +63 -63
  48. package/docs/classes/SynthGroup.html +28 -28
  49. package/docs/classes/TremoloEffect.html +6 -6
  50. package/docs/classes/TruePeakDetector.html +8 -8
  51. package/docs/classes/WaveshaperEffect.html +6 -6
  52. package/docs/functions/encodeMonoToFoaSN3D.html +1 -1
  53. package/docs/functions/integratedLoudness.html +1 -1
  54. package/docs/functions/integratedUngatedLoudness.html +1 -1
  55. package/docs/functions/loudnessRange.html +1 -1
  56. package/docs/functions/timeStretch.html +1 -1
  57. package/docs/functions/timeStretchChannels.html +1 -1
  58. package/docs/functions/truePeakDb.html +1 -1
  59. package/docs/hierarchy.html +3 -3
  60. package/docs/index.html +48 -0
  61. package/docs/interfaces/AudioBuffer.html +8 -8
  62. package/docs/interfaces/AudioBufferSourceNode.html +21 -21
  63. package/docs/interfaces/AudioEventCallbacks.html +1 -1
  64. package/docs/interfaces/AudioListener.html +12 -12
  65. package/docs/interfaces/AudioNode.html +13 -13
  66. package/docs/interfaces/AudioParam.html +7 -7
  67. package/docs/interfaces/AudioWorklet.html +2 -2
  68. package/docs/interfaces/AudioWorkletNode.html +15 -15
  69. package/docs/interfaces/BarberpoleOptions.html +5 -5
  70. package/docs/interfaces/BaseContext.html +25 -25
  71. package/docs/interfaces/BaseSound.html +16 -16
  72. package/docs/interfaces/BiquadCoefficients.html +6 -6
  73. package/docs/interfaces/BiquadFilterNode.html +19 -19
  74. package/docs/interfaces/BuiltEffectGraph.html +6 -6
  75. package/docs/interfaces/CacheErrorEvent.html +5 -5
  76. package/docs/interfaces/CacheHitEvent.html +4 -4
  77. package/docs/interfaces/CacheMissEvent.html +4 -4
  78. package/docs/interfaces/CacophonyEffect.html +2 -2
  79. package/docs/interfaces/CacophonyLogger.html +4 -4
  80. package/docs/interfaces/ChannelMergerNode.html +13 -13
  81. package/docs/interfaces/ChannelSplitterNode.html +13 -13
  82. package/docs/interfaces/ConvolverNode.html +15 -15
  83. package/docs/interfaces/CreateSpriteOptions.html +3 -3
  84. package/docs/interfaces/DeviceChangeEvent.html +3 -3
  85. package/docs/interfaces/DynamicsOptions.html +7 -7
  86. package/docs/interfaces/FadeStartEvent.html +4 -4
  87. package/docs/interfaces/FdnReverbOptions.html +6 -6
  88. package/docs/interfaces/FoaDecoderOptions.html +2 -2
  89. package/docs/interfaces/FrequencyShifterOptions.html +3 -3
  90. package/docs/interfaces/GainNode.html +14 -14
  91. package/docs/interfaces/GlobalPlaybackEvent.html +3 -3
  92. package/docs/interfaces/HarmonizerOptions.html +6 -6
  93. package/docs/interfaces/ImpulseResponseOptions.html +5 -5
  94. package/docs/interfaces/LoadingCompleteEvent.html +5 -5
  95. package/docs/interfaces/LoadingErrorEvent.html +5 -5
  96. package/docs/interfaces/LoadingProgressEvent.html +6 -6
  97. package/docs/interfaces/LoadingStartEvent.html +3 -3
  98. package/docs/interfaces/LoudnessChannelInput.html +3 -3
  99. package/docs/interfaces/LoudnessReading.html +5 -5
  100. package/docs/interfaces/MediaElementSourceNode.html +14 -14
  101. package/docs/interfaces/MediaStreamAudioSourceNode.html +14 -14
  102. package/docs/interfaces/MediaStreamSoundOptions.html +4 -4
  103. package/docs/interfaces/MicrophoneStreamOptions.html +7 -7
  104. package/docs/interfaces/ModulatedDelayOptions.html +8 -8
  105. package/docs/interfaces/OfflineOptions.html +5 -5
  106. package/docs/interfaces/OscillatorNode.html +19 -19
  107. package/docs/interfaces/PannerNode.html +27 -27
  108. package/docs/interfaces/PcmStreamBufferEvent.html +2 -2
  109. package/docs/interfaces/PcmStreamSoundOptions.html +6 -6
  110. package/docs/interfaces/PhaserOptions.html +7 -7
  111. package/docs/interfaces/PlayOptions.html +13 -13
  112. package/docs/interfaces/PlaybackErrorEvent.html +5 -5
  113. package/docs/interfaces/PrepareSeekOptions.html +65 -0
  114. package/docs/interfaces/ReverbOptions.html +13 -13
  115. package/docs/interfaces/RuntimeOptions.html +8 -8
  116. package/docs/interfaces/ScheduledCallbackHandle.html +2 -2
  117. package/docs/interfaces/SendGainOptions.html +3 -3
  118. package/docs/interfaces/SoundCleanupHoldings.html +4 -4
  119. package/docs/interfaces/SoundErrorEvent.html +6 -6
  120. package/docs/interfaces/SpectralFreezeOptions.html +4 -4
  121. package/docs/interfaces/SpriteRegion.html +4 -4
  122. package/docs/interfaces/StereoPannerNode.html +14 -14
  123. package/docs/interfaces/StereoToBFormatOptions.html +2 -2
  124. package/docs/interfaces/StereoWidenerOptions.html +4 -4
  125. package/docs/interfaces/StreamCapabilities.html +5 -5
  126. package/docs/interfaces/TimeStretchOptions.html +5 -5
  127. package/docs/interfaces/TremoloOptions.html +5 -5
  128. package/docs/interfaces/WaveshaperOptions.html +5 -5
  129. package/docs/modules.html +2 -2
  130. package/docs/types/AudioSinkId.html +1 -1
  131. package/docs/types/BaseAudioEvents.html +11 -11
  132. package/docs/types/BuiltEffect.html +1 -1
  133. package/docs/types/BusConnectionTarget.html +1 -1
  134. package/docs/types/CacheEventCallback.html +1 -1
  135. package/docs/types/CacophonyEvents.html +20 -20
  136. package/docs/types/ErrorEventCallback.html +1 -1
  137. package/docs/types/FadeType.html +1 -1
  138. package/docs/types/HrtfPannerOptions.html +1 -1
  139. package/docs/types/ImpulseResponseSource.html +1 -1
  140. package/docs/types/LoadingEventCallback.html +1 -1
  141. package/docs/types/LoopCount.html +1 -1
  142. package/docs/types/LoudnessChannel.html +1 -1
  143. package/docs/types/Orientation.html +3 -3
  144. package/docs/types/PanCloneOverrides.html +6 -6
  145. package/docs/types/PanType.html +1 -1
  146. package/docs/types/PcmStreamEvents.html +6 -6
  147. package/docs/types/PcmStreamState.html +1 -1
  148. package/docs/types/PlaybackEvents.html +1 -1
  149. package/docs/types/Position.html +1 -1
  150. package/docs/types/SoundEvents.html +1 -1
  151. package/docs/types/SoundType.html +1 -1
  152. package/docs/types/SourceNode.html +1 -1
  153. package/docs/types/SpriteMap.html +1 -1
  154. package/docs/types/SynthEvents.html +1 -1
  155. package/docs/types/ThreeDOptions.html +1 -1
  156. package/docs/types/WebCodecsStreamSound.html +1 -1
  157. package/docs/variables/CHANNEL_WEIGHTS.html +1 -1
  158. package/docs/variables/K_WEIGHTING_STAGE1_48K.html +1 -1
  159. package/docs/variables/K_WEIGHTING_STAGE2_48K.html +1 -1
  160. package/docs/variables/consoleLogger.html +1 -1
  161. package/docs/variables/noopLogger.html +1 -1
  162. package/package.json +1 -1
package/dist/playback.mjs CHANGED
@@ -13,7 +13,11 @@ var s = class s extends a {
13
13
  _offset = 0;
14
14
  _startTime = 0;
15
15
  _playbackRate = 1;
16
+ _clipRegion;
17
+ _scheduledStop;
16
18
  _mediaPlayPromise;
19
+ _cancelPrepareSeek;
20
+ _mediaPreparationGate;
17
21
  _fadeInConfig;
18
22
  _fadeOutConfig;
19
23
  _loopEndCallback;
@@ -24,7 +28,9 @@ var s = class s extends a {
24
28
  }
25
29
  setupSourceNode(e) {
26
30
  if ("mediaElement" in e && e.mediaElement) e.mediaElement.onended = this.loopEnded;
27
- else if ("onended" in e) e.onended = this.loopEnded;
31
+ else if ("onended" in e) e.onended = () => {
32
+ this.source === e && this.loopEnded();
33
+ };
28
34
  else throw Error("Unsupported source type");
29
35
  }
30
36
  get occlusion() {
@@ -41,7 +47,7 @@ var s = class s extends a {
41
47
  this._occlusion.setAmount(n, t);
42
48
  }
43
49
  setEffectChainEndpoints(e, t) {
44
- this._occlusion?.setOutput(t), super.setEffectChainEndpoints(e, this._occlusion?.input ?? t);
50
+ this._occlusion?.setOutput(t), super.setEffectChainEndpoints(this._mediaPreparationGate ?? e, this._occlusion?.input ?? t);
45
51
  }
46
52
  get duration() {
47
53
  if (this.origin.region) return this.origin.region.duration;
@@ -51,6 +57,37 @@ var s = class s extends a {
51
57
  get playbackRate() {
52
58
  return this._playbackRate;
53
59
  }
60
+ get supportsClipLoop() {
61
+ return !!this.source && "buffer" in this.source && !!this.buffer;
62
+ }
63
+ setLoopRegion(e, t) {
64
+ if (this.assertNotCleanedUp(), !this.supportsClipLoop) throw new DOMException("Clip bounds require a decoded buffer", "NotSupportedError");
65
+ if (this._state === "playing") throw Error("Pause playback before configuring clip bounds");
66
+ if (!this.buffer || !Number.isFinite(e) || !Number.isFinite(t) || e < 0 || t > this.duration || (t - e) * this.buffer.sampleRate + 2 ** -52 * Math.max(1, Math.abs(e * this.buffer.sampleRate), Math.abs(t * this.buffer.sampleRate)) * 4 < 1 || Math.round(t * this.buffer.sampleRate) <= Math.round(e * this.buffer.sampleRate)) throw RangeError("Clip bounds must span at least one sample within the Sound duration");
67
+ if (this._offset >= t) throw RangeError("The current offset must precede the clip end");
68
+ if (this.loopCount !== 0 && this.loopCount !== "infinite") throw Error("Clip bounds support only zero or infinite loop count");
69
+ if (this._fadeInConfig?.perLoop) throw Error("Per-loop fades are not supported with clip bounds");
70
+ this._clipRegion = {
71
+ start: e,
72
+ end: t
73
+ }, this.configureBufferLoopSource();
74
+ }
75
+ scheduleStop(e) {
76
+ if (this.assertNotCleanedUp(), !this.supportsClipLoop || !this.source || !("stop" in this.source)) throw new DOMException("Scheduled ends require a decoded buffer", "NotSupportedError");
77
+ if (!Number.isFinite(e) || e < 0) throw RangeError("End time must be finite and non-negative");
78
+ if (this._state !== "playing") throw Error("Start the buffer before scheduling its end");
79
+ if (this.loopCount !== 0 && this.loopCount !== "infinite") throw Error("Scheduled ends do not support legacy numeric repeats");
80
+ if (this._fadeOutConfig) throw Error("An absolute end cannot use an ended-time fade-out");
81
+ let t = Math.max(this.context.currentTime, e);
82
+ this.source.stop(t), this._scheduledStop = {
83
+ source: this.source,
84
+ at: t
85
+ };
86
+ }
87
+ _validateLoopCount(e) {
88
+ if (this._clipRegion && e !== 0 && e !== "infinite") throw Error("Clip bounds support only zero or infinite loop count");
89
+ if (this._scheduledStop && e !== 0 && e !== "infinite") throw Error("Scheduled ends do not support legacy numeric repeats");
90
+ }
54
91
  get volume() {
55
92
  return super.volume;
56
93
  }
@@ -67,6 +104,16 @@ var s = class s extends a {
67
104
  }
68
105
  loopEnded = () => {
69
106
  if (!(!this.source || this._state !== "playing")) {
107
+ if (this._scheduledStop?.source === this.source) {
108
+ let e = this.source;
109
+ this._scheduledStop = void 0, "onended" in e && (e.onended = null);
110
+ try {
111
+ this.emit("ended", void 0);
112
+ } finally {
113
+ this.source === e && this._state === "playing" && this.stop(), this.source === e && this.state === "stopped" && this.removeFromOrigin();
114
+ }
115
+ return;
116
+ }
70
117
  if ("mediaElement" in this.source && this._mediaPlayPromise) {
71
118
  this._mediaPlayPromise.then(() => this._runLoopEnded());
72
119
  return;
@@ -75,14 +122,24 @@ var s = class s extends a {
75
122
  }
76
123
  };
77
124
  _runLoopEnded() {
78
- !this.source || this._state !== "playing" || (this.currentLoop++, this.loopCount !== "infinite" && this.currentLoop > this.loopCount ? (this.emit("ended", void 0), this._fadeOutConfig ? this.fadeOut(this._fadeOutConfig.duration, this._fadeOutConfig.type).then(() => {
79
- this._state !== "stopped" && this.source && (this.stop(), this.removeFromOrigin());
80
- }) : (this.stop(), this.removeFromOrigin())) : (this._loopEndCallback?.(), this.seek(0), this._state !== "playing" && this.source && !("mediaElement" in this.source) && this.play(), this._fadeInConfig?.perLoop && this.gainNode && (this.gainNode.gain.setValueAtTime(1e-4, this.gainNode.context.currentTime), this.fadeTo(this._fadeInConfig.targetVolume, this._fadeInConfig.duration, this._fadeInConfig.type))));
125
+ if (!this.source || this._state !== "playing") return;
126
+ let e = this.source;
127
+ if (this.currentLoop++, this.loopCount !== "infinite" && this.currentLoop > this.loopCount) {
128
+ if (this.emit("ended", void 0), this.source !== e || this._state !== "playing") return;
129
+ this._fadeOutConfig ? this.fadeOut(this._fadeOutConfig.duration, this._fadeOutConfig.type).then(() => {
130
+ this._state !== "stopped" && this.source === e && (this.stop(), this.source === e && this.state === "stopped" && this.removeFromOrigin());
131
+ }) : (this.stop(), this.source === e && this.state === "stopped" && this.removeFromOrigin());
132
+ } else {
133
+ if (this._loopEndCallback?.(), this.source !== e || this._state !== "playing") return;
134
+ this.seek(0), this._state !== "playing" && this.source && !("mediaElement" in this.source) && this.play(), this._fadeInConfig?.perLoop && this.gainNode && (this.gainNode.gain.setValueAtTime(1e-4, this.gainNode.context.currentTime), this.fadeTo(this._fadeInConfig.targetVolume, this._fadeInConfig.duration, this._fadeInConfig.type));
135
+ }
81
136
  }
82
137
  play(e) {
83
138
  if (!this.source) throw Error("Cannot play a sound that has been cleaned up");
84
- if (n(e, this.panType, "mediaElement" in this.source ? "media" : "buffer"), this._state === "playing") return [this];
85
- t(this, this.context, e), e?.playbackRate !== void 0 && (this.playbackRate = e.playbackRate), e?.loopCount !== void 0 && this.loop(e.loopCount), e?.fadeIn !== void 0 && this.fadeIn(e.fadeIn, e.fadeType, {
139
+ if (n(e, this.panType, "mediaElement" in this.source ? "media" : "buffer"), e?.loopCount !== void 0 && this._validateLoopCount(e.loopCount), this._clipRegion && e?.fadeInPerLoop) throw Error("Per-loop fades are not supported with clip bounds");
140
+ if (this._scheduledStop && e?.fadeOut !== void 0) throw Error("An absolute end cannot use an ended-time fade-out");
141
+ if (this._state === "playing") return [this];
142
+ this._cancelPrepareSeek?.(), this._scheduledStop = void 0, t(this, this.context, e), e?.playbackRate !== void 0 && (this.playbackRate = e.playbackRate), e?.loopCount !== void 0 && this.loop(e.loopCount), e?.fadeIn !== void 0 && this.fadeIn(e.fadeIn, e.fadeType, {
86
143
  perLoop: e.fadeInPerLoop,
87
144
  startTime: e.at
88
145
  }), e?.fadeOut !== void 0 && this.configureFadeOut(e.fadeOut, e.fadeType);
@@ -92,9 +149,9 @@ var s = class s extends a {
92
149
  try {
93
150
  let t;
94
151
  if (this._state === "paused") {
95
- if ("mediaElement" in this.source && this.source.mediaElement) t = this.source.mediaElement.play();
152
+ if ("mediaElement" in this.source && this.source.mediaElement) this.source.mediaElement.currentTime !== this._offset && (this.source.mediaElement.currentTime = this._offset), t = this.source.mediaElement.play();
96
153
  else if (this.recreateSource(), "start" in this.source && !this.startBufferSource(e?.at ?? 0)) return [this];
97
- } else if ("mediaElement" in this.source && this.source.mediaElement) this.source.mediaElement.currentTime = this._offset, t = this.source.mediaElement.play();
154
+ } else if ("mediaElement" in this.source && this.source.mediaElement) this.source.mediaElement.currentTime !== this._offset && (this.source.mediaElement.currentTime = this._offset), t = this.source.mediaElement.play();
98
155
  else if ("start" in this.source && (this.recreateSource(), !this.startBufferSource(e?.at ?? 0))) return [this];
99
156
  if (t) {
100
157
  let e = t.then(() => {
@@ -128,19 +185,26 @@ var s = class s extends a {
128
185
  }
129
186
  }
130
187
  emitPlayStarted(e) {
131
- this._state === "stopped" && !this.origin.playbacks.includes(this) && this.origin._readmitPlayback(this), super.emitPlayStarted(e);
188
+ this._mediaPreparationGate?.gain.setValueAtTime(1, this.context.currentTime), this._state === "stopped" && !this.origin.playbacks.includes(this) && this.origin._readmitPlayback(this), super.emitPlayStarted(e);
132
189
  }
133
190
  pause() {
134
- if (!this.source || this._state !== "playing") return;
135
- let e = Math.max(0, (this.context.currentTime - this._startTime) * this._playbackRate);
136
- this._offset = this.regionRelativeOffset(this._offset + e), "mediaElement" in this.source && this.source.mediaElement ? this.source.mediaElement.pause() : "stop" in this.source && this.source.stop(), this.emitPaused();
191
+ if (!(!this.source || this._state !== "playing")) {
192
+ if (this._mediaPlayPromise = void 0, this._scheduledStop = void 0, "mediaElement" in this.source && this.source.mediaElement) this._mediaPreparationGate?.gain.setValueAtTime(0, this.context.currentTime), this.source.mediaElement.pause(), this._offset = this.source.mediaElement.currentTime;
193
+ else {
194
+ let e = Math.max(0, (this.context.currentTime - this._startTime) * this._playbackRate);
195
+ this._offset = this.regionRelativeOffset(this._offset + e), "stop" in this.source && this.source.stop();
196
+ }
197
+ this.emitPaused();
198
+ }
137
199
  }
138
200
  stop() {
139
201
  if (!this.source) throw Error("Cannot stop a sound that has been cleaned up");
202
+ this._cancelPrepareSeek?.(), this._scheduledStop = void 0, this._mediaPreparationGate?.gain.setValueAtTime(0, this.context.currentTime);
140
203
  let e = this._mediaPlayPromise !== void 0;
141
204
  this._mediaPlayPromise = void 0, !(!e && (this._state === "stopped" || this._state === "unplayed")) && (this.cancelFade(), "stop" in this.source && this._state === "playing" && this.source.stop(), "mediaElement" in this.source && this.source.mediaElement && (this.source.mediaElement.pause(), this.source.mediaElement.currentTime = 0), this._offset = 0, this._startTime = 0, this.emitStopped());
142
205
  }
143
206
  fadeIn(e, t, n) {
207
+ if (n?.perLoop && this._clipRegion) throw Error("Per-loop fades are not supported with clip bounds");
144
208
  return n?.perLoop && (this._fadeInConfig = {
145
209
  duration: e,
146
210
  type: t ?? "linear",
@@ -149,6 +213,7 @@ var s = class s extends a {
149
213
  }), super.fadeIn(e, t, { startTime: n?.startTime });
150
214
  }
151
215
  configureFadeOut(e, t) {
216
+ if (this._scheduledStop) throw Error("An absolute end cannot use an ended-time fade-out");
152
217
  this._fadeOutConfig = {
153
218
  duration: e,
154
219
  type: t ?? "linear"
@@ -159,15 +224,90 @@ var s = class s extends a {
159
224
  this._state !== "stopped" && this.source && this.stop();
160
225
  });
161
226
  }
227
+ async prepareSeek(e, t = {}) {
228
+ if (this.assertNotCleanedUp(), !Number.isFinite(e) || e < 0) throw RangeError("Seek time must be finite and non-negative");
229
+ let n = t.timeoutMs ?? 1e4;
230
+ if (!Number.isFinite(n) || n <= 0 || n > 2147483647) throw RangeError("Seek timeout must be positive and at most 2147483647 milliseconds");
231
+ if (t.signal?.aborted) throw new DOMException("Seek preparation aborted", "AbortError");
232
+ if (this._state === "playing" || this._mediaPlayPromise) throw Error("Pause playback before preparing a seek");
233
+ this._cancelPrepareSeek?.();
234
+ let r = this.source;
235
+ if (!("mediaElement" in r)) {
236
+ if (this._clipRegion && e >= this._clipRegion.end) throw RangeError("Seek time must precede the clip end");
237
+ if (!Number.isFinite(this.duration) || e > this.duration) throw RangeError("Seek time exceeds duration");
238
+ this._offset = e, this.emit("seek", e);
239
+ return;
240
+ }
241
+ let i = r.mediaElement;
242
+ if (!i.paused) throw Error("Pause playback before preparing a seek");
243
+ if (!this._mediaPreparationGate) {
244
+ let e = this.context.createGain();
245
+ e.gain.value = 0, this._mediaPreparationGate = e, this.setEffectChainEndpoints(r, this.panner), r.connect(e);
246
+ }
247
+ this._mediaPreparationGate.gain.setValueAtTime(0, this.context.currentTime), await new Promise((r, a) => {
248
+ let o = !1, s = !1, c = !1, l = [
249
+ "loadedmetadata",
250
+ "durationchange",
251
+ "progress",
252
+ "loadeddata",
253
+ "canplay",
254
+ "seeked",
255
+ "error",
256
+ "emptied",
257
+ "play"
258
+ ], u = (n) => {
259
+ if (!o) {
260
+ o = !0, clearTimeout(p);
261
+ for (let e of l) i.removeEventListener(e, f);
262
+ t.signal?.removeEventListener("abort", d), this._cancelPrepareSeek === d && (this._cancelPrepareSeek = void 0), n === void 0 ? (this._offset = e, r()) : a(n);
263
+ }
264
+ }, d = () => u(new DOMException("Seek preparation aborted", "AbortError")), f = (t) => {
265
+ if (!o) try {
266
+ if (i.error || t?.type === "error") {
267
+ u(Error(i.error?.message || "Media failed during seek preparation"));
268
+ return;
269
+ }
270
+ if (t?.type === "emptied" && s || t?.type === "play") {
271
+ d();
272
+ return;
273
+ }
274
+ if (i.readyState < 1) return;
275
+ if (!Number.isFinite(i.duration)) {
276
+ u(new DOMException("Seek preparation requires finite-duration media", "NotSupportedError"));
277
+ return;
278
+ }
279
+ if (e > i.duration) {
280
+ u(/* @__PURE__ */ RangeError("Seek time exceeds duration"));
281
+ return;
282
+ }
283
+ if (s) t?.type === "seeked" && (c = !1);
284
+ else {
285
+ let t = !1;
286
+ for (let n = 0; n < i.seekable.length; n++) e >= i.seekable.start(n) && e <= i.seekable.end(n) && (t = !0);
287
+ if (!t) return;
288
+ s = !0, c = i.seeking || i.currentTime !== e, i.currentTime !== e && (i.currentTime = e);
289
+ }
290
+ !c && !i.seeking && i.readyState >= 2 && Math.abs(i.currentTime - e) < .001 && u();
291
+ } catch (e) {
292
+ u(e);
293
+ }
294
+ }, p = setTimeout(() => u(new DOMException("Seek preparation timed out", "TimeoutError")), n);
295
+ this._cancelPrepareSeek = d;
296
+ for (let e of l) i.addEventListener(e, f);
297
+ t.signal?.addEventListener("abort", d, { once: !0 }), f();
298
+ }), this.emit("seek", e);
299
+ }
162
300
  seek(e) {
163
301
  if (!this.source || !this.gainNode || !this.panner) throw Error("Cannot seek a sound that has been cleaned up");
164
302
  if (!Number.isFinite(e) || e < 0 || this.origin.region !== void 0 && e > this.duration) throw Error("Invalid time value for seek");
165
- if (this.origin.soundType === "streaming" && "mediaElement" in this.source && (this.source.mediaElement.seekable?.length ?? 0) === 0) throw this.source.mediaElement.duration === Infinity ? Error("Live media-element streams do not expose a seekable range") : Error("Media-element stream does not currently expose a seekable range");
303
+ if (this._clipRegion && e >= this._clipRegion.end) throw RangeError("Seek time must precede the clip end");
304
+ if (this._cancelPrepareSeek?.(), this.origin.soundType === "streaming" && "mediaElement" in this.source && (this.source.mediaElement.seekable?.length ?? 0) === 0) throw this.source.mediaElement.duration === Infinity ? Error("Live media-element streams do not expose a seekable range") : Error("Media-element stream does not currently expose a seekable range");
166
305
  let t = this._state === "playing";
167
306
  t && this.pause(), this._offset = e, "mediaElement" in this.source && this.source.mediaElement && (this.source.mediaElement.currentTime = e), t && this.play(), this.emit("seek", e);
168
307
  }
169
308
  get currentTime() {
170
309
  if (this._state === "playing") {
310
+ if (this.source && "mediaElement" in this.source) return this.source.mediaElement.currentTime;
171
311
  let e = Math.max(0, (this.context.currentTime - this._startTime) * this._playbackRate), t = this._offset + e;
172
312
  return this.regionRelativeOffset(t);
173
313
  } else return this._offset;
@@ -179,7 +319,7 @@ var s = class s extends a {
179
319
  recreateSource() {
180
320
  if (!this.buffer || !this.panner || !this.context || !this.gainNode) throw Error("Cannot recreate source of a sound that has been cleaned up");
181
321
  try {
182
- this.source && (!("mediaElement" in this.source) && "onended" in this.source && (this.source.onended = null), this.source.disconnect()), this.source = this.context.createBufferSource(), this.source.buffer = this.buffer, this.setEffectChainEndpoints(this.source, this.panner), this.source.onended = this.loopEnded, this.playbackRate = this._playbackRate, this.configureBufferLoopSource();
322
+ this.source && (!("mediaElement" in this.source) && "onended" in this.source && (this.source.onended = null), this.source.disconnect()), this.source = this.context.createBufferSource(), this._scheduledStop = void 0, this.source.buffer = this.buffer, this.setEffectChainEndpoints(this.source, this.panner), this.setupSourceNode(this.source), this.playbackRate = this._playbackRate, this.configureBufferLoopSource();
183
323
  } catch (e) {
184
324
  throw this.emitAsync("error", {
185
325
  error: e,
@@ -194,7 +334,7 @@ var s = class s extends a {
194
334
  "loop" in this.source && (this.source.loop = e), "mediaElement" in this.source && this.source.mediaElement && (this.source.mediaElement.loop = e);
195
335
  }
196
336
  cleanup() {
197
- if (this._mediaPlayPromise = void 0, this.source) {
337
+ if (this._scheduledStop = void 0, this._cancelPrepareSeek?.(), this._mediaPreparationGate?.gain.setValueAtTime(0, this.context.currentTime), this._mediaPlayPromise = void 0, this.source) {
198
338
  if ("mediaElement" in this.source && this.source.mediaElement) {
199
339
  let e = this.source.mediaElement;
200
340
  e.pause();
@@ -203,7 +343,7 @@ var s = class s extends a {
203
343
  } catch {}
204
344
  e.loop = !1, e.onended = null, e.src = "", e.load();
205
345
  } else "onended" in this.source && (this.source.onended = null, "stop" in this.source && this._state === "playing" && this.source.stop());
206
- this._offset = 0, this._startTime = 0, this.markStopped(), this.currentLoop = 0, this.source.disconnect(), this.source = void 0, super.cleanup(), this._occlusion?.destroy(), this._occlusion = void 0;
346
+ this._offset = 0, this._startTime = 0, this.markStopped(), this.currentLoop = 0, this.source.disconnect(), this.source = void 0, super.cleanup(), this._mediaPreparationGate?.disconnect(), this._mediaPreparationGate = void 0, this._occlusion?.destroy(), this._occlusion = void 0;
207
347
  }
208
348
  }
209
349
  assertNotCleanedUp() {
@@ -218,10 +358,15 @@ var s = class s extends a {
218
358
  loop(e) {
219
359
  if (!this.source) throw Error("Cannot loop a sound that has been cleaned up");
220
360
  if (e !== void 0) {
221
- let t = this.loopCount, n = e === "infinite" ? "infinite" : Math.max(0, e);
222
- if (t === "infinite" != (n === "infinite") && this.origin.region && this._state === "playing" && "loop" in this.source && "stop" in this.source) {
361
+ this._validateLoopCount(e);
362
+ let t = this.loopCount, n = e === "infinite" ? "infinite" : Math.max(0, e), r = t === "infinite" != (n === "infinite");
363
+ if (r && (this.origin.region || this._clipRegion) && this._state === "playing" && "loop" in this.source && "stop" in this.source) {
223
364
  let e = Math.max(this._startTime, this.context.currentTime), r = Math.max(0, (this.context.currentTime - this._startTime) * this._playbackRate);
224
- return this._offset = this.regionRelativeOffset(this.regionRelativeOffset(this._offset + r, t), n), "onended" in this.source && (this.source.onended = null), this.source.stop(), this.loopCount = n, this.currentLoop = 0, this._startTime = e, this.recreateSource(), this.startBufferSource(e), this.loopCount;
365
+ return this._offset = this.regionRelativeOffset(this.regionRelativeOffset(this._offset + r, t), n), "onended" in this.source && (this.source.onended = null), this.source.stop(), this._scheduledStop = void 0, this.loopCount = n, this.currentLoop = 0, this._startTime = e, this.recreateSource(), this.startBufferSource(e), this.loopCount;
366
+ }
367
+ if (r && this._state === "playing" && "buffer" in this.source) {
368
+ let e = Math.max(0, (this.context.currentTime - this._startTime) * this._playbackRate);
369
+ this._offset = this.regionRelativeOffset(this._offset + e, t), this._startTime = Math.max(this._startTime, this.context.currentTime);
225
370
  }
226
371
  this.loopCount = n, this.currentLoop = 0;
227
372
  }
@@ -233,15 +378,24 @@ var s = class s extends a {
233
378
  return this.loopCount;
234
379
  }
235
380
  configureBufferLoopSource() {
236
- !this.source || !("loop" in this.source) || (this.source.loop = this.loopCount === "infinite", this.source.buffer && (this.source.loopEnd = this.origin.region ? this.origin.region.start + this.origin.region.duration : this.source.buffer.duration, this.source.loopStart = this.origin.region?.start ?? 0));
381
+ !this.source || !("loop" in this.source) || (this.source.loop = this.loopCount === "infinite", this.source.buffer && (this.source.loopEnd = this._clipRegion ? (this.origin.region?.start ?? 0) + this._clipRegion.end : this.origin.region ? this.origin.region.start + this.origin.region.duration : this.source.buffer.duration, this.source.loopStart = (this.origin.region?.start ?? 0) + (this._clipRegion?.start ?? 0)));
237
382
  }
238
383
  regionRelativeOffset(e, t = this.loopCount) {
384
+ if (this._clipRegion) {
385
+ let { start: n, end: r } = this._clipRegion;
386
+ return t === "infinite" && e >= r ? n + (e - n) % (r - n) : Math.min(r, e);
387
+ }
239
388
  let n = this.origin.region;
240
- return n ? t === "infinite" ? e % n.duration : Math.min(n.duration, e) : e;
389
+ return n ? t === "infinite" ? e % n.duration : Math.min(n.duration, e) : t === "infinite" && this.buffer && this.buffer.duration > 0 ? e % this.buffer.duration : e;
241
390
  }
242
391
  startBufferSource(e) {
243
392
  if (!this.source || !("start" in this.source)) return !1;
244
393
  let t = this.origin.region;
394
+ if (this._clipRegion) {
395
+ if (this._offset >= this._clipRegion.end) return !1;
396
+ let n = (t?.start ?? 0) + this._offset;
397
+ return this.loopCount === "infinite" ? this.source.start(e, n) : this.source.start(e, n, this._clipRegion.end - this._offset), !0;
398
+ }
245
399
  if (!t) return this.source.start(e, this._offset), !0;
246
400
  if (this._offset === t.duration) return !1;
247
401
  let n = t.start + this._offset;
@@ -283,7 +437,8 @@ var s = class s extends a {
283
437
  }
284
438
  clone(e = {}) {
285
439
  if (!this.source || !this.gainNode || !this.context) throw Error("Cannot clone a sound that has been cleaned up");
286
- i(e.spatialSmoothingTau ?? this.spatialSmoothingTau);
440
+ if (this._scheduledStop) throw Error("Cannot clone a playback with an armed absolute end");
441
+ this._validateLoopCount(e.loopCount ?? this.loopCount), i(e.spatialSmoothingTau ?? this.spatialSmoothingTau);
287
442
  let t = e.panType ?? this.panType;
288
443
  r(e, t);
289
444
  let n = this.context.createGain();
@@ -296,7 +451,7 @@ var s = class s extends a {
296
451
  a = this.context.createMediaElementSource(e);
297
452
  } else throw Error("Unsupported source type");
298
453
  let o = e.loopCount === void 0 ? this.loopCount : e.loopCount, c = new s(this.origin, a, n);
299
- return c.loopCount = o, c.currentLoop = this.currentLoop, c.setPanType(t, this.context), c.spatialSmoothingTau = e.spatialSmoothingTau ?? this.spatialSmoothingTau, t === "HRTF" ? (this.panType === "HRTF" && (c.threeDOptions = this.threeDOptions), e.threeDOptions !== void 0 && (c.threeDOptions = e.threeDOptions), e.position !== void 0 && (c.position = e.position)) : c.stereoPan = e.stereoPan ?? this.stereoPan ?? 0, c.volume = this.volume, c.playbackRate = this._playbackRate, c._offset = this._offset, c.setOcclusion(this.occlusion, 0), this._fadeInConfig && (c._fadeInConfig = { ...this._fadeInConfig }), this._fadeOutConfig && (c._fadeOutConfig = { ...this._fadeOutConfig }), this._state === "paused" ? c.markPaused() : this._state === "stopped" && c.markStopped(), this._filters.forEach((e) => {
454
+ return c.loopCount = o, c._clipRegion = this._clipRegion && { ...this._clipRegion }, c.currentLoop = this.currentLoop, c.setPanType(t, this.context), c.spatialSmoothingTau = e.spatialSmoothingTau ?? this.spatialSmoothingTau, t === "HRTF" ? (this.panType === "HRTF" && (c.threeDOptions = this.threeDOptions), e.threeDOptions !== void 0 && (c.threeDOptions = e.threeDOptions), e.position !== void 0 && (c.position = e.position)) : c.stereoPan = e.stereoPan ?? this.stereoPan ?? 0, c.volume = this.volume, c.playbackRate = this._playbackRate, c._offset = this._clipRegion && this._state === "playing" ? this.currentTime : this._offset, c.setOcclusion(this.occlusion, 0), this._fadeInConfig && (c._fadeInConfig = { ...this._fadeInConfig }), this._fadeOutConfig && (c._fadeOutConfig = { ...this._fadeOutConfig }), this._state === "paused" ? c.markPaused() : this._state === "stopped" && c.markStopped(), this._filters.forEach((e) => {
300
455
  let t = this.context.createBiquadFilter();
301
456
  t.type = e.type, t.frequency.value = e.frequency.value, t.Q.value = e.Q.value, t.gain.value = e.gain.value, c.addFilter(t);
302
457
  }), this._pitchFactor !== 1 && c.setPitchShift(this._pitchFactor).catch(() => {}), this._state === "playing" && c.play(), this.origin.playbacks.push(c), c;