@datamoc/mw_games 0.42.0 → 0.44.2

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 (333) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +84 -1
  3. package/REFERENCE.md +134 -6
  4. package/api-surface.json +1814 -116
  5. package/data/microgm-essential.PROVENANCE.md +13 -0
  6. package/data/microgm-essential.sf2 +0 -0
  7. package/data/soundfont-catalog.json +8 -0
  8. package/dist/actors/Resource.d.ts.map +1 -1
  9. package/dist/actors/Resource.js +2 -3
  10. package/dist/actors/Resource.js.map +1 -1
  11. package/dist/audio/AdaptiveMusic.d.ts +85 -0
  12. package/dist/audio/AdaptiveMusic.d.ts.map +1 -0
  13. package/dist/audio/AdaptiveMusic.js +230 -0
  14. package/dist/audio/AdaptiveMusic.js.map +1 -0
  15. package/dist/audio/AudioLoader.d.ts +33 -0
  16. package/dist/audio/AudioLoader.d.ts.map +1 -0
  17. package/dist/audio/AudioLoader.js +58 -0
  18. package/dist/audio/AudioLoader.js.map +1 -0
  19. package/dist/audio/AudioViews.d.ts +70 -0
  20. package/dist/audio/AudioViews.d.ts.map +1 -0
  21. package/dist/audio/AudioViews.js +119 -0
  22. package/dist/audio/AudioViews.js.map +1 -0
  23. package/dist/audio/BusBudget.d.ts +89 -0
  24. package/dist/audio/BusBudget.d.ts.map +1 -0
  25. package/dist/audio/BusBudget.js +120 -0
  26. package/dist/audio/BusBudget.js.map +1 -0
  27. package/dist/audio/Channels.d.ts +66 -1
  28. package/dist/audio/Channels.d.ts.map +1 -1
  29. package/dist/audio/Channels.js +119 -31
  30. package/dist/audio/Channels.js.map +1 -1
  31. package/dist/audio/CoreHost.d.ts +114 -0
  32. package/dist/audio/CoreHost.d.ts.map +1 -0
  33. package/dist/audio/CoreHost.js +335 -0
  34. package/dist/audio/CoreHost.js.map +1 -0
  35. package/dist/audio/DecoderGuard.d.ts +139 -0
  36. package/dist/audio/DecoderGuard.d.ts.map +1 -0
  37. package/dist/audio/DecoderGuard.js +208 -0
  38. package/dist/audio/DecoderGuard.js.map +1 -0
  39. package/dist/audio/Fft.d.ts +3 -0
  40. package/dist/audio/Fft.d.ts.map +1 -0
  41. package/dist/audio/Fft.js +45 -0
  42. package/dist/audio/Fft.js.map +1 -0
  43. package/dist/audio/LoopTags.d.ts +18 -0
  44. package/dist/audio/LoopTags.d.ts.map +1 -1
  45. package/dist/audio/LoopTags.js +20 -0
  46. package/dist/audio/LoopTags.js.map +1 -1
  47. package/dist/audio/Midi.d.ts +35 -0
  48. package/dist/audio/Midi.d.ts.map +1 -1
  49. package/dist/audio/Midi.js +71 -1
  50. package/dist/audio/Midi.js.map +1 -1
  51. package/dist/audio/MidiRender.d.ts +47 -0
  52. package/dist/audio/MidiRender.d.ts.map +1 -1
  53. package/dist/audio/MidiRender.js +100 -2
  54. package/dist/audio/MidiRender.js.map +1 -1
  55. package/dist/audio/MidiSynth.d.ts +147 -0
  56. package/dist/audio/MidiSynth.d.ts.map +1 -0
  57. package/dist/audio/MidiSynth.js +205 -0
  58. package/dist/audio/MidiSynth.js.map +1 -0
  59. package/dist/audio/MusicPlayer.d.ts +80 -0
  60. package/dist/audio/MusicPlayer.d.ts.map +1 -0
  61. package/dist/audio/MusicPlayer.js +147 -0
  62. package/dist/audio/MusicPlayer.js.map +1 -0
  63. package/dist/audio/Playable.d.ts +7 -0
  64. package/dist/audio/Playable.d.ts.map +1 -1
  65. package/dist/audio/Playable.js.map +1 -1
  66. package/dist/audio/Positional.d.ts +12 -4
  67. package/dist/audio/Positional.d.ts.map +1 -1
  68. package/dist/audio/Positional.js +5 -2
  69. package/dist/audio/Positional.js.map +1 -1
  70. package/dist/audio/Resample.d.ts +61 -0
  71. package/dist/audio/Resample.d.ts.map +1 -0
  72. package/dist/audio/Resample.js +97 -0
  73. package/dist/audio/Resample.js.map +1 -0
  74. package/dist/audio/SampleDecoder.d.ts +16 -2
  75. package/dist/audio/SampleDecoder.d.ts.map +1 -1
  76. package/dist/audio/SampleDecoder.js +10 -6
  77. package/dist/audio/SampleDecoder.js.map +1 -1
  78. package/dist/audio/Settings.d.ts +99 -0
  79. package/dist/audio/Settings.d.ts.map +1 -0
  80. package/dist/audio/Settings.js +134 -0
  81. package/dist/audio/Settings.js.map +1 -0
  82. package/dist/audio/Sfx.d.ts +148 -0
  83. package/dist/audio/Sfx.d.ts.map +1 -0
  84. package/dist/audio/Sfx.js +213 -0
  85. package/dist/audio/Sfx.js.map +1 -0
  86. package/dist/audio/Sound.d.ts +2 -0
  87. package/dist/audio/Sound.d.ts.map +1 -1
  88. package/dist/audio/Sound.js +3 -0
  89. package/dist/audio/Sound.js.map +1 -1
  90. package/dist/audio/SoundFont.d.ts +5 -3
  91. package/dist/audio/SoundFont.d.ts.map +1 -1
  92. package/dist/audio/SoundFont.js +10 -8
  93. package/dist/audio/SoundFont.js.map +1 -1
  94. package/dist/audio/SpectralDistance.d.ts +34 -0
  95. package/dist/audio/SpectralDistance.d.ts.map +1 -0
  96. package/dist/audio/SpectralDistance.js +64 -0
  97. package/dist/audio/SpectralDistance.js.map +1 -0
  98. package/dist/audio/StreamChannel.d.ts +150 -0
  99. package/dist/audio/StreamChannel.d.ts.map +1 -0
  100. package/dist/audio/StreamChannel.js +390 -0
  101. package/dist/audio/StreamChannel.js.map +1 -0
  102. package/dist/audio/StreamPlayable.d.ts +73 -0
  103. package/dist/audio/StreamPlayable.d.ts.map +1 -0
  104. package/dist/audio/StreamPlayable.js +130 -0
  105. package/dist/audio/StreamPlayable.js.map +1 -0
  106. package/dist/audio/Synth.d.ts +1 -1
  107. package/dist/audio/Synth.d.ts.map +1 -1
  108. package/dist/audio/Synth.js +4 -0
  109. package/dist/audio/Synth.js.map +1 -1
  110. package/dist/audio/SyxBank.d.ts +73 -0
  111. package/dist/audio/SyxBank.d.ts.map +1 -0
  112. package/dist/audio/SyxBank.js +130 -0
  113. package/dist/audio/SyxBank.js.map +1 -0
  114. package/dist/audio/Underrun.d.ts +25 -0
  115. package/dist/audio/Underrun.d.ts.map +1 -0
  116. package/dist/audio/Underrun.js +18 -0
  117. package/dist/audio/Underrun.js.map +1 -0
  118. package/dist/audio/Wav.d.ts +56 -0
  119. package/dist/audio/Wav.d.ts.map +1 -0
  120. package/dist/audio/Wav.js +164 -0
  121. package/dist/audio/Wav.js.map +1 -0
  122. package/dist/audio/WebMidi.d.ts +58 -0
  123. package/dist/audio/WebMidi.d.ts.map +1 -0
  124. package/dist/audio/WebMidi.js +79 -0
  125. package/dist/audio/WebMidi.js.map +1 -0
  126. package/dist/audio/humanize/Onsets.d.ts.map +1 -1
  127. package/dist/audio/humanize/Onsets.js +1 -44
  128. package/dist/audio/humanize/Onsets.js.map +1 -1
  129. package/dist/audio/index.d.ts +43 -7
  130. package/dist/audio/index.d.ts.map +1 -1
  131. package/dist/audio/index.js +21 -3
  132. package/dist/audio/index.js.map +1 -1
  133. package/dist/battle/AttackPreview.d.ts.map +1 -1
  134. package/dist/battle/AttackPreview.js +8 -8
  135. package/dist/battle/AttackPreview.js.map +1 -1
  136. package/dist/battle/StatStages.d.ts.map +1 -1
  137. package/dist/battle/StatStages.js +2 -1
  138. package/dist/battle/StatStages.js.map +1 -1
  139. package/dist/board/Engine.d.ts +2 -0
  140. package/dist/board/Engine.d.ts.map +1 -1
  141. package/dist/board/Engine.js +1 -1
  142. package/dist/board/Engine.js.map +1 -1
  143. package/dist/board/HexSkirmish.d.ts.map +1 -1
  144. package/dist/board/HexSkirmish.js +11 -72
  145. package/dist/board/HexSkirmish.js.map +1 -1
  146. package/dist/board/Tactics.d.ts.map +1 -1
  147. package/dist/board/Tactics.js +3 -5
  148. package/dist/board/Tactics.js.map +1 -1
  149. package/dist/board/tourney.d.ts.map +1 -1
  150. package/dist/board/tourney.js +2 -19
  151. package/dist/board/tourney.js.map +1 -1
  152. package/dist/core/Handles.js +7 -7
  153. package/dist/core/Handles.js.map +1 -1
  154. package/dist/mw_games.global.js +25 -22
  155. package/dist/mwl/conditions.d.ts +12 -0
  156. package/dist/mwl/conditions.d.ts.map +1 -1
  157. package/dist/mwl/conditions.js +49 -8
  158. package/dist/mwl/conditions.js.map +1 -1
  159. package/dist/mwl/index.d.ts +13 -0
  160. package/dist/mwl/index.d.ts.map +1 -1
  161. package/dist/mwl/index.js +7 -0
  162. package/dist/mwl/index.js.map +1 -1
  163. package/dist/mwl/objects.d.ts +31 -0
  164. package/dist/mwl/objects.d.ts.map +1 -0
  165. package/dist/mwl/objects.js +83 -0
  166. package/dist/mwl/objects.js.map +1 -0
  167. package/dist/mwl/package-container.d.ts +185 -0
  168. package/dist/mwl/package-container.d.ts.map +1 -0
  169. package/dist/mwl/package-container.js +446 -0
  170. package/dist/mwl/package-container.js.map +1 -0
  171. package/dist/mwl/package-document.d.ts +217 -0
  172. package/dist/mwl/package-document.d.ts.map +1 -0
  173. package/dist/mwl/package-document.js +639 -0
  174. package/dist/mwl/package-document.js.map +1 -0
  175. package/dist/mwl/package-rom.d.ts +37 -0
  176. package/dist/mwl/package-rom.d.ts.map +1 -0
  177. package/dist/mwl/package-rom.js +96 -0
  178. package/dist/mwl/package-rom.js.map +1 -0
  179. package/dist/mwl/package-verify.d.ts +34 -0
  180. package/dist/mwl/package-verify.d.ts.map +1 -0
  181. package/dist/mwl/package-verify.js +111 -0
  182. package/dist/mwl/package-verify.js.map +1 -0
  183. package/dist/mwl/package.d.ts +184 -0
  184. package/dist/mwl/package.d.ts.map +1 -0
  185. package/dist/mwl/package.js +382 -0
  186. package/dist/mwl/package.js.map +1 -0
  187. package/dist/mwl/schema.d.ts +1 -1
  188. package/dist/mwl/schema.d.ts.map +1 -1
  189. package/dist/mwl/tables.d.ts +2 -0
  190. package/dist/mwl/tables.d.ts.map +1 -1
  191. package/dist/mwl/tables.js +13 -0
  192. package/dist/mwl/tables.js.map +1 -1
  193. package/dist/physics/PhysicsWorld.d.ts.map +1 -1
  194. package/dist/physics/PhysicsWorld.js +4 -3
  195. package/dist/physics/PhysicsWorld.js.map +1 -1
  196. package/dist/physics/XpbdWorld.d.ts +23 -5
  197. package/dist/physics/XpbdWorld.d.ts.map +1 -1
  198. package/dist/physics/XpbdWorld.js +91 -6
  199. package/dist/physics/XpbdWorld.js.map +1 -1
  200. package/dist/testing/FakeAudio.d.ts +149 -0
  201. package/dist/testing/FakeAudio.d.ts.map +1 -0
  202. package/dist/testing/FakeAudio.js +219 -0
  203. package/dist/testing/FakeAudio.js.map +1 -0
  204. package/dist/testing/index.d.ts +18 -0
  205. package/dist/testing/index.d.ts.map +1 -1
  206. package/dist/testing/index.js +22 -0
  207. package/dist/testing/index.js.map +1 -1
  208. package/dist/threads/index.d.ts +2 -2
  209. package/dist/threads/index.d.ts.map +1 -1
  210. package/dist/threads/index.js +1 -1
  211. package/dist/threads/index.js.map +1 -1
  212. package/dist/threads/spawn.d.ts +32 -0
  213. package/dist/threads/spawn.d.ts.map +1 -1
  214. package/dist/threads/spawn.js +35 -11
  215. package/dist/threads/spawn.js.map +1 -1
  216. package/dist/three-d/Collision3D.d.ts +46 -2
  217. package/dist/three-d/Collision3D.d.ts.map +1 -1
  218. package/dist/three-d/Collision3D.js +46 -2
  219. package/dist/three-d/Collision3D.js.map +1 -1
  220. package/dist/three-d/Grid.d.ts +12 -1
  221. package/dist/three-d/Grid.d.ts.map +1 -1
  222. package/dist/three-d/Grid.js +12 -1
  223. package/dist/three-d/Grid.js.map +1 -1
  224. package/dist/three-d/Vox.d.ts +13 -1
  225. package/dist/three-d/Vox.d.ts.map +1 -1
  226. package/dist/three-d/Vox.js +13 -1
  227. package/dist/three-d/Vox.js.map +1 -1
  228. package/dist/two-d/Game.d.ts +32 -1
  229. package/dist/two-d/Game.d.ts.map +1 -1
  230. package/dist/two-d/Game.js +105 -14
  231. package/dist/two-d/Game.js.map +1 -1
  232. package/dist/two-d/Scene2D.d.ts +24 -0
  233. package/dist/two-d/Scene2D.d.ts.map +1 -1
  234. package/dist/two-d/Scene2D.js +30 -0
  235. package/dist/two-d/Scene2D.js.map +1 -1
  236. package/dist/two-d/render/HazeLayer.d.ts.map +1 -1
  237. package/dist/two-d/render/HazeLayer.js +2 -1
  238. package/dist/two-d/render/HazeLayer.js.map +1 -1
  239. package/dist/two-d/render/ImageModifiers.d.ts +4 -2
  240. package/dist/two-d/render/ImageModifiers.d.ts.map +1 -1
  241. package/dist/two-d/render/ImageModifiers.js +13 -8
  242. package/dist/two-d/render/ImageModifiers.js.map +1 -1
  243. package/dist/two-d/render/NeonGlow.d.ts.map +1 -1
  244. package/dist/two-d/render/NeonGlow.js +2 -1
  245. package/dist/two-d/render/NeonGlow.js.map +1 -1
  246. package/dist/two-d/render/Particles.d.ts.map +1 -1
  247. package/dist/two-d/render/Particles.js +2 -1
  248. package/dist/two-d/render/Particles.js.map +1 -1
  249. package/dist/two-d/render/SceneTransition.d.ts +301 -0
  250. package/dist/two-d/render/SceneTransition.d.ts.map +1 -0
  251. package/dist/two-d/render/SceneTransition.js +458 -0
  252. package/dist/two-d/render/SceneTransition.js.map +1 -0
  253. package/dist/two-d/render/ScreenEffects.d.ts.map +1 -1
  254. package/dist/two-d/render/ScreenEffects.js +2 -1
  255. package/dist/two-d/render/ScreenEffects.js.map +1 -1
  256. package/dist/two-d/render/SkeletalSprite.d.ts.map +1 -1
  257. package/dist/two-d/render/SkeletalSprite.js +2 -1
  258. package/dist/two-d/render/SkeletalSprite.js.map +1 -1
  259. package/dist/two-d/render/Types2D.d.ts +2 -2
  260. package/dist/two-d/render/Types2D.d.ts.map +1 -1
  261. package/dist/two-d/render/Types2D.js +2 -2
  262. package/dist/two-d/render/Types2D.js.map +1 -1
  263. package/dist/two-d/render/index.d.ts +2 -0
  264. package/dist/two-d/render/index.d.ts.map +1 -1
  265. package/dist/two-d/render/index.js +1 -0
  266. package/dist/two-d/render/index.js.map +1 -1
  267. package/dist/two-d/ui/Bar.d.ts.map +1 -1
  268. package/dist/two-d/ui/Bar.js +3 -5
  269. package/dist/two-d/ui/Bar.js.map +1 -1
  270. package/dist/two-d/ui/Button.d.ts +23 -0
  271. package/dist/two-d/ui/Button.d.ts.map +1 -1
  272. package/dist/two-d/ui/Button.js +37 -0
  273. package/dist/two-d/ui/Button.js.map +1 -1
  274. package/dist/two-d/ui/Dropdown.d.ts +5 -1
  275. package/dist/two-d/ui/Dropdown.d.ts.map +1 -1
  276. package/dist/two-d/ui/Dropdown.js +8 -1
  277. package/dist/two-d/ui/Dropdown.js.map +1 -1
  278. package/dist/two-d/ui/DropdownView.d.ts +63 -3
  279. package/dist/two-d/ui/DropdownView.d.ts.map +1 -1
  280. package/dist/two-d/ui/DropdownView.js +168 -9
  281. package/dist/two-d/ui/DropdownView.js.map +1 -1
  282. package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
  283. package/dist/two-d/ui/FloatingText.js +2 -1
  284. package/dist/two-d/ui/FloatingText.js.map +1 -1
  285. package/dist/two-d/ui/IconGrid.d.ts.map +1 -1
  286. package/dist/two-d/ui/IconGrid.js +2 -1
  287. package/dist/two-d/ui/IconGrid.js.map +1 -1
  288. package/dist/two-d/ui/ListView.d.ts.map +1 -1
  289. package/dist/two-d/ui/ListView.js +2 -1
  290. package/dist/two-d/ui/ListView.js.map +1 -1
  291. package/dist/two-d/ui/ScrollBox.d.ts +96 -0
  292. package/dist/two-d/ui/ScrollBox.d.ts.map +1 -1
  293. package/dist/two-d/ui/ScrollBox.js +218 -5
  294. package/dist/two-d/ui/ScrollBox.js.map +1 -1
  295. package/dist/two-d/ui/ScrollText.d.ts +131 -0
  296. package/dist/two-d/ui/ScrollText.d.ts.map +1 -0
  297. package/dist/two-d/ui/ScrollText.js +218 -0
  298. package/dist/two-d/ui/ScrollText.js.map +1 -0
  299. package/dist/two-d/ui/Slider.d.ts.map +1 -1
  300. package/dist/two-d/ui/Slider.js +4 -4
  301. package/dist/two-d/ui/Slider.js.map +1 -1
  302. package/dist/two-d/ui/Tooltip.d.ts +70 -1
  303. package/dist/two-d/ui/Tooltip.d.ts.map +1 -1
  304. package/dist/two-d/ui/Tooltip.js +115 -3
  305. package/dist/two-d/ui/Tooltip.js.map +1 -1
  306. package/dist/two-d/ui/Window.d.ts +35 -2
  307. package/dist/two-d/ui/Window.d.ts.map +1 -1
  308. package/dist/two-d/ui/Window.js +58 -3
  309. package/dist/two-d/ui/Window.js.map +1 -1
  310. package/dist/two-d/ui/index.d.ts +7 -5
  311. package/dist/two-d/ui/index.d.ts.map +1 -1
  312. package/dist/two-d/ui/index.js +3 -2
  313. package/dist/two-d/ui/index.js.map +1 -1
  314. package/dist/version.d.ts +1 -1
  315. package/dist/version.js +1 -1
  316. package/package.json +14 -3
  317. package/sbom.cdx.json +19 -260
  318. package/tools/audio.d.mts +75 -0
  319. package/tools/audio.mjs +437 -0
  320. package/tools/browser-driver.mjs +3 -3
  321. package/tools/browser-smoke.mjs +19 -2
  322. package/tools/build-tablebases.mjs +7 -3
  323. package/tools/compile-openings.mjs +7 -3
  324. package/tools/compile-resources.d.mts +2 -0
  325. package/tools/compile-resources.mjs +13 -1
  326. package/tools/emit-page.d.mts +6 -0
  327. package/tools/emit-page.mjs +43 -3
  328. package/tools/find-chrome.mjs +4 -2
  329. package/tools/init.mjs +11 -3
  330. package/tools/mwl.mjs +144 -2
  331. package/tools/play-uci.mjs +9 -5
  332. package/tools/vite.mjs +9 -1
  333. package/tools.md +2 -1
@@ -0,0 +1,390 @@
1
+ import { Signal } from '../core/Signal.js';
2
+ import { onAudioUnderrun } from './Underrun.js';
3
+ const BLOCK_FRAMES = 2048;
4
+ /**
5
+ * The streaming twin of `Channel`: it queues short buffers on the audio clock ahead of the
6
+ * playhead from a `BlockSource` instead of owning one decoded buffer, so a long or endless
7
+ * track costs a couple of seconds of memory rather than the whole render. It loops on a
8
+ * region, seeks, retunes volume, pitch and pan, fades an endless source out after a limit, and
9
+ * reports `onUnderrun` when the clock got ahead of what was queued.
10
+ *
11
+ * Chunks are queued by a timer it runs itself (`pumpMs`), so a game that never calls `update()`
12
+ * still hears sound; a timer throttled in a background tab is the reason the lookahead is two
13
+ * seconds. A pitch change applies from the next chunk, up to the lookahead later.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { StreamChannel, type BlockSource } from '@datamoc/mw_games/audio';
18
+ *
19
+ * declare const context: AudioContext;
20
+ * declare const tune: BlockSource;
21
+ *
22
+ * const channel = new StreamChannel(context);
23
+ * channel.onEnded.add(() => console.log('done'));
24
+ * channel.play(tune, { repeat: false, volume: 0.8 });
25
+ * console.log(channel.position());
26
+ * channel.stop(1);
27
+ * ```
28
+ */
29
+ export class StreamChannel {
30
+ constructor(context, destination, options = {}) {
31
+ /** fires once when a source that does not repeat has played out, or an endless one reached its limit */
32
+ this.onEnded = new Signal();
33
+ /** fires when the clock passed the end of the queue before the next chunk was ready, with how late it was in seconds */
34
+ this.onUnderrun = new Signal();
35
+ /** fires when the source threw while being read: the voice stops instead of letting the error escape from a timer */
36
+ this.onError = new Signal();
37
+ this.timer = null;
38
+ this.source = null;
39
+ this.gainNode = null;
40
+ this.pannerNode = null;
41
+ this.params = { volume: 1, pitch: 1, pan: 0 };
42
+ this.repeat = false;
43
+ this.loopStart = 0;
44
+ this.loopEnd = null;
45
+ this.endAfterFrames = Infinity;
46
+ this.endFadeFrames = 0;
47
+ this.chunks = [];
48
+ this.nextWhen = 0;
49
+ this.active = false;
50
+ this.drained = false;
51
+ this.lastTimeline = 0;
52
+ this.outputFrames = 0;
53
+ this.underruns = 0;
54
+ //Source frames read but not yet consumed, with the fractional playhead inside them.
55
+ this.pendingLeft = new Float32Array(BLOCK_FRAMES * 4);
56
+ this.pendingRight = new Float32Array(BLOCK_FRAMES * 4);
57
+ this.pendingLength = 0;
58
+ this.cursor = 0;
59
+ this.readFrame = 0;
60
+ this.timeline = 0;
61
+ this.sourceEnded = false;
62
+ this.context = context;
63
+ this.destination = destination ?? context.destination;
64
+ this.chunkSeconds = options.chunkSeconds ?? 0.5;
65
+ this.lookahead = options.lookaheadSeconds ?? 2;
66
+ this.pumpMs = options.pumpMs ?? 100;
67
+ this.schedule =
68
+ options.schedule ??
69
+ ((callback, ms) => {
70
+ const handle = setTimeout(callback, ms);
71
+ //a pump must never be what keeps a Node process (a test, a build tool) alive
72
+ handle.unref?.();
73
+ return handle;
74
+ });
75
+ this.unschedule = options.unschedule ?? ((handle) => clearTimeout(handle));
76
+ }
77
+ get isPlaying() {
78
+ return this.active;
79
+ }
80
+ /** how many times the queue ran dry since the channel was made */
81
+ get underrunCount() {
82
+ return this.underruns;
83
+ }
84
+ /** seconds of the source, or Infinity when it has no end */
85
+ get duration() {
86
+ return this.source?.length === undefined ? Infinity : this.source.length / this.source.sampleRate;
87
+ }
88
+ /** starts the source with these parameters, stopping whatever played before */
89
+ play(source, options = {}) {
90
+ this.stop();
91
+ this.source = source;
92
+ this.params = { volume: options.volume ?? 1, pitch: options.pitch ?? 1, pan: options.pan ?? 0 };
93
+ if (!(this.params.pitch > 0))
94
+ throw new Error('stream pitch must be positive');
95
+ this.repeat = options.repeat ?? false;
96
+ const rate = source.sampleRate;
97
+ const length = source.length;
98
+ this.loopStart = Math.max(0, Math.floor((options.loop?.start ?? 0) * rate));
99
+ this.loopEnd = options.loop ? Math.floor(options.loop.end * rate) : (length ?? null);
100
+ if (this.loopEnd !== null && this.loopEnd <= this.loopStart)
101
+ this.loopEnd = null;
102
+ this.endAfterFrames =
103
+ options.endAfterSeconds === undefined
104
+ ? Infinity
105
+ : Math.floor(options.endAfterSeconds * this.context.sampleRate);
106
+ this.endFadeFrames = Math.floor((options.endFadeSeconds ?? 2) * this.context.sampleRate);
107
+ this.wire();
108
+ this.begin(Math.max(0, Math.floor((options.pos ?? 0) * rate)));
109
+ this.active = true;
110
+ this.pump();
111
+ }
112
+ /** retunes the playing voice in place: no restart, no seek */
113
+ update(params = {}) {
114
+ if (params.volume !== undefined)
115
+ this.params.volume = params.volume;
116
+ if (params.pitch !== undefined) {
117
+ if (!(params.pitch > 0))
118
+ throw new Error('stream pitch must be positive');
119
+ this.params.pitch = params.pitch;
120
+ }
121
+ if (params.pan !== undefined)
122
+ this.params.pan = params.pan;
123
+ if (this.gainNode)
124
+ this.gainNode.gain.setTargetAtTime(this.params.volume, this.context.currentTime, 0.02);
125
+ if (this.pannerNode)
126
+ this.pannerNode.pan.value = this.params.pan;
127
+ if (this.active)
128
+ this.pump();
129
+ }
130
+ /** stops the voice, fading out over `fadeSeconds` first when positive; the playhead is remembered for a later `play` with the same `pos` */
131
+ stop(fadeSeconds = 0) {
132
+ const hadVoice = this.active || this.chunks.length > 0;
133
+ if (hadVoice && this.source)
134
+ this.lastTimeline = this.positionFrames();
135
+ const gain = this.gainNode;
136
+ const chunks = this.chunks;
137
+ const now = this.context.currentTime;
138
+ this.cancelTimer();
139
+ this.active = false;
140
+ this.chunks = [];
141
+ this.gainNode = null;
142
+ this.pannerNode = null;
143
+ if (!hadVoice)
144
+ return;
145
+ const fading = fadeSeconds > 0 && gain;
146
+ if (fading) {
147
+ gain.gain.cancelScheduledValues(now);
148
+ gain.gain.setValueAtTime(gain.gain.value, now);
149
+ gain.gain.linearRampToValueAtTime(0, now + fadeSeconds);
150
+ }
151
+ for (const chunk of chunks) {
152
+ chunk.source.onended = null;
153
+ try {
154
+ chunk.source.stop(fading ? now + fadeSeconds + 0.02 : undefined);
155
+ }
156
+ catch {
157
+ //already stopped
158
+ }
159
+ }
160
+ }
161
+ /** seconds into the source, wrapping inside the loop region while it repeats */
162
+ position() {
163
+ return this.source ? this.positionFrames() / this.source.sampleRate : 0;
164
+ }
165
+ /** the playhead as a source frame, the exact unit a save stores and a restore skips to */
166
+ positionFrames() {
167
+ const source = this.source;
168
+ if (!source)
169
+ return 0;
170
+ const now = this.context.currentTime;
171
+ let current;
172
+ for (const chunk of this.chunks)
173
+ if (chunk.when <= now)
174
+ current = chunk;
175
+ if (!current)
176
+ return Math.floor(this.timelineAt(this.chunks[0]?.timeline ?? this.lastTimeline));
177
+ const ratio = (this.params.pitch * source.sampleRate) / this.context.sampleRate;
178
+ const elapsed = Math.min(now - current.when, current.seconds) * this.context.sampleRate * ratio;
179
+ return Math.floor(this.timelineAt(current.timeline + elapsed));
180
+ }
181
+ /** moves the playhead, restarting the queue from there */
182
+ seek(seconds) {
183
+ const source = this.source;
184
+ if (!source)
185
+ return;
186
+ const wasActive = this.active;
187
+ this.stop();
188
+ this.begin(Math.max(0, Math.floor(seconds * source.sampleRate)));
189
+ if (wasActive) {
190
+ this.wire();
191
+ this.active = true;
192
+ this.pump();
193
+ }
194
+ }
195
+ /** queues whatever chunks the lookahead allows; called by the timer and safe to call each frame */
196
+ pump() {
197
+ if (!this.active || !this.source || !this.gainNode)
198
+ return;
199
+ const now = this.context.currentTime;
200
+ this.chunks = this.chunks.filter((chunk) => chunk.when + chunk.seconds >= now - 0.5);
201
+ if (this.nextWhen < now) {
202
+ if (this.chunks.length > 0 || this.outputFrames > 0) {
203
+ this.underruns++;
204
+ this.onUnderrun.dispatch(now - this.nextWhen);
205
+ onAudioUnderrun.dispatch({ source: 'stream', lateSeconds: now - this.nextWhen });
206
+ }
207
+ this.nextWhen = now + 0.02;
208
+ }
209
+ try {
210
+ while (!this.drained && this.nextWhen < now + this.lookahead)
211
+ this.queueChunk();
212
+ }
213
+ catch (error) {
214
+ this.stop();
215
+ this.onError.dispatch(error instanceof Error ? error : new Error(String(error)));
216
+ return;
217
+ }
218
+ if (this.active && !this.drained && this.pumpMs > 0 && this.timer === null) {
219
+ this.timer = this.schedule(() => {
220
+ this.timer = null;
221
+ this.pump();
222
+ }, this.pumpMs);
223
+ }
224
+ }
225
+ wire() {
226
+ const gain = this.context.createGain();
227
+ gain.gain.value = this.params.volume;
228
+ if (typeof this.context.createStereoPanner === 'function') {
229
+ const panner = this.context.createStereoPanner();
230
+ panner.pan.value = this.params.pan;
231
+ gain.connect(panner);
232
+ panner.connect(this.destination);
233
+ this.pannerNode = panner;
234
+ }
235
+ else {
236
+ gain.connect(this.destination);
237
+ }
238
+ this.gainNode = gain;
239
+ }
240
+ cancelTimer() {
241
+ if (this.timer !== null)
242
+ this.unschedule(this.timer);
243
+ this.timer = null;
244
+ }
245
+ begin(frame) {
246
+ this.source?.seek(frame);
247
+ this.readFrame = frame;
248
+ this.timeline = frame;
249
+ this.lastTimeline = frame;
250
+ this.pendingLength = 0;
251
+ this.cursor = 0;
252
+ this.sourceEnded = false;
253
+ this.drained = false;
254
+ this.outputFrames = 0;
255
+ this.nextWhen = this.context.currentTime + 0.02;
256
+ }
257
+ /** a position inside the loop region while repeating, else as given */
258
+ timelineAt(frame) {
259
+ if (this.repeat && this.loopEnd !== null && frame >= this.loopEnd) {
260
+ return this.loopStart + ((frame - this.loopStart) % (this.loopEnd - this.loopStart));
261
+ }
262
+ return frame;
263
+ }
264
+ queueChunk() {
265
+ const rate = this.context.sampleRate;
266
+ const wanted = Math.max(1, Math.ceil(this.chunkSeconds * rate));
267
+ const left = new Float32Array(wanted);
268
+ const right = new Float32Array(wanted);
269
+ const startTimeline = this.timeline;
270
+ const produced = this.fill(left, right, wanted);
271
+ if (produced === 0) {
272
+ this.finish(null);
273
+ return;
274
+ }
275
+ const buffer = this.context.createBuffer(2, produced, rate);
276
+ buffer.getChannelData(0).set(left.subarray(0, produced));
277
+ buffer.getChannelData(1).set(right.subarray(0, produced));
278
+ const node = this.context.createBufferSource();
279
+ node.buffer = buffer;
280
+ node.connect(this.gainNode);
281
+ const when = this.nextWhen;
282
+ const seconds = produced / rate;
283
+ node.start(when);
284
+ this.chunks.push({ source: node, when, seconds, timeline: startTimeline });
285
+ this.nextWhen = when + seconds;
286
+ if (produced < wanted)
287
+ this.finish(node);
288
+ }
289
+ /** marks the stream as fully queued; the ending fires when the last chunk has sounded */
290
+ finish(last) {
291
+ this.drained = true;
292
+ this.cancelTimer();
293
+ const lastNode = last ?? this.chunks.at(-1)?.source ?? null;
294
+ const done = () => {
295
+ if (!this.active)
296
+ return;
297
+ this.active = false;
298
+ this.chunks = [];
299
+ this.lastTimeline = this.timeline;
300
+ this.onEnded.dispatch();
301
+ };
302
+ const tailChunk = this.chunks.at(-1);
303
+ //a chunk that already sounded out will not fire `onended` again, so end now
304
+ if (lastNode && tailChunk && tailChunk.when + tailChunk.seconds > this.context.currentTime)
305
+ lastNode.onended = done;
306
+ else
307
+ done();
308
+ }
309
+ /** resamples source frames into the two output arrays; returns how many it wrote */
310
+ fill(left, right, count) {
311
+ const source = this.source;
312
+ const step = (this.params.pitch * source.sampleRate) / this.context.sampleRate;
313
+ let produced = 0;
314
+ while (produced < count) {
315
+ if (this.outputFrames >= this.endAfterFrames + this.endFadeFrames) {
316
+ this.sourceEnded = true;
317
+ break;
318
+ }
319
+ const index = Math.floor(this.cursor);
320
+ if (index + 1 >= this.pendingLength && !this.sourceEnded) {
321
+ this.readMore();
322
+ continue;
323
+ }
324
+ if (index >= this.pendingLength)
325
+ break;
326
+ const next = Math.min(index + 1, this.pendingLength - 1);
327
+ const fraction = this.cursor - index;
328
+ let l = this.pendingLeft[index] + (this.pendingLeft[next] - this.pendingLeft[index]) * fraction;
329
+ let r = this.pendingRight[index] + (this.pendingRight[next] - this.pendingRight[index]) * fraction;
330
+ if (this.outputFrames >= this.endAfterFrames) {
331
+ const gain = 1 - (this.outputFrames - this.endAfterFrames) / Math.max(1, this.endFadeFrames);
332
+ l *= gain;
333
+ r *= gain;
334
+ }
335
+ left[produced] = l;
336
+ right[produced] = r;
337
+ produced++;
338
+ this.outputFrames++;
339
+ this.cursor += step;
340
+ this.timeline += step;
341
+ if (this.repeat && this.loopEnd !== null && this.timeline >= this.loopEnd) {
342
+ this.timeline = this.loopStart + (this.timeline - this.loopEnd);
343
+ }
344
+ }
345
+ this.compact();
346
+ return produced;
347
+ }
348
+ /** appends the next block of source frames, wrapping to the loop start at its end */
349
+ readMore() {
350
+ const source = this.source;
351
+ let want = BLOCK_FRAMES;
352
+ if (this.repeat && this.loopEnd !== null) {
353
+ if (this.readFrame >= this.loopEnd) {
354
+ source.seek(this.loopStart);
355
+ this.readFrame = this.loopStart;
356
+ }
357
+ want = Math.min(want, this.loopEnd - this.readFrame);
358
+ }
359
+ this.ensureCapacity(this.pendingLength + want);
360
+ const got = source.read(this.pendingLeft, this.pendingRight, this.pendingLength, want);
361
+ this.pendingLength += got;
362
+ this.readFrame += got;
363
+ if (got < want && !(this.repeat && this.loopEnd !== null))
364
+ this.sourceEnded = true;
365
+ if (got === 0 && this.repeat && this.loopEnd !== null && this.readFrame < this.loopEnd)
366
+ this.sourceEnded = true;
367
+ }
368
+ ensureCapacity(frames) {
369
+ if (frames <= this.pendingLeft.length)
370
+ return;
371
+ const size = Math.max(frames, this.pendingLeft.length * 2);
372
+ const left = new Float32Array(size);
373
+ const right = new Float32Array(size);
374
+ left.set(this.pendingLeft.subarray(0, this.pendingLength));
375
+ right.set(this.pendingRight.subarray(0, this.pendingLength));
376
+ this.pendingLeft = left;
377
+ this.pendingRight = right;
378
+ }
379
+ /** drops consumed frames so the buffer does not grow with the length of the track */
380
+ compact() {
381
+ const drop = Math.floor(this.cursor);
382
+ if (drop < BLOCK_FRAMES)
383
+ return;
384
+ this.pendingLeft.copyWithin(0, drop, this.pendingLength);
385
+ this.pendingRight.copyWithin(0, drop, this.pendingLength);
386
+ this.pendingLength -= drop;
387
+ this.cursor -= drop;
388
+ }
389
+ }
390
+ //# sourceMappingURL=StreamChannel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamChannel.js","sourceRoot":"","sources":["../../src/audio/StreamChannel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { StreamChannel, type BlockSource, type StreamChannelOptions } from './StreamChannel.ts';
2
+ import type { LoopRegion } from './Channels.ts';
3
+ import type { Playable } from './Playable.ts';
4
+ export interface StreamPlayableOptions {
5
+ destination?: AudioNode;
6
+ /** forwarded to the underlying `StreamChannel` */
7
+ channel?: StreamChannelOptions;
8
+ /** repeat region in seconds while `loop` is true; the whole source when omitted */
9
+ loopRegion?: LoopRegion | null;
10
+ /** stop an endless source after this many seconds, fading the last `endFadeSeconds`; never when omitted */
11
+ endAfterSeconds?: number;
12
+ endFadeSeconds?: number;
13
+ }
14
+ /**
15
+ * A `BlockSource` behind the `Playable` surface, so `Music` and `Orchestrator` can play a
16
+ * synthesised stream without knowing it is one. The behaviours `HTMLAudioElement` leaves
17
+ * implicit, decided here once:
18
+ *
19
+ * - `currentTime` reads the playhead and moves it by seeking the source; while a source loops it
20
+ * stays inside the loop region, and once a source that does not loop has ended it holds the
21
+ * duration. An endless source reports `Infinity` for `duration`.
22
+ * - `playbackRate` is supported by resampling: speed and pitch change together, from the next
23
+ * queued chunk (up to the lookahead later).
24
+ * - `onended` fires once when a source that does not loop plays out or an endless source reaches
25
+ * `endAfterSeconds`; a looping source never fires it.
26
+ * - the source's own sample rate is converted to the context's, so the caller never creates a
27
+ * context at a particular rate.
28
+ * - `play()` resumes a suspended context first, which a browser only allows after a gesture.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { StreamPlayable, type BlockSource } from '@datamoc/mw_games/audio';
33
+ *
34
+ * declare const context: AudioContext;
35
+ * declare const tune: BlockSource;
36
+ *
37
+ * const stream = new StreamPlayable(context, tune);
38
+ * stream.loop = true;
39
+ * stream.volume = 0.7;
40
+ * void stream.play();
41
+ * ```
42
+ */
43
+ export declare class StreamPlayable implements Playable {
44
+ onended: ((event: Event) => unknown) | null;
45
+ /** the underlying voice, for underrun reports and the other things `Playable` has no word for */
46
+ readonly channel: StreamChannel;
47
+ private readonly context;
48
+ private readonly source;
49
+ private readonly options;
50
+ private volume_;
51
+ private loop_;
52
+ private rate_;
53
+ private position_;
54
+ private paused_;
55
+ private ended_;
56
+ constructor(context: AudioContext, source: BlockSource, options?: StreamPlayableOptions);
57
+ /** seconds of the source, Infinity when it has no end */
58
+ get duration(): number;
59
+ get paused(): boolean;
60
+ get ended(): boolean;
61
+ get currentTime(): number;
62
+ set currentTime(seconds: number);
63
+ get volume(): number;
64
+ set volume(level: number);
65
+ get loop(): boolean;
66
+ set loop(value: boolean);
67
+ get playbackRate(): number;
68
+ set playbackRate(rate: number);
69
+ play(): Promise<void>;
70
+ pause(): void;
71
+ private start;
72
+ }
73
+ //# sourceMappingURL=StreamPlayable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamPlayable.d.ts","sourceRoot":"","sources":["../../src/audio/StreamPlayable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,qBAAqB;IACrC,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,kDAAkD;IAClD,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,mFAAmF;IACnF,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,2GAA2G;IAC3G,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,cAAe,YAAW,QAAQ;IAC9C,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,GAAG,IAAI,CAAQ;IACnD,iGAAiG;IACjG,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,qBAA0B,EAW1F;IAED,yDAAyD;IACzD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,WAAW,IAAI,MAAM,CAGxB;IAED,IAAI,WAAW,CAAC,OAAO,EAAE,MAAM,EAK9B;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAGvB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAKtB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,EAI5B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1B;IAED,KAAK,IAAI,IAAI,CAKZ;IAED,OAAO,CAAC,KAAK;CAWb"}
@@ -0,0 +1,130 @@
1
+ import { StreamChannel } from './StreamChannel.js';
2
+ /**
3
+ * A `BlockSource` behind the `Playable` surface, so `Music` and `Orchestrator` can play a
4
+ * synthesised stream without knowing it is one. The behaviours `HTMLAudioElement` leaves
5
+ * implicit, decided here once:
6
+ *
7
+ * - `currentTime` reads the playhead and moves it by seeking the source; while a source loops it
8
+ * stays inside the loop region, and once a source that does not loop has ended it holds the
9
+ * duration. An endless source reports `Infinity` for `duration`.
10
+ * - `playbackRate` is supported by resampling: speed and pitch change together, from the next
11
+ * queued chunk (up to the lookahead later).
12
+ * - `onended` fires once when a source that does not loop plays out or an endless source reaches
13
+ * `endAfterSeconds`; a looping source never fires it.
14
+ * - the source's own sample rate is converted to the context's, so the caller never creates a
15
+ * context at a particular rate.
16
+ * - `play()` resumes a suspended context first, which a browser only allows after a gesture.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { StreamPlayable, type BlockSource } from '@datamoc/mw_games/audio';
21
+ *
22
+ * declare const context: AudioContext;
23
+ * declare const tune: BlockSource;
24
+ *
25
+ * const stream = new StreamPlayable(context, tune);
26
+ * stream.loop = true;
27
+ * stream.volume = 0.7;
28
+ * void stream.play();
29
+ * ```
30
+ */
31
+ export class StreamPlayable {
32
+ constructor(context, source, options = {}) {
33
+ this.onended = null;
34
+ this.volume_ = 1;
35
+ this.loop_ = false;
36
+ this.rate_ = 1;
37
+ this.position_ = 0;
38
+ this.paused_ = true;
39
+ this.ended_ = false;
40
+ this.context = context;
41
+ this.source = source;
42
+ this.options = options;
43
+ this.channel = new StreamChannel(context, options.destination, options.channel);
44
+ this.channel.onEnded.add(() => {
45
+ this.ended_ = true;
46
+ this.paused_ = true;
47
+ this.position_ = this.duration;
48
+ if (this.onended)
49
+ this.onended(typeof Event === 'function' ? new Event('ended') : {});
50
+ });
51
+ }
52
+ /** seconds of the source, Infinity when it has no end */
53
+ get duration() {
54
+ return this.source.length === undefined ? Infinity : this.source.length / this.source.sampleRate;
55
+ }
56
+ get paused() {
57
+ return this.paused_;
58
+ }
59
+ get ended() {
60
+ return this.ended_;
61
+ }
62
+ get currentTime() {
63
+ if (this.ended_)
64
+ return Number.isFinite(this.duration) ? this.duration : this.position_;
65
+ return this.channel.isPlaying ? this.channel.position() : this.position_;
66
+ }
67
+ set currentTime(seconds) {
68
+ const target = Math.max(0, Number(seconds) || 0);
69
+ this.ended_ = false;
70
+ if (this.channel.isPlaying)
71
+ this.channel.seek(target);
72
+ else
73
+ this.position_ = target;
74
+ }
75
+ get volume() {
76
+ return this.volume_;
77
+ }
78
+ set volume(level) {
79
+ this.volume_ = Math.min(1, Math.max(0, level));
80
+ this.channel.update({ volume: this.volume_ });
81
+ }
82
+ get loop() {
83
+ return this.loop_;
84
+ }
85
+ set loop(value) {
86
+ if (value === this.loop_)
87
+ return;
88
+ this.loop_ = value;
89
+ //the repeat mode is fixed when a stream starts, so a playing one restarts where it is
90
+ if (this.channel.isPlaying)
91
+ this.start(this.channel.position());
92
+ }
93
+ get playbackRate() {
94
+ return this.rate_;
95
+ }
96
+ set playbackRate(rate) {
97
+ if (!(rate > 0))
98
+ return;
99
+ this.rate_ = rate;
100
+ this.channel.update({ pitch: rate });
101
+ }
102
+ async play() {
103
+ if (this.context.state === 'suspended')
104
+ await this.context.resume();
105
+ if (this.ended_)
106
+ this.position_ = 0;
107
+ this.ended_ = false;
108
+ this.paused_ = false;
109
+ this.start(this.position_);
110
+ }
111
+ pause() {
112
+ if (this.paused_)
113
+ return;
114
+ this.position_ = this.channel.isPlaying ? this.channel.position() : this.position_;
115
+ this.paused_ = true;
116
+ this.channel.stop();
117
+ }
118
+ start(seconds) {
119
+ this.channel.play(this.source, {
120
+ volume: this.volume_,
121
+ pitch: this.rate_,
122
+ pos: seconds,
123
+ repeat: this.loop_,
124
+ loop: this.options.loopRegion,
125
+ endAfterSeconds: this.options.endAfterSeconds,
126
+ endFadeSeconds: this.options.endFadeSeconds,
127
+ });
128
+ }
129
+ }
130
+ //# sourceMappingURL=StreamPlayable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamPlayable.js","sourceRoot":"","sources":["../../src/audio/StreamPlayable.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import { Sound, type SoundOptions } from './Sound.ts';
6
6
  * sounds, exposed here as something a running game can call itself to generate a tone or a
7
7
  * procedural SFX on the fly, rather than only ever play a pre-baked clip.
8
8
  */
9
- export type Waveform = 'square' | 'triangle' | 'sine' | 'noise';
9
+ export type Waveform = 'square' | 'triangle' | 'sine' | 'sawtooth' | 'noise';
10
10
  export interface ToneOptions {
11
11
  waveform?: Waveform;
12
12
  /** Hz; ignored for `'noise'` */
@@ -1 +1 @@
1
- {"version":3,"file":"Synth.d.ts","sourceRoot":"","sources":["../../src/audio/Synth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,WAAgB,GAAG,MAAM,CAsBhE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACvB,OAAO,GAAE,WAAgB,EACzB,MAAM,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,QAAkC,GAC7D,QAAQ,CAMV;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,WAAgB,EAAE,KAAK,GAAE,YAAiB,GAAG,KAAK,CAEvF"}
1
+ {"version":3,"file":"Synth.d.ts","sourceRoot":"","sources":["../../src/audio/Synth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC;AAE7E,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,WAAgB,GAAG,MAAM,CAsBhE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACvB,OAAO,GAAE,WAAgB,EACzB,MAAM,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,QAAkC,GAC7D,QAAQ,CAMV;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,WAAgB,EAAE,KAAK,GAAE,YAAiB,GAAG,KAAK,CAEvF"}
@@ -76,6 +76,10 @@ function waveSample(waveform, phase, seed, index) {
76
76
  return Math.sin(phase);
77
77
  case 'square':
78
78
  return Math.sin(phase) >= 0 ? 1 : -1;
79
+ case 'sawtooth': {
80
+ const cycles = phase / (2 * Math.PI);
81
+ return 2 * (cycles - Math.floor(cycles)) - 1;
82
+ }
79
83
  case 'triangle': {
80
84
  const cycles = phase / (2 * Math.PI);
81
85
  const f = cycles - Math.floor(cycles);