@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.
- package/CHANGELOG.md +126 -0
- package/README.md +84 -1
- package/REFERENCE.md +134 -6
- package/api-surface.json +1814 -116
- package/data/microgm-essential.PROVENANCE.md +13 -0
- package/data/microgm-essential.sf2 +0 -0
- package/data/soundfont-catalog.json +8 -0
- package/dist/actors/Resource.d.ts.map +1 -1
- package/dist/actors/Resource.js +2 -3
- package/dist/actors/Resource.js.map +1 -1
- package/dist/audio/AdaptiveMusic.d.ts +85 -0
- package/dist/audio/AdaptiveMusic.d.ts.map +1 -0
- package/dist/audio/AdaptiveMusic.js +230 -0
- package/dist/audio/AdaptiveMusic.js.map +1 -0
- package/dist/audio/AudioLoader.d.ts +33 -0
- package/dist/audio/AudioLoader.d.ts.map +1 -0
- package/dist/audio/AudioLoader.js +58 -0
- package/dist/audio/AudioLoader.js.map +1 -0
- package/dist/audio/AudioViews.d.ts +70 -0
- package/dist/audio/AudioViews.d.ts.map +1 -0
- package/dist/audio/AudioViews.js +119 -0
- package/dist/audio/AudioViews.js.map +1 -0
- package/dist/audio/BusBudget.d.ts +89 -0
- package/dist/audio/BusBudget.d.ts.map +1 -0
- package/dist/audio/BusBudget.js +120 -0
- package/dist/audio/BusBudget.js.map +1 -0
- package/dist/audio/Channels.d.ts +66 -1
- package/dist/audio/Channels.d.ts.map +1 -1
- package/dist/audio/Channels.js +119 -31
- package/dist/audio/Channels.js.map +1 -1
- package/dist/audio/CoreHost.d.ts +114 -0
- package/dist/audio/CoreHost.d.ts.map +1 -0
- package/dist/audio/CoreHost.js +335 -0
- package/dist/audio/CoreHost.js.map +1 -0
- package/dist/audio/DecoderGuard.d.ts +139 -0
- package/dist/audio/DecoderGuard.d.ts.map +1 -0
- package/dist/audio/DecoderGuard.js +208 -0
- package/dist/audio/DecoderGuard.js.map +1 -0
- package/dist/audio/Fft.d.ts +3 -0
- package/dist/audio/Fft.d.ts.map +1 -0
- package/dist/audio/Fft.js +45 -0
- package/dist/audio/Fft.js.map +1 -0
- package/dist/audio/LoopTags.d.ts +18 -0
- package/dist/audio/LoopTags.d.ts.map +1 -1
- package/dist/audio/LoopTags.js +20 -0
- package/dist/audio/LoopTags.js.map +1 -1
- package/dist/audio/Midi.d.ts +35 -0
- package/dist/audio/Midi.d.ts.map +1 -1
- package/dist/audio/Midi.js +71 -1
- package/dist/audio/Midi.js.map +1 -1
- package/dist/audio/MidiRender.d.ts +47 -0
- package/dist/audio/MidiRender.d.ts.map +1 -1
- package/dist/audio/MidiRender.js +100 -2
- package/dist/audio/MidiRender.js.map +1 -1
- package/dist/audio/MidiSynth.d.ts +147 -0
- package/dist/audio/MidiSynth.d.ts.map +1 -0
- package/dist/audio/MidiSynth.js +205 -0
- package/dist/audio/MidiSynth.js.map +1 -0
- package/dist/audio/MusicPlayer.d.ts +80 -0
- package/dist/audio/MusicPlayer.d.ts.map +1 -0
- package/dist/audio/MusicPlayer.js +147 -0
- package/dist/audio/MusicPlayer.js.map +1 -0
- package/dist/audio/Playable.d.ts +7 -0
- package/dist/audio/Playable.d.ts.map +1 -1
- package/dist/audio/Playable.js.map +1 -1
- package/dist/audio/Positional.d.ts +12 -4
- package/dist/audio/Positional.d.ts.map +1 -1
- package/dist/audio/Positional.js +5 -2
- package/dist/audio/Positional.js.map +1 -1
- package/dist/audio/Resample.d.ts +61 -0
- package/dist/audio/Resample.d.ts.map +1 -0
- package/dist/audio/Resample.js +97 -0
- package/dist/audio/Resample.js.map +1 -0
- package/dist/audio/SampleDecoder.d.ts +16 -2
- package/dist/audio/SampleDecoder.d.ts.map +1 -1
- package/dist/audio/SampleDecoder.js +10 -6
- package/dist/audio/SampleDecoder.js.map +1 -1
- package/dist/audio/Settings.d.ts +99 -0
- package/dist/audio/Settings.d.ts.map +1 -0
- package/dist/audio/Settings.js +134 -0
- package/dist/audio/Settings.js.map +1 -0
- package/dist/audio/Sfx.d.ts +148 -0
- package/dist/audio/Sfx.d.ts.map +1 -0
- package/dist/audio/Sfx.js +213 -0
- package/dist/audio/Sfx.js.map +1 -0
- package/dist/audio/Sound.d.ts +2 -0
- package/dist/audio/Sound.d.ts.map +1 -1
- package/dist/audio/Sound.js +3 -0
- package/dist/audio/Sound.js.map +1 -1
- package/dist/audio/SoundFont.d.ts +5 -3
- package/dist/audio/SoundFont.d.ts.map +1 -1
- package/dist/audio/SoundFont.js +10 -8
- package/dist/audio/SoundFont.js.map +1 -1
- package/dist/audio/SpectralDistance.d.ts +34 -0
- package/dist/audio/SpectralDistance.d.ts.map +1 -0
- package/dist/audio/SpectralDistance.js +64 -0
- package/dist/audio/SpectralDistance.js.map +1 -0
- package/dist/audio/StreamChannel.d.ts +150 -0
- package/dist/audio/StreamChannel.d.ts.map +1 -0
- package/dist/audio/StreamChannel.js +390 -0
- package/dist/audio/StreamChannel.js.map +1 -0
- package/dist/audio/StreamPlayable.d.ts +73 -0
- package/dist/audio/StreamPlayable.d.ts.map +1 -0
- package/dist/audio/StreamPlayable.js +130 -0
- package/dist/audio/StreamPlayable.js.map +1 -0
- package/dist/audio/Synth.d.ts +1 -1
- package/dist/audio/Synth.d.ts.map +1 -1
- package/dist/audio/Synth.js +4 -0
- package/dist/audio/Synth.js.map +1 -1
- package/dist/audio/SyxBank.d.ts +73 -0
- package/dist/audio/SyxBank.d.ts.map +1 -0
- package/dist/audio/SyxBank.js +130 -0
- package/dist/audio/SyxBank.js.map +1 -0
- package/dist/audio/Underrun.d.ts +25 -0
- package/dist/audio/Underrun.d.ts.map +1 -0
- package/dist/audio/Underrun.js +18 -0
- package/dist/audio/Underrun.js.map +1 -0
- package/dist/audio/Wav.d.ts +56 -0
- package/dist/audio/Wav.d.ts.map +1 -0
- package/dist/audio/Wav.js +164 -0
- package/dist/audio/Wav.js.map +1 -0
- package/dist/audio/WebMidi.d.ts +58 -0
- package/dist/audio/WebMidi.d.ts.map +1 -0
- package/dist/audio/WebMidi.js +79 -0
- package/dist/audio/WebMidi.js.map +1 -0
- package/dist/audio/humanize/Onsets.d.ts.map +1 -1
- package/dist/audio/humanize/Onsets.js +1 -44
- package/dist/audio/humanize/Onsets.js.map +1 -1
- package/dist/audio/index.d.ts +43 -7
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +21 -3
- package/dist/audio/index.js.map +1 -1
- package/dist/battle/AttackPreview.d.ts.map +1 -1
- package/dist/battle/AttackPreview.js +8 -8
- package/dist/battle/AttackPreview.js.map +1 -1
- package/dist/battle/StatStages.d.ts.map +1 -1
- package/dist/battle/StatStages.js +2 -1
- package/dist/battle/StatStages.js.map +1 -1
- package/dist/board/Engine.d.ts +2 -0
- package/dist/board/Engine.d.ts.map +1 -1
- package/dist/board/Engine.js +1 -1
- package/dist/board/Engine.js.map +1 -1
- package/dist/board/HexSkirmish.d.ts.map +1 -1
- package/dist/board/HexSkirmish.js +11 -72
- package/dist/board/HexSkirmish.js.map +1 -1
- package/dist/board/Tactics.d.ts.map +1 -1
- package/dist/board/Tactics.js +3 -5
- package/dist/board/Tactics.js.map +1 -1
- package/dist/board/tourney.d.ts.map +1 -1
- package/dist/board/tourney.js +2 -19
- package/dist/board/tourney.js.map +1 -1
- package/dist/core/Handles.js +7 -7
- package/dist/core/Handles.js.map +1 -1
- package/dist/mw_games.global.js +25 -22
- package/dist/mwl/conditions.d.ts +12 -0
- package/dist/mwl/conditions.d.ts.map +1 -1
- package/dist/mwl/conditions.js +49 -8
- package/dist/mwl/conditions.js.map +1 -1
- package/dist/mwl/index.d.ts +13 -0
- package/dist/mwl/index.d.ts.map +1 -1
- package/dist/mwl/index.js +7 -0
- package/dist/mwl/index.js.map +1 -1
- package/dist/mwl/objects.d.ts +31 -0
- package/dist/mwl/objects.d.ts.map +1 -0
- package/dist/mwl/objects.js +83 -0
- package/dist/mwl/objects.js.map +1 -0
- package/dist/mwl/package-container.d.ts +185 -0
- package/dist/mwl/package-container.d.ts.map +1 -0
- package/dist/mwl/package-container.js +446 -0
- package/dist/mwl/package-container.js.map +1 -0
- package/dist/mwl/package-document.d.ts +217 -0
- package/dist/mwl/package-document.d.ts.map +1 -0
- package/dist/mwl/package-document.js +639 -0
- package/dist/mwl/package-document.js.map +1 -0
- package/dist/mwl/package-rom.d.ts +37 -0
- package/dist/mwl/package-rom.d.ts.map +1 -0
- package/dist/mwl/package-rom.js +96 -0
- package/dist/mwl/package-rom.js.map +1 -0
- package/dist/mwl/package-verify.d.ts +34 -0
- package/dist/mwl/package-verify.d.ts.map +1 -0
- package/dist/mwl/package-verify.js +111 -0
- package/dist/mwl/package-verify.js.map +1 -0
- package/dist/mwl/package.d.ts +184 -0
- package/dist/mwl/package.d.ts.map +1 -0
- package/dist/mwl/package.js +382 -0
- package/dist/mwl/package.js.map +1 -0
- package/dist/mwl/schema.d.ts +1 -1
- package/dist/mwl/schema.d.ts.map +1 -1
- package/dist/mwl/tables.d.ts +2 -0
- package/dist/mwl/tables.d.ts.map +1 -1
- package/dist/mwl/tables.js +13 -0
- package/dist/mwl/tables.js.map +1 -1
- package/dist/physics/PhysicsWorld.d.ts.map +1 -1
- package/dist/physics/PhysicsWorld.js +4 -3
- package/dist/physics/PhysicsWorld.js.map +1 -1
- package/dist/physics/XpbdWorld.d.ts +23 -5
- package/dist/physics/XpbdWorld.d.ts.map +1 -1
- package/dist/physics/XpbdWorld.js +91 -6
- package/dist/physics/XpbdWorld.js.map +1 -1
- package/dist/testing/FakeAudio.d.ts +149 -0
- package/dist/testing/FakeAudio.d.ts.map +1 -0
- package/dist/testing/FakeAudio.js +219 -0
- package/dist/testing/FakeAudio.js.map +1 -0
- package/dist/testing/index.d.ts +18 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +22 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/threads/index.d.ts +2 -2
- package/dist/threads/index.d.ts.map +1 -1
- package/dist/threads/index.js +1 -1
- package/dist/threads/index.js.map +1 -1
- package/dist/threads/spawn.d.ts +32 -0
- package/dist/threads/spawn.d.ts.map +1 -1
- package/dist/threads/spawn.js +35 -11
- package/dist/threads/spawn.js.map +1 -1
- package/dist/three-d/Collision3D.d.ts +46 -2
- package/dist/three-d/Collision3D.d.ts.map +1 -1
- package/dist/three-d/Collision3D.js +46 -2
- package/dist/three-d/Collision3D.js.map +1 -1
- package/dist/three-d/Grid.d.ts +12 -1
- package/dist/three-d/Grid.d.ts.map +1 -1
- package/dist/three-d/Grid.js +12 -1
- package/dist/three-d/Grid.js.map +1 -1
- package/dist/three-d/Vox.d.ts +13 -1
- package/dist/three-d/Vox.d.ts.map +1 -1
- package/dist/three-d/Vox.js +13 -1
- package/dist/three-d/Vox.js.map +1 -1
- package/dist/two-d/Game.d.ts +32 -1
- package/dist/two-d/Game.d.ts.map +1 -1
- package/dist/two-d/Game.js +105 -14
- package/dist/two-d/Game.js.map +1 -1
- package/dist/two-d/Scene2D.d.ts +24 -0
- package/dist/two-d/Scene2D.d.ts.map +1 -1
- package/dist/two-d/Scene2D.js +30 -0
- package/dist/two-d/Scene2D.js.map +1 -1
- package/dist/two-d/render/HazeLayer.d.ts.map +1 -1
- package/dist/two-d/render/HazeLayer.js +2 -1
- package/dist/two-d/render/HazeLayer.js.map +1 -1
- package/dist/two-d/render/ImageModifiers.d.ts +4 -2
- package/dist/two-d/render/ImageModifiers.d.ts.map +1 -1
- package/dist/two-d/render/ImageModifiers.js +13 -8
- package/dist/two-d/render/ImageModifiers.js.map +1 -1
- package/dist/two-d/render/NeonGlow.d.ts.map +1 -1
- package/dist/two-d/render/NeonGlow.js +2 -1
- package/dist/two-d/render/NeonGlow.js.map +1 -1
- package/dist/two-d/render/Particles.d.ts.map +1 -1
- package/dist/two-d/render/Particles.js +2 -1
- package/dist/two-d/render/Particles.js.map +1 -1
- package/dist/two-d/render/SceneTransition.d.ts +301 -0
- package/dist/two-d/render/SceneTransition.d.ts.map +1 -0
- package/dist/two-d/render/SceneTransition.js +458 -0
- package/dist/two-d/render/SceneTransition.js.map +1 -0
- package/dist/two-d/render/ScreenEffects.d.ts.map +1 -1
- package/dist/two-d/render/ScreenEffects.js +2 -1
- package/dist/two-d/render/ScreenEffects.js.map +1 -1
- package/dist/two-d/render/SkeletalSprite.d.ts.map +1 -1
- package/dist/two-d/render/SkeletalSprite.js +2 -1
- package/dist/two-d/render/SkeletalSprite.js.map +1 -1
- package/dist/two-d/render/Types2D.d.ts +2 -2
- package/dist/two-d/render/Types2D.d.ts.map +1 -1
- package/dist/two-d/render/Types2D.js +2 -2
- package/dist/two-d/render/Types2D.js.map +1 -1
- package/dist/two-d/render/index.d.ts +2 -0
- package/dist/two-d/render/index.d.ts.map +1 -1
- package/dist/two-d/render/index.js +1 -0
- package/dist/two-d/render/index.js.map +1 -1
- package/dist/two-d/ui/Bar.d.ts.map +1 -1
- package/dist/two-d/ui/Bar.js +3 -5
- package/dist/two-d/ui/Bar.js.map +1 -1
- package/dist/two-d/ui/Button.d.ts +23 -0
- package/dist/two-d/ui/Button.d.ts.map +1 -1
- package/dist/two-d/ui/Button.js +37 -0
- package/dist/two-d/ui/Button.js.map +1 -1
- package/dist/two-d/ui/Dropdown.d.ts +5 -1
- package/dist/two-d/ui/Dropdown.d.ts.map +1 -1
- package/dist/two-d/ui/Dropdown.js +8 -1
- package/dist/two-d/ui/Dropdown.js.map +1 -1
- package/dist/two-d/ui/DropdownView.d.ts +63 -3
- package/dist/two-d/ui/DropdownView.d.ts.map +1 -1
- package/dist/two-d/ui/DropdownView.js +168 -9
- package/dist/two-d/ui/DropdownView.js.map +1 -1
- package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
- package/dist/two-d/ui/FloatingText.js +2 -1
- package/dist/two-d/ui/FloatingText.js.map +1 -1
- package/dist/two-d/ui/IconGrid.d.ts.map +1 -1
- package/dist/two-d/ui/IconGrid.js +2 -1
- package/dist/two-d/ui/IconGrid.js.map +1 -1
- package/dist/two-d/ui/ListView.d.ts.map +1 -1
- package/dist/two-d/ui/ListView.js +2 -1
- package/dist/two-d/ui/ListView.js.map +1 -1
- package/dist/two-d/ui/ScrollBox.d.ts +96 -0
- package/dist/two-d/ui/ScrollBox.d.ts.map +1 -1
- package/dist/two-d/ui/ScrollBox.js +218 -5
- package/dist/two-d/ui/ScrollBox.js.map +1 -1
- package/dist/two-d/ui/ScrollText.d.ts +131 -0
- package/dist/two-d/ui/ScrollText.d.ts.map +1 -0
- package/dist/two-d/ui/ScrollText.js +218 -0
- package/dist/two-d/ui/ScrollText.js.map +1 -0
- package/dist/two-d/ui/Slider.d.ts.map +1 -1
- package/dist/two-d/ui/Slider.js +4 -4
- package/dist/two-d/ui/Slider.js.map +1 -1
- package/dist/two-d/ui/Tooltip.d.ts +70 -1
- package/dist/two-d/ui/Tooltip.d.ts.map +1 -1
- package/dist/two-d/ui/Tooltip.js +115 -3
- package/dist/two-d/ui/Tooltip.js.map +1 -1
- package/dist/two-d/ui/Window.d.ts +35 -2
- package/dist/two-d/ui/Window.d.ts.map +1 -1
- package/dist/two-d/ui/Window.js +58 -3
- package/dist/two-d/ui/Window.js.map +1 -1
- package/dist/two-d/ui/index.d.ts +7 -5
- package/dist/two-d/ui/index.d.ts.map +1 -1
- package/dist/two-d/ui/index.js +3 -2
- package/dist/two-d/ui/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -3
- package/sbom.cdx.json +19 -260
- package/tools/audio.d.mts +75 -0
- package/tools/audio.mjs +437 -0
- package/tools/browser-driver.mjs +3 -3
- package/tools/browser-smoke.mjs +19 -2
- package/tools/build-tablebases.mjs +7 -3
- package/tools/compile-openings.mjs +7 -3
- package/tools/compile-resources.d.mts +2 -0
- package/tools/compile-resources.mjs +13 -1
- package/tools/emit-page.d.mts +6 -0
- package/tools/emit-page.mjs +43 -3
- package/tools/find-chrome.mjs +4 -2
- package/tools/init.mjs +11 -3
- package/tools/mwl.mjs +144 -2
- package/tools/play-uci.mjs +9 -5
- package/tools/vite.mjs +9 -1
- package/tools.md +2 -1
|
@@ -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":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A sound effect described as data and voiced by a synthesis backend instead of read from a
|
|
3
|
+
* file. mwg owns the shape the game and the mixer need (an id, a priority, what a second
|
|
4
|
+
* trigger does, a caption); whatever else a backend wants (chip, voices, curves) rides along
|
|
5
|
+
* untouched, so a retro-instrument package can carry its own `SfxDefinition` through unchanged.
|
|
6
|
+
*/
|
|
7
|
+
export interface SfxDefinition {
|
|
8
|
+
id: string;
|
|
9
|
+
/** higher cuts lower when the mixer is full; 0 when omitted */
|
|
10
|
+
priority?: number;
|
|
11
|
+
/** what a second trigger does while the first still sounds: `'restart'` (the default), `'ignore'`, or `'layer'` */
|
|
12
|
+
retrigger?: 'restart' | 'ignore' | 'layer';
|
|
13
|
+
/** shown by a captioning overlay each time the effect plays */
|
|
14
|
+
caption?: string;
|
|
15
|
+
[backendData: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/** what a play carries into the backend: the mix mwg computed and a seed that makes any randomness reproducible */
|
|
18
|
+
export interface SfxPlayOptions {
|
|
19
|
+
/** 0 to 1, after distance and the sound's own volume */
|
|
20
|
+
gain: number;
|
|
21
|
+
/** -1 (left) to 1 (right) */
|
|
22
|
+
pan: number;
|
|
23
|
+
/** playback ratio, 1 unchanged */
|
|
24
|
+
pitch: number;
|
|
25
|
+
/** the only source of chance an effect may use */
|
|
26
|
+
seed: number;
|
|
27
|
+
}
|
|
28
|
+
/** one effect sounding on a backend */
|
|
29
|
+
export interface SfxVoice {
|
|
30
|
+
stop(): void;
|
|
31
|
+
/** false once it has finished by itself or been stopped */
|
|
32
|
+
readonly active: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** the synthesis side: a chip engine that can voice an effect, wherever it keeps its voices */
|
|
35
|
+
export interface SfxBackend {
|
|
36
|
+
/** returns null when it cannot voice the effect (no free channel, unknown chip), which is not an error */
|
|
37
|
+
play(definition: SfxDefinition, options: SfxPlayOptions): SfxVoice | null;
|
|
38
|
+
}
|
|
39
|
+
/** effects by id, so a sound can be made from an id the content named */
|
|
40
|
+
export declare class SfxBank {
|
|
41
|
+
private readonly definitions;
|
|
42
|
+
constructor(definitions?: Iterable<SfxDefinition>);
|
|
43
|
+
define(definition: SfxDefinition): void;
|
|
44
|
+
has(id: string): boolean;
|
|
45
|
+
get(id: string): SfxDefinition;
|
|
46
|
+
ids(): string[];
|
|
47
|
+
}
|
|
48
|
+
/** a granted slot in the arbiter; `release` frees it */
|
|
49
|
+
export interface SfxGrant {
|
|
50
|
+
release(): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The shared voice limit between chip effects and the bus's `'se'` jingles: both ask here before
|
|
54
|
+
* they sound, and when the limit is reached the lowest-priority voice (the oldest among equals)
|
|
55
|
+
* is cut for a new one of equal or higher priority; a new one below everything sounding is
|
|
56
|
+
* refused. One arbiter handed to a bus and to every `SfxSound` makes them a single pool.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { SfxArbiter } from '@datamoc/mw_games/audio';
|
|
61
|
+
*
|
|
62
|
+
* const arbiter = new SfxArbiter({ maxVoices: 2 });
|
|
63
|
+
* const a = arbiter.request(1, () => console.log('a cut'));
|
|
64
|
+
* arbiter.request(1, () => console.log('b cut'));
|
|
65
|
+
* arbiter.request(5, () => console.log('c cut')); // cuts a, the oldest of the lowest priority
|
|
66
|
+
* a?.release();
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare class SfxArbiter {
|
|
70
|
+
private readonly maxVoices;
|
|
71
|
+
private readonly voices;
|
|
72
|
+
constructor(options?: {
|
|
73
|
+
maxVoices?: number;
|
|
74
|
+
});
|
|
75
|
+
get active(): number;
|
|
76
|
+
/**
|
|
77
|
+
* Asks for a slot. `stop` cuts the voice when a better one needs its place; `active` says
|
|
78
|
+
* whether it still sounds, so a voice that ended by itself frees its slot for any asker, not
|
|
79
|
+
* only for the sound that owned it.
|
|
80
|
+
*/
|
|
81
|
+
request(priority: number, stop: () => void, active?: () => boolean): SfxGrant | null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A reproducible seed for an effect: the same id on the same game frame (and the same ordinal
|
|
85
|
+
* within that frame) always gives the same number, so a replay or a lockstep peer hears exactly
|
|
86
|
+
* what the original run heard, and audio never perturbs the simulation.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* import { sfxSeed } from '@datamoc/mw_games/audio';
|
|
91
|
+
*
|
|
92
|
+
* sfxSeed('arrow', 120) === sfxSeed('arrow', 120); // true
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function sfxSeed(id: string, frame: number, ordinal?: number): number;
|
|
96
|
+
export interface SfxSoundOptions {
|
|
97
|
+
/** where an effect given by id is looked up */
|
|
98
|
+
bank?: SfxBank;
|
|
99
|
+
/** the shared voice limit; none means a voice is never cut for another */
|
|
100
|
+
arbiter?: SfxArbiter;
|
|
101
|
+
/** the game's own frame counter, never the wall clock, so seeds repeat across runs; 0 when omitted */
|
|
102
|
+
frame?: () => number;
|
|
103
|
+
/** shown with each play when the definition has no caption of its own */
|
|
104
|
+
caption?: string;
|
|
105
|
+
/** level, 0 to 1; 1 when omitted */
|
|
106
|
+
volume?: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The data-driven counterpart of `Sound`: it plays an `SfxDefinition` (or the id of one in a
|
|
110
|
+
* bank) through a backend, with the same `play(gain, pitch)` shape so a `SoundSource` can
|
|
111
|
+
* attenuate it, and a pan on top. It applies the definition's retrigger rule, asks the shared
|
|
112
|
+
* arbiter for a voice, fires the caption, and hands the backend a deterministic seed.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { SfxArbiter, SfxBank, SfxSound, SoundSource, AudioListener, type SfxBackend } from '@datamoc/mw_games/audio';
|
|
117
|
+
*
|
|
118
|
+
* declare const chip: SfxBackend;
|
|
119
|
+
*
|
|
120
|
+
* const bank = new SfxBank([{ id: 'arrow', priority: 5, caption: 'an arrow flies' }]);
|
|
121
|
+
* const arrow = new SfxSound(chip, 'arrow', { bank, arbiter: new SfxArbiter(), frame: () => 0 });
|
|
122
|
+
* new SoundSource(arrow, { x: 3, y: 4 }).playFor(new AudioListener());
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare class SfxSound {
|
|
126
|
+
volume: number;
|
|
127
|
+
/** how many plays reached the backend, for a test to assert on without a speaker */
|
|
128
|
+
plays: number;
|
|
129
|
+
private readonly backend;
|
|
130
|
+
private readonly definition;
|
|
131
|
+
private readonly options;
|
|
132
|
+
private readonly live;
|
|
133
|
+
private suspended_;
|
|
134
|
+
private lastFrame;
|
|
135
|
+
private ordinal;
|
|
136
|
+
constructor(backend: SfxBackend, effect: SfxDefinition | string, options?: SfxSoundOptions);
|
|
137
|
+
get isSuspended(): boolean;
|
|
138
|
+
/** voices that still sound */
|
|
139
|
+
get activeVoices(): number;
|
|
140
|
+
/** plays the effect; returns false when it was ignored, refused by the arbiter or the backend had no voice */
|
|
141
|
+
play(gain?: number, pitch?: number, pan?: number): boolean;
|
|
142
|
+
stopAll(): void;
|
|
143
|
+
suspend(): void;
|
|
144
|
+
resume(): void;
|
|
145
|
+
/** drops voices that finished by themselves, returning their arbiter slots */
|
|
146
|
+
private prune;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=Sfx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sfx.d.ts","sourceRoot":"","sources":["../../src/audio/Sfx.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { onCaption } from './Captions.js';
|
|
2
|
+
/** effects by id, so a sound can be made from an id the content named */
|
|
3
|
+
export class SfxBank {
|
|
4
|
+
constructor(definitions = []) {
|
|
5
|
+
this.definitions = new Map();
|
|
6
|
+
for (const definition of definitions)
|
|
7
|
+
this.define(definition);
|
|
8
|
+
}
|
|
9
|
+
define(definition) {
|
|
10
|
+
if (!definition.id)
|
|
11
|
+
throw new Error('a sound effect needs an id');
|
|
12
|
+
this.definitions.set(definition.id, definition);
|
|
13
|
+
}
|
|
14
|
+
has(id) {
|
|
15
|
+
return this.definitions.has(id);
|
|
16
|
+
}
|
|
17
|
+
get(id) {
|
|
18
|
+
const definition = this.definitions.get(id);
|
|
19
|
+
if (!definition)
|
|
20
|
+
throw new Error(`no sound effect "${id}" in the bank`);
|
|
21
|
+
return definition;
|
|
22
|
+
}
|
|
23
|
+
ids() {
|
|
24
|
+
return [...this.definitions.keys()];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The shared voice limit between chip effects and the bus's `'se'` jingles: both ask here before
|
|
29
|
+
* they sound, and when the limit is reached the lowest-priority voice (the oldest among equals)
|
|
30
|
+
* is cut for a new one of equal or higher priority; a new one below everything sounding is
|
|
31
|
+
* refused. One arbiter handed to a bus and to every `SfxSound` makes them a single pool.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { SfxArbiter } from '@datamoc/mw_games/audio';
|
|
36
|
+
*
|
|
37
|
+
* const arbiter = new SfxArbiter({ maxVoices: 2 });
|
|
38
|
+
* const a = arbiter.request(1, () => console.log('a cut'));
|
|
39
|
+
* arbiter.request(1, () => console.log('b cut'));
|
|
40
|
+
* arbiter.request(5, () => console.log('c cut')); // cuts a, the oldest of the lowest priority
|
|
41
|
+
* a?.release();
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export class SfxArbiter {
|
|
45
|
+
constructor(options = {}) {
|
|
46
|
+
this.voices = [];
|
|
47
|
+
this.maxVoices = Math.max(1, Math.floor(options.maxVoices ?? 8));
|
|
48
|
+
}
|
|
49
|
+
get active() {
|
|
50
|
+
return this.voices.length;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Asks for a slot. `stop` cuts the voice when a better one needs its place; `active` says
|
|
54
|
+
* whether it still sounds, so a voice that ended by itself frees its slot for any asker, not
|
|
55
|
+
* only for the sound that owned it.
|
|
56
|
+
*/
|
|
57
|
+
request(priority, stop, active = () => true) {
|
|
58
|
+
for (let i = this.voices.length - 1; i >= 0; i--)
|
|
59
|
+
if (!this.voices[i].active())
|
|
60
|
+
this.voices.splice(i, 1);
|
|
61
|
+
if (this.voices.length >= this.maxVoices) {
|
|
62
|
+
let victim = 0;
|
|
63
|
+
for (let i = 1; i < this.voices.length; i++) {
|
|
64
|
+
if (this.voices[i].priority < this.voices[victim].priority)
|
|
65
|
+
victim = i;
|
|
66
|
+
}
|
|
67
|
+
if (this.voices[victim].priority > priority)
|
|
68
|
+
return null;
|
|
69
|
+
const [cut] = this.voices.splice(victim, 1);
|
|
70
|
+
cut.stop();
|
|
71
|
+
}
|
|
72
|
+
const entry = { priority, stop, active };
|
|
73
|
+
this.voices.push(entry);
|
|
74
|
+
return {
|
|
75
|
+
release: () => {
|
|
76
|
+
const at = this.voices.indexOf(entry);
|
|
77
|
+
if (at !== -1)
|
|
78
|
+
this.voices.splice(at, 1);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A reproducible seed for an effect: the same id on the same game frame (and the same ordinal
|
|
85
|
+
* within that frame) always gives the same number, so a replay or a lockstep peer hears exactly
|
|
86
|
+
* what the original run heard, and audio never perturbs the simulation.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* import { sfxSeed } from '@datamoc/mw_games/audio';
|
|
91
|
+
*
|
|
92
|
+
* sfxSeed('arrow', 120) === sfxSeed('arrow', 120); // true
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function sfxSeed(id, frame, ordinal = 0) {
|
|
96
|
+
let hash = 0x811c9dc5;
|
|
97
|
+
const mix = (value) => {
|
|
98
|
+
hash = Math.imul(hash ^ (value & 0xff), 0x01000193);
|
|
99
|
+
hash = Math.imul(hash ^ ((value >>> 8) & 0xff), 0x01000193);
|
|
100
|
+
hash = Math.imul(hash ^ ((value >>> 16) & 0xff), 0x01000193);
|
|
101
|
+
hash = Math.imul(hash ^ ((value >>> 24) & 0xff), 0x01000193);
|
|
102
|
+
};
|
|
103
|
+
for (let i = 0; i < id.length; i++)
|
|
104
|
+
mix(id.charCodeAt(i));
|
|
105
|
+
mix(Math.floor(frame));
|
|
106
|
+
mix(ordinal);
|
|
107
|
+
return hash >>> 0;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The data-driven counterpart of `Sound`: it plays an `SfxDefinition` (or the id of one in a
|
|
111
|
+
* bank) through a backend, with the same `play(gain, pitch)` shape so a `SoundSource` can
|
|
112
|
+
* attenuate it, and a pan on top. It applies the definition's retrigger rule, asks the shared
|
|
113
|
+
* arbiter for a voice, fires the caption, and hands the backend a deterministic seed.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* import { SfxArbiter, SfxBank, SfxSound, SoundSource, AudioListener, type SfxBackend } from '@datamoc/mw_games/audio';
|
|
118
|
+
*
|
|
119
|
+
* declare const chip: SfxBackend;
|
|
120
|
+
*
|
|
121
|
+
* const bank = new SfxBank([{ id: 'arrow', priority: 5, caption: 'an arrow flies' }]);
|
|
122
|
+
* const arrow = new SfxSound(chip, 'arrow', { bank, arbiter: new SfxArbiter(), frame: () => 0 });
|
|
123
|
+
* new SoundSource(arrow, { x: 3, y: 4 }).playFor(new AudioListener());
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export class SfxSound {
|
|
127
|
+
constructor(backend, effect, options = {}) {
|
|
128
|
+
/** how many plays reached the backend, for a test to assert on without a speaker */
|
|
129
|
+
this.plays = 0;
|
|
130
|
+
this.live = [];
|
|
131
|
+
this.suspended_ = false;
|
|
132
|
+
this.lastFrame = Number.NaN;
|
|
133
|
+
this.ordinal = 0;
|
|
134
|
+
this.backend = backend;
|
|
135
|
+
this.options = options;
|
|
136
|
+
this.volume = options.volume ?? 1;
|
|
137
|
+
if (typeof effect === 'string') {
|
|
138
|
+
if (!options.bank)
|
|
139
|
+
throw new Error(`a sound effect given by id ("${effect}") needs a bank`);
|
|
140
|
+
this.definition = options.bank.get(effect);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
this.definition = effect;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
get isSuspended() {
|
|
147
|
+
return this.suspended_;
|
|
148
|
+
}
|
|
149
|
+
/** voices that still sound */
|
|
150
|
+
get activeVoices() {
|
|
151
|
+
this.prune();
|
|
152
|
+
return this.live.length;
|
|
153
|
+
}
|
|
154
|
+
/** plays the effect; returns false when it was ignored, refused by the arbiter or the backend had no voice */
|
|
155
|
+
play(gain = 1, pitch = 1, pan = 0) {
|
|
156
|
+
if (this.suspended_)
|
|
157
|
+
return false;
|
|
158
|
+
this.prune();
|
|
159
|
+
const rule = this.definition.retrigger ?? 'restart';
|
|
160
|
+
if (this.live.length > 0 && rule === 'ignore')
|
|
161
|
+
return false;
|
|
162
|
+
if (this.live.length > 0 && rule === 'restart')
|
|
163
|
+
this.stopAll();
|
|
164
|
+
const frame = this.options.frame?.() ?? 0;
|
|
165
|
+
this.ordinal = frame === this.lastFrame ? this.ordinal + 1 : 0;
|
|
166
|
+
this.lastFrame = frame;
|
|
167
|
+
let voice = null;
|
|
168
|
+
const grant = this.options.arbiter?.request(this.definition.priority ?? 0, () => {
|
|
169
|
+
voice?.stop();
|
|
170
|
+
}, () => voice?.active ?? true) ?? null;
|
|
171
|
+
if (this.options.arbiter && !grant)
|
|
172
|
+
return false;
|
|
173
|
+
voice = this.backend.play(this.definition, {
|
|
174
|
+
gain: Math.max(0, Math.min(1, gain * this.volume)),
|
|
175
|
+
pan: Math.max(-1, Math.min(1, pan)),
|
|
176
|
+
pitch,
|
|
177
|
+
seed: sfxSeed(this.definition.id, frame, this.ordinal),
|
|
178
|
+
});
|
|
179
|
+
if (!voice) {
|
|
180
|
+
grant?.release();
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
this.live.push({ voice, grant });
|
|
184
|
+
this.plays++;
|
|
185
|
+
const caption = this.definition.caption ?? this.options.caption;
|
|
186
|
+
if (caption)
|
|
187
|
+
onCaption.dispatch({ text: caption });
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
stopAll() {
|
|
191
|
+
for (const { voice, grant } of this.live.splice(0)) {
|
|
192
|
+
voice.stop();
|
|
193
|
+
grant?.release();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
suspend() {
|
|
197
|
+
this.suspended_ = true;
|
|
198
|
+
this.stopAll();
|
|
199
|
+
}
|
|
200
|
+
resume() {
|
|
201
|
+
this.suspended_ = false;
|
|
202
|
+
}
|
|
203
|
+
/** drops voices that finished by themselves, returning their arbiter slots */
|
|
204
|
+
prune() {
|
|
205
|
+
for (let i = this.live.length - 1; i >= 0; i--) {
|
|
206
|
+
if (!this.live[i].voice.active) {
|
|
207
|
+
this.live[i].grant?.release();
|
|
208
|
+
this.live.splice(i, 1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=Sfx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sfx.js","sourceRoot":"","sources":["../../src/audio/Sfx.ts"],"names":[],"mappings":"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"}
|
package/dist/audio/Sound.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export declare class Sound {
|
|
|
28
28
|
private caption?;
|
|
29
29
|
private suspended_;
|
|
30
30
|
volume: number;
|
|
31
|
+
/** how many times `play` actually started a clip (not counting calls made while suspended), for a test to assert on without a microphone */
|
|
32
|
+
plays: number;
|
|
31
33
|
constructor(path: string, options?: SoundOptions);
|
|
32
34
|
/**
|
|
33
35
|
* Plays one instance from the pool.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,WAAW,YAAY;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IAEpC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,WAAW,YAAY;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IAEpC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,4IAA4I;IAC5I,KAAK,SAAK;IAEV,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,EAMnD;IAED;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,IAAI,SAAI,EAAE,KAAK,SAAI,GAAG,IAAI,CAe9B;IAED,OAAO,IAAI,IAAI,CAKd;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAGd;IAED,wCAAwC;IACxC,MAAM,IAAI,IAAI,CAEb;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;CACD"}
|
package/dist/audio/Sound.js
CHANGED
|
@@ -18,6 +18,8 @@ export class Sound {
|
|
|
18
18
|
constructor(path, options = {}) {
|
|
19
19
|
this.next = 0;
|
|
20
20
|
this.suspended_ = false;
|
|
21
|
+
/** how many times `play` actually started a clip (not counting calls made while suspended), for a test to assert on without a microphone */
|
|
22
|
+
this.plays = 0;
|
|
21
23
|
const size = Math.max(1, options.poolSize ?? 4);
|
|
22
24
|
const create = options.create ?? createAudio;
|
|
23
25
|
this.volume = options.volume ?? 1;
|
|
@@ -39,6 +41,7 @@ export class Sound {
|
|
|
39
41
|
play(gain = 1, pitch = 1) {
|
|
40
42
|
if (this.suspended_)
|
|
41
43
|
return;
|
|
44
|
+
this.plays++;
|
|
42
45
|
const audio = this.pool[this.next];
|
|
43
46
|
this.next = (this.next + 1) % this.pool.length;
|
|
44
47
|
audio.volume = this.volume * gain;
|