@datamoc/mw_games 0.43.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 (255) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +22 -0
  3. package/REFERENCE.md +82 -6
  4. package/api-surface.json +1028 -2
  5. package/dist/actors/Resource.d.ts.map +1 -1
  6. package/dist/actors/Resource.js +2 -3
  7. package/dist/actors/Resource.js.map +1 -1
  8. package/dist/audio/AdaptiveMusic.d.ts +85 -0
  9. package/dist/audio/AdaptiveMusic.d.ts.map +1 -0
  10. package/dist/audio/AdaptiveMusic.js +230 -0
  11. package/dist/audio/AdaptiveMusic.js.map +1 -0
  12. package/dist/audio/AudioLoader.d.ts +7 -2
  13. package/dist/audio/AudioLoader.d.ts.map +1 -1
  14. package/dist/audio/AudioLoader.js +23 -18
  15. package/dist/audio/AudioLoader.js.map +1 -1
  16. package/dist/audio/AudioViews.d.ts +70 -0
  17. package/dist/audio/AudioViews.d.ts.map +1 -0
  18. package/dist/audio/AudioViews.js +119 -0
  19. package/dist/audio/AudioViews.js.map +1 -0
  20. package/dist/audio/BusBudget.d.ts +89 -0
  21. package/dist/audio/BusBudget.d.ts.map +1 -0
  22. package/dist/audio/BusBudget.js +120 -0
  23. package/dist/audio/BusBudget.js.map +1 -0
  24. package/dist/audio/Channels.d.ts +65 -1
  25. package/dist/audio/Channels.d.ts.map +1 -1
  26. package/dist/audio/Channels.js +117 -30
  27. package/dist/audio/Channels.js.map +1 -1
  28. package/dist/audio/CoreHost.d.ts +114 -0
  29. package/dist/audio/CoreHost.d.ts.map +1 -0
  30. package/dist/audio/CoreHost.js +335 -0
  31. package/dist/audio/CoreHost.js.map +1 -0
  32. package/dist/audio/DecoderGuard.d.ts +139 -0
  33. package/dist/audio/DecoderGuard.d.ts.map +1 -0
  34. package/dist/audio/DecoderGuard.js +208 -0
  35. package/dist/audio/DecoderGuard.js.map +1 -0
  36. package/dist/audio/Fft.d.ts +3 -0
  37. package/dist/audio/Fft.d.ts.map +1 -0
  38. package/dist/audio/Fft.js +45 -0
  39. package/dist/audio/Fft.js.map +1 -0
  40. package/dist/audio/LoopTags.d.ts +18 -0
  41. package/dist/audio/LoopTags.d.ts.map +1 -1
  42. package/dist/audio/LoopTags.js +20 -0
  43. package/dist/audio/LoopTags.js.map +1 -1
  44. package/dist/audio/Midi.d.ts +35 -0
  45. package/dist/audio/Midi.d.ts.map +1 -1
  46. package/dist/audio/Midi.js +71 -1
  47. package/dist/audio/Midi.js.map +1 -1
  48. package/dist/audio/MidiRender.d.ts +31 -0
  49. package/dist/audio/MidiRender.d.ts.map +1 -1
  50. package/dist/audio/MidiRender.js +79 -2
  51. package/dist/audio/MidiRender.js.map +1 -1
  52. package/dist/audio/MidiSynth.d.ts +147 -0
  53. package/dist/audio/MidiSynth.d.ts.map +1 -0
  54. package/dist/audio/MidiSynth.js +205 -0
  55. package/dist/audio/MidiSynth.js.map +1 -0
  56. package/dist/audio/Playable.d.ts +7 -0
  57. package/dist/audio/Playable.d.ts.map +1 -1
  58. package/dist/audio/Playable.js.map +1 -1
  59. package/dist/audio/Positional.d.ts +12 -4
  60. package/dist/audio/Positional.d.ts.map +1 -1
  61. package/dist/audio/Positional.js +5 -2
  62. package/dist/audio/Positional.js.map +1 -1
  63. package/dist/audio/Resample.d.ts +61 -0
  64. package/dist/audio/Resample.d.ts.map +1 -0
  65. package/dist/audio/Resample.js +97 -0
  66. package/dist/audio/Resample.js.map +1 -0
  67. package/dist/audio/Settings.d.ts +99 -0
  68. package/dist/audio/Settings.d.ts.map +1 -0
  69. package/dist/audio/Settings.js +134 -0
  70. package/dist/audio/Settings.js.map +1 -0
  71. package/dist/audio/Sfx.d.ts +148 -0
  72. package/dist/audio/Sfx.d.ts.map +1 -0
  73. package/dist/audio/Sfx.js +213 -0
  74. package/dist/audio/Sfx.js.map +1 -0
  75. package/dist/audio/SpectralDistance.d.ts +34 -0
  76. package/dist/audio/SpectralDistance.d.ts.map +1 -0
  77. package/dist/audio/SpectralDistance.js +64 -0
  78. package/dist/audio/SpectralDistance.js.map +1 -0
  79. package/dist/audio/StreamChannel.d.ts +150 -0
  80. package/dist/audio/StreamChannel.d.ts.map +1 -0
  81. package/dist/audio/StreamChannel.js +390 -0
  82. package/dist/audio/StreamChannel.js.map +1 -0
  83. package/dist/audio/StreamPlayable.d.ts +73 -0
  84. package/dist/audio/StreamPlayable.d.ts.map +1 -0
  85. package/dist/audio/StreamPlayable.js +130 -0
  86. package/dist/audio/StreamPlayable.js.map +1 -0
  87. package/dist/audio/SyxBank.d.ts +73 -0
  88. package/dist/audio/SyxBank.d.ts.map +1 -0
  89. package/dist/audio/SyxBank.js +130 -0
  90. package/dist/audio/SyxBank.js.map +1 -0
  91. package/dist/audio/Underrun.d.ts +25 -0
  92. package/dist/audio/Underrun.d.ts.map +1 -0
  93. package/dist/audio/Underrun.js +18 -0
  94. package/dist/audio/Underrun.js.map +1 -0
  95. package/dist/audio/Wav.d.ts +56 -0
  96. package/dist/audio/Wav.d.ts.map +1 -0
  97. package/dist/audio/Wav.js +164 -0
  98. package/dist/audio/Wav.js.map +1 -0
  99. package/dist/audio/WebMidi.d.ts +58 -0
  100. package/dist/audio/WebMidi.d.ts.map +1 -0
  101. package/dist/audio/WebMidi.js +79 -0
  102. package/dist/audio/WebMidi.js.map +1 -0
  103. package/dist/audio/humanize/Onsets.d.ts.map +1 -1
  104. package/dist/audio/humanize/Onsets.js +1 -44
  105. package/dist/audio/humanize/Onsets.js.map +1 -1
  106. package/dist/audio/index.d.ts +38 -6
  107. package/dist/audio/index.d.ts.map +1 -1
  108. package/dist/audio/index.js +19 -3
  109. package/dist/audio/index.js.map +1 -1
  110. package/dist/battle/AttackPreview.d.ts.map +1 -1
  111. package/dist/battle/AttackPreview.js +8 -8
  112. package/dist/battle/AttackPreview.js.map +1 -1
  113. package/dist/battle/StatStages.d.ts.map +1 -1
  114. package/dist/battle/StatStages.js +2 -1
  115. package/dist/battle/StatStages.js.map +1 -1
  116. package/dist/board/Engine.d.ts +2 -0
  117. package/dist/board/Engine.d.ts.map +1 -1
  118. package/dist/board/Engine.js +1 -1
  119. package/dist/board/Engine.js.map +1 -1
  120. package/dist/board/HexSkirmish.d.ts.map +1 -1
  121. package/dist/board/HexSkirmish.js +11 -72
  122. package/dist/board/HexSkirmish.js.map +1 -1
  123. package/dist/board/Tactics.d.ts.map +1 -1
  124. package/dist/board/Tactics.js +3 -5
  125. package/dist/board/Tactics.js.map +1 -1
  126. package/dist/board/tourney.d.ts.map +1 -1
  127. package/dist/board/tourney.js +2 -19
  128. package/dist/board/tourney.js.map +1 -1
  129. package/dist/core/Handles.js +7 -7
  130. package/dist/core/Handles.js.map +1 -1
  131. package/dist/mw_games.global.js +25 -22
  132. package/dist/mwl/conditions.d.ts +12 -0
  133. package/dist/mwl/conditions.d.ts.map +1 -1
  134. package/dist/mwl/conditions.js +49 -8
  135. package/dist/mwl/conditions.js.map +1 -1
  136. package/dist/mwl/index.d.ts +11 -0
  137. package/dist/mwl/index.d.ts.map +1 -1
  138. package/dist/mwl/index.js +6 -0
  139. package/dist/mwl/index.js.map +1 -1
  140. package/dist/mwl/package-container.d.ts +185 -0
  141. package/dist/mwl/package-container.d.ts.map +1 -0
  142. package/dist/mwl/package-container.js +446 -0
  143. package/dist/mwl/package-container.js.map +1 -0
  144. package/dist/mwl/package-document.d.ts +217 -0
  145. package/dist/mwl/package-document.d.ts.map +1 -0
  146. package/dist/mwl/package-document.js +639 -0
  147. package/dist/mwl/package-document.js.map +1 -0
  148. package/dist/mwl/package-rom.d.ts +37 -0
  149. package/dist/mwl/package-rom.d.ts.map +1 -0
  150. package/dist/mwl/package-rom.js +96 -0
  151. package/dist/mwl/package-rom.js.map +1 -0
  152. package/dist/mwl/package-verify.d.ts +34 -0
  153. package/dist/mwl/package-verify.d.ts.map +1 -0
  154. package/dist/mwl/package-verify.js +111 -0
  155. package/dist/mwl/package-verify.js.map +1 -0
  156. package/dist/mwl/package.d.ts +184 -0
  157. package/dist/mwl/package.d.ts.map +1 -0
  158. package/dist/mwl/package.js +382 -0
  159. package/dist/mwl/package.js.map +1 -0
  160. package/dist/physics/PhysicsWorld.d.ts.map +1 -1
  161. package/dist/physics/PhysicsWorld.js +4 -3
  162. package/dist/physics/PhysicsWorld.js.map +1 -1
  163. package/dist/physics/XpbdWorld.d.ts.map +1 -1
  164. package/dist/physics/XpbdWorld.js +2 -1
  165. package/dist/physics/XpbdWorld.js.map +1 -1
  166. package/dist/testing/FakeAudio.d.ts +149 -0
  167. package/dist/testing/FakeAudio.d.ts.map +1 -0
  168. package/dist/testing/FakeAudio.js +219 -0
  169. package/dist/testing/FakeAudio.js.map +1 -0
  170. package/dist/testing/index.d.ts +2 -0
  171. package/dist/testing/index.d.ts.map +1 -1
  172. package/dist/testing/index.js +1 -0
  173. package/dist/testing/index.js.map +1 -1
  174. package/dist/threads/index.d.ts +2 -2
  175. package/dist/threads/index.d.ts.map +1 -1
  176. package/dist/threads/index.js +1 -1
  177. package/dist/threads/index.js.map +1 -1
  178. package/dist/threads/spawn.d.ts +32 -0
  179. package/dist/threads/spawn.d.ts.map +1 -1
  180. package/dist/threads/spawn.js +35 -11
  181. package/dist/threads/spawn.js.map +1 -1
  182. package/dist/two-d/Game.d.ts +13 -2
  183. package/dist/two-d/Game.d.ts.map +1 -1
  184. package/dist/two-d/Game.js +42 -18
  185. package/dist/two-d/Game.js.map +1 -1
  186. package/dist/two-d/render/HazeLayer.d.ts.map +1 -1
  187. package/dist/two-d/render/HazeLayer.js +2 -1
  188. package/dist/two-d/render/HazeLayer.js.map +1 -1
  189. package/dist/two-d/render/ImageModifiers.d.ts +4 -2
  190. package/dist/two-d/render/ImageModifiers.d.ts.map +1 -1
  191. package/dist/two-d/render/ImageModifiers.js +13 -8
  192. package/dist/two-d/render/ImageModifiers.js.map +1 -1
  193. package/dist/two-d/render/NeonGlow.d.ts.map +1 -1
  194. package/dist/two-d/render/NeonGlow.js +2 -1
  195. package/dist/two-d/render/NeonGlow.js.map +1 -1
  196. package/dist/two-d/render/Particles.d.ts.map +1 -1
  197. package/dist/two-d/render/Particles.js +2 -1
  198. package/dist/two-d/render/Particles.js.map +1 -1
  199. package/dist/two-d/render/SceneTransition.d.ts +29 -0
  200. package/dist/two-d/render/SceneTransition.d.ts.map +1 -1
  201. package/dist/two-d/render/SceneTransition.js +27 -0
  202. package/dist/two-d/render/SceneTransition.js.map +1 -1
  203. package/dist/two-d/render/ScreenEffects.d.ts.map +1 -1
  204. package/dist/two-d/render/ScreenEffects.js +2 -1
  205. package/dist/two-d/render/ScreenEffects.js.map +1 -1
  206. package/dist/two-d/render/SkeletalSprite.d.ts.map +1 -1
  207. package/dist/two-d/render/SkeletalSprite.js +2 -1
  208. package/dist/two-d/render/SkeletalSprite.js.map +1 -1
  209. package/dist/two-d/render/Types2D.d.ts +2 -2
  210. package/dist/two-d/render/Types2D.d.ts.map +1 -1
  211. package/dist/two-d/render/Types2D.js +2 -2
  212. package/dist/two-d/render/Types2D.js.map +1 -1
  213. package/dist/two-d/render/index.d.ts +2 -2
  214. package/dist/two-d/render/index.d.ts.map +1 -1
  215. package/dist/two-d/render/index.js +1 -1
  216. package/dist/two-d/render/index.js.map +1 -1
  217. package/dist/two-d/ui/Bar.d.ts.map +1 -1
  218. package/dist/two-d/ui/Bar.js +3 -5
  219. package/dist/two-d/ui/Bar.js.map +1 -1
  220. package/dist/two-d/ui/Dropdown.d.ts +5 -1
  221. package/dist/two-d/ui/Dropdown.d.ts.map +1 -1
  222. package/dist/two-d/ui/Dropdown.js +8 -1
  223. package/dist/two-d/ui/Dropdown.js.map +1 -1
  224. package/dist/two-d/ui/DropdownView.d.ts +15 -1
  225. package/dist/two-d/ui/DropdownView.d.ts.map +1 -1
  226. package/dist/two-d/ui/DropdownView.js +21 -3
  227. package/dist/two-d/ui/DropdownView.js.map +1 -1
  228. package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
  229. package/dist/two-d/ui/FloatingText.js +2 -1
  230. package/dist/two-d/ui/FloatingText.js.map +1 -1
  231. package/dist/two-d/ui/IconGrid.d.ts.map +1 -1
  232. package/dist/two-d/ui/IconGrid.js +2 -1
  233. package/dist/two-d/ui/IconGrid.js.map +1 -1
  234. package/dist/two-d/ui/ListView.d.ts.map +1 -1
  235. package/dist/two-d/ui/ListView.js +2 -1
  236. package/dist/two-d/ui/ListView.js.map +1 -1
  237. package/dist/two-d/ui/ScrollBox.d.ts +6 -0
  238. package/dist/two-d/ui/ScrollBox.d.ts.map +1 -1
  239. package/dist/two-d/ui/ScrollBox.js +12 -1
  240. package/dist/two-d/ui/ScrollBox.js.map +1 -1
  241. package/dist/two-d/ui/ScrollText.d.ts +11 -2
  242. package/dist/two-d/ui/ScrollText.d.ts.map +1 -1
  243. package/dist/two-d/ui/ScrollText.js +18 -2
  244. package/dist/two-d/ui/ScrollText.js.map +1 -1
  245. package/dist/two-d/ui/Slider.d.ts.map +1 -1
  246. package/dist/two-d/ui/Slider.js +4 -4
  247. package/dist/two-d/ui/Slider.js.map +1 -1
  248. package/dist/version.d.ts +1 -1
  249. package/dist/version.js +1 -1
  250. package/package.json +8 -1
  251. package/sbom.cdx.json +6 -6
  252. package/tools/audio.d.mts +75 -0
  253. package/tools/audio.mjs +437 -0
  254. package/tools/mwl.mjs +144 -2
  255. package/tools.md +1 -1
@@ -3,6 +3,13 @@
3
3
  * already provides, named here so a test (or a game with its own audio backend) can supply
4
4
  * a fake without a real `Audio` element existing, which nothing outside a browser can
5
5
  * create.
6
+ *
7
+ * A backend that synthesises its sound instead of decoding a file (see `StreamPlayable`) has to
8
+ * answer the questions `HTMLAudioElement` answers implicitly: `currentTime` reads and seeks the
9
+ * playhead and holds the duration once a track that does not loop has ended; `playbackRate` is
10
+ * honoured (by resampling) or ignored but never throws; `onended` fires once at the end of a
11
+ * track that does not loop and never for one that does; and `play()` may return a promise that
12
+ * settles when the audio is allowed to start.
6
13
  */
7
14
  export interface Playable {
8
15
  play(): void | Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"Playable.d.ts","sourceRoot":"","sources":["../../src/audio/Playable.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACxB,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;+EAC2E;IAC3E,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAUlD"}
1
+ {"version":3,"file":"Playable.d.ts","sourceRoot":"","sources":["../../src/audio/Playable.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,QAAQ;IACxB,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;+EAC2E;IAC3E,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAUlD"}
@@ -1 +1 @@
1
- {"version":3,"file":"Playable.js","sourceRoot":"","sources":["../../src/audio/Playable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AAqBhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACvC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACJ,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,0FAA0F;QAC1F,OAAO,CAAC,IAAI,CAAC,GAAI,KAAe,CAAC,OAAO,OAAO,IAAI,kBAAkB,CAAC,CAAC;QACvE,OAAO,cAAc,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,4FAA4F;AAC5F,SAAS,cAAc;IACtB,OAAO,EAAE,IAAI,KAAI,CAAC,EAAE,KAAK,KAAI,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1E,CAAC"}
1
+ {"version":3,"file":"Playable.js","sourceRoot":"","sources":["../../src/audio/Playable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AA4BhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACvC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACJ,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,0FAA0F;QAC1F,OAAO,CAAC,IAAI,CAAC,GAAI,KAAe,CAAC,OAAO,OAAO,IAAI,kBAAkB,CAAC,CAAC;QACvE,OAAO,cAAc,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,4FAA4F;AAC5F,SAAS,cAAc;IACtB,OAAO,EAAE,IAAI,KAAI,CAAC,EAAE,KAAK,KAAI,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1E,CAAC"}
@@ -1,4 +1,7 @@
1
- import type { Sound } from './Sound.ts';
1
+ /** anything a `SoundSource` can play at a mix: a `Sound`, or an `SfxSound` that also takes a pan */
2
+ export interface PositionalSound {
3
+ play(gain?: number, pitch?: number, pan?: number): unknown;
4
+ }
2
5
  /** A point in the same 2D space the game's camera works in, in world units. */
3
6
  export interface AudioPoint {
4
7
  x: number;
@@ -113,13 +116,18 @@ export declare class SoundSource implements AudioPoint {
113
116
  y: number;
114
117
  private readonly sound;
115
118
  private readonly falloff;
116
- constructor(sound: Sound, options?: SoundSourceOptions);
119
+ constructor(sound: PositionalSound, options?: SoundSourceOptions);
117
120
  moveTo(x: number, y: number): void;
118
121
  gainTo(listener: AudioPoint): number;
119
122
  panTo(listener: AudioPoint & {
120
123
  facing: number;
121
124
  }): number;
122
- /** Plays the source at the gain the listener hears it; returns the gain actually used. */
123
- playFor(listener: AudioPoint): number;
125
+ /**
126
+ * Plays the source at the gain the listener hears it; returns the gain actually used. A listener
127
+ * with a `facing` also pans the sound, for a sound that takes a pan (a `Sound` ignores it).
128
+ */
129
+ playFor(listener: AudioPoint | (AudioPoint & {
130
+ facing: number;
131
+ })): number;
124
132
  }
125
133
  //# sourceMappingURL=Positional.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Positional.d.ts","sourceRoot":"","sources":["../../src/audio/Positional.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACV;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC5B,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAU9E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAW9F;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAc,YAAW,UAAU;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IAEV,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IAEf,YAAY,OAAO,GAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,EAIpE;IAED,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAGjC;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1B;CACD;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACvD,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,WAAY,YAAW,UAAU;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IAEV,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,YAAY,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,EASzD;IAED,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAGjC;IAED,MAAM,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAEnC;IAED,KAAK,CAAC,QAAQ,EAAE,UAAU,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAEvD;IAED,0FAA0F;IAC1F,OAAO,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAKpC;CACD"}
1
+ {"version":3,"file":"Positional.d.ts","sourceRoot":"","sources":["../../src/audio/Positional.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,MAAM,WAAW,eAAe;IAC/B,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3D;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACV;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC5B,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAU9E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAW9F;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAc,YAAW,UAAU;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IAEV,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IAEf,YAAY,OAAO,GAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,EAIpE;IAED,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAGjC;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1B;CACD;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACvD,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,WAAY,YAAW,UAAU;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IAEV,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,YAAY,KAAK,EAAE,eAAe,EAAE,OAAO,GAAE,kBAAuB,EASnE;IAED,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAGjC;IAED,MAAM,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAEnC;IAED,KAAK,CAAC,QAAQ,EAAE,UAAU,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAEvD;IAED;;;OAGG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,GAAG,CAAC,UAAU,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,MAAM,CAKxE;CACD"}
@@ -123,12 +123,15 @@ export class SoundSource {
123
123
  panTo(listener) {
124
124
  return audioPan(listener, this);
125
125
  }
126
- /** Plays the source at the gain the listener hears it; returns the gain actually used. */
126
+ /**
127
+ * Plays the source at the gain the listener hears it; returns the gain actually used. A listener
128
+ * with a `facing` also pans the sound, for a sound that takes a pan (a `Sound` ignores it).
129
+ */
127
130
  playFor(listener) {
128
131
  const gain = this.gainTo(listener);
129
132
  if (gain <= 0)
130
133
  return 0;
131
- this.sound.play(gain);
134
+ this.sound.play(gain, 1, 'facing' in listener ? this.panTo(listener) : 0);
132
135
  return gain;
133
136
  }
134
137
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Positional.js","sourceRoot":"","sources":["../../src/audio/Positional.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Positional.js","sourceRoot":"","sources":["../../src/audio/Positional.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Plain-array helpers for fitting rendered audio to a budget: cut to a length with a fade,
3
+ * fold to mono, halve the rate, convert between rates. They work on the `Float32Array`
4
+ * pairs a render produces, so nothing here needs an `AudioContext`.
5
+ */
6
+ /** stereo frames at a sample rate: the shape every renderer in this module hands back */
7
+ export interface StereoFrames {
8
+ sampleRate: number;
9
+ left: Float32Array;
10
+ right: Float32Array;
11
+ }
12
+ /**
13
+ * Converts stereo frames to another sample rate by linear interpolation. Linear is audibly
14
+ * soft above the new Nyquist, which is the right trade for ambience and background music where
15
+ * this is used to save memory; a game that needs a clean conversion renders at the target
16
+ * rate instead.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { resampleStereo } from '@datamoc/mw_games/audio';
21
+ *
22
+ * declare const frames: { sampleRate: number; left: Float32Array; right: Float32Array };
23
+ *
24
+ * const half = resampleStereo(frames, frames.sampleRate / 2);
25
+ * console.log(half.left.length);
26
+ * ```
27
+ */
28
+ export declare function resampleStereo<T extends StereoFrames>(frames: T, sampleRate: number): T;
29
+ /** what a track may cost: every field optional, an omitted one leaves that dimension alone */
30
+ export interface RenderBudget {
31
+ /** cut the track to this many seconds, fading the cut so it does not click */
32
+ maxSeconds?: number;
33
+ /** seconds of fade at a cut; 0.5 when omitted */
34
+ fadeSeconds?: number;
35
+ /** fold both channels into one, stored twice-referenced so a stereo consumer still works */
36
+ mono?: boolean;
37
+ /** halve the sample rate, for ambience that carries no high frequencies */
38
+ halfRate?: boolean;
39
+ }
40
+ /**
41
+ * Fits a rendered track to a budget: cuts it to `maxSeconds` with a fade, folds it to mono
42
+ * and halves its rate, in that order. A mono track shares one array between both channels, so
43
+ * it costs half the memory in the renderer's hands even though a stereo `AudioBuffer` made
44
+ * from it later still has two. Loop points past the cut are pulled back to the new end.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { fitToBudget, renderMidiToBuffer, parseMidi } from '@datamoc/mw_games/audio';
49
+ *
50
+ * declare const midiBytes: ArrayBuffer;
51
+ *
52
+ * const ambience = fitToBudget(renderMidiToBuffer(parseMidi(midiBytes)), { maxSeconds: 60, mono: true, halfRate: true });
53
+ * console.log(ambience.duration);
54
+ * ```
55
+ */
56
+ export declare function fitToBudget<T extends StereoFrames & {
57
+ loopStart?: number;
58
+ loopEnd?: number;
59
+ duration?: number;
60
+ }>(track: T, budget: RenderBudget): T;
61
+ //# sourceMappingURL=Resample.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Resample.d.ts","sourceRoot":"","sources":["../../src/audio/Resample.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,CAiBvF;AAED,8FAA8F;AAC9F,MAAM,WAAW,YAAY;IAC5B,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/G,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,YAAY,GAClB,CAAC,CAyBH"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Plain-array helpers for fitting rendered audio to a budget: cut to a length with a fade,
3
+ * fold to mono, halve the rate, convert between rates. They work on the `Float32Array`
4
+ * pairs a render produces, so nothing here needs an `AudioContext`.
5
+ */
6
+ /**
7
+ * Converts stereo frames to another sample rate by linear interpolation. Linear is audibly
8
+ * soft above the new Nyquist, which is the right trade for ambience and background music where
9
+ * this is used to save memory; a game that needs a clean conversion renders at the target
10
+ * rate instead.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { resampleStereo } from '@datamoc/mw_games/audio';
15
+ *
16
+ * declare const frames: { sampleRate: number; left: Float32Array; right: Float32Array };
17
+ *
18
+ * const half = resampleStereo(frames, frames.sampleRate / 2);
19
+ * console.log(half.left.length);
20
+ * ```
21
+ */
22
+ export function resampleStereo(frames, sampleRate) {
23
+ if (!(sampleRate > 0))
24
+ throw new Error('target sample rate must be positive');
25
+ if (sampleRate === frames.sampleRate)
26
+ return frames;
27
+ const length = Math.max(1, Math.round((frames.left.length * sampleRate) / frames.sampleRate));
28
+ const step = frames.sampleRate / sampleRate;
29
+ const left = new Float32Array(length);
30
+ const right = new Float32Array(length);
31
+ const last = frames.left.length - 1;
32
+ for (let i = 0; i < length; i++) {
33
+ const position = i * step;
34
+ const floor = Math.min(last, Math.floor(position));
35
+ const next = Math.min(last, floor + 1);
36
+ const fraction = position - floor;
37
+ left[i] = frames.left[floor] + (frames.left[next] - frames.left[floor]) * fraction;
38
+ right[i] = frames.right[floor] + (frames.right[next] - frames.right[floor]) * fraction;
39
+ }
40
+ return { ...frames, sampleRate, left, right };
41
+ }
42
+ /**
43
+ * Fits a rendered track to a budget: cuts it to `maxSeconds` with a fade, folds it to mono
44
+ * and halves its rate, in that order. A mono track shares one array between both channels, so
45
+ * it costs half the memory in the renderer's hands even though a stereo `AudioBuffer` made
46
+ * from it later still has two. Loop points past the cut are pulled back to the new end.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * import { fitToBudget, renderMidiToBuffer, parseMidi } from '@datamoc/mw_games/audio';
51
+ *
52
+ * declare const midiBytes: ArrayBuffer;
53
+ *
54
+ * const ambience = fitToBudget(renderMidiToBuffer(parseMidi(midiBytes)), { maxSeconds: 60, mono: true, halfRate: true });
55
+ * console.log(ambience.duration);
56
+ * ```
57
+ */
58
+ export function fitToBudget(track, budget) {
59
+ let result = track;
60
+ if (budget.maxSeconds !== undefined) {
61
+ if (!(budget.maxSeconds > 0))
62
+ throw new Error('budget maxSeconds must be positive');
63
+ const limit = Math.max(1, Math.floor(budget.maxSeconds * result.sampleRate));
64
+ if (result.left.length > limit) {
65
+ const left = result.left.slice(0, limit);
66
+ const right = result.right.slice(0, limit);
67
+ const fade = Math.min(limit, Math.floor((budget.fadeSeconds ?? 0.5) * result.sampleRate));
68
+ for (let i = 0; i < fade; i++) {
69
+ const gain = i / fade;
70
+ left[limit - 1 - i] *= gain;
71
+ right[limit - 1 - i] *= gain;
72
+ }
73
+ const seconds = limit / result.sampleRate;
74
+ result = withEnd({ ...result, left, right }, seconds);
75
+ }
76
+ }
77
+ if (budget.mono) {
78
+ const mixed = new Float32Array(result.left.length);
79
+ for (let i = 0; i < mixed.length; i++)
80
+ mixed[i] = (result.left[i] + result.right[i]) / 2;
81
+ result = { ...result, left: mixed, right: mixed };
82
+ }
83
+ if (budget.halfRate)
84
+ result = resampleStereo(result, result.sampleRate / 2);
85
+ return result;
86
+ }
87
+ function withEnd(track, seconds) {
88
+ const next = { ...track };
89
+ if (next.duration !== undefined)
90
+ next.duration = seconds;
91
+ if (next.loopEnd !== undefined)
92
+ next.loopEnd = Math.min(next.loopEnd, seconds);
93
+ if (next.loopStart !== undefined)
94
+ next.loopStart = Math.min(next.loopStart, Math.max(0, seconds - 0.1));
95
+ return next;
96
+ }
97
+ //# sourceMappingURL=Resample.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Resample.js","sourceRoot":"","sources":["../../src/audio/Resample.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import type { AudioBus, BusKind } from './Channels.ts';
2
+ /** the television standard a retro machine ran at: it sets the clock its music was timed by */
3
+ export type AudioRegion = 'pal' | 'ntsc';
4
+ /**
5
+ * How a machine profile's output stage is treated: `faithful` keeps what the hardware did
6
+ * (DC offset, aliasing and all), `clean` removes the DC and softens the top end.
7
+ */
8
+ export type OutputStage = 'faithful' | 'clean';
9
+ /** player-facing audio settings as plain data, the shape a save or a settings file holds */
10
+ export interface AudioSettingsData {
11
+ /** volume per bus kind, 0 to 100 like every other volume in the bus API */
12
+ busVolumes: Record<BusKind, number>;
13
+ /** the MIDI engine name content is voiced by first, or null for the internal path */
14
+ preferredEngine: string | null;
15
+ region: AudioRegion;
16
+ outputStage: OutputStage;
17
+ /** whether caption text is shown for captioned sounds */
18
+ captions: boolean;
19
+ /** dynamics control, 0 (untouched) to 1 (a soft limiter at full), for loudness comfort */
20
+ dynamics: number;
21
+ /** softening of bright or broadband sounds, 0 (untouched) to 1 (most softened) */
22
+ soften: number;
23
+ }
24
+ /**
25
+ * The playback rate a tune authored for one region runs at on the other: a PAL machine's
26
+ * 50 Hz clock against NTSC's 60 Hz makes the same file slower and lower, which is the
27
+ * difference a player who grew up with the other standard hears first.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { regionPlaybackRate } from '@datamoc/mw_games/audio';
32
+ *
33
+ * console.log(regionPlaybackRate('pal', 'ntsc')); // 0.8333..., slower on PAL
34
+ * console.log(regionPlaybackRate('ntsc', 'ntsc')); // 1, its own region
35
+ * ```
36
+ */
37
+ export declare function regionPlaybackRate(region: AudioRegion, authored?: AudioRegion): number;
38
+ /**
39
+ * The low-pass cutoff, in hertz, that a `soften` amount asks an output stage for: nothing
40
+ * at 0, down to a muffled 3.5 kHz at 1, never above what the sample rate can represent.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { softenCutoff } from '@datamoc/mw_games/audio';
45
+ *
46
+ * console.log(softenCutoff(0, 44100)); // 18000, effectively open
47
+ * console.log(softenCutoff(1, 44100) < softenCutoff(0.5, 44100)); // true, more soften, lower cutoff
48
+ * ```
49
+ */
50
+ export declare function softenCutoff(soften: number, sampleRate: number): number;
51
+ /**
52
+ * One sample through the dynamics control: at 0 the sample is untouched, and as the amount
53
+ * rises a soft knee pulls peaks toward full scale without ever passing it, so a loud game
54
+ * gets quieter peaks rather than clipped ones.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { dynamicsLimit } from '@datamoc/mw_games/audio';
59
+ *
60
+ * console.log(dynamicsLimit(0.5, 0)); // 0.5, untouched
61
+ * console.log(dynamicsLimit(4, 1) < 1); // true, a hot sample is pulled under full scale
62
+ * ```
63
+ */
64
+ export declare function dynamicsLimit(sample: number, dynamics: number): number;
65
+ /**
66
+ * The player's audio settings in one place, over the pieces that already exist: bus
67
+ * volumes apply to an `AudioBus` (`applyToBus`), the preferred engine names what
68
+ * `resolveMidiSynth` is asked for first, the region and output stage describe the machine
69
+ * profile a retro package renders for, and captions, dynamics and soften are the
70
+ * accessibility dials. Everything clamps on the way in, and `snapshot`/`restore` round-trip
71
+ * the plain-data shape a settings screen persists.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import { AudioSettings } from '@datamoc/mw_games/audio';
76
+ *
77
+ * const settings = new AudioSettings({ preferredEngine: 'retro:mt32', soften: 0.4 });
78
+ * settings.setBusVolume('se', 130);
79
+ * console.log(settings.snapshot().busVolumes.se); // 100, clamped
80
+ * ```
81
+ */
82
+ export declare class AudioSettings {
83
+ private data;
84
+ constructor(initial?: Partial<AudioSettingsData>);
85
+ /** a copy of the current settings, safe to store */
86
+ snapshot(): AudioSettingsData;
87
+ /** takes settings in, clamping every dial; a partial update keeps the rest */
88
+ restore(data: Partial<AudioSettingsData>): void;
89
+ setBusVolume(kind: BusKind, volume: number): void;
90
+ /** pushes the per-bus volumes onto a bus's mixer, the settings' one live connection */
91
+ applyToBus(bus: AudioBus): void;
92
+ /** the rate content authored for `authored` should play at under this region */
93
+ playbackRate(authored?: AudioRegion): number;
94
+ /** the cutoff the current soften amount (and a clean stage's extra softening) asks for */
95
+ cutoff(sampleRate: number): number;
96
+ /** one sample through the current dynamics amount */
97
+ limit(sample: number): number;
98
+ }
99
+ //# sourceMappingURL=Settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../src/audio/Settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAEvD,+FAA+F;AAC/F,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;AAEzC;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AAE/C,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpC,qFAAqF;IACrF,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;IACzB,yDAAyD;IACzD,QAAQ,EAAE,OAAO,CAAC;IAClB,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,GAAE,WAAoB,GAAG,MAAM,CAG9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAIvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKtE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAa;IACzB,OAAO,CAAC,IAAI,CAAoB;IAEhC,YAAY,OAAO,GAAE,OAAO,CAAC,iBAAiB,CAAM,EAWnD;IAED,oDAAoD;IACpD,QAAQ,IAAI,iBAAiB,CAE5B;IAED,8EAA8E;IAC9E,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAY9C;IAED,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAEhD;IAED,uFAAuF;IACvF,UAAU,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAI9B;IAED,gFAAgF;IAChF,YAAY,CAAC,QAAQ,GAAE,WAAoB,GAAG,MAAM,CAEnD;IAED,0FAA0F;IAC1F,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGjC;IAED,qDAAqD;IACrD,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5B;CACD"}
@@ -0,0 +1,134 @@
1
+ import { clamp } from '../core/Math.js';
2
+ /**
3
+ * The playback rate a tune authored for one region runs at on the other: a PAL machine's
4
+ * 50 Hz clock against NTSC's 60 Hz makes the same file slower and lower, which is the
5
+ * difference a player who grew up with the other standard hears first.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { regionPlaybackRate } from '@datamoc/mw_games/audio';
10
+ *
11
+ * console.log(regionPlaybackRate('pal', 'ntsc')); // 0.8333..., slower on PAL
12
+ * console.log(regionPlaybackRate('ntsc', 'ntsc')); // 1, its own region
13
+ * ```
14
+ */
15
+ export function regionPlaybackRate(region, authored = 'ntsc') {
16
+ if (region === authored)
17
+ return 1;
18
+ return region === 'pal' ? 50 / 60 : 60 / 50;
19
+ }
20
+ /**
21
+ * The low-pass cutoff, in hertz, that a `soften` amount asks an output stage for: nothing
22
+ * at 0, down to a muffled 3.5 kHz at 1, never above what the sample rate can represent.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { softenCutoff } from '@datamoc/mw_games/audio';
27
+ *
28
+ * console.log(softenCutoff(0, 44100)); // 18000, effectively open
29
+ * console.log(softenCutoff(1, 44100) < softenCutoff(0.5, 44100)); // true, more soften, lower cutoff
30
+ * ```
31
+ */
32
+ export function softenCutoff(soften, sampleRate) {
33
+ const amount = clamp(soften, 0, 1);
34
+ const cutoff = 18000 * (3500 / 18000) ** amount;
35
+ return Math.min(cutoff, sampleRate / 2);
36
+ }
37
+ /**
38
+ * One sample through the dynamics control: at 0 the sample is untouched, and as the amount
39
+ * rises a soft knee pulls peaks toward full scale without ever passing it, so a loud game
40
+ * gets quieter peaks rather than clipped ones.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { dynamicsLimit } from '@datamoc/mw_games/audio';
45
+ *
46
+ * console.log(dynamicsLimit(0.5, 0)); // 0.5, untouched
47
+ * console.log(dynamicsLimit(4, 1) < 1); // true, a hot sample is pulled under full scale
48
+ * ```
49
+ */
50
+ export function dynamicsLimit(sample, dynamics) {
51
+ const amount = clamp(dynamics, 0, 1);
52
+ if (amount === 0)
53
+ return sample;
54
+ const limited = Math.tanh(sample);
55
+ return sample * (1 - amount) + limited * amount;
56
+ }
57
+ /**
58
+ * The player's audio settings in one place, over the pieces that already exist: bus
59
+ * volumes apply to an `AudioBus` (`applyToBus`), the preferred engine names what
60
+ * `resolveMidiSynth` is asked for first, the region and output stage describe the machine
61
+ * profile a retro package renders for, and captions, dynamics and soften are the
62
+ * accessibility dials. Everything clamps on the way in, and `snapshot`/`restore` round-trip
63
+ * the plain-data shape a settings screen persists.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * import { AudioSettings } from '@datamoc/mw_games/audio';
68
+ *
69
+ * const settings = new AudioSettings({ preferredEngine: 'retro:mt32', soften: 0.4 });
70
+ * settings.setBusVolume('se', 130);
71
+ * console.log(settings.snapshot().busVolumes.se); // 100, clamped
72
+ * ```
73
+ */
74
+ export class AudioSettings {
75
+ constructor(initial = {}) {
76
+ this.data = {
77
+ busVolumes: { bgm: 90, bgs: 90, me: 90, se: 90 },
78
+ preferredEngine: null,
79
+ region: 'ntsc',
80
+ outputStage: 'faithful',
81
+ captions: false,
82
+ dynamics: 0,
83
+ soften: 0,
84
+ };
85
+ this.restore(initial);
86
+ }
87
+ /** a copy of the current settings, safe to store */
88
+ snapshot() {
89
+ return { ...this.data, busVolumes: { ...this.data.busVolumes } };
90
+ }
91
+ /** takes settings in, clamping every dial; a partial update keeps the rest */
92
+ restore(data) {
93
+ if (data.busVolumes) {
94
+ for (const kind of Object.keys(data.busVolumes)) {
95
+ this.data.busVolumes[kind] = clamp(data.busVolumes[kind], 0, 100);
96
+ }
97
+ }
98
+ if (data.preferredEngine !== undefined)
99
+ this.data.preferredEngine = data.preferredEngine;
100
+ if (data.region !== undefined)
101
+ this.data.region = data.region;
102
+ if (data.outputStage !== undefined)
103
+ this.data.outputStage = data.outputStage;
104
+ if (data.captions !== undefined)
105
+ this.data.captions = data.captions;
106
+ if (data.dynamics !== undefined)
107
+ this.data.dynamics = clamp(data.dynamics, 0, 1);
108
+ if (data.soften !== undefined)
109
+ this.data.soften = clamp(data.soften, 0, 1);
110
+ }
111
+ setBusVolume(kind, volume) {
112
+ this.data.busVolumes[kind] = clamp(volume, 0, 100);
113
+ }
114
+ /** pushes the per-bus volumes onto a bus's mixer, the settings' one live connection */
115
+ applyToBus(bus) {
116
+ for (const kind of Object.keys(this.data.busVolumes)) {
117
+ bus.setMixer(kind, { volume: this.data.busVolumes[kind] / 100 });
118
+ }
119
+ }
120
+ /** the rate content authored for `authored` should play at under this region */
121
+ playbackRate(authored = 'ntsc') {
122
+ return regionPlaybackRate(this.data.region, authored);
123
+ }
124
+ /** the cutoff the current soften amount (and a clean stage's extra softening) asks for */
125
+ cutoff(sampleRate) {
126
+ const extra = this.data.outputStage === 'clean' ? 0.25 : 0;
127
+ return softenCutoff(clamp(this.data.soften + extra, 0, 1), sampleRate);
128
+ }
129
+ /** one sample through the current dynamics amount */
130
+ limit(sample) {
131
+ return dynamicsLimit(sample, this.data.dynamics);
132
+ }
133
+ }
134
+ //# sourceMappingURL=Settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Settings.js","sourceRoot":"","sources":["../../src/audio/Settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AA4BxC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAmB,EAAE,QAAQ,GAAgB,MAAM;IACrF,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IAClC,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,UAAkB;IAC9D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,KAAK,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,MAAM,CAAC;IAChD,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,QAAgB;IAC7D,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,MAAM,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,aAAa;IAGzB,YAAY,OAAO,GAA+B,EAAE;QACnD,IAAI,CAAC,IAAI,GAAG;YACX,UAAU,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;YAChD,eAAe,EAAE,IAAI;YACrB,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,UAAU;YACvB,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,CAAC;YACX,MAAM,EAAE,CAAC;SACT,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,oDAAoD;IACpD,QAAQ;QACP,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;IAClE,CAAC;IAED,8EAA8E;IAC9E,OAAO,CAAC,IAAgC;QACvC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAc,EAAE,CAAC;gBAC9D,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;YACnE,CAAC;QACF,CAAC;QACD,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;QACzF,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC9D,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QAC7E,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QACpE,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACjF,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,YAAY,CAAC,IAAa,EAAE,MAAc;QACzC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC;IAED,uFAAuF;IACvF,UAAU,CAAC,GAAa;QACvB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAc,EAAE,CAAC;YACnE,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;QAClE,CAAC;IACF,CAAC;IAED,gFAAgF;IAChF,YAAY,CAAC,QAAQ,GAAgB,MAAM;QAC1C,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvD,CAAC;IAED,0FAA0F;IAC1F,MAAM,CAAC,UAAkB;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,OAAO,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IACxE,CAAC;IAED,qDAAqD;IACrD,KAAK,CAAC,MAAc;QACnB,OAAO,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC;CACD"}