cacophony 0.33.0 → 0.34.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 (271) hide show
  1. package/CHANGELOG.md +1600 -0
  2. package/README.md +680 -484
  3. package/dist/automation.cjs +2 -0
  4. package/dist/automation.cjs.map +1 -0
  5. package/dist/automation.d.ts +8 -0
  6. package/dist/automation.mjs +15 -0
  7. package/dist/automation.mjs.map +1 -0
  8. package/dist/autoplayUnlock.cjs +1 -1
  9. package/dist/autoplayUnlock.cjs.map +1 -1
  10. package/dist/autoplayUnlock.d.ts +2 -0
  11. package/dist/autoplayUnlock.mjs +32 -26
  12. package/dist/autoplayUnlock.mjs.map +1 -1
  13. package/dist/basePlayback.cjs +1 -1
  14. package/dist/basePlayback.cjs.map +1 -1
  15. package/dist/basePlayback.d.ts +6 -18
  16. package/dist/basePlayback.mjs +5 -2
  17. package/dist/basePlayback.mjs.map +1 -1
  18. package/dist/bundles/dattorro-reverb-bundle.js_url.cjs +1 -1
  19. package/dist/bundles/dattorro-reverb-bundle.js_url.cjs.map +1 -1
  20. package/dist/bundles/dattorro-reverb-bundle.js_url.mjs +1 -1
  21. package/dist/bundles/dattorro-reverb-bundle.js_url.mjs.map +1 -1
  22. package/dist/bundles/fdn-reverb-bundle.js_url.cjs +1 -1
  23. package/dist/bundles/fdn-reverb-bundle.js_url.cjs.map +1 -1
  24. package/dist/bundles/fdn-reverb-bundle.js_url.mjs +1 -1
  25. package/dist/bundles/fdn-reverb-bundle.js_url.mjs.map +1 -1
  26. package/dist/bundles/modulated-delay-bundle.js_url.cjs +1 -1
  27. package/dist/bundles/modulated-delay-bundle.js_url.cjs.map +1 -1
  28. package/dist/bundles/modulated-delay-bundle.js_url.mjs +1 -1
  29. package/dist/bundles/modulated-delay-bundle.js_url.mjs.map +1 -1
  30. package/dist/cacophony.cjs +1 -1
  31. package/dist/cacophony.cjs.map +1 -1
  32. package/dist/cacophony.d.ts +60 -9
  33. package/dist/cacophony.mjs +157 -80
  34. package/dist/cacophony.mjs.map +1 -1
  35. package/dist/container.cjs +1 -1
  36. package/dist/container.cjs.map +1 -1
  37. package/dist/container.d.ts +7 -1
  38. package/dist/container.mjs +28 -21
  39. package/dist/container.mjs.map +1 -1
  40. package/dist/context.d.ts +6 -0
  41. package/dist/effectChain.cjs +1 -1
  42. package/dist/effectChain.cjs.map +1 -1
  43. package/dist/effectChain.mjs +22 -27
  44. package/dist/effectChain.mjs.map +1 -1
  45. package/dist/effects.cjs +1 -1
  46. package/dist/effects.cjs.map +1 -1
  47. package/dist/effects.d.ts +2 -1
  48. package/dist/effects.mjs +56 -47
  49. package/dist/effects.mjs.map +1 -1
  50. package/dist/events.d.ts +13 -3
  51. package/dist/group.cjs +1 -1
  52. package/dist/group.cjs.map +1 -1
  53. package/dist/group.d.ts +5 -2
  54. package/dist/group.mjs +13 -7
  55. package/dist/group.mjs.map +1 -1
  56. package/dist/index.d.ts +3 -2
  57. package/dist/mediaStream.cjs +1 -1
  58. package/dist/mediaStream.cjs.map +1 -1
  59. package/dist/mediaStream.d.ts +1 -1
  60. package/dist/mediaStream.mjs +7 -7
  61. package/dist/mediaStream.mjs.map +1 -1
  62. package/dist/node.cjs +2 -2
  63. package/dist/node.cjs.map +1 -1
  64. package/dist/node.d.ts +8 -7
  65. package/dist/node.mjs +49 -44
  66. package/dist/node.mjs.map +1 -1
  67. package/dist/occlusion.cjs +2 -0
  68. package/dist/occlusion.cjs.map +1 -0
  69. package/dist/occlusion.d.ts +19 -0
  70. package/dist/occlusion.mjs +41 -0
  71. package/dist/occlusion.mjs.map +1 -0
  72. package/dist/pannerMixin.cjs +1 -1
  73. package/dist/pannerMixin.cjs.map +1 -1
  74. package/dist/pannerMixin.d.ts +23 -50
  75. package/dist/pannerMixin.mjs +57 -29
  76. package/dist/pannerMixin.mjs.map +1 -1
  77. package/dist/pcmStream.cjs +1 -1
  78. package/dist/pcmStream.cjs.map +1 -1
  79. package/dist/pcmStream.d.ts +1 -1
  80. package/dist/pcmStream.mjs +8 -8
  81. package/dist/pcmStream.mjs.map +1 -1
  82. package/dist/playOptions.cjs +1 -1
  83. package/dist/playOptions.cjs.map +1 -1
  84. package/dist/playOptions.d.ts +5 -1
  85. package/dist/playOptions.mjs +27 -20
  86. package/dist/playOptions.mjs.map +1 -1
  87. package/dist/playback.cjs +1 -1
  88. package/dist/playback.cjs.map +1 -1
  89. package/dist/playback.d.ts +19 -1
  90. package/dist/playback.mjs +45 -23
  91. package/dist/playback.mjs.map +1 -1
  92. package/dist/routableSource.cjs +1 -1
  93. package/dist/routableSource.cjs.map +1 -1
  94. package/dist/routableSource.d.ts +26 -3
  95. package/dist/routableSource.mjs +69 -34
  96. package/dist/routableSource.mjs.map +1 -1
  97. package/dist/sessionLifecycle.cjs +2 -0
  98. package/dist/sessionLifecycle.cjs.map +1 -0
  99. package/dist/sessionLifecycle.d.ts +13 -0
  100. package/dist/sessionLifecycle.mjs +55 -0
  101. package/dist/sessionLifecycle.mjs.map +1 -0
  102. package/dist/sound.cjs +1 -1
  103. package/dist/sound.cjs.map +1 -1
  104. package/dist/sound.mjs +22 -18
  105. package/dist/sound.mjs.map +1 -1
  106. package/dist/spatialAutomation.cjs +2 -0
  107. package/dist/spatialAutomation.cjs.map +1 -0
  108. package/dist/spatialAutomation.d.ts +11 -0
  109. package/dist/spatialAutomation.mjs +53 -0
  110. package/dist/spatialAutomation.mjs.map +1 -0
  111. package/dist/synth.cjs +1 -1
  112. package/dist/synth.cjs.map +1 -1
  113. package/dist/synth.mjs +13 -9
  114. package/dist/synth.mjs.map +1 -1
  115. package/dist/synthGroup.cjs +1 -1
  116. package/dist/synthGroup.cjs.map +1 -1
  117. package/dist/synthGroup.mjs +2 -0
  118. package/dist/synthGroup.mjs.map +1 -1
  119. package/dist/synthPlayback.cjs +1 -1
  120. package/dist/synthPlayback.cjs.map +1 -1
  121. package/dist/synthPlayback.mjs +1 -6
  122. package/dist/synthPlayback.mjs.map +1 -1
  123. package/docs/assets/hierarchy.js +1 -1
  124. package/docs/assets/highlight.css +4 -4
  125. package/docs/assets/navigation.js +1 -1
  126. package/docs/assets/search.js +1 -1
  127. package/docs/classes/AudioCache.html +4 -4
  128. package/docs/classes/AudioSprite.html +7 -7
  129. package/docs/classes/BarberpoleEffect.html +7 -7
  130. package/docs/classes/BiquadEffect.html +3 -3
  131. package/docs/classes/Bus.html +21 -21
  132. package/docs/classes/Cacophony.html +186 -98
  133. package/docs/classes/DynamicsEffect.html +7 -7
  134. package/docs/classes/FdnReverbEffect.html +7 -7
  135. package/docs/classes/FoaDecoder.html +5 -5
  136. package/docs/classes/FoaDecoderEffect.html +3 -3
  137. package/docs/classes/FrequencyShifterEffect.html +7 -7
  138. package/docs/classes/Group.html +53 -36
  139. package/docs/classes/HarmonizerEffect.html +7 -7
  140. package/docs/classes/ImpulseResponseEffect.html +3 -3
  141. package/docs/classes/KWeightingFilter.html +4 -4
  142. package/docs/classes/LoudnessMeter.html +11 -11
  143. package/docs/classes/MediaStreamPlayback.html +146 -147
  144. package/docs/classes/MediaStreamSound.html +118 -54
  145. package/docs/classes/MicrophoneStream.html +119 -55
  146. package/docs/classes/ModulatedDelayEffect.html +7 -7
  147. package/docs/classes/PcmStreamPlayback.html +146 -147
  148. package/docs/classes/PcmStreamSound.html +128 -64
  149. package/docs/classes/PhaserEffect.html +7 -7
  150. package/docs/classes/Playback.html +218 -175
  151. package/docs/classes/ReverbEffect.html +7 -7
  152. package/docs/classes/Scheduler.html +3 -3
  153. package/docs/classes/ShareEffect.html +3 -3
  154. package/docs/classes/Sound.html +136 -78
  155. package/docs/classes/SpectralFreezeEffect.html +7 -7
  156. package/docs/classes/StereoWidenerEffect.html +3 -3
  157. package/docs/classes/Synth.html +126 -68
  158. package/docs/classes/SynthGroup.html +28 -28
  159. package/docs/classes/TremoloEffect.html +7 -7
  160. package/docs/classes/TruePeakDetector.html +8 -8
  161. package/docs/classes/WaveshaperEffect.html +7 -7
  162. package/docs/functions/encodeMonoToFoaSN3D.html +1 -1
  163. package/docs/functions/integratedLoudness.html +1 -1
  164. package/docs/functions/integratedUngatedLoudness.html +1 -1
  165. package/docs/functions/loudnessRange.html +1 -1
  166. package/docs/functions/timeStretch.html +1 -1
  167. package/docs/functions/timeStretchChannels.html +1 -1
  168. package/docs/functions/truePeakDb.html +1 -1
  169. package/docs/hierarchy.html +28 -28
  170. package/docs/index.html +165 -2
  171. package/docs/interfaces/AudioBuffer.html +8 -8
  172. package/docs/interfaces/AudioBufferSourceNode.html +22 -22
  173. package/docs/interfaces/AudioEventCallbacks.html +2 -2
  174. package/docs/interfaces/AudioListener.html +12 -12
  175. package/docs/interfaces/AudioNode.html +14 -14
  176. package/docs/interfaces/AudioParam.html +26 -8
  177. package/docs/interfaces/AudioWorklet.html +2 -2
  178. package/docs/interfaces/AudioWorkletNode.html +16 -16
  179. package/docs/interfaces/BarberpoleOptions.html +5 -5
  180. package/docs/interfaces/BaseContext.html +25 -25
  181. package/docs/interfaces/BaseSound.html +16 -16
  182. package/docs/interfaces/BiquadCoefficients.html +6 -6
  183. package/docs/interfaces/BiquadFilterNode.html +20 -20
  184. package/docs/interfaces/BuiltEffectGraph.html +6 -6
  185. package/docs/interfaces/CacheErrorEvent.html +5 -5
  186. package/docs/interfaces/CacheHitEvent.html +4 -4
  187. package/docs/interfaces/CacheMissEvent.html +4 -4
  188. package/docs/interfaces/CacophonyEffect.html +2 -2
  189. package/docs/interfaces/CacophonyLogger.html +4 -4
  190. package/docs/interfaces/ChannelMergerNode.html +14 -14
  191. package/docs/interfaces/ChannelSplitterNode.html +14 -14
  192. package/docs/interfaces/ConvolverNode.html +16 -16
  193. package/docs/interfaces/CreateSpriteOptions.html +3 -3
  194. package/docs/interfaces/DeviceChangeEvent.html +64 -0
  195. package/docs/interfaces/DynamicsOptions.html +7 -7
  196. package/docs/interfaces/FadeStartEvent.html +4 -4
  197. package/docs/interfaces/FdnReverbOptions.html +6 -6
  198. package/docs/interfaces/FoaDecoderOptions.html +2 -2
  199. package/docs/interfaces/FrequencyShifterOptions.html +3 -3
  200. package/docs/interfaces/GainNode.html +15 -15
  201. package/docs/interfaces/GlobalPlaybackEvent.html +3 -3
  202. package/docs/interfaces/HarmonizerOptions.html +6 -6
  203. package/docs/interfaces/ImpulseResponseOptions.html +5 -5
  204. package/docs/interfaces/LoadingCompleteEvent.html +5 -5
  205. package/docs/interfaces/LoadingErrorEvent.html +5 -5
  206. package/docs/interfaces/LoadingProgressEvent.html +6 -6
  207. package/docs/interfaces/LoadingStartEvent.html +3 -3
  208. package/docs/interfaces/LoudnessChannelInput.html +3 -3
  209. package/docs/interfaces/LoudnessReading.html +5 -5
  210. package/docs/interfaces/MediaElementSourceNode.html +15 -15
  211. package/docs/interfaces/MediaStreamAudioSourceNode.html +15 -15
  212. package/docs/interfaces/MediaStreamSoundOptions.html +5 -5
  213. package/docs/interfaces/MicrophoneStreamOptions.html +8 -8
  214. package/docs/interfaces/ModulatedDelayOptions.html +8 -8
  215. package/docs/interfaces/OfflineOptions.html +5 -5
  216. package/docs/interfaces/OscillatorNode.html +20 -20
  217. package/docs/interfaces/PannerNode.html +28 -28
  218. package/docs/interfaces/PcmStreamBufferEvent.html +2 -2
  219. package/docs/interfaces/PcmStreamSoundOptions.html +6 -6
  220. package/docs/interfaces/PhaserOptions.html +7 -7
  221. package/docs/interfaces/PlayOptions.html +13 -13
  222. package/docs/interfaces/PlaybackErrorEvent.html +5 -5
  223. package/docs/interfaces/ReverbOptions.html +13 -13
  224. package/docs/interfaces/RuntimeOptions.html +41 -15
  225. package/docs/interfaces/ScheduledCallbackHandle.html +2 -2
  226. package/docs/interfaces/SendGainOptions.html +68 -0
  227. package/docs/interfaces/SoundCleanupHoldings.html +4 -4
  228. package/docs/interfaces/SoundErrorEvent.html +6 -6
  229. package/docs/interfaces/SpectralFreezeOptions.html +4 -4
  230. package/docs/interfaces/SpriteRegion.html +4 -4
  231. package/docs/interfaces/StereoPannerNode.html +15 -15
  232. package/docs/interfaces/StereoToBFormatOptions.html +2 -2
  233. package/docs/interfaces/StereoWidenerOptions.html +4 -4
  234. package/docs/interfaces/StreamCapabilities.html +5 -5
  235. package/docs/interfaces/TimeStretchOptions.html +5 -5
  236. package/docs/interfaces/TremoloOptions.html +5 -5
  237. package/docs/interfaces/WaveshaperOptions.html +5 -5
  238. package/docs/modules.html +4 -4
  239. package/docs/types/AudioSinkId.html +35 -0
  240. package/docs/types/BaseAudioEvents.html +11 -11
  241. package/docs/types/BuiltEffect.html +1 -1
  242. package/docs/types/BusConnectionTarget.html +1 -1
  243. package/docs/types/CacheEventCallback.html +1 -1
  244. package/docs/types/CacophonyEvents.html +40 -20
  245. package/docs/types/ErrorEventCallback.html +1 -1
  246. package/docs/types/FadeType.html +1 -1
  247. package/docs/types/HrtfPannerOptions.html +1 -1
  248. package/docs/types/ImpulseResponseSource.html +1 -1
  249. package/docs/types/LoadingEventCallback.html +1 -1
  250. package/docs/types/LoopCount.html +1 -1
  251. package/docs/types/LoudnessChannel.html +1 -1
  252. package/docs/types/Orientation.html +3 -3
  253. package/docs/types/PanCloneOverrides.html +13 -7
  254. package/docs/types/PanType.html +1 -1
  255. package/docs/types/PcmStreamEvents.html +6 -6
  256. package/docs/types/PcmStreamState.html +1 -1
  257. package/docs/types/PlaybackEvents.html +1 -1
  258. package/docs/types/Position.html +1 -1
  259. package/docs/types/SoundEvents.html +1 -1
  260. package/docs/types/SoundType.html +1 -1
  261. package/docs/types/SourceNode.html +1 -1
  262. package/docs/types/SpriteMap.html +1 -1
  263. package/docs/types/SynthEvents.html +1 -1
  264. package/docs/types/ThreeDOptions.html +5 -15
  265. package/docs/types/WebCodecsStreamSound.html +1 -1
  266. package/docs/variables/CHANNEL_WEIGHTS.html +1 -1
  267. package/docs/variables/K_WEIGHTING_STAGE1_48K.html +1 -1
  268. package/docs/variables/K_WEIGHTING_STAGE2_48K.html +1 -1
  269. package/docs/variables/consoleLogger.html +1 -1
  270. package/docs/variables/noopLogger.html +1 -1
  271. package/package.json +6 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,1600 @@
1
+ # Changelog
2
+
3
+ All notable changes to cacophony are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While cacophony is 0.x, a minor release
6
+ may contain breaking changes; they are always listed first under **⚠ Breaking** with a migration
7
+ note. Patch releases do not break.
8
+
9
+ Entries before 0.33.0 were reconstructed in September 2026 from the git history, pull requests,
10
+ and source diffs. Some versions were tagged but never published to npm; they are marked as such.
11
+
12
+ <!--
13
+ How to add an entry: put a bullet under "## [Unreleased]" in the same PR as the change, in the
14
+ right subsection (⚠ Breaking, Added, Changed, Deprecated, Removed, Fixed, Security). Write it for
15
+ someone upgrading, not as a commit message. `npm version` stamps the release; see CLAUDE.md.
16
+ -->
17
+
18
+ ## [Unreleased]
19
+
20
+ ## [0.34.0] - 2026-10-03
21
+
22
+ ### ⚠ Breaking
23
+ - Spatial setters (`stereoPan`, `position`, `threeDOptions`) now reject incompatible panning modes, invalid values, or cleaned-up voices before changing any source defaults or voices. Group spatial setters preflight every member too. `ThreeDOptions` now describes HRTF configuration only; assigning it never switches modes. Migration: select the intended `panType` when creating a source or in `play({ panType, ... })`, and control mixed-mode voices individually. (#104)
24
+ - `Sound.clone`, `Synth.clone`, and `Playback.clone` reject invalid spatial overrides or overrides incompatible with the clone's target mode before allocation, instead of ignoring them or storing unusable defaults. Direct HRTF setters now share invocation constraints, including nonnegative cone angles. Migration: remove spatial overrides for the other mode and pass finite values within the documented bounds. (#104)
25
+
26
+ ### Fixed
27
+ - Group positions are validated even with no members, and position inputs and returned snapshots no longer share mutable arrays with the group. (#104)
28
+ - Reverb and delay tails no longer cut off when their input goes away. `createReverb`, `createFdnReverb`, and the `createDelay` family (delay, chorus, flanger, vibrato, doubling) keep rendering after the last source feeding them ends or is disconnected, for example a one-shot sending to a shared reverb bus. Before, the effect fell silent at that moment. (#255)
29
+ - `createFdnReverb` no longer follows the pan of its input. A source panned to one side used to produce reverb on that side only; the wet signal is now spread across both channels, which have different echo patterns and stay decorrelated at every `diffusion` setting, while the dry part of `mix` keeps its position. (#255)
30
+
31
+ ### Added
32
+ - Browser audio-session recovery via `RuntimeOptions.autoRecover` (default `true`), platform `suspend`/`resume`/`interrupted` events, audio-output `devicechange` notifications, and `Cacophony.dispose()` for listener teardown. Visible-page recovery and gesture unlock preserve explicit user pauses. Recovery starts only after the context has first run, leaving autoplay unlock to the gesture path; a gesture unlock now also emits `resume`. Concurrent `pause()` calls settle with the pending suspension. (#209)
33
+ - Runtime `Cacophony.setOutputDevice()` and `outputDevice` for supporting browser and Node contexts, shared `AudioSinkId`, and `sinkChange` events for the active output. Platform permission errors propagate unchanged; offline selection is a no-op. (#203)
34
+ - Opt-in spatial motion smoothing via `RuntimeOptions.spatialSmoothingTau`, source/voice `spatialSmoothingTau`, and `Cacophony.listenerSmoothingTau` (seconds). Position, orientation and stereo pan retarget smoothly; initial and replayed poses remain immediate, getters report requested targets, and setting the constant to `0` snaps pending transitions. Legacy listener APIs remain immediate. (#201)
35
+ - Sources and Groups can remove an aux send with `removeSend(busOrName)` and ramp an existing send with `routeTo(busOrName, gain, { duration, type })`. Removal disconnects every voice's send and prevents future voices or bus drains from recreating it, while preserving primary routing. Ramps reuse existing nodes, interrupt smoothly, and take milliseconds; instant changes remain the default. (#241)
36
+ - `Playback.setOcclusion(amount, duration?)` renders per-voice obstruction with a dedicated pre-panner low-pass and attenuation stage, independently of volume and fades. Amounts clamp to `[0, 1]`; transitions default to 50 ms; resetting to `0` restores the clear path. `Playback.occlusion` reports the requested amount. The caller owns geometry, materials, and policy. (#221)
37
+
38
+ ### Changed
39
+ - `routeTo(busOrName, sendGain)` throws `RangeError` for a non-finite send gain instead of storing it, and `routeTo` throws `TypeError` when ramp options are passed without a send gain instead of ignoring them. (#241)
40
+ - `createReverb`, `createFdnReverb`, and the `createDelay` family now always take stereo in and give stereo out: a mono input is up-mixed and an input with more than two channels is down-mixed. A mono source through `createChorus`, `createFlanger`, `createVibrato`, or `createDoubling` therefore comes out as stereo, with the two channels modulated a quarter cycle apart; a plain `createDelay` is unchanged. An effect placed on a bus carrying more than two channels (for example first-order ambisonics ahead of a decoder) now folds it to stereo. (#255)
41
+ - `createFdnReverb` excites its wet path with the mono sum of the input, as `createReverb` already did. The wet level is unchanged for a centred source, about 3 dB lower for a hard-panned source or for wide stereo material, and zero for content that is out of phase between left and right. (#255)
42
+
43
+ ## [0.33.0] - 2026-09-26
44
+
45
+ ### Fixed
46
+ - `Playback.cleanup()` (and so `Sound.cleanup()`) stops started native buffer sources instead of only disconnecting them. Looping sources no longer keep running and holding decoded audio after cleanup. Shared buffers, sibling voices, and replay of stopped playbacks are unaffected. (#239)
47
+
48
+ ## [0.32.1] - 2026-09-13
49
+
50
+ ### Fixed
51
+ - `Playback.seek()` emits the `seek` event (with the new time) once after an accepted seek on buffer and media-element playbacks. Rejected seeks emit nothing. `Sound` does not relay a collective `seek` event. (#213, #237)
52
+
53
+ ## [0.32.0] - 2026-09-13
54
+
55
+ ### ⚠ Breaking
56
+ - `play(options)` validates its options before creating a voice and throws on invalid values: a `RangeError` for out-of-range numbers such as a negative `fadeIn` or `at`, and an `Error` for controls the source cannot honor. For example, `Synth` and live streams now reject `fadeOut`, `fadeInPerLoop`, `playbackRate`, and `loopCount` where they used to ignore them. Migration: drop the unsupported fields from those calls. (#232, #236)
57
+ - `AudioCache` follows HTTP caching rules (via `http-cache-semantics`) for both the in-memory decoded cache and the persistent Cache API store. Responses marked `no-store` are no longer retained, and stale memory hits get revalidated. `setCacheExpirationTime()` now only sets the fallback TTL for responses that have no cache directives, expiry, or validators, and it throws a `RangeError` for negative or non-finite values. Persistent entries written by older versions are ignored and fetched again once. Migration: for static audio, use versioned URLs or send `Cache-Control` headers, and expose `ETag`/`Age`/`Vary` through CORS where freshness depends on them. (#229, #234)
58
+ - `sound.playbacks` no longer keeps stopped playbacks: they are removed when the next voice is prepared. A reaped `Playback` you still hold can be replayed and gets re-attached to the Sound's current routing. Migration: keep your own references if you need stopped voices to stay in that list. (#212, #235)
59
+
60
+ ### Added
61
+ - `PlayOptions` accepts per-voice `volume`, `playbackRate`, `loopCount`, `panType`, `stereoPan`, `position`, and `threeDOptions`. They apply to the new voice before its source starts and leave the Sound's defaults and other voices alone. `Group.play(options)` checks the options against every member first. (#232, #236)
62
+ - `BasePlayback.state`: a read-only lifecycle state for any playback. (#235)
63
+ - New runtime dependency `http-cache-semantics` (pinned to 4.1.1). Its license is included in `THIRD_PARTY_NOTICES.md`. (#234)
64
+
65
+ ### Fixed
66
+ - `Sound.clone({ filters: [] })` clears the inherited filters. Before this, an empty array fell back to the source's filters. (#231, #233)
67
+ - `AudioCache` fixes: cross-origin audio is cached again. An unreadable or missing persistent body is evicted and fetched again. Aborting a `data:` URL load counts as an abort. A progress callback that throws no longer breaks a shared load for other subscribers. Progress stays capped when `Content-Length` underestimates the body. Abort notifications to loading-error callbacks work again. (#234)
68
+ - If `play()` fails partway, the voices it just created are stopped and removed. A reused stream voice survives a failed replay. (#236)
69
+
70
+ ## [0.31.0] - 2026-09-08
71
+
72
+ ### ⚠ Breaking
73
+ - `Cacophony.createStereoToBFormatNode(signal?)` is now `createStereoToBFormatNode(options?, signal?)`. Migration: change `createStereoToBFormatNode(signal)` to `createStereoToBFormatNode({}, signal)`. (#193, #220)
74
+
75
+ ### Added
76
+ - Audio sprites: `cacophony.createSprite(sourceUrlOrBuffer, map)` returns an `AudioSprite` whose named regions are ordinary `Sound`s over one shared `AudioBuffer`. Playback, seeking, looping, cloning, and time-stretching stay inside each region. Exports `AudioSprite`, `SpriteMap`, `SpriteRegion`, and `CreateSpriteOptions`. (#196, #223)
77
+ - `cacophony sprite <file...> --out atlas.wav --map atlas.json [--gap ms]` CLI command that packs files into a WAV atlas and a matching `SpriteMap`. It runs headless, with no audio output device needed. (#223, #224)
78
+ - Sample-accurate scheduling: `play({ at })` starts a buffer sound at an absolute `AudioContext` time, and a scheduled `fadeIn` starts at that time too. Media, PCM, microphone, and synth sources throw if you pass `at`. (#195, #222)
79
+ - Exported `Scheduler` class (lookahead callback scheduler on `AudioContext` time, with `ScheduledCallbackHandle.cancel()`). (#195, #222)
80
+ - BCC stereo-to-FOA encoder: `createStereoToBFormatNode({ algorithm: "bcc" })`. The default stays `"perceptual"`. `StereoToBFormatOptions` is exported. (#193, #220)
81
+ - `fadeTo()`/`fadeIn()` accept an optional `startTime` option (plus `startValue` for `fadeTo`) for scheduled fades. (#222)
82
+
83
+ ### Changed
84
+ - MediaBunny (the WebCodecs streaming adapter) and each AudioWorklet bundle are loaded on demand instead of at import time. `package.json` declares `sideEffects` so bundlers can tree-shake unused modules. The PR reports a consumer bundle's initial gzip size dropping from about 275 KB to about 36 KB. `loadWorklets()` still preloads everything. Your bundler and host must serve the extra code-split chunks. (#225)
85
+ - `createWorkletNode()` accepts a worklet URL as a string or as an async loader (`() => Promise<string>`). A custom `resolveWorkletUrl` still receives a string. (#225)
86
+
87
+ ### Fixed
88
+ - Autoplay unlock treats iOS Safari's `interrupted` context state like `suspended`, so audio comes back after the next gesture. (#215, #217)
89
+ - Setting `Bus.gain` cancels any scheduled gain ramps first, so a ramp no longer overrides a later direct assignment. (#214, #218)
90
+
91
+ ## [0.30.4] - 2026-08-14
92
+
93
+ ### Fixed
94
+ - The published TypeScript declarations include `BasePlayback`, `VolumeMixin`, and the public time-stretch types, which 0.30.3 was missing. (#192)
95
+
96
+ ## [0.30.3] - 2026-08-14
97
+
98
+ _First npm release after 0.29.0 through 0.30.2, which were tagged but not published. Upgrading from an earlier npm version? Read those entries too._
99
+
100
+ ### Fixed
101
+ - Firefox no longer crashes with `this.listener.forwardX is undefined`. Its native `AudioListener` has no position/orientation `AudioParam`s, so listener setters fall back to `setPosition()`/`setOrientation()` there (and on partial implementations), and getters return a cached pose. (#189, #190, #191)
102
+
103
+ ### Changed
104
+ - The exported `AudioListener` type marks its `positionX`...`upZ` params as optional and adds optional `setPosition`/`setOrientation`. TypeScript code that reads those params directly may need a guard. (#190)
105
+
106
+ ## [0.30.2] - 2026-08-14
107
+
108
+ _Tagged but not published to npm._
109
+
110
+ ### Changed
111
+ - Failed release: the release workflow ran tests before building. Its contents were re-released as 0.30.3.
112
+
113
+ ## [0.30.1] - 2026-08-13
114
+
115
+ _Tagged but not published to npm._
116
+
117
+ ### Changed
118
+ - Version bump only. No code changes.
119
+
120
+ ## [0.30.0] - 2026-08-11
121
+
122
+ _Tagged but not published to npm._
123
+
124
+ ### ⚠ Breaking
125
+ - The public `applyFilters()` and `removeFilters()` methods are removed from `Sound`, `Playback`, and other `FilterManager` subclasses. Migration: call `removeFilter()` for each filter, and use `addFilter()`/`addFilters()` to build chains. (#161, #176)
126
+ - `Bus.addFilter()` throws synchronously when given a raw `AudioNode` (or a biquad from a different `AudioContext`) instead of returning a rejected Promise. Migration: wrap the call in `try`/`catch` as well as `.catch()`, or wrap the node with `cacophony.shareEffect()`. (#168, #170, #187, #188)
127
+
128
+ ### Added
129
+ - Five new AudioWorklet effects: `createFrequencyShifter()`, `createBarberpole()`, `createHarmonizer()`, `createSpectralFreeze()`, and `createStereoWidener()`. Exports `FrequencyShifterEffect`, `BarberpoleEffect`, `HarmonizerEffect`, `SpectralFreezeEffect`, `StereoWidenerEffect`, and their `*Options` types. (#155)
130
+ - `MicrophoneStreamOptions.stopTracksOnStop` (default `true`). Set it to `false` to keep the microphone stream alive after stop or cleanup. (#160, #181)
131
+ - PCM and media-stream playbacks emit `resume` when resumed, like buffer and synth playbacks. (#173, #185)
132
+
133
+ ### Changed
134
+ - Effect-chain guard errors have new messages, prefixed with the owning chain's scope. An effect whose `build()` returns something other than an `AudioNode` or `BuiltEffectGraph` is rejected with a clear error. (#168, #170)
135
+ - HLS errors say whether `hls.js` is not installed or Media Source Extensions are unsupported. Other import failures are re-thrown unchanged. hls.js errors are always reported with `recoverable: false`. (#162, #186)
136
+ - `FoaDecoder` rejects HRIR buffers with fewer than 4 channels and requires `createBuffer` support in the context. (#165, #184)
137
+ - Media-element streams: `seek()` throws when the element exposes no seekable range (for example, live streams). A loop setting made before playback applies to new playbacks. Cleanup clears and reloads the element's source. (#59, #154)
138
+
139
+ ### Fixed
140
+ - Autoplay unlock re-arms whenever the context becomes suspended later on (not only at construction) and retries after a failed `resume()`. (#158, #179)
141
+ - `AudioCache`: one caller aborting a shared in-flight load no longer cancels it for other callers. The request is cancelled only after every caller aborts. (#159, #180)
142
+ - `MediaStreamPlayback` cleanup stops tracks it owns, and play/resume keep tracks you disabled on purpose disabled. (#160, #181)
143
+ - `Synth.clone()` produces independent oscillator options and position, and honors an empty `filters` override. (#156, #177)
144
+ - An effect graph rejected as a duplicate by `EffectChain` or `Bus` is disposed instead of leaked. (#157, #178)
145
+ - `Bus.addFilter()` keeps declaration order when several async effect builds are in flight at once. (#168, #187)
146
+ - WebCodecs streams report a seek during a decoder reopen separately from a disposed stream. (#163, #182)
147
+ - The WebCodecs streaming resampler writes into preallocated `Float32Array`s, so it no longer allocates boxed arrays for every chunk. (#164, #183)
148
+
149
+ ## [0.29.0] - 2026-07-31
150
+
151
+ _Tagged but not published to npm._
152
+
153
+ ### ⚠ Breaking
154
+ - `createStream()` now resolves to `Sound | WebCodecsStreamSound`. When `AudioDecoder` exists and the URL can be demuxed, the stream is decoded through WebCodecs into a PCM ring buffer instead of an `HTMLAudioElement`. Otherwise it falls back to the media-element `Sound`. Narrow the result before calling `Sound`-only methods. (#131, #137)
155
+ - Playback filters and pitch shifting now run before the panner (`source → effects → panner → gain`), not after it. There is no option to keep the old post-panner placement, so an effect chain on an HRTF-panned sound will sound different. (#140, #150)
156
+ - `Sound.resume()` and `Synth.resume()` only resume paused playbacks. Before this, they also restarted playbacks that had been stopped or never played. (#105, #123)
157
+ - `cacophony.volume = 0` no longer counts as muted. `muted` is now tracked separately from the gain value, and a later nonzero volume applies immediately. (#103, #139)
158
+ - `setGlobalVolume()` called while muted is deferred until `unmute()`, the same as the `volume` setter. Before, it unmuted the output. (#141, #149)
159
+ - `cacophony.master.destroy()` throws. Destroying the master bus used to silence the instance permanently. (#142, #147)
160
+ - The microphone was rebuilt on `MediaStreamSound`. `MicrophoneStream` now extends `MediaStreamSound`, `MicrophonePlayback` is an alias of `MediaStreamPlayback`, and microphone output goes through the master bus. `getMicrophoneStream(options?)` takes `constraints`, `panType`, `stereoPan` and `threeDOptions`, and acquisition failures reject the promise. (#102, #110, #153)
161
+ - `AudioCache` keeps decoded buffers and in-flight requests per audio context, and `clearMemoryCache()` clears only the instance it is called on. Before, one context could receive a buffer decoded at another context's sample rate. (#107, #124)
162
+ - `mediabunny` is a new required runtime dependency, used for WebCodecs demuxing. `node-web-audio-api` is a new `optionalDependency`, so npm installs it by default unless you pass `--omit=optional`.
163
+
164
+ ### Added
165
+ - `cacophony/node` entry point with `createNodeCacophony()` and `createOfflineNodeCacophony()`, plus `decodeAudioFile()`, for running the engine headlessly on `node-web-audio-api`. The native backend is loaded lazily and needs Node 22 or later. (#93)
166
+ - `cacophony` CLI (`bin/cacophony.mjs`) with `render`, `play`, `synth`, `meter` and `repl` commands. Set `CACOPHONY_SINK=none` for machines with no audio device. (#93)
167
+ - `createPcmStreamSound()` / `PcmStreamSound`: a push-based PCM source backed by an AudioWorklet ring buffer, with backpressure, underrun/drain/ended events, pause/resume and `AbortSignal` teardown. (#130, #134)
168
+ - HLS: `.m3u8` URLs passed to `createStream()` use native HLS when the browser supports it. Otherwise they load the optional `hls.js` peer dependency. (#132, #138)
169
+ - `StreamCapabilities` (`transport`, `seekable`, `live`, `duration`) reported on streamed sounds. `createStream()` accepts a `panType` argument. (#137, #145)
170
+ - Per-source effects: `addEffect()` / `removeEffect()` on `Sound`, `Synth`, PCM streams and media streams. Each playback gets its own copy of the effect, in declaration order. (#113, #151)
171
+ - Playback effect chains support reordering, bypass and parameter automation (`setFilterOrder`, `setFilterBypassed`, `isFilterBypassed`), matching `Bus`. (#140, #150)
172
+ - `MediaStreamSound.routeTo()`, so live streams can be routed through buses like `Sound` and `Synth`. (#112, #127)
173
+ - `Group.resetOrder()` restarts ordered playback after a non-looping sequence runs out. `Group.play()`, `playRandom()` and `playOrdered()` accept `PlayOptions`. (#109, #136)
174
+ - `Playback.isPaused`, and one shared playback state machine across all playback types. (#106, #125)
175
+ - `Cacophony.createOffline()` takes a third `runtimeOptions` argument. (#108, #135)
176
+ - `RuntimeOptions.logger` (`CacophonyLogger`, `consoleLogger`, `noopLogger`), `RuntimeOptions.quiet` and `RuntimeOptions.resolveWorkletUrl`. (#93)
177
+ - `MicrophoneStream` and `MicrophoneStreamOptions` are exported. (#145, #153)
178
+
179
+ ### Changed
180
+ - `AudioCache` falls back to fetch-only when the Cache API is unavailable, as in Node. (#93)
181
+ - The unused chunked-WAV `stream.ts` decoder was removed, and the docs now say that `"streaming"` sounds are backed by a media element. (#129, #133)
182
+
183
+ ### Fixed
184
+ - Changing `panType` on a live playback, or cloning with a `panType` override, no longer leaves it silent. (#101, #122)
185
+ - `Playback.clone()` connects the clone to the origin's route, really starts clones of playing buffer playbacks, and registers them on `Sound.playbacks`. (#100, #121)
186
+ - `Playback.clone()` keeps fade-in/fade-out settings and pitch shift, with its own worklet node. (#143, #148)
187
+ - Group playback goes through `Sound.play()`, so member `play` events fire and fades apply. (#109, #136)
188
+ - `createSound(urls[])` format fallback works under Node, where there is no `Audio` global. (#144, #146)
189
+ - `SynthPlayback` inherits from `BasePlayback` directly. This removes the `OscillatorMixin` trap that restarted oscillators unconditionally. (#111, #120)
190
+ - `SynthPlayback` emits playback and global lifecycle events. An unset synth `detune` defaults to `0`. A failed send-gain allocation is disposed. (#145, #152)
191
+ - The first HTML/streaming playback reuses the media element already loaded for metadata, instead of fetching the media twice. (#114, #128)
192
+ - An empty `SynthGroup` returns `detune` `0`. (#114, #128)
193
+ - Offline fades resolve on context time, not wall-clock time. (#114, #128)
194
+ - A media stream whose tracks ended on `stop()` refuses to replay instead of reporting a dead replay as active. (#114, #128)
195
+ - Worklet effects load under Node. (#93)
196
+
197
+ ## [0.28.0] - 2026-06-12
198
+
199
+ _First npm release after 0.26.0, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
200
+
201
+ ### Added
202
+ - `createImpulseResponse(source, options?)` / `ImpulseResponseEffect`: native `ConvolverNode` convolution from an `AudioBuffer` or URL. URL impulse responses are fetched once per context and URL. `normalize` defaults to `false`, and optional `dry`/`wet` settings expose automatable params. (#94)
203
+ - `createFoaDecoderEffect()` / `FoaDecoderEffect`: an FOA-to-binaural decoder usable as a bus filter. (#94)
204
+ - `CacophonyEffect.build()` may return a `BuiltEffectGraph` (`input`, `output`, optional `params` and `dispose`) for multi-node effects. The new `BuiltEffect` and `BuiltEffectGraph` types are exported, along with the `ConvolverNode` type. (#94)
205
+ - `MediaStreamSoundOptions.primeWithMediaElement` (default `true`).
206
+
207
+ ### Changed
208
+ - The return type of `CacophonyEffect.build()` widened from `AudioNode` to `BuiltEffect`. Code that calls `build()` on an effect directly and treats the result as an `AudioNode` must handle the graph form. (#94)
209
+
210
+ ### Fixed
211
+ - Remote WebRTC streams in Chromium no longer play as silence through `MediaStreamSound`. Playback attaches the stream to a muted `HTMLAudioElement` to start decoding. Opt out with `primeWithMediaElement: false`.
212
+ - An aborted impulse-response load no longer breaks concurrent or later loads of the same URL. (#94)
213
+ - `Bus.destroy()` finishes cleanup even if an effect's `dispose()` throws. (#94)
214
+
215
+ ## [0.26.0] - 2026-06-03
216
+
217
+ _Tagged but not published to npm._
218
+
219
+ ### ⚠ Breaking
220
+ - Removed the per-worklet public methods on `Cacophony`: `loadPhaseVocoder`, `createPhaseVocoderNode`, `loadDattorroReverb`, `createDattorroReverbNode`, `loadDynamics`, `createDynamicsNode`, `loadFdnReverb`, `createFdnReverbNode`, `loadWaveshaper`, `createWaveshaperNode`, `loadModulatedDelay`, `createModulatedDelayNode`, `loadPhaser`, `createPhaserNode`, `loadTremolo`, `createTremoloNode`, `loadLoudnessMeter` and `createLoudnessMeterNode`. Use the effect factories (`createReverb()`, `createCompressor()`, ...) or `loadWorklets()` plus `buildWorkletEffect()`. (#92)
221
+ - Worklet effect classes (`ReverbEffect`, `DynamicsEffect`, `FdnReverbEffect`, `WaveshaperEffect`, `ModulatedDelayEffect`, `PhaserEffect`, `TremoloEffect`) now extend a shared `WorkletEffect` base with the constructor `(host: WorkletEffectHost, options)`. Build them through the `Cacophony` factory methods instead of calling the constructors. (#92)
222
+
223
+ ## [0.25.1] - 2026-06-03
224
+
225
+ ### Added
226
+ - `Bus.drainTo(target)` and `bus.destroy({ drainTo })` move live routed `Sound` and `Synth` instances to another bus before teardown. (#91)
227
+ - `Bus.setFilterOrder()`, `Bus.setFilterBypassed()` / `isFilterBypassed()` and `rampFilterParam()` for live effect-chain control. (#91)
228
+ - The `shape` option on waveshaper and tremolo effects and the `interpolation` option on modulated-delay effects accept string aliases as well as numeric indices. (#91)
229
+
230
+ ### Changed
231
+ - `Bus.addFilter()` resolves to the built `AudioNode` instead of `void`. (#91)
232
+ - Bus filter-chain changes rewire only the edges that changed, instead of rebuilding the whole chain. (#91)
233
+
234
+ ### Fixed
235
+ - Sounds routed to a bus only through sends are tracked correctly when that bus is drained. (#91)
236
+ - Synth routes are registered for bus draining. (#91)
237
+
238
+ ## [0.25.0] - 2026-06-03
239
+
240
+ ### Changed
241
+ - Version bump only. No code changes since 0.24.0.
242
+
243
+ ## [0.24.0] - 2026-06-02
244
+
245
+ ### Added
246
+ - DSP effects built on AudioWorklets, each with a `Cacophony` factory:
247
+ - dynamics: `createCompressor`, `createLimiter`, `createGate`
248
+ - reverb: `createFdnReverb`
249
+ - distortion: `createWaveshaper`, `createDistortion` (antialiased)
250
+ - modulated delay: `createDelay`, `createChorus`, `createFlanger`, `createVibrato`, `createDoubling`
251
+ - `createPhaser`
252
+ - tremolo: `createTremolo`, `createAutoPan`
253
+
254
+ The matching `*Effect` classes and `*Options` types are exported. (#88, #90)
255
+ - Pitch shifting through the phase vocoder: `Sound.setPitchShift(factor)` / `pitchShift`, also on playbacks. (#88)
256
+ - Offline time-stretching: `Sound.timeStretch(factor)`, `Cacophony.timeStretchBuffer()`, and the exported `timeStretch` / `timeStretchChannels`. (#88)
257
+ - ITU-R BS.1770-5 loudness metering: `createLoudnessMeter()` / `LoudnessMeter`, plus the exported `integratedLoudness`, `loudnessRange`, `KWeightingFilter`, `TruePeakDetector`, `truePeakDb` and related helpers. (#88)
258
+ - `createFoaDecoder()` / `FoaDecoder` for FOA-to-binaural decoding, and `encodeMonoToFoaSN3D()`. (#88)
259
+ - Per-worklet `load*()` / `create*Node()` methods. These were removed again in 0.26.0. (#88, #90)
260
+
261
+ ### Changed
262
+ - `loadWorklets()` loads every bundled worklet: phase vocoder, stereo-to-B-format, Dattorro and FDN reverb, dynamics, waveshaper, modulated delay, phaser, tremolo and loudness meter. (#88, #90)
263
+
264
+ ## [0.23.0] - 2026-05-30
265
+
266
+ ### ⚠ Breaking
267
+ - `SoundType` is now a type-only string union, `"html" | "streaming" | "buffer" | "oscillator"`, instead of an enum. Replace `SoundType.HTML`, `SoundType.Streaming`, `SoundType.Buffer` and `SoundType.Oscillator` with the lowercase strings, and switch the import to `import type`. (#79)
268
+ - `Cacophony.pause()` and `Cacophony.resume()` are now `async` and return `Promise<void>`. (#79)
269
+ - The decoded-buffer memory cache is limited to 64 MiB, estimated from buffer size, instead of 100 entries.
270
+ - `stereoPan` on sound containers is typed `number` instead of `number | null`, and `threeDOptions` uses the new `ThreeDOptions` type instead of `any` or `PannerOptions`. (#79)
271
+
272
+ ### Added
273
+ - Buses and sends: the `Bus` class, `cacophony.master`, `createBus()`, `getBus()`, `listBuses()`, and `routeTo(bus | name, sendGain?)` on `Sound`, `Synth` and `Group`. `bus.connect(target, gain?)` adds a gained send. (#83)
274
+ - An effect interface for bus chains: `CacophonyEffect`, `BiquadEffect`, `ShareEffect` (`shareEffect(node)`), and `createReverb()` / `ReverbEffect`, which uses the bundled Dattorro reverb worklet. (#83)
275
+ - `SynthPlayback` gains `outputNode`, `connect()` and `disconnect()`, matching `Playback`. (#83)
276
+ - Autoplay unlock: when the context starts suspended, the first touch, click or key press resumes it. Adds `cacophony.locked` and an `unlock` event. Opt out with `RuntimeOptions.autoUnlock: false`. (#81, #86)
277
+ - `createSound([url1, url2, ...])` picks the first URL whose format the browser can play, and falls back to the next one on a decode `EncodingError`. Only `"buffer"` sounds support this. (#82, #85)
278
+ - New type exports: `HrtfPannerOptions`, `PanCloneOverrides`, `ThreeDOptions` and `AudioWorklet`. The package entry now lists every export by name instead of re-exporting whole modules; no previously exported names were removed. (#79)
279
+
280
+ ### Fixed
281
+ - The microphone stream's gain node is connected to the output (`context.destination`). Before, it was created but never connected, so microphone input that used to be silent is now audible (monitoring). (#80)
282
+ - `Cacophony.resume()` always calls through to the context. This fixes resuming after the context was suspended outside Cacophony. (#79)
283
+ - Media-element sources are cloned per playback. `loopEnded` waits for the source to be ready. A race in `stopWithFade` is fixed. (#79)
284
+ - `Sound` rolls back its preplay state when playback creation fails, and unregisters playback listeners on cleanup. (#79)
285
+ - A cancelled fade resolves its promise and snaps to the final value. (#79)
286
+ - The event emitter copies its listener list before emitting, so `once` listeners are removed correctly while an event is being emitted. (#79)
287
+ - Numeric options accept `0`: defaults use `??` instead of `||`. (#79)
288
+ - Phase vocoder options are no longer overwritten. (#79)
289
+ - `createSound(buffer, soundType)` honors the `soundType` argument instead of always creating a buffer sound. (#79)
290
+ - Worklet modules are tracked per `BaseContext`, so a second context loads its own copy. (#83)
291
+ - Cache metadata keys are handled consistently when entries are written, read and removed.
292
+
293
+ ## [0.21.0] - 2026-05-11
294
+
295
+ ### Added
296
+ - `MediaStreamSound` and `MediaStreamPlayback`, created with `cacophony.createMediaStreamSound(stream, { panType?, stopTracksOnStop? })`. They play a live `MediaStream`, such as WebRTC or a capture, through Cacophony's panning, volume and filters. `stopTracksOnStop` defaults to `true`. (#78)
297
+
298
+ ### Changed
299
+ - `GlobalPlaybackEvent.source` is typed `BaseSound | Sound | Synth`, so `MediaStreamSound` can fire global play, pause and stop events. (#78)
300
+
301
+ ## [0.20.8] - 2026-05-09
302
+
303
+ ### Changed
304
+ - The `stereo-to-bformat` upmix worklet steers less high-frequency energy into the side (Y) channel: treble only moves sideways when the band is both off-center and side-dominant, so centered vocals stop drifting to the sides.
305
+
306
+ ## [0.20.7] - 2026-05-09
307
+
308
+ ### Changed
309
+ - The `stereo-to-bformat` upmix worklet gates direction with per-band stereo coherence: centered (in-phase) material feeds the front (X) cue and is kept out of the side (Y) cue, while hard-panned, anti-phase and diffuse material keeps its side cue.
310
+
311
+ ## [0.20.6] - 2026-05-09
312
+
313
+ ### Changed
314
+ - Internal changes only (dependency lockfile refresh).
315
+
316
+ ## [0.20.5] - 2026-05-09
317
+
318
+ ### Changed
319
+ - The `stereo-to-bformat` upmix worklet now writes a frontal cue to the X channel from mid and high-band center content. Before this, X was always silent.
320
+
321
+ ## [0.20.4] - 2026-05-09
322
+
323
+ ### Changed
324
+ - The `stereo-to-bformat` upmix worklet splits the signal into three bands (crossovers at 250 Hz and 2 kHz) and weights W and Y per band. Lows stay mostly omnidirectional and the upper bands carry the horizontal cue. This replaces the plain mid/side mapping, so the output sounds different.
325
+
326
+ ## [0.20.3] - 2026-05-09
327
+
328
+ ### Changed
329
+ - Internal changes only (version bump; no source changes).
330
+
331
+ ## [0.20.2] - 2026-05-09
332
+
333
+ ### ⚠ Breaking
334
+ - `createStereoToBFormatNode()` now outputs first-order ambisonics in ACN channel order (W, Y, Z, X) instead of W, X, Y, Z. If you route its four output channels by index, swap channel 1 and channel 3.
335
+
336
+ ### Changed
337
+ - The `stereo-to-bformat` worklet logs per-channel peak statistics to the console with `console.info` about every 750 render quanta (roughly 2 seconds at 48 kHz).
338
+
339
+ ## [0.20.1] - 2026-05-09
340
+
341
+ ### Changed
342
+ - `createWorkletNode()` remembers which worklet modules it has already loaded and skips repeat `addModule()` calls. It now logs each construct and load step with `console.info`/`console.warn` under a `[cacophony/worklet]` prefix.
343
+ - `loadWorklets()` loads the `stereo-to-bformat` module without constructing a node.
344
+
345
+ ## [0.20.0] - 2026-05-09
346
+
347
+ ### Added
348
+ - `Cacophony.createStereoToBFormatNode()` returns an `AudioWorkletNode` that takes a stereo input and outputs a 4-channel first-order ambisonic (B-format) signal. `loadStereoToBFormatWorklet()` preloads its module, and `loadWorklets()` loads it too.
349
+ - `Cacophony.createSplitter(numChannels)` and `Cacophony.createMerger(numChannels)` create channel splitter and merger nodes. They throw if the context doesn't support those nodes.
350
+ - `createWorkletNode()` accepts an optional fourth argument of `AudioWorkletNodeOptions`. The `RuntimeOptions.createAudioWorkletNode` factory receives these options.
351
+ - The `AudioWorkletNode`, `ChannelSplitterNode` and `ChannelMergerNode` types are exported. `BaseContext` gains optional `createChannelSplitter`/`createChannelMerger` methods.
352
+
353
+ ## [0.19.0] - 2026-04-15
354
+
355
+ ### ⚠ Breaking
356
+ - `standardized-audio-context` is no longer a dependency. It is now an optional peer dependency, and `new Cacophony()` with no arguments creates the browser's native `AudioContext`. If you want the `standardized-audio-context` implementation, install it yourself and pass its `AudioContext` to the constructor. (#56)
357
+ - Public types now use structural interfaces (`BaseContext`, `AudioNode`, `GainNode`, `PannerNode`, ...) exported from the package root instead of `standardized-audio-context`'s `I*` types. `Cacophony.context` and `Sound.context` are typed as `BaseContext`. Code that relied on those properties having `standardized-audio-context` types may need casts or type updates. (#56)
358
+ - Setting `stereoPan` outside `-1..1` throws a `RangeError` instead of clamping silently. Clamp the value yourself before you assign it.
359
+ - `SynthGroup.setVolume(v)` is removed. Use `synthGroup.volume = v` instead. (#71)
360
+ - `createSound()` with `SoundType.HTML` or `SoundType.Streaming`, and `createStream()`, now wait for the media element's `loadedmetadata` event before resolving. They reject when the media fails to load or the `AbortSignal` fires. Before, they resolved immediately.
361
+ - Event payloads that were typed `void` (`stop`, `pause`, `resume`, `ended`, `loopEnd`, `fadeEnd`, `fadeCancel`, `mute`, `unmute`, `suspend`) are now typed `undefined`. This only matters for code that names these event types explicitly.
362
+
363
+ ### Added
364
+ - Offline rendering: `Cacophony.createOffline({ numberOfChannels, length, sampleRate })`, `cacophony.isOffline`, and `cacophony.startRendering()`. (#56)
365
+ - The `Cacophony` constructor takes an optional third `RuntimeOptions` argument. Its `createAudioWorkletNode` factory lets non-native contexts build worklet nodes without the global `AudioWorkletNode`. (#56)
366
+ - `AudioCache` is exported from the package root.
367
+ - `on()` on `Cacophony`, `Sound`, `Synth` and playbacks returns an unsubscribe function.
368
+ - `Sound.resume()` and `Synth.resume()` resume paused playbacks. Synth playbacks now pause for real and resume from a fresh oscillator that keeps its settings. (#53, #68)
369
+ - `SynthGroup` has the same core controls as `Group`. `play()` returns the playbacks, and the group gains `pause()`, `resume()`, `isPlaying`, `addFilter()`/`removeFilter()`, fades, `stopWithFade()`, the `volume` getter/setter, and `stereoPan`, `position`, `frequency`, `detune` and `type` accessors. (#71)
370
+ - Events that were declared but never fired now fire: `ended` and `loopEnd` on `Sound`/`Playback`, `resume` on playbacks and sounds, and `mute`, `unmute`, `suspend`, `resume` and `volumeChange` on `Cacophony`. `volumeChange` also fires on `Playback` and `Synth`. (#53)
371
+ - Sounds are registered with a `FinalizationRegistry`, so if a `Sound` is garbage-collected without `cleanup()`, its source and gain nodes are disconnected and its media elements are released. (#74)
372
+
373
+ ### Changed
374
+ - For HTML/media-element playbacks, `play()` sets `isPlaying` and emits `play`/`globalPlay` only after `HTMLMediaElement.play()` resolves. If it rejects (for example, because of autoplay policy), the state reverts and an `error` event is emitted. (#50)
375
+ - `Sound` emits `play` once for each playback, after that playback actually starts, not synchronously with the first playback. A rejected media playback no longer produces a false `play` event on the sound. (#70)
376
+ - Playbacks that end on their own (all loops done) are removed from `Sound.playbacks`. (#49)
377
+ - Revalidated cache hits (HTTP 304) report `cacheType: "conditional"` in `cacheHit` events instead of `"browser"`.
378
+ - `loadingComplete` reports the real decoded `duration` instead of `0`. `loadingError` events carry an `errorType` of `"network"`, `"decode"` or `"abort"`.
379
+ - `createStream()` no longer starts a hidden, uncontrollable background stream alongside the `Sound` it returns. The returned `Sound` is the only playback path. (#56)
380
+ - Creating media-element or media-stream sources on a context that doesn't support them (such as an `OfflineAudioContext`) throws a clear error. (#56)
381
+
382
+ ### Fixed
383
+ - `cacophony.on('loadingError', ...)` fires on network and decode failures. Before, the callback was never invoked. (#47)
384
+ - `Group.playOrdered(false)` returns `undefined` after the last sound instead of crashing. (#48)
385
+ - The `Group.volume` getter returns `1` for an empty group instead of `NaN`. (#51)
386
+ - Oscillator `frequency: 0` and `detune: 0` are applied instead of being ignored, and the `frequency` getter no longer turns `0` into `440`. (#52)
387
+ - `getMicrophoneStream()` makes a single `getUserMedia` request (one permission prompt), and the first `play()` returns the playback instead of `[]`. (#54)
388
+ - `MicrophonePlayback` keeps its panner in the chain when filters are added or removed. (#55)
389
+ - `Playback.cleanup()` fully stops a media-backed playback (pauses and resets the element and detaches handlers) before it disconnects nodes. (#69)
390
+ - `setGlobalVolume()` with the current value no longer emits a `volumeChange` event.
391
+
392
+ ## [0.18.3] - 2026-02-20
393
+
394
+ _First npm release after 0.18.0 through 0.18.2, which were tagged but not published. Upgrading from an earlier npm version? Read those entries too._
395
+
396
+ ### Changed
397
+ - Internal changes only (release workflow moved to npm trusted publishing).
398
+
399
+ ## [0.18.2] - 2026-02-20
400
+
401
+ _Tagged but not published to npm._
402
+
403
+ ### Fixed
404
+ - `stop()` on a `Sound` or `Synth` also cleans up each playback it drops, so stopped playbacks no longer leave orphaned audio nodes.
405
+
406
+ ## [0.18.1] - 2026-02-19
407
+
408
+ _Tagged but not published to npm._
409
+
410
+ ### Changed
411
+ - Internal changes only (release workflow and dev-dependency updates).
412
+
413
+ ## [0.18.0] - 2026-02-19
414
+
415
+ _Tagged but not published to npm._
416
+
417
+ ### Added
418
+ - Fades on playbacks, sounds, synths and groups: `fadeTo(value, ms, type?)`, `fadeIn(ms, type?)`, `fadeOut(ms, type?)`, `stopWithFade(ms, type?)`, `cancelFade()`, and an `isFading` getter. `Playback.fadeIn()`/`fadeOut()` were last available in 0.8.7; these replace them. Each fade returns a promise that resolves when the fade finishes, and `type` is `"linear"` (the default) or `"exponential"`.
419
+ - `Sound.play(options?)` takes `PlayOptions`: `fadeIn`, `fadeOut` (applied when the last loop ends), `fadeType`, and `fadeInPerLoop` (fade in again on every loop).
420
+ - Playbacks emit `fadeStart` (`{ target, duration, type }`), `fadeEnd` and `fadeCancel` events.
421
+ - Event types from `events.ts` (such as `PlaybackEvents`, `SoundEvents` and `CacophonyEvents`) are exported from the package root.
422
+
423
+ ### Changed
424
+ - Setting `volume` directly, or calling `stop()`, cancels any fade in progress.
425
+
426
+ ## [0.17.1] - 2025-11-18
427
+
428
+ _First npm release after 0.17.0, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
429
+
430
+ ### Fixed
431
+ - Type-checking fix for the filter cloning added in 0.17.0.
432
+
433
+ ## [0.17.0] - 2025-11-18
434
+
435
+ _Tagged but not published to npm._
436
+
437
+ ### ⚠ Breaking
438
+ - Filters added to a `Sound` or `Synth` are cloned into each new playback instead of being shared. Changing the original `BiquadFilterNode` no longer affects playbacks that already exist. To change the sound of a playback that is already playing, adjust that playback's own filters.
439
+ - `addFilter()`/`removeFilter()` on a `Sound`, `Synth` or `Group` only affect future playbacks, not ones already playing.
440
+ - `addFilter()` throws if the same filter instance is added twice. `removeFilter()` throws if the filter was never added. Guard those calls if you relied on them being no-ops.
441
+
442
+ ## [0.16.1] - 2025-11-17
443
+
444
+ _First npm release after 0.16.0, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
445
+
446
+ ### Fixed
447
+ - `removeFilter()` removes the exact filter instance you pass in. Before, it compared property values and could remove the wrong filters or fail to remove one.
448
+ - The package `exports` map lists `types` first, so TypeScript resolves the bundled type definitions correctly under `moduleResolution: "bundler"`/`"node16"`.
449
+
450
+ ### Changed
451
+ - `emit()`, `emitAsync()` and `eventEmitter` on playbacks are now public.
452
+
453
+ ## [0.16.0] - 2025-10-09
454
+
455
+ _Tagged but not published to npm._
456
+
457
+ ### Added
458
+ - `Playback.outputNode`, `Playback.connect(destination)` and `Playback.disconnect(destination?)` let you route a playback's output into your own Web Audio graph.
459
+
460
+ ## [0.15.3] - 2025-10-08
461
+
462
+ ### Fixed
463
+ - `globalPlay`, `globalStop` and `globalPause` now fire from `Playback.play()`/`stop()`/`pause()`, so they also fire for sounds started through `Group.play()`, `Group.playOrdered()` and `Group.playRandom()`, not only through `Sound.play()`. `Synth` still emits them from `Synth.play()`/`stop()`/`pause()`.
464
+ - `Playback` emits its `pause` event. The event was declared in 0.15.0 but never fired.
465
+
466
+ ## [0.15.2] - 2025-10-03
467
+
468
+ ### Added
469
+ - Global playback events on `Cacophony`: `globalPlay`, `globalStop` and `globalPause`, each with a `{ source, timestamp }` payload where `source` is the `Sound` or `Synth`. In this release they fire only from `Sound.play()`/`stop()`/`pause()` and the matching `Synth` methods.
470
+ - `Cacophony.emit()` and `Cacophony.emitAsync()` are now public (previously `protected`).
471
+ - `Sound` and `Synth` constructors take an optional trailing `cacophony` argument, and `Sound.clone()`/`Synth.clone()` carry it over. The factory methods (`createSound`, `createStream`, `createOscillator`) pass it for you.
472
+
473
+ ## [0.15.1] - 2025-10-02
474
+
475
+ ### Changed
476
+ - Internal changes only (dev-dependency updates and a test fix).
477
+
478
+ ## [0.15.0] - 2025-08-14
479
+
480
+ ### Added
481
+ - Typed event system. `Cacophony`, `Sound`, `Synth` and every `Playback` expose `on(event, listener)` and `off(event, listener)`. (#23)
482
+ - `Cacophony`: `loadingStart`, `loadingProgress` (`{ url, loaded, total, progress }`, where `progress` is `-1` when the size is unknown), `loadingComplete`, `cacheHit` (`cacheType`: `memory` / `browser`), `cacheMiss` (`reason`: `not-found` / `expired`) and `cacheError`.
483
+ - `Sound`: `play` (receives the `Playback`), `stop`, `pause`, `volumeChange`, `rateChange` and `soundError`, which forwards playback errors.
484
+ - `Synth`: `play`, `stop`, `pause`, `frequencyChange`, `detuneChange` and `typeChange`.
485
+ - `Playback`: `play`, `stop` and `error`.
486
+ - `AbortSignal` support. `createSound(url, soundType, panType, signal)`, `createGroupFromUrls(urls, soundType, panType, signal)`, `createStream(url, signal)`, `loadWorklets(signal)` and `createWorkletNode(name, url, signal)` accept an optional signal that cancels the fetch, stream read or worklet load. (#22)
487
+ - Download progress reporting. `AudioCache` reads response bodies as a stream and reports progress to every caller waiting on the same URL.
488
+ - `AudioCache` honors `Cache-Control`. A cached entry within its `max-age` is served without a network request, and `no-cache`, `no-store` and `must-revalidate` force revalidation.
489
+ - `Sound.cleanup()` cleans up all of the sound's playbacks and removes its event listeners. `Playback.cleanup()` also disconnects the panner, gain node and filters and removes listeners.
490
+
491
+ ### Changed
492
+ - `createSound(url, SoundType.Streaming)` returns a streaming `Sound` right away instead of downloading and decoding the whole file through the cache.
493
+ - `createStream()` again starts the internal chunked stream reader in the background, as in 0.13.0–0.13.2. That reader plays decoded chunks straight to `context.destination`, alongside the returned `Sound`.
494
+ - `Playback.addFilter()` inserts the filter node you pass instead of cloning it. `Sound` therefore reuses its own filter nodes in every playback it creates. `Playback.removeFilter()` now uses the shared `FilterManager` matching logic and no longer disconnects the removed node. `addFilter()`/`removeFilter()` throw on a cleaned-up playback.
495
+ - `createWorkletNode()` rethrows the original error (such as an `AbortError`) instead of a generic "Could not load worklet" error. It loads modules with `credentials: "same-origin"`.
496
+ - The custom-cache interface's `getAudioBuffer(context, url, signal?, callbacks?)` has two new optional parameters, so existing custom caches keep working.
497
+
498
+ ### Fixed
499
+ - `AudioCache` recovers when metadata exists but the cached body is missing: it refetches from the network instead of failing.
500
+ - Streams that fail to fetch or read log the error instead of producing an unhandled rejection.
501
+
502
+ ## [0.14.2] - 2025-07-02
503
+
504
+ ### Changed
505
+ - `AudioCache` sends a conditional request (`If-None-Match` / `If-Modified-Since`) every time a URL is not in memory and its cached entry has an `ETag` or `Last-Modified`. The 24-hour TTL (`AudioCache.setCacheExpirationTime()`) applies only to entries with no validators. In 0.14.0, entries without validators were always refetched and entries with validators were only rechecked after the TTL.
506
+
507
+ ### Fixed
508
+ - If the server returns `304 Not Modified` but the cached body has disappeared, the cache fetches the file again without validators instead of failing.
509
+
510
+ ## [0.14.1] - 2025-05-13
511
+
512
+ ### Fixed
513
+ - `Playback.pause()` stops the underlying `AudioBufferSourceNode` instead of only disconnecting it.
514
+ - A replaced source's `onended` handler is detached, so a stale source finishing after a pause or seek no longer triggers the loop/stop logic.
515
+ - Looping buffer-backed playbacks restart through `play()` at the loop boundary, which avoids calling `start()` twice on the same source node.
516
+
517
+ ## [0.14.0] - 2025-02-11
518
+
519
+ ### ⚠ Breaking
520
+ - Build output renamed and reduced. The package now ships only `dist/index.mjs` (ESM) and `dist/index.cjs` (CJS), declared through an `exports` map. The old `dist/cacophony.es.js`, `dist/cacophony.cjs.js`, `dist/cacophony.umd.js` and `dist/cacophony.iife.js` are gone. Import from `"cacophony"` instead of a file path. UMD and IIFE `<script>` users need a bundler or an ESM `<script type="module">`.
521
+
522
+ ### Changed
523
+ - `AudioCache` revalidates cached entries with `ETag`/`Last-Modified` once the TTL has passed. Before, it reused them indefinitely. A `304` response refreshes the stored timestamp.
524
+ - Concurrent requests for the same URL share one in-flight load, whether it comes from the network or the browser cache.
525
+
526
+ ### Fixed
527
+ - If writing to the browser cache fails, `AudioCache` deletes the partial body and metadata so a later load does not read a half-written entry.
528
+
529
+ ## [0.13.5] - 2024-10-28
530
+
531
+ _First npm release after 0.13.4, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
532
+
533
+ ### Fixed
534
+ - Fixes a TypeScript type error in `Playback.addFilter()` introduced in 0.13.4.
535
+
536
+ ## [0.13.4] - 2024-10-28
537
+
538
+ _Tagged but not published to npm._
539
+
540
+ ### ⚠ Breaking
541
+ - `Group.playRandom()`, `Group.preplayRandom()`, `Group.playOrdered()` and `Group.preplayOrdered()` return `Playback | undefined` and return `undefined` for an empty group. Before, `preplayOrdered()`/`playOrdered()` threw and the random variants failed. Add a check for `undefined`.
542
+ - Setting `Playback.playbackRate` to `0` or a negative value throws `"Playback rate must be greater than 0"`.
543
+
544
+ ### Added
545
+ - `new Cacophony(context?, cache?)` accepts a custom cache object with `getAudioBuffer(context, url)` and `clearMemoryCache()`, for example to stub loading in tests.
546
+
547
+ ### Fixed
548
+ - `Playback.addFilter()` appends to the filter chain again instead of replacing the existing filters (regression in 0.13.1).
549
+ - `Sound.clone()` applies the cloned volume, playback rate, loop count and panning through the public setters, so the values reach the clone correctly. It sets `position`/`threeDOptions` only for `HRTF` sounds and `stereoPan` only for `stereo` sounds, and it honors a `position` override even when it is falsy.
550
+
551
+ ## [0.13.3] - 2024-10-14
552
+
553
+ ### Fixed
554
+ - Replaying or seeking an HTML/streaming playback after it stops reuses its media element instead of swapping in an empty buffer source.
555
+ - `Playback.duration` no longer throws for media-element sources without a decoded buffer. It returns the element's duration, or `NaN` when the duration is unknown.
556
+ - `createStream()` no longer starts a second background fetch that played audio straight to the destination, bypassing volume and panning.
557
+
558
+ ## [0.13.2] - 2024-10-14
559
+
560
+ _First npm release after 0.13.1, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
561
+
562
+ ### Fixed
563
+ - `loop(n)` sets `loopCount` to `n` again (0.13.1 stored `n - 1`, so a playback repeated one time fewer than requested). Calling `loop(n)` on a playback that is already playing also stores `n` now; before 0.13.1 it stored `n - 1`.
564
+
565
+ ## [0.13.1] - 2024-10-14
566
+
567
+ _Tagged but not published to npm._
568
+
569
+ ### Changed
570
+ - `Playback` timing rewrite. `currentTime` accounts for `playbackRate`, changing the rate mid-play keeps the position, and `pause()`/`play()` resumes from the paused position.
571
+ - `Playback.seek()` keeps the current play/pause state and throws `"Invalid time value for seek"` for negative or non-finite times.
572
+ - `Playback.play()` does nothing on a playback that is already playing.
573
+ - Reading `playbackRate` no longer throws after cleanup, and setting it after cleanup is a silent no-op.
574
+ - `Playback.clone()` copies position, state, current loop, playback rate and filters (as fresh filter nodes). If the original is playing, the clone starts playing too.
575
+ - `loop(n)` stores `n - 1` (reverted in 0.13.2).
576
+
577
+ ### Fixed
578
+ - Filter clones now copy `gain`, and removing a filter disconnects its node.
579
+
580
+ ## [0.13.0] - 2024-10-08
581
+
582
+ ### ⚠ Breaking
583
+ - `Sound.duration` returns `NaN` instead of `0` when the duration is unknown. When the sound has playbacks, it returns the first playback's duration, so HTML and streaming sounds report the media element's duration. Replace `duration === 0` checks with `Number.isNaN(duration)`.
584
+
585
+ ### Added
586
+ - `Playback.duration` returns the media element's duration for HTML/streaming playbacks.
587
+
588
+ ## [0.12.10] - 2024-09-23
589
+
590
+ ### Fixed
591
+ - HTML and streaming sounds can be played again. Since 0.12.0 `Playback.play()` tried to rebuild a buffer source for every source type and threw "Cannot recreate source of a sound that has been cleaned up" for non-buffer sounds; now it only rebuilds buffered sources.
592
+ - `setPanType()` does nothing when the pan type is unchanged and a panner already exists. When the type does change, it disconnects the old panner first, so panners no longer pile up in the graph.
593
+
594
+ ## [0.12.9] - 2024-08-22
595
+
596
+ ### Fixed
597
+ - `stop()` on an infinitely looping playback now stops it. The playback is marked stopped before the source node is stopped, and the loop-end handler ignores `ended` events from playbacks that are not playing, so stopping no longer restarts the loop.
598
+
599
+ ## [0.12.8] - 2024-08-22
600
+
601
+ ### Changed
602
+ - `Playback.stop()` no longer wraps the source stop in `try`/`finally`. If the underlying node throws, the error propagates and the playback's state is left unchanged.
603
+
604
+ ## [0.12.7] - 2024-08-21
605
+
606
+ ### ⚠ Breaking
607
+ - `Playback.loop(count)` now returns the stored loop count (a number or `'infinite'`) instead of `'infinite'`/`0`. It sets the native `loop` flag only for `'infinite'`, so a finite count is handled by replaying the source. Calling it on a playing playback treats the current pass as the first. Code that compared the return value against `'infinite'`/`0` should read `loopCount` instead.
608
+ - `Playback.handleLoop` is renamed to `loopEnded`. Update any code that called or reassigned it.
609
+
610
+ ### Fixed
611
+ - Looping works again. The loop handler still checked the `_playing` flag that 0.12.0 stopped updating, so loops did not repeat correctly in 0.12.0–0.12.6. A sound can now also switch from looping to non-looping (and back) while it plays.
612
+ - `Playback.stop()` no longer silently swallows errors thrown by the underlying source node.
613
+
614
+ ## [0.12.6] - 2024-08-21
615
+
616
+ ### Added
617
+ - `Group.randomSound()` returns a random `Sound` from the group without playing it. It throws on an empty group. `preplayRandom()` now uses it.
618
+
619
+ ## [0.12.5] - 2024-08-19
620
+
621
+ ### Changed
622
+ - Internal changes only (README updates).
623
+
624
+ ## [0.12.4] - 2024-08-19
625
+
626
+ ### Fixed
627
+ - Cache entries written by versions before 0.12.3 have no timestamp. These entries (when they also lack `ETag`/`Last-Modified`) are now treated as expired and refetched instead of being served indefinitely.
628
+
629
+ ## [0.12.3] - 2024-08-19
630
+
631
+ ### Changed
632
+ - The persistent audio cache now expires entries after 24 hours, but only entries that have no `ETag` or `Last-Modified` header. Entries that have one of those headers are served from the Cache API without a network request. The expiry is set through the internal `AudioCache.setCacheExpirationTime()`, which the package does not export.
633
+
634
+ ## [0.12.2] - 2024-08-15
635
+
636
+ ### Added
637
+ - `Group.preplayRandom()` and `Group.preplayOrdered(shouldLoop)` return a prepared, not-yet-started `Playback`. `playRandom()` and `playOrdered()` are now built on them.
638
+
639
+ ### Changed
640
+ - Empty-group errors from the random and ordered methods now say "Cannot prepare …" instead of "Cannot play …".
641
+
642
+ ## [0.12.1] - 2024-08-12
643
+
644
+ _First npm release after 0.12.0, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
645
+
646
+ ### Changed
647
+ - Internal changes only (tests).
648
+
649
+ ## [0.12.0] - 2024-08-12
650
+
651
+ _Tagged but not published to npm._
652
+
653
+ ### ⚠ Breaking
654
+ - `Playback.resume()` (added in 0.11.1) is removed again. Call `play()` on a paused playback to continue from where it paused.
655
+ - The public `Playback.pauseTime` and `Playback.startTime` fields are removed. Read the new `currentTime` getter instead.
656
+
657
+ ### Added
658
+ - `Playback.currentTime` getter returns the current position in seconds.
659
+
660
+ ### Changed
661
+ - `Playback` now tracks an explicit state (unplayed, playing, paused, stopped). `isPlaying` is true only in the playing state, and `pause()` does nothing unless the playback is playing. `seek(time)` stores the offset and restarts playback only if it was playing, so a later `play()` starts from the seeked position.
662
+
663
+ ## [0.11.1] - 2024-08-09
664
+
665
+ ### Added
666
+ - `Playback.resume()` continues a paused playback from its pause position.
667
+
668
+ ### Fixed
669
+ - Pausing and resuming buffered playbacks resumes at the right offset. `play()` and `seek()` now record the start time, which pause offsets are measured from. Pausing an HTML-backed playback also remembers its position.
670
+
671
+ ## [0.11.0] - 2024-08-05
672
+
673
+ ### ⚠ Breaking
674
+ - The `Playback` constructor is now `new Playback(origin: Sound, source, gainNode)`. Context, loop count and pan type come from the origin `Sound`. `SynthPlayback` likewise becomes `new SynthPlayback(origin: Synth, source, gainNode)`. Code that built playbacks directly must pass the owning `Sound`/`Synth` first.
675
+
676
+ ### Added
677
+ - Playbacks have an `origin` property that points back to the `Sound` or `Synth` that created them.
678
+
679
+ ### Fixed
680
+ - `Playback.clone()` copies the source playback's volume.
681
+
682
+ ## [0.10.4] - 2024-07-15
683
+
684
+ ### Added
685
+ - `createOscillator(options, panType)` accepts an optional `panType` (`'HRTF'` by default), so a synth can use stereo panning.
686
+
687
+ ## [0.10.3] - 2024-07-12
688
+
689
+ ### Changed
690
+ - `BasePlayback` moved to its own module and is no longer exported from the package entry point.
691
+ - Source formatting sweep across `playback.ts` and `container.ts` (no behavior change).
692
+
693
+ ### Fixed
694
+ - Seeking a stopped or paused playback now takes effect: the next `play()` starts from the seeked time instead of from 0.
695
+
696
+ ## [0.10.2] - 2024-07-02
697
+
698
+ _First npm release after 0.9.7 through 0.10.1, which were tagged but not published. Upgrading from an earlier npm version? Read those entries too._
699
+
700
+ ### Fixed
701
+ - `MicrophonePlayback` is exported from the package entry point correctly (0.10.0 imported it from a bare `'microphone'` path).
702
+
703
+ ## [0.10.1] - 2024-07-02
704
+
705
+ _Tagged but not published to npm._
706
+
707
+ ### Fixed
708
+ - The internal `MicrophoneStream` import in `cacophony.ts` now uses a relative path.
709
+
710
+ ## [0.10.0] - 2024-07-02
711
+
712
+ _Tagged but not published to npm._
713
+
714
+ ### ⚠ Breaking
715
+ - The microphone classes moved to `microphone.ts`, and only `MicrophonePlayback` is exported from the package. `MicrophoneStream` is no longer importable from `cacophony`; get it from `Cacophony.getMicrophoneStream()` instead of importing the class.
716
+
717
+ ## [0.9.7] - 2024-07-01
718
+
719
+ _Tagged but not published to npm._
720
+
721
+ ### ⚠ Breaking
722
+ - `Sound.type` and `Synth.type` (the `SoundType`) are renamed to `soundType`. On `Synth`, `type` now means the oscillator waveform. Rename `.type` to `.soundType` wherever you read the source kind.
723
+
724
+ ### Added
725
+ - `SynthGroup` (exported) for controlling several synths together: `addSynth()`, `removeSynth()`, `play()`, `stop()`, `setVolume()`, and `stereoPan`/`position` setters.
726
+ - `Synth` and `SynthPlayback` get `frequency`, `detune` and `type` accessors. On a `Synth`, setting one updates all of its active playbacks.
727
+
728
+ ### Changed
729
+ - `play()`/`preplay()` on a container are typed to return that container's own playback type (e.g. `SynthPlayback[]` for a `Synth`).
730
+
731
+ ## [0.9.6] - 2024-06-25
732
+
733
+ ### Changed
734
+ - Internal changes only (the build now runs `tsc --noEmit` first).
735
+
736
+ ## [0.9.5] - 2024-06-24
737
+
738
+ ### Changed
739
+ - Members of the mixin-based classes (`playbacks`, `_volume`, `_position`, `panner`, `gainNode`, `_filters`, and others) are now public. TypeScript mixins cannot have private or protected members, and this let the type declarations be emitted. `Sound.playbacks` is accessible again.
740
+ - `index.ts` exports `Sound` and `Synth` by name instead of re-exporting their whole modules.
741
+
742
+ ## [0.9.4] - 2024-06-24
743
+
744
+ ### Changed
745
+ - Internal typing fixes (`Playback.source` is declared public on the base class).
746
+
747
+ ## [0.9.3] - 2024-06-24
748
+
749
+ ### Changed
750
+ - The npm package no longer ships the `src` directory, only `dist`, `docs` and `README.md`.
751
+
752
+ ## [0.9.2] - 2024-06-24
753
+
754
+ ### Fixed
755
+ - Several modules imported siblings through bare specifiers (`"pannerMixin"`, `"volumeMixin"`, `"oscillatorMixin"`). These are now relative imports, so builds and type resolution work in consuming projects.
756
+
757
+ ## [0.9.1] - 2024-06-24
758
+
759
+ ### Fixed
760
+ - Synth oscillator options (`frequency`, `detune`, `type`) are applied to the oscillator's `AudioParam` values. Before, they were `Object.assign`ed onto the node, which did not set them. (#7)
761
+ - `Synth.clone()` defaults `stereoPan` to `0` when the source has none.
762
+
763
+ ## [0.9.0] - 2024-06-21
764
+
765
+ ### ⚠ Breaking
766
+ - `Cacophony.createOscillator(options)` now returns a `Synth` (with `play()`, `stop()`, `volume`, `position`, filters, `clone()`, …) instead of a raw `OscillatorNode` connected to the output. Call `synth.play()` instead of `oscillator.start()`. (#6)
767
+ - `Playback.fadeIn()` and `Playback.fadeOut()` are removed, with no replacement in this release.
768
+ - `Sound.playbacks` became `protected` (it is public again in 0.9.5).
769
+ - The `BaseSound` interface no longer requires `playbackRate`, `loop` or `duration`, and gains optional `position` and `threeDOptions`.
770
+
771
+ ### Added
772
+ - `Synth` class (exported) for oscillator-based sound with the same volume, panning, 3D-position and filter controls as `Sound`, plus `oscillatorOptions`. `SoundType.Oscillator` identifies it. (#6)
773
+
774
+ ### Changed
775
+ - `Sound`, `Playback` and `Synth` are now composed from shared mixins (`PlaybackContainer`, `VolumeMixin`, `PannerMixin`). The existing volume, stereo pan, position and `threeDOptions` accessors behave the same. (#5)
776
+
777
+ ## [0.8.7] - 2024-06-11
778
+
779
+ ### Changed
780
+ - The in-memory decoded-buffer cache is capped at 100 entries (down from 500).
781
+
782
+ ## [0.8.6] - 2024-06-11
783
+
784
+ ### Added
785
+ - `Cacophony.clearMemoryCache()` empties the in-memory decoded-buffer cache and clears pending requests.
786
+
787
+ ## [0.8.5] - 2024-06-11
788
+
789
+ ### Changed
790
+ - The in-memory decoded-buffer cache is now an LRU with a size limit (500 entries) instead of growing without bound.
791
+
792
+ ### Fixed
793
+ - A failed load now clears its pending request, so the next request for the same URL retries instead of getting the failed promise.
794
+
795
+ ## [0.8.4] - 2024-05-23
796
+
797
+ ### Changed
798
+ - Decoded `AudioBuffer`s are kept in memory by URL, so creating several sounds from the same URL decodes the audio only once. The data: URL path is cached the same way.
799
+
800
+ ## [0.8.3] - 2024-04-01
801
+
802
+ ### Added
803
+ - Loading audio from the network stores the `ETag`/`Last-Modified` headers and sends `If-None-Match`/`If-Modified-Since` on later fetches. A `304` reuses the cached body.
804
+
805
+ ### Fixed
806
+ - Non-OK responses stored in the browser cache are no longer used as audio data.
807
+
808
+ ## [0.8.2] - 2024-03-04
809
+
810
+ ### Changed
811
+ - `Playback.addFilter()` adds a copy of the filter (type, frequency, Q), so the same filter node is never wired into more than one playback. `removeFilter()` matches filters by their parameter values instead of node identity, so the filter you passed in can still be removed.
812
+
813
+ ## [0.8.1] - 2024-02-23
814
+
815
+ ### Fixed
816
+ - Loop counting and `isPlaying` are correct when a sound finishes naturally: `isPlaying` becomes `false` at the end of the last loop.
817
+ - `stop()` ignores errors thrown by the underlying node (e.g. stopping a source that already ended).
818
+ - Seeking a buffered playback keeps its end-of-playback handler (so looping continues after a seek), and the source is no longer connected to the gain node twice.
819
+
820
+ ## [0.8.0] - 2024-02-23
821
+
822
+ ### Fixed
823
+ - Looping sounds can be stopped. Before this, the end-of-loop handler restarted the source after `stop()`. Stopping a sound twice and then playing it again also works.
824
+
825
+ ## [0.7.0] - 2024-02-23
826
+
827
+ ### ⚠ Breaking
828
+ - `resume()` is removed from `Sound`, `Playback`, `Group` and the `BaseSound` interface. Call `play()` on a paused playback to continue.
829
+ - `pause()` no longer suspends the whole `AudioContext` (which paused every sound). It now stops only that playback and records the position; `play()` restarts from there.
830
+
831
+ ### Fixed
832
+ - Setting `threeDOptions` accepts `0` for numeric options (cone angles and gain, distances, rolloff, position and orientation). Before, `0` was ignored and the previous value kept.
833
+
834
+ ## [0.6.2] - 2024-02-23
835
+
836
+ ### Changed
837
+ - `isPlaying` is tracked by the playback itself instead of inferred from the node. It returns `false` rather than throwing after cleanup.
838
+
839
+ ### Fixed
840
+ - `Sound.clone()` honors falsy overrides (`volume: 0`, `loopCount: 0`, `stereoPan: 0`) and copies `stereoPan` to the clone.
841
+ - `createSound()` accepts any `AudioBuffer`-like object, not only instances of the global `AudioBuffer` class (e.g. buffers from `standardized-audio-context`).
842
+ - `stop()` stops buffer-backed playbacks again. From 0.5.22 through 0.6.1, `isPlaying` was always `false` for them, so `Playback.stop()` and `Sound.stop()` returned without stopping anything.
843
+ - A finite loop count no longer plays one extra repetition.
844
+
845
+ ## [0.6.1] - 2024-02-22
846
+
847
+ ### Fixed
848
+ - `Playback.clone()` uses the newly created source node instead of reusing the original one.
849
+
850
+ ## [0.6.0] - 2024-02-21
851
+
852
+ ### ⚠ Breaking
853
+ - `isPlaying` is now a getter instead of a method on `Sound`, `Playback`, `Group`, `MicrophonePlayback`, `MicrophoneStream` and the `BaseSound` interface. Replace `x.isPlaying()` with `x.isPlaying`.
854
+
855
+ ### Added
856
+ - `new Group(sounds)` accepts an initial array of sounds.
857
+
858
+ ## [0.5.23] - 2024-02-21
859
+
860
+ ### Changed
861
+ - `Playback.clone()` now builds a fresh source node (a new `AudioBufferSourceNode` sharing the same `AudioBuffer`, or a new media-element source). However, the clone is still constructed with the original's source node, so a cloned playback continues to share the source of the playback it was cloned from. Fixed in 0.6.1.
862
+
863
+ ## [0.5.22] - 2024-02-21
864
+
865
+ ### Changed
866
+ - `Playback.isPlaying()` inspects the underlying node instead of an internal flag: HTML-audio playbacks report `!mediaElement.paused`. Buffer playbacks check a `playbackState` property that Web Audio source nodes don't have, so they always report `false`. Because `Playback.stop()` returns early when `isPlaying()` is false, `stop()` (and `Sound.stop()`) no longer stops buffer-backed playbacks. This regression is fixed in 0.6.2.
867
+ - When a finite loop runs out, the playback calls `stop()` instead of just clearing its playing flag.
868
+
869
+ ## [0.5.21] - 2024-02-21
870
+
871
+ ### Changed
872
+ - `Sound.stop()` empties `sound.playbacks` after stopping them, so stopped playbacks are no longer tracked by (or updated through) the sound.
873
+
874
+ ## [0.5.20] - 2024-02-21
875
+
876
+ ### Fixed
877
+ - `Sound.clone()` and `Playback.clone()` can be called with no arguments again; `overrides` is optional and every field in it is optional.
878
+
879
+ ## [0.5.19] - 2024-02-20
880
+
881
+ _First npm release after 0.5.18, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
882
+
883
+ ### Changed
884
+ - Internal change only: reordered the loop-count condition in the loop-restart handler. No change in behavior.
885
+
886
+ ## [0.5.18] - 2024-02-20
887
+
888
+ _Tagged but not published to npm._
889
+
890
+ ### Changed
891
+ - Reworked the end-of-loop check in `Playback` so it compares `currentLoop > loopCount` and only restarts the source while repeats remain.
892
+
893
+ ## [0.5.17] - 2024-02-20
894
+
895
+ ### Added
896
+ - `Playback.clone({ loopCount?, panType? })` creates a new `Playback` with its own gain node.
897
+
898
+ ### Changed
899
+ - `Sound.clone()` overrides are typed as a dedicated options shape (`panType`, `stereoPan`, `threeDOptions`, `loopCount`, `playbackRate`, `volume`, `position`, `filters`) instead of `Partial<Sound>`.
900
+
901
+ ## [0.5.16] - 2024-02-20
902
+
903
+ _First npm release after 0.5.15, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
904
+
905
+ ### ⚠ Breaking
906
+ - `Sound.clone(overrides)` takes a required `overrides` object (`panType`, `threeDOptions`, `loopCount`, `playbackRate`, `volume`, `position`, `filters`, ...). Calling `clone()` with no argument throws at runtime. Pass `{}` for a plain copy until 0.5.20 makes the argument optional.
907
+
908
+ ### Fixed
909
+ - Cloned sounds get their own copy of the filter list instead of sharing the original's filter array.
910
+
911
+ ## [0.5.15] - 2024-02-19
912
+
913
+ _Tagged but not published to npm._
914
+
915
+ ### Changed
916
+ - First attempt at `Sound.clone(overrides)`. The constructor call passed arguments in the wrong positions, so this version's clone was broken; it was replaced in 0.5.16.
917
+
918
+ ## [0.5.14] - 2024-02-16
919
+
920
+ ### Added
921
+ - `filters` read-only getter on `Sound` and `Playback` exposes the current filter chain.
922
+ - `addFilters(filters)` and `removeFilters(filters)` add or remove several filters in one call.
923
+
924
+ ## [0.5.13] - 2024-02-16
925
+
926
+ ### Fixed
927
+ - Removed debug `console.log` output from the loop callback (added in 0.5.11).
928
+
929
+ ## [0.5.12] - 2024-02-15
930
+
931
+ _First npm release after 0.5.11, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
932
+
933
+ ### Fixed
934
+ - Looping buffer playbacks: when a finite loop restarts, the new source goes through the panner and filter chain only. It is no longer also wired straight to the gain node, which bypassed panning and filters.
935
+ - The loop callback does nothing after the playback has been cleaned up.
936
+
937
+ ## [0.5.11] - 2024-02-15
938
+
939
+ _Tagged but not published to npm._
940
+
941
+ ### Fixed
942
+ - Finite loop counts replay the sound: the loop callback connects the re-created buffer source, re-arms its `onended` handler, and starts it. Earlier versions re-created the source without connecting it. This version logs every loop iteration to the console.
943
+
944
+ ## [0.5.10] - 2024-02-14
945
+
946
+ ### Fixed
947
+ - `loop()` on a buffer-backed `Playback` works again. It no longer relies on an `instanceof AudioBufferSourceNode` check, which failed for standardized-audio-context nodes and threw `Unsupported source type`.
948
+
949
+ ## [0.5.9] - 2024-02-14
950
+
951
+ ### Fixed
952
+ - Attempted fix for looping broken by a type-only import in `Playback`. It did not work; see 0.5.10.
953
+
954
+ ## [0.5.8] - 2024-02-13
955
+
956
+ ### Added
957
+ - `seek()` works on HTML-audio (`SoundType.HTML`) playbacks by setting the media element's `currentTime`. Before, it only worked for buffer sounds.
958
+
959
+ ### Changed
960
+ - Listener orientation and playback position set the `AudioParam.value` directly instead of using `setValueAtTime`, to avoid ordering races in calling code.
961
+ - `Position` is a labeled tuple (`[x, y, z]`). Added JSDoc for `Cacophony` and `Playback`.
962
+
963
+ ## [0.5.7] - 2024-02-12
964
+
965
+ _First npm release after 0.5.6, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
966
+
967
+ ### Changed
968
+ - Internal type-alias fixes for standardized-audio-context types.
969
+
970
+ ## [0.5.6] - 2024-02-12
971
+
972
+ _Tagged but not published to npm._
973
+
974
+ ### Fixed
975
+ - `Sound.loop(count)` applies the loop setting to active playbacks through `Playback.loop()` instead of only setting the native `loop` flag.
976
+ - The loop-restart handler is bound to its `Playback`.
977
+
978
+ ## [0.5.5] - 2024-02-07
979
+
980
+ ### Fixed
981
+ - Published type declarations include the shared context types. In 0.5.0–0.5.4 the `.d.ts` files imported a `./context` module that wasn't shipped, which broke type checking for TypeScript consumers.
982
+
983
+ ## [0.5.4] - 2024-02-05
984
+
985
+ ### Added
986
+ - `createGroupFromUrls(urls, soundType?, panType?)` passes the sound type and pan type through to each created sound.
987
+
988
+ ## [0.5.3] - 2024-02-05
989
+
990
+ ### Fixed
991
+ - `createBiquadFilter({ frequency: 0 })` and `createOscillator({ frequency: 0 })` honor a frequency of `0` instead of replacing it with the default (350 Hz and 440 Hz).
992
+
993
+ ## [0.5.2] - 2024-02-05
994
+
995
+ ### Changed
996
+ - `stereoPan` values outside −1..1 are clamped instead of throwing.
997
+
998
+ ### Fixed
999
+ - `createOscillator()` without a `frequency` defaults to 440 Hz instead of setting an undefined frequency.
1000
+
1001
+ ## [0.5.1] - 2024-02-05
1002
+
1003
+ ### Fixed
1004
+ - Stereo panning (`panType: 'stereo'`) works: `stereoPan` no longer relies on an `instanceof StereoPannerNode` check, which always failed, so the getter returned `null` and the setter threw.
1005
+ - `createSound(url, SoundType.HTML, panType)` honors `panType` for HTML-audio sounds.
1006
+
1007
+ ## [0.5.0] - 2024-02-05
1008
+
1009
+ ### Changed
1010
+ - The library source is split into modules (`sound.ts`, `playback.ts`, `group.ts`, `filters.ts`). `index` now re-exports `Sound`, `Playback` and `Group` from their own modules. Public signatures are unchanged.
1011
+ - `BaseSound` is exported again (it was unexported in 0.4.2).
1012
+
1013
+ ## [0.4.6] - 2024-02-05
1014
+
1015
+ ### Changed
1016
+ - The `Playback` constructor throws `Invalid pan type` for a `panType` other than `'HRTF'` or `'stereo'`.
1017
+
1018
+ ## [0.4.5] - 2024-02-05
1019
+
1020
+ ### Changed
1021
+ - Internal changes only (redundant `panType` assignment and doc comments).
1022
+
1023
+ ## [0.4.4] - 2024-02-05
1024
+
1025
+ ### Added
1026
+ - `createSound()` accepts base64 `data:` URLs, which are decoded directly instead of fetched or cached.
1027
+
1028
+ ### Changed
1029
+ - The second parameter of `createSound()` is renamed from `type` to `soundType`. This only affects the parameter name.
1030
+
1031
+ ### Fixed
1032
+ - `createOscillator()` no longer throws when no `periodicWave` is given.
1033
+
1034
+ ## [0.4.3] - 2024-02-05
1035
+
1036
+ ### Added
1037
+ - `createSound(urlOrBuffer, soundType?, panType?)` takes a `panType` (`'HRTF'` or `'stereo'`), so buffer sounds can use stereo panning.
1038
+
1039
+ ## [0.4.2] - 2024-02-05
1040
+
1041
+ ### ⚠ Breaking
1042
+ - `BaseSound` is no longer exported, and it no longer requires `position` or `threeDOptions`. Code that imported it must use `Sound`, `Playback` or `Group` instead until 0.5.0 exports it again.
1043
+ - `Group.sounds` and `Group.addSound()` accept only `Sound` instances instead of any `BaseSound`.
1044
+ - The `Playback` constructor's fifth parameter changed from `hrtf: boolean` to `panType: PanType`. Pass `'HRTF'` or `'stereo'` instead of `true` or `false`.
1045
+
1046
+ ### Added
1047
+ - `PanType` type (`'HRTF' | 'stereo'`) and `Sound.panType`. New playbacks get either a 3D panner or a stereo panner based on it.
1048
+ - `Sound.stereoPan` getter and setter, applied to the sound's current and future playbacks.
1049
+
1050
+ ## [0.4.1] - 2024-02-01
1051
+
1052
+ ### Added
1053
+ - Stereo panning: `Playback` can use a `StereoPannerNode` instead of an HRTF `PannerNode`, controlled with the `stereoPan` getter and setter (−1..1). On stereo playbacks, `position` and `threeDOptions` throw.
1054
+
1055
+ ## [0.4.0] - 2024-01-22
1056
+
1057
+ _First npm release after 0.3.13, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
1058
+
1059
+ ### Added
1060
+ - `Group.playRandom()` plays one random sound from the group and returns its `Playback`.
1061
+ - `Group.playOrdered(shouldLoop = true)` plays the group's sounds one at a time in order, one per call, and wraps around when `shouldLoop` is set.
1062
+ - JSDoc for most `Sound` and `Playback` methods.
1063
+
1064
+ ### Changed
1065
+ - `Sound.pause()` and `Sound.resume()` delegate to each of the sound's playbacks instead of suspending or resuming the `AudioContext` directly. With no playbacks they now do nothing.
1066
+
1067
+ ## [0.3.13] - 2024-01-22
1068
+
1069
+ _Tagged but not published to npm._
1070
+
1071
+ ### Added
1072
+ - `Group.playRandom()` and `Group.playOrdered()` (first published in 0.4.0).
1073
+
1074
+ ### Changed
1075
+ - This version imports an event-emitter module that isn't in the repository, so it doesn't build. 0.4.0 removed that import.
1076
+
1077
+ ## [0.3.12] - 2024-01-02
1078
+
1079
+ ### Added
1080
+ - `Sound.clone()` returns a new `Sound` with the same buffer, loop count, playback rate, volume, position, 3D options and filters.
1081
+
1082
+ ## [0.3.11] - 2024-01-01
1083
+
1084
+ ### Changed
1085
+ - The prebuilt AudioWorklet bundles (phase vocoder and overlap-add processors) are committed to the repository, and the phase-vocoder processing was restored to its original algorithm.
1086
+
1087
+ ## [0.3.10] - 2024-01-01
1088
+
1089
+ ### Added
1090
+ - `Cacophony.createWorkletNode(name, url)` creates an `AudioWorkletNode` and loads the module from `url` if the processor isn't registered yet.
1091
+
1092
+ ### Changed
1093
+ - Audio worklets are built in a separate Rollup step and embedded in the package, so `loadWorklets()` can load the phase-vocoder worklet from the published build.
1094
+
1095
+ ## [0.3.9] - 2023-12-28
1096
+
1097
+ ### ⚠ Breaking
1098
+ - `createBiquadFilter()` takes an options object `{ type, frequency, gain, Q }` instead of a filter type string. Replace `createBiquadFilter('lowpass')` with `createBiquadFilter({ type: 'lowpass' })`. The defaults are `lowpass`, 350 Hz, gain 0 and Q 1.
1099
+
1100
+ ### Added
1101
+ - `createPanner(options)` creates a `PannerNode` from a partial `IPannerOptions` object.
1102
+
1103
+ ## [0.3.8] - 2023-12-28
1104
+
1105
+ ### Added
1106
+ - `createOscillator({ frequency, type, periodicWave })` creates an `OscillatorNode` connected to the global output. It throws when `periodicWave` is omitted until 0.4.4.
1107
+
1108
+ ## [0.3.7] - 2023-12-26
1109
+
1110
+ _First npm release after 0.3.6, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
1111
+
1112
+ ### Added
1113
+ - `duration` on `Sound`, `Playback`, `Group` (the longest sound in the group) and the microphone classes (always `0`). `duration` is now a required member of `BaseSound`.
1114
+
1115
+ ## [0.3.6] - 2023-12-26
1116
+
1117
+ _Tagged but not published to npm._
1118
+
1119
+ ### Added
1120
+ - `duration` on `Sound`, `Playback`, `Group` and `MicrophoneStream` (first published in 0.3.7). HTML and streaming sounds report `0`.
1121
+
1122
+ ## [0.3.5] - 2023-12-24
1123
+
1124
+ ### Changed
1125
+ - Build changes to bundle the phase-vocoder AudioWorklet; no public API changes.
1126
+
1127
+ ## [0.3.4] - 2023-12-24
1128
+
1129
+ ### Changed
1130
+ - Worklet bundling change (the phase-vocoder URL is imported as a worker URL); no public API changes.
1131
+
1132
+ ## [0.3.3] - 2023-12-23
1133
+
1134
+ _First npm release after 0.3.2, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
1135
+
1136
+ ### Added
1137
+ - `Cacophony.loadWorklets()` registers the phase-vocoder AudioWorklet when the context supports AudioWorklet (first published here; added in 0.3.2). Nothing in the public API uses the worklet yet.
1138
+ - New runtime dependency: `fft.js`.
1139
+
1140
+ ## [0.3.2] - 2023-12-23
1141
+
1142
+ _Tagged but not published to npm._
1143
+
1144
+ ### Added
1145
+ - Initial AudioWorklet support: phase-vocoder and overlap-add processors plus `Cacophony.loadWorklets()`.
1146
+
1147
+ ### Changed
1148
+ - The chunked WAV streaming helper moved to its own module.
1149
+
1150
+ ## [0.3.1] - 2023-12-17
1151
+
1152
+ ### Changed
1153
+ - Version bump only; no code changes.
1154
+
1155
+ ## [0.3.0] - 2023-12-17
1156
+
1157
+ ### Changed
1158
+ - Version bump only; no code changes since 0.2.1. The minor bump marks the switch to the Vite build and module format shipped in 0.2.1.
1159
+
1160
+ ## [0.2.1] - 2023-12-17
1161
+
1162
+ ### ⚠ Breaking
1163
+ - The package is built with Vite and declares `"type": "module"`. `main` changed from `dist/index.js` to `dist/cacophony.cjs.js`, a new `module` entry points to `dist/cacophony.es.js`, and UMD and IIFE bundles are also shipped. Deep imports of the old `dist/*.js` files no longer work. The CommonJS file uses a `.js` extension inside a `"type": "module"` package, so Node's `require()` treats it as ESM and fails; use `import` or a bundler. (#1)
1164
+
1165
+ ### Changed
1166
+ - The TypeScript sources (`src/`) are included in the published package.
1167
+
1168
+ ## [0.2.0] - 2023-12-16
1169
+
1170
+ ### ⚠ Breaking
1171
+ - `playbackRate` is a required member of `BaseSound`. Custom `BaseSound` implementations must add it. `MicrophonePlayback` no longer declares that it implements `BaseSound`.
1172
+
1173
+ ### Added
1174
+ - `playbackRate` getter and setter on `Sound`, `Playback` and `Group`. On a `Sound` it applies to current and future playbacks. It works for buffer and HTML-audio sources.
1175
+ - TypeDoc API docs are generated on build and shipped in the package (`docs/`), and the README links to https://cacophony.js.org.
1176
+
1177
+ ## [0.1.39] - 2023-12-13
1178
+
1179
+ ### ⚠ Breaking
1180
+ - Removed `Cacophony.stopAll()`, which closed the `AudioContext` permanently. Stop sounds individually, or call `pause()` to suspend the context.
1181
+
1182
+ ## [0.1.38] - 2023-12-06
1183
+
1184
+ ### ⚠ Breaking
1185
+ - `Cacophony.listenerOrientation` is an `Orientation` object `{ forward: Position, up: Position }` instead of a forward vector. Use the new `listenerForwardOrientation` for the old behavior.
1186
+
1187
+ ### Added
1188
+ - `Orientation` type, plus `listenerForwardOrientation` and `listenerUpOrientation` getters and setters on `Cacophony`.
1189
+
1190
+ ## [0.1.37] - 2023-12-06
1191
+
1192
+ ### Added
1193
+ - `Cacophony.listenerPosition` and `Cacophony.listenerOrientation` getters and setters control the 3D audio listener.
1194
+ - Generated TypeDoc API documentation site; the README was rewritten to match the current API.
1195
+
1196
+ ## [0.1.36] - 2023-12-06
1197
+
1198
+ ### Fixed
1199
+ - 3D positioning works when filters are attached. Filters sit between the panner and the gain node; before, they replaced the panner in the chain.
1200
+
1201
+ ## [0.1.35] - 2023-12-06
1202
+
1203
+ ### Changed
1204
+ - The default `coneOuterAngle` for sounds is 360 instead of 0, so sounds are audible in all directions by default.
1205
+ - Updated `standardized-audio-context` to ^25.3.60.
1206
+
1207
+ ## [0.1.34] - 2023-12-05
1208
+
1209
+ ### Changed
1210
+ - `Playback.position` schedules the change with `setValueAtTime` instead of assigning `value` directly.
1211
+
1212
+ ## [0.1.33] - 2023-12-05
1213
+
1214
+ ### Fixed
1215
+ - New playbacks inherit the sound's `threeDOptions`.
1216
+ - The source is no longer connected straight to the gain node in addition to the panner, which had let the unpanned signal through.
1217
+
1218
+ ## [0.1.32] - 2023-12-04
1219
+
1220
+ ### Changed
1221
+ - `Playback.threeDOptions` assigns each panner property explicitly instead of using `Object.assign`. Falsy values such as `0` are ignored and keep the current setting.
1222
+
1223
+ ## [0.1.31] - 2023-11-29
1224
+
1225
+ ### Added
1226
+ - `Sound.threeDOptions` getter and setter (panner cone, distance model, rolloff and so on), merged into the sound's current playbacks. `Sound.position` is stored in these options.
1227
+
1228
+ ## [0.1.30] - 2023-11-26
1229
+
1230
+ ### Fixed
1231
+ - HTML-audio sounds set `crossOrigin = 'anonymous'` on the audio element that is actually played, so cross-origin media can be routed through Web Audio when the server allows CORS.
1232
+
1233
+ ## [0.1.29] - 2023-11-26
1234
+
1235
+ ### Changed
1236
+ - CORS adjustment to the HTML-audio path in `createSound()`. The element it touches isn't the one used for playback, so behavior is unchanged; see 0.1.30.
1237
+
1238
+ ## [0.1.28] - 2023-11-25
1239
+
1240
+ ### Changed
1241
+ - Sets `crossOrigin = 'anonymous'` on an audio element created in `createSound(url, SoundType.HTML)`. That element isn't the one used for playback, so this had no effect; see 0.1.30.
1242
+
1243
+ ## [0.1.27] - 2023-11-25
1244
+
1245
+ ### Added
1246
+ - `createSound(url, SoundType.HTML)` creates a sound backed by an HTML `<audio>` element instead of downloading and decoding the whole file.
1247
+
1248
+ ### Fixed
1249
+ - `createSound(buffer, type)` always creates a buffer sound, whatever `type` is passed.
1250
+
1251
+ ## [0.1.26] - 2023-11-25
1252
+
1253
+ ### ⚠ Breaking
1254
+ - The `Sound` constructor's fifth parameter changed from `html: boolean` to `type: SoundType`, and the `Sound.html` property was replaced by `Sound.type`.
1255
+
1256
+ ### Added
1257
+ - `SoundType` enum (`HTML`, `Streaming`, `Buffer`) and an optional `type` parameter on `createSound()`.
1258
+ - Experimental chunked streaming in `createStream()`: the URL is fetched and decoded chunk by chunk, assuming a 44-byte WAV header. This audio goes straight to the context destination and bypasses the global volume.
1259
+
1260
+ ## [0.1.25] - 2023-11-22
1261
+
1262
+ ### Fixed
1263
+ - Setting `Sound.volume` changes only that sound's playbacks. Before, it also set the global gain, so one sound's volume affected everything. New playbacks start at the sound's volume.
1264
+ - `Playback.cleanup()` is safe to call more than once.
1265
+
1266
+ ## [0.1.24] - 2023-11-20
1267
+
1268
+ ### Fixed
1269
+ - Setting `Cacophony.volume` while muted only stores the value to restore on unmute instead of unmuting.
1270
+
1271
+ ## [0.1.23] - 2023-11-20
1272
+
1273
+ ### Fixed
1274
+ - Setting `Cacophony.volume` while muted updates the volume that `unmute()` restores.
1275
+
1276
+ ## [0.1.22] - 2023-11-20
1277
+
1278
+ ### Fixed
1279
+ - Calling `mute()` twice no longer loses the volume to restore, and `unmute()` does nothing when not muted.
1280
+
1281
+ ## [0.1.21] - 2023-11-20
1282
+
1283
+ ### Added
1284
+ - `Cacophony.muted` getter and setter.
1285
+
1286
+ ## [0.1.20] - 2023-11-19
1287
+
1288
+ ### ⚠ Breaking
1289
+ - `createStream(url)` returns a `Sound` (backed by an HTML `<audio>` element) instead of a `Playback`. Call `sound.play()` to start it.
1290
+ - The `Sound` constructor takes an optional fifth `html` flag, and `buffer` may be `undefined`.
1291
+
1292
+ ### Changed
1293
+ - `createSound()` is typed as returning `Promise<Sound>` again (it was `Promise<BaseSound>` since 0.1.13).
1294
+
1295
+ ## [0.1.19] - 2023-11-19
1296
+
1297
+ ### ⚠ Breaking
1298
+ - The `Sound` constructor takes the source `url` as its first parameter: `new Sound(url, buffer, context, globalGainNode)`.
1299
+
1300
+ ### Changed
1301
+ - `Playback` tracks its own playing state. `isPlaying()` reflects `play()` and `stop()` instead of the context state, and it throws after `cleanup()`.
1302
+ - `stop()` on a playback that isn't playing does nothing, and `seek()` on a stopped playback moves the position without starting playback.
1303
+ - Loops restart only while the playback is still playing.
1304
+
1305
+ ## [0.1.18] - 2023-11-19
1306
+
1307
+ _First npm release after 0.1.16 and 0.1.17, which were tagged but not published. Upgrading from an earlier npm version? Read those entries too._
1308
+
1309
+ ### Changed
1310
+ - Internal changes only (release workflow permissions).
1311
+
1312
+ ## [0.1.17] - 2023-11-19
1313
+
1314
+ _Tagged but not published to npm._
1315
+
1316
+ ### Changed
1317
+ - Internal changes only (release workflow).
1318
+
1319
+ ## [0.1.16] - 2023-11-19
1320
+
1321
+ _Tagged but not published to npm._
1322
+
1323
+ ### Changed
1324
+ - Looping HTML-audio playbacks restart by seeking to 0; buffer playbacks re-create their source on each loop.
1325
+ - Added a GitHub Actions workflow that builds and publishes to npm on tag, and updated the README (microphone input, fades, `cleanup()`).
1326
+
1327
+ ## [0.1.15] - 2023-11-19
1328
+
1329
+ ### ⚠ Breaking
1330
+ - `isPlaying()` is a required member of `BaseSound`.
1331
+
1332
+ ### Added
1333
+ - `Group.isPlaying()`, `MicrophoneStream.isPlaying()` and `MicrophonePlayback.isPlaying()`.
1334
+
1335
+ ## [0.1.14] - 2023-11-19
1336
+
1337
+ _First npm release after 0.1.13, which was tagged but not published. Upgrading from an earlier npm version? Read that entry too._
1338
+
1339
+ ### Changed
1340
+ - Internal changes only (tests).
1341
+
1342
+ ## [0.1.13] - 2023-11-19
1343
+
1344
+ _Tagged but not published to npm._
1345
+
1346
+ ### Changed
1347
+ - `createSound()` is typed as returning `Promise<BaseSound>` (reverted in 0.1.20).
1348
+ - `Group.addSound()` accepts any `BaseSound`, and `Group.resume()` calls each member's `resume()`.
1349
+
1350
+ ## [0.1.12] - 2023-11-19
1351
+
1352
+ ### Added
1353
+ - `Playback` is exported.
1354
+
1355
+ ## [0.1.11] - 2023-11-19
1356
+
1357
+ ### Changed
1358
+ - `createStream(url)` returns a single `Playback` again (reverting 0.1.10).
1359
+
1360
+ ## [0.1.10] - 2023-11-19
1361
+
1362
+ ### Changed
1363
+ - `createStream(url)` returns `Promise<Playback[]>`.
1364
+
1365
+ ## [0.1.9] - 2023-11-19
1366
+
1367
+ ### ⚠ Breaking
1368
+ - `StreamPlayback` is renamed to `MicrophonePlayback`.
1369
+
1370
+ ### Added
1371
+ - `Cacophony.createStream(url)` plays audio through an HTML `<audio>` element routed into Web Audio. `Playback` supports media-element sources for play, stop, loop and seek.
1372
+
1373
+ ### Fixed
1374
+ - Creating a `Playback` no longer starts its source immediately. `Sound.play()` had been starting the same source node twice.
1375
+
1376
+ ## [0.1.8] - 2023-11-19
1377
+
1378
+ ### Added
1379
+ - `isPlaying()` on `Sound` and `Playback`.
1380
+ - `Playback.threeDOptions` getter and setter for the underlying `PannerNode` settings; `threeDOptions` is an optional `BaseSound` member.
1381
+
1382
+ ## [0.1.7] - 2023-11-18
1383
+
1384
+ ### Fixed
1385
+ - The npm package includes the compiled library. Earlier tarballs (verified for 0.1.3–0.1.6) shipped only `dist/index.js`, which re-exported a module missing from the tarball, so the package could not be imported.
1386
+
1387
+ ## [0.1.6] - 2023-11-18
1388
+
1389
+ ### Added
1390
+ - `seek(time)` on `Sound`, `Playback` and `Group`.
1391
+ - Microphone input: `Cacophony.getMicrophoneStream()` returns a `MicrophoneStream`, played through a `StreamPlayback`, with volume, filters, pause and resume.
1392
+ - `Playback.sourceLoop` setter.
1393
+
1394
+ ### Changed
1395
+ - `BaseSound.play()` returns `BaseSound[]`; `seek` and `loop` are optional `BaseSound` members.
1396
+ - `Sound.globalGainNode` and the internal nodes of `Playback` (`context`, `source`, `gainNode`, `panner`, `buffer`) are private.
1397
+ - `Sound.play()` starts playbacks through `Playback.play()`.
1398
+
1399
+ ## [0.1.5] - 2023-11-18
1400
+
1401
+ ### ⚠ Breaking
1402
+ - `moveTo(x, y, z)` is replaced by a `position` property on `Sound`, `Playback` and `Group`. Replace `sound.moveTo(x, y, z)` with `sound.position = [x, y, z]`.
1403
+
1404
+ ### Added
1405
+ - `Position` and `LoopCount` types are exported.
1406
+
1407
+ ### Changed
1408
+ - `Group.volume` returns the average volume of its sounds instead of the first sound's volume.
1409
+
1410
+ ## [0.1.4] - 2023-11-18
1411
+
1412
+ ### Added
1413
+ - `Playback.cleanup()` disconnects and releases a playback's nodes; later calls on the playback throw.
1414
+
1415
+ ### Fixed
1416
+ - `Playback.fadeIn()` ramps from 0 to full volume using Web Audio automation and supports the `'exponential'` fade type. The old version used an interval timer that finished almost immediately.
1417
+
1418
+ ## [0.1.3] - 2023-10-06
1419
+
1420
+ _0.1.0–0.1.2 were published to npm but not tagged; this block covers everything from the first commit to 0.1.3._
1421
+
1422
+ ### Added
1423
+ - First release. `Cacophony` wraps a (standardized-audio-context) `AudioContext` with a global gain node and provides `createSound(url | AudioBuffer)`, `createGroup(sounds)`, `createGroupFromUrls(urls)`, `createBiquadFilter(type)`, `pause()`, `resume()`, `stopAll()`, `setGlobalVolume()`, a `volume` property, `mute()` and `unmute()`.
1424
+ - `Sound` holds a decoded buffer and creates a `Playback` for each `play()` call. It supports `stop()`, `pause()`, `resume()`, `loop(count | 'infinite')`, `volume`, `moveTo(x, y, z)` for 3D positioning, and `addFilter()`/`removeFilter()` for filters.
1425
+ - `Group` controls several sounds together.
1426
+ - Sounds loaded from URLs are cached with the browser Cache API (`CacheManager`), and concurrent loads of the same URL are de-duplicated.
1427
+ - `Playback.fadeIn()` and `Playback.fadeOut()` with `'linear'` or `'exponential'` fades.
1428
+
1429
+ [Unreleased]: https://github.com/ctoth/cacophony/compare/v0.34.0...HEAD
1430
+ [0.34.0]: https://github.com/ctoth/cacophony/compare/v0.33.0...v0.34.0
1431
+ [0.33.0]: https://github.com/ctoth/cacophony/compare/v0.32.1...v0.33.0
1432
+ [0.32.1]: https://github.com/ctoth/cacophony/compare/v0.32.0...v0.32.1
1433
+ [0.32.0]: https://github.com/ctoth/cacophony/compare/v0.31.0...v0.32.0
1434
+ [0.31.0]: https://github.com/ctoth/cacophony/compare/v0.30.4...v0.31.0
1435
+ [0.30.4]: https://github.com/ctoth/cacophony/compare/v0.30.3...v0.30.4
1436
+ [0.30.3]: https://github.com/ctoth/cacophony/compare/v0.30.2...v0.30.3
1437
+ [0.30.2]: https://github.com/ctoth/cacophony/compare/v0.30.1...v0.30.2
1438
+ [0.30.1]: https://github.com/ctoth/cacophony/compare/v0.30.0...v0.30.1
1439
+ [0.30.0]: https://github.com/ctoth/cacophony/compare/v0.29.0...v0.30.0
1440
+ [0.29.0]: https://github.com/ctoth/cacophony/compare/v0.28.0...v0.29.0
1441
+ [0.28.0]: https://github.com/ctoth/cacophony/compare/v0.26.0...v0.28.0
1442
+ [0.26.0]: https://github.com/ctoth/cacophony/compare/v0.25.1...v0.26.0
1443
+ [0.25.1]: https://github.com/ctoth/cacophony/compare/v0.25.0...v0.25.1
1444
+ [0.25.0]: https://github.com/ctoth/cacophony/compare/v0.24.0...v0.25.0
1445
+ [0.24.0]: https://github.com/ctoth/cacophony/compare/v0.23.0...v0.24.0
1446
+ [0.23.0]: https://github.com/ctoth/cacophony/compare/v0.21.0...v0.23.0
1447
+ [0.21.0]: https://github.com/ctoth/cacophony/compare/v0.20.8...v0.21.0
1448
+ [0.20.8]: https://github.com/ctoth/cacophony/compare/v0.20.7...v0.20.8
1449
+ [0.20.7]: https://github.com/ctoth/cacophony/compare/v0.20.6...v0.20.7
1450
+ [0.20.6]: https://github.com/ctoth/cacophony/compare/v0.20.5...v0.20.6
1451
+ [0.20.5]: https://github.com/ctoth/cacophony/compare/v0.20.4...v0.20.5
1452
+ [0.20.4]: https://github.com/ctoth/cacophony/compare/v0.20.3...v0.20.4
1453
+ [0.20.3]: https://github.com/ctoth/cacophony/compare/v0.20.2...v0.20.3
1454
+ [0.20.2]: https://github.com/ctoth/cacophony/compare/v0.20.1...v0.20.2
1455
+ [0.20.1]: https://github.com/ctoth/cacophony/compare/v0.20.0...v0.20.1
1456
+ [0.20.0]: https://github.com/ctoth/cacophony/compare/v0.19.0...v0.20.0
1457
+ [0.19.0]: https://github.com/ctoth/cacophony/compare/v0.18.3...v0.19.0
1458
+ [0.18.3]: https://github.com/ctoth/cacophony/compare/v0.18.2...v0.18.3
1459
+ [0.18.2]: https://github.com/ctoth/cacophony/compare/v0.18.1...v0.18.2
1460
+ [0.18.1]: https://github.com/ctoth/cacophony/compare/v0.18.0...v0.18.1
1461
+ [0.18.0]: https://github.com/ctoth/cacophony/compare/v0.17.1...v0.18.0
1462
+ [0.17.1]: https://github.com/ctoth/cacophony/compare/v0.17.0...v0.17.1
1463
+ [0.17.0]: https://github.com/ctoth/cacophony/compare/v0.16.1...v0.17.0
1464
+ [0.16.1]: https://github.com/ctoth/cacophony/compare/v0.16.0...v0.16.1
1465
+ [0.16.0]: https://github.com/ctoth/cacophony/compare/v0.15.3...v0.16.0
1466
+ [0.15.3]: https://github.com/ctoth/cacophony/compare/v0.15.2...v0.15.3
1467
+ [0.15.2]: https://github.com/ctoth/cacophony/compare/v0.15.1...v0.15.2
1468
+ [0.15.1]: https://github.com/ctoth/cacophony/compare/v0.15.0...v0.15.1
1469
+ [0.15.0]: https://github.com/ctoth/cacophony/compare/v0.14.2...v0.15.0
1470
+ [0.14.2]: https://github.com/ctoth/cacophony/compare/v0.14.1...v0.14.2
1471
+ [0.14.1]: https://github.com/ctoth/cacophony/compare/v0.14.0...v0.14.1
1472
+ [0.14.0]: https://github.com/ctoth/cacophony/compare/v0.13.5...v0.14.0
1473
+ [0.13.5]: https://github.com/ctoth/cacophony/compare/v0.13.4...v0.13.5
1474
+ [0.13.4]: https://github.com/ctoth/cacophony/compare/v0.13.3...v0.13.4
1475
+ [0.13.3]: https://github.com/ctoth/cacophony/compare/v0.13.2...v0.13.3
1476
+ [0.13.2]: https://github.com/ctoth/cacophony/compare/v0.13.1...v0.13.2
1477
+ [0.13.1]: https://github.com/ctoth/cacophony/compare/v0.13.0...v0.13.1
1478
+ [0.13.0]: https://github.com/ctoth/cacophony/compare/v0.12.10...v0.13.0
1479
+ [0.12.10]: https://github.com/ctoth/cacophony/compare/v0.12.9...v0.12.10
1480
+ [0.12.9]: https://github.com/ctoth/cacophony/compare/v0.12.8...v0.12.9
1481
+ [0.12.8]: https://github.com/ctoth/cacophony/compare/v0.12.7...v0.12.8
1482
+ [0.12.7]: https://github.com/ctoth/cacophony/compare/v0.12.6...v0.12.7
1483
+ [0.12.6]: https://github.com/ctoth/cacophony/compare/v0.12.5...v0.12.6
1484
+ [0.12.5]: https://github.com/ctoth/cacophony/compare/v0.12.4...v0.12.5
1485
+ [0.12.4]: https://github.com/ctoth/cacophony/compare/v0.12.3...v0.12.4
1486
+ [0.12.3]: https://github.com/ctoth/cacophony/compare/v0.12.2...v0.12.3
1487
+ [0.12.2]: https://github.com/ctoth/cacophony/compare/v0.12.1...v0.12.2
1488
+ [0.12.1]: https://github.com/ctoth/cacophony/compare/v0.12.0...v0.12.1
1489
+ [0.12.0]: https://github.com/ctoth/cacophony/compare/v0.11.1...v0.12.0
1490
+ [0.11.1]: https://github.com/ctoth/cacophony/compare/v0.11.0...v0.11.1
1491
+ [0.11.0]: https://github.com/ctoth/cacophony/compare/v0.10.4...v0.11.0
1492
+ [0.10.4]: https://github.com/ctoth/cacophony/compare/v0.10.3...v0.10.4
1493
+ [0.10.3]: https://github.com/ctoth/cacophony/compare/v0.10.2...v0.10.3
1494
+ [0.10.2]: https://github.com/ctoth/cacophony/compare/v0.10.1...v0.10.2
1495
+ [0.10.1]: https://github.com/ctoth/cacophony/compare/v0.10.0...v0.10.1
1496
+ [0.10.0]: https://github.com/ctoth/cacophony/compare/v0.9.7...v0.10.0
1497
+ [0.9.7]: https://github.com/ctoth/cacophony/compare/v0.9.6...v0.9.7
1498
+ [0.9.6]: https://github.com/ctoth/cacophony/compare/v0.9.5...v0.9.6
1499
+ [0.9.5]: https://github.com/ctoth/cacophony/compare/v0.9.4...v0.9.5
1500
+ [0.9.4]: https://github.com/ctoth/cacophony/compare/v0.9.3...v0.9.4
1501
+ [0.9.3]: https://github.com/ctoth/cacophony/compare/v0.9.2...v0.9.3
1502
+ [0.9.2]: https://github.com/ctoth/cacophony/compare/v0.9.1...v0.9.2
1503
+ [0.9.1]: https://github.com/ctoth/cacophony/compare/v0.9.0...v0.9.1
1504
+ [0.9.0]: https://github.com/ctoth/cacophony/compare/v0.8.7...v0.9.0
1505
+ [0.8.7]: https://github.com/ctoth/cacophony/compare/v0.8.6...v0.8.7
1506
+ [0.8.6]: https://github.com/ctoth/cacophony/compare/v0.8.5...v0.8.6
1507
+ [0.8.5]: https://github.com/ctoth/cacophony/compare/v0.8.4...v0.8.5
1508
+ [0.8.4]: https://github.com/ctoth/cacophony/compare/v0.8.3...v0.8.4
1509
+ [0.8.3]: https://github.com/ctoth/cacophony/compare/v0.8.2...v0.8.3
1510
+ [0.8.2]: https://github.com/ctoth/cacophony/compare/v0.8.1...v0.8.2
1511
+ [0.8.1]: https://github.com/ctoth/cacophony/compare/v0.8.0...v0.8.1
1512
+ [0.8.0]: https://github.com/ctoth/cacophony/compare/v0.7.0...v0.8.0
1513
+ [0.7.0]: https://github.com/ctoth/cacophony/compare/v0.6.2...v0.7.0
1514
+ [0.6.2]: https://github.com/ctoth/cacophony/compare/v0.6.1...v0.6.2
1515
+ [0.6.1]: https://github.com/ctoth/cacophony/compare/v0.6.0...v0.6.1
1516
+ [0.6.0]: https://github.com/ctoth/cacophony/compare/v0.5.23...v0.6.0
1517
+ [0.5.23]: https://github.com/ctoth/cacophony/compare/v0.5.22...v0.5.23
1518
+ [0.5.22]: https://github.com/ctoth/cacophony/compare/v0.5.21...v0.5.22
1519
+ [0.5.21]: https://github.com/ctoth/cacophony/compare/v0.5.20...v0.5.21
1520
+ [0.5.20]: https://github.com/ctoth/cacophony/compare/v0.5.19...v0.5.20
1521
+ [0.5.19]: https://github.com/ctoth/cacophony/compare/v0.5.18...v0.5.19
1522
+ [0.5.18]: https://github.com/ctoth/cacophony/compare/v0.5.17...v0.5.18
1523
+ [0.5.17]: https://github.com/ctoth/cacophony/compare/v0.5.16...v0.5.17
1524
+ [0.5.16]: https://github.com/ctoth/cacophony/compare/v0.5.15...v0.5.16
1525
+ [0.5.15]: https://github.com/ctoth/cacophony/compare/v0.5.14...v0.5.15
1526
+ [0.5.14]: https://github.com/ctoth/cacophony/compare/v0.5.13...v0.5.14
1527
+ [0.5.13]: https://github.com/ctoth/cacophony/compare/v0.5.12...v0.5.13
1528
+ [0.5.12]: https://github.com/ctoth/cacophony/compare/v0.5.11...v0.5.12
1529
+ [0.5.11]: https://github.com/ctoth/cacophony/compare/v0.5.10...v0.5.11
1530
+ [0.5.10]: https://github.com/ctoth/cacophony/compare/v0.5.9...v0.5.10
1531
+ [0.5.9]: https://github.com/ctoth/cacophony/compare/v0.5.8...v0.5.9
1532
+ [0.5.8]: https://github.com/ctoth/cacophony/compare/v0.5.7...v0.5.8
1533
+ [0.5.7]: https://github.com/ctoth/cacophony/compare/v0.5.6...v0.5.7
1534
+ [0.5.6]: https://github.com/ctoth/cacophony/compare/v0.5.5...v0.5.6
1535
+ [0.5.5]: https://github.com/ctoth/cacophony/compare/v0.5.4...v0.5.5
1536
+ [0.5.4]: https://github.com/ctoth/cacophony/compare/v0.5.3...v0.5.4
1537
+ [0.5.3]: https://github.com/ctoth/cacophony/compare/v0.5.2...v0.5.3
1538
+ [0.5.2]: https://github.com/ctoth/cacophony/compare/v0.5.1...v0.5.2
1539
+ [0.5.1]: https://github.com/ctoth/cacophony/compare/v0.5.0...v0.5.1
1540
+ [0.5.0]: https://github.com/ctoth/cacophony/compare/v0.4.6...v0.5.0
1541
+ [0.4.6]: https://github.com/ctoth/cacophony/compare/v0.4.5...v0.4.6
1542
+ [0.4.5]: https://github.com/ctoth/cacophony/compare/v0.4.4...v0.4.5
1543
+ [0.4.4]: https://github.com/ctoth/cacophony/compare/v0.4.3...v0.4.4
1544
+ [0.4.3]: https://github.com/ctoth/cacophony/compare/v0.4.2...v0.4.3
1545
+ [0.4.2]: https://github.com/ctoth/cacophony/compare/v0.4.1...v0.4.2
1546
+ [0.4.1]: https://github.com/ctoth/cacophony/compare/v0.4.0...v0.4.1
1547
+ [0.4.0]: https://github.com/ctoth/cacophony/compare/v0.3.13...v0.4.0
1548
+ [0.3.13]: https://github.com/ctoth/cacophony/compare/v0.3.12...v0.3.13
1549
+ [0.3.12]: https://github.com/ctoth/cacophony/compare/v0.3.11...v0.3.12
1550
+ [0.3.11]: https://github.com/ctoth/cacophony/compare/v0.3.10...v0.3.11
1551
+ [0.3.10]: https://github.com/ctoth/cacophony/compare/v0.3.9...v0.3.10
1552
+ [0.3.9]: https://github.com/ctoth/cacophony/compare/v0.3.8...v0.3.9
1553
+ [0.3.8]: https://github.com/ctoth/cacophony/compare/v0.3.7...v0.3.8
1554
+ [0.3.7]: https://github.com/ctoth/cacophony/compare/v0.3.6...v0.3.7
1555
+ [0.3.6]: https://github.com/ctoth/cacophony/compare/v0.3.5...v0.3.6
1556
+ [0.3.5]: https://github.com/ctoth/cacophony/compare/v0.3.4...v0.3.5
1557
+ [0.3.4]: https://github.com/ctoth/cacophony/compare/v0.3.3...v0.3.4
1558
+ [0.3.3]: https://github.com/ctoth/cacophony/compare/v0.3.2...v0.3.3
1559
+ [0.3.2]: https://github.com/ctoth/cacophony/compare/v0.3.1...v0.3.2
1560
+ [0.3.1]: https://github.com/ctoth/cacophony/compare/v0.3.0...v0.3.1
1561
+ [0.3.0]: https://github.com/ctoth/cacophony/compare/v0.2.1...v0.3.0
1562
+ [0.2.1]: https://github.com/ctoth/cacophony/compare/v0.2.0...v0.2.1
1563
+ [0.2.0]: https://github.com/ctoth/cacophony/compare/v0.1.39...v0.2.0
1564
+ [0.1.39]: https://github.com/ctoth/cacophony/compare/v0.1.38...v0.1.39
1565
+ [0.1.38]: https://github.com/ctoth/cacophony/compare/v0.1.37...v0.1.38
1566
+ [0.1.37]: https://github.com/ctoth/cacophony/compare/v0.1.36...v0.1.37
1567
+ [0.1.36]: https://github.com/ctoth/cacophony/compare/v0.1.35...v0.1.36
1568
+ [0.1.35]: https://github.com/ctoth/cacophony/compare/v0.1.34...v0.1.35
1569
+ [0.1.34]: https://github.com/ctoth/cacophony/compare/v0.1.33...v0.1.34
1570
+ [0.1.33]: https://github.com/ctoth/cacophony/compare/v0.1.32...v0.1.33
1571
+ [0.1.32]: https://github.com/ctoth/cacophony/compare/v0.1.31...v0.1.32
1572
+ [0.1.31]: https://github.com/ctoth/cacophony/compare/v0.1.30...v0.1.31
1573
+ [0.1.30]: https://github.com/ctoth/cacophony/compare/v0.1.29...v0.1.30
1574
+ [0.1.29]: https://github.com/ctoth/cacophony/compare/v0.1.28...v0.1.29
1575
+ [0.1.28]: https://github.com/ctoth/cacophony/compare/v0.1.27...v0.1.28
1576
+ [0.1.27]: https://github.com/ctoth/cacophony/compare/v0.1.26...v0.1.27
1577
+ [0.1.26]: https://github.com/ctoth/cacophony/compare/v0.1.25...v0.1.26
1578
+ [0.1.25]: https://github.com/ctoth/cacophony/compare/v0.1.24...v0.1.25
1579
+ [0.1.24]: https://github.com/ctoth/cacophony/compare/v0.1.23...v0.1.24
1580
+ [0.1.23]: https://github.com/ctoth/cacophony/compare/v0.1.22...v0.1.23
1581
+ [0.1.22]: https://github.com/ctoth/cacophony/compare/v0.1.21...v0.1.22
1582
+ [0.1.21]: https://github.com/ctoth/cacophony/compare/v0.1.20...v0.1.21
1583
+ [0.1.20]: https://github.com/ctoth/cacophony/compare/v0.1.19...v0.1.20
1584
+ [0.1.19]: https://github.com/ctoth/cacophony/compare/v0.1.18...v0.1.19
1585
+ [0.1.18]: https://github.com/ctoth/cacophony/compare/v0.1.17...v0.1.18
1586
+ [0.1.17]: https://github.com/ctoth/cacophony/compare/v0.1.16...v0.1.17
1587
+ [0.1.16]: https://github.com/ctoth/cacophony/compare/v0.1.15...v0.1.16
1588
+ [0.1.15]: https://github.com/ctoth/cacophony/compare/v0.1.14...v0.1.15
1589
+ [0.1.14]: https://github.com/ctoth/cacophony/compare/v0.1.13...v0.1.14
1590
+ [0.1.13]: https://github.com/ctoth/cacophony/compare/v0.1.12...v0.1.13
1591
+ [0.1.12]: https://github.com/ctoth/cacophony/compare/v0.1.11...v0.1.12
1592
+ [0.1.11]: https://github.com/ctoth/cacophony/compare/v0.1.10...v0.1.11
1593
+ [0.1.10]: https://github.com/ctoth/cacophony/compare/v0.1.9...v0.1.10
1594
+ [0.1.9]: https://github.com/ctoth/cacophony/compare/v0.1.8...v0.1.9
1595
+ [0.1.8]: https://github.com/ctoth/cacophony/compare/v0.1.7...v0.1.8
1596
+ [0.1.7]: https://github.com/ctoth/cacophony/compare/v0.1.6...v0.1.7
1597
+ [0.1.6]: https://github.com/ctoth/cacophony/compare/v0.1.5...v0.1.6
1598
+ [0.1.5]: https://github.com/ctoth/cacophony/compare/v0.1.4...v0.1.5
1599
+ [0.1.4]: https://github.com/ctoth/cacophony/compare/v0.1.3...v0.1.4
1600
+ [0.1.3]: https://github.com/ctoth/cacophony/releases/tag/v0.1.3