@magmacrunch/adenosine-audio 0.2.4 → 0.3.1

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 ADDED
@@ -0,0 +1,60 @@
1
+ # Changelog — `@magmacrunch/adenosine-audio`
2
+
3
+ This package only. Repo-wide changes — CI, guard scripts, build tooling — live
4
+ in the [monorepo changelog](../../CHANGELOG.md), which is also where the
5
+ narrative for every release before this file existed is written up.
6
+
7
+ The seven packages version independently, so a number here means nothing about
8
+ any other package.
9
+
10
+ ## Unreleased
11
+
12
+ _Nothing yet._
13
+
14
+ ## 0.3.1 — 2026-09-05
15
+
16
+ ### Fixed
17
+
18
+ - `pauseMusic()` no longer loses the playhead. It stopped the source without
19
+ recording how far it had got, and `playMusic()` always called `start(0)`, so
20
+ every resume replayed the track from its opening. The elapsed time is now
21
+ banked from the context clock on pause and passed as the start offset — and
22
+ wrapped into the buffer, since a looping source given an offset past its end
23
+ clamps rather than wrapping, which would pin a resumed loop to its final
24
+ sample.
25
+
26
+ `stopMusic()` and `destroyMusic()` clear that offset. They are ends rather
27
+ than pauses, and the difference was not expressed anywhere before.
28
+
29
+ - `onVisibilityChange()` no longer starts music that was never playing. Its
30
+ resume branch tested only whether a track had been *loaded*, so a page that
31
+ deliberately had not started one — a title screen, or a game whose music
32
+ belongs to a run in progress — began playing on the first tab-return, with
33
+ no `playMusic()` call anywhere in the app. It now resumes only what it
34
+ paused, which also makes its two branches symmetric: the hide branch already
35
+ guarded on `musicStarted`.
36
+
37
+ Reported as [#14](https://github.com/magmacrunch-media/adenosine/issues/14).
38
+ Two of the four games using this module had already routed around it.
39
+
40
+ ## 0.3.0 — 2026-08-29
41
+
42
+ ### Changed
43
+
44
+ - Dead `VisibilityOptions` import removed. No behaviour change.
45
+
46
+ ### Changed — packaging
47
+
48
+ - `dist/index.global.js` is now a declared export (`./global`) with `unpkg` and
49
+ `jsdelivr` fields, so the bundle the browser tools load off the CDN is named
50
+ in the manifest instead of merely existing at a known path.
51
+ - `CHANGELOG.md` now ships in the tarball.
52
+ - Dead `.d.ts.map` files are gone. They pointed at a `src/` the tarball has
53
+ never included, so an editor's go-to-definition followed them nowhere.
54
+
55
+ ## Earlier releases
56
+
57
+ `0.2.4`, `0.2.3`, `0.2.2`, `0.2.1`, `0.2.0`
58
+
59
+ Descriptions for these are in the [monorepo changelog](../../CHANGELOG.md).
60
+ They are not restated here rather than restated approximately.
package/NOTICE CHANGED
@@ -5,4 +5,4 @@ This product includes software developed by Magma Crunch Media
5
5
  (https://magmacrunch.com).
6
6
 
7
7
  Adenosine is a collection of lightweight web game engines:
8
- https://github.com/magmacrunchmedia/adenosine
8
+ https://github.com/magmacrunch-media/adenosine
package/README.md CHANGED
@@ -36,7 +36,7 @@ itself off.
36
36
  Straight from a CDN — no npm, no build step:
37
37
 
38
38
  ```html
39
- <script src="https://cdn.jsdelivr.net/npm/@magmacrunch/adenosine-audio@0.2/dist/index.global.js"></script>
39
+ <script src="https://cdn.jsdelivr.net/npm/@magmacrunch/adenosine-audio@0.3/dist/index.global.js"></script>
40
40
  ```
41
41
 
42
42
  The IIFE build exposes `window.AdAudio`. The version is pinned to a minor here on purpose:
@@ -57,6 +57,6 @@ ESM only. The `exports` map declares no `require` condition, so this cannot be
57
57
 
58
58
  [Apache-2.0](LICENSE) — Copyright 2026 Magma Crunch Media.
59
59
 
60
- Part of [adenosine](https://github.com/magmacrunchmedia/adenosine), a collection
60
+ Part of [adenosine](https://github.com/magmacrunch-media/adenosine), a collection
61
61
  of lightweight web game engines by [magmacrunch media](https://magmacrunch.com).
62
62
  Keep the `NOTICE` file with any copy you distribute.
@@ -1,4 +1,3 @@
1
1
  export declare function getCtx(): AudioContext;
2
2
  export declare function resumeCtx(): Promise<void>;
3
3
  export declare function closeCtx(): void;
4
- //# sourceMappingURL=audio-context.d.ts.map
package/dist/index.d.ts CHANGED
@@ -6,4 +6,3 @@ export declare function playMusic(fadeIn?: number): Promise<void>;
6
6
  export declare function destroy(): void;
7
7
  export { _loadMusic as loadMusic, _pauseMusic as pauseMusic, _stopMusic as stopMusic, _setMusicVolume as setMusicVolume, _setMusicMuted as setMusicMuted, _isMusicMuted as isMusicMuted, _isMusicPlaying as isMusicPlaying, _toggleMusicMute as toggleMusicMute, _loadSfx as loadSfx, _playSfx as playSfx, _setSfxVolume as setSfxVolume, _setSfxGlobalVolume as setSfxGlobalVolume, _setSfxMuted as setSfxMuted, _isSfxMuted as isSfxMuted, _toggleSfxMute as toggleSfxMute, onVisibilityChange as handleVisibility, };
8
8
  export type { AudioManifest, MusicConfig, SfxConfig, VisibilityOptions } from './types.js';
9
- //# sourceMappingURL=index.d.ts.map
@@ -71,6 +71,9 @@ var AdAudio = (() => {
71
71
  var musicMuted = false;
72
72
  var musicVolume = 0.3;
73
73
  var visibilityHandler = null;
74
+ var musicStartedAt = 0;
75
+ var musicOffset = 0;
76
+ var pausedByVisibility = false;
74
77
  async function loadMusic(url, opts) {
75
78
  musicVolume = opts?.volume ?? 0.3;
76
79
  const res = await fetch(url);
@@ -88,13 +91,16 @@ var AdAudio = (() => {
88
91
  musicSource.buffer = musicBuffer;
89
92
  musicSource.loop = true;
90
93
  musicSource.connect(musicGain);
91
- musicSource.start(0);
94
+ musicOffset = musicBuffer.duration > 0 ? musicOffset % musicBuffer.duration : 0;
95
+ musicSource.start(0, musicOffset);
96
+ musicStartedAt = ctx2.currentTime;
92
97
  const target = musicMuted ? 0 : musicVolume;
93
98
  musicGain.gain.linearRampToValueAtTime(target, ctx2.currentTime + fadeIn);
94
99
  musicStarted = true;
95
100
  }
96
101
  function pauseMusic() {
97
102
  if (musicSource) {
103
+ musicOffset += getCtx().currentTime - musicStartedAt;
98
104
  musicSource.onended = null;
99
105
  musicSource.stop();
100
106
  musicSource = null;
@@ -103,6 +109,8 @@ var AdAudio = (() => {
103
109
  }
104
110
  function stopMusic() {
105
111
  pauseMusic();
112
+ musicOffset = 0;
113
+ pausedByVisibility = false;
106
114
  if (musicGain) {
107
115
  musicGain.disconnect();
108
116
  musicGain = null;
@@ -145,10 +153,15 @@ var AdAudio = (() => {
145
153
  document.removeEventListener("visibilitychange", visibilityHandler);
146
154
  }
147
155
  visibilityHandler = () => {
148
- if (document.hidden && pause && musicStarted) {
149
- pauseMusic();
150
- } else if (!document.hidden && pause && musicBuffer) {
151
- playMusic(0);
156
+ if (!pause) return;
157
+ if (document.hidden) {
158
+ if (musicStarted) {
159
+ pausedByVisibility = true;
160
+ pauseMusic();
161
+ }
162
+ } else if (pausedByVisibility) {
163
+ pausedByVisibility = false;
164
+ void playMusic(0);
152
165
  }
153
166
  };
154
167
  document.addEventListener("visibilitychange", visibilityHandler);
@@ -158,6 +171,9 @@ var AdAudio = (() => {
158
171
  musicBuffer = null;
159
172
  musicStarted = false;
160
173
  musicMuted = false;
174
+ musicOffset = 0;
175
+ musicStartedAt = 0;
176
+ pausedByVisibility = false;
161
177
  if (visibilityHandler) {
162
178
  document.removeEventListener("visibilitychange", visibilityHandler);
163
179
  visibilityHandler = null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/audio-context.ts","../src/music.ts","../src/sfx.ts"],"sourcesContent":["import type { AudioManifest, VisibilityOptions } from './types.js';\nimport {\n loadMusic as _loadMusic,\n playMusic as _playMusic,\n pauseMusic as _pauseMusic,\n stopMusic as _stopMusic,\n setMusicVolume as _setMusicVolume,\n setMusicMuted as _setMusicMuted,\n isMusicMuted as _isMusicMuted,\n isMusicPlaying as _isMusicPlaying,\n toggleMusicMute as _toggleMusicMute,\n onVisibilityChange,\n destroyMusic,\n} from './music.js';\nimport {\n loadSfx as _loadSfx,\n playSfx as _playSfx,\n setSfxVolume as _setSfxVolume,\n setSfxGlobalVolume as _setSfxGlobalVolume,\n setSfxMuted as _setSfxMuted,\n isSfxMuted as _isSfxMuted,\n toggleSfxMute as _toggleSfxMute,\n destroySfx,\n} from './sfx.js';\nimport { closeCtx } from './audio-context.js';\n\nlet musicFadeIn = 2.0;\n\nexport async function init(manifest: AudioManifest): Promise<void> {\n if (manifest.music) {\n await _loadMusic(manifest.music.url, {\n volume: manifest.music.volume,\n fadeIn: manifest.music.fadeIn,\n });\n musicFadeIn = manifest.music.fadeIn ?? 2.0;\n }\n\n if (manifest.sfx) {\n const entries = Object.entries(manifest.sfx);\n await Promise.all(\n entries.map(([name, cfg]) => _loadSfx(name, cfg.url, { volume: cfg.volume, pool: cfg.pool }))\n );\n }\n\n if (manifest.autoVisibility !== false) {\n onVisibilityChange(true);\n }\n}\n\nexport async function playMusic(fadeIn?: number): Promise<void> {\n await _playMusic(fadeIn ?? musicFadeIn);\n}\n\nexport function destroy(): void {\n destroyMusic();\n destroySfx();\n closeCtx();\n}\n\nexport {\n _loadMusic as loadMusic,\n _pauseMusic as pauseMusic,\n _stopMusic as stopMusic,\n _setMusicVolume as setMusicVolume,\n _setMusicMuted as setMusicMuted,\n _isMusicMuted as isMusicMuted,\n _isMusicPlaying as isMusicPlaying,\n _toggleMusicMute as toggleMusicMute,\n _loadSfx as loadSfx,\n _playSfx as playSfx,\n _setSfxVolume as setSfxVolume,\n _setSfxGlobalVolume as setSfxGlobalVolume,\n _setSfxMuted as setSfxMuted,\n _isSfxMuted as isSfxMuted,\n _toggleSfxMute as toggleSfxMute,\n onVisibilityChange as handleVisibility,\n};\n\nexport type { AudioManifest, MusicConfig, SfxConfig, VisibilityOptions } from './types.js';\n","let ctx: AudioContext | null = null;\n\nexport function getCtx(): AudioContext {\n if (!ctx) {\n ctx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return ctx;\n}\n\nexport async function resumeCtx(): Promise<void> {\n const c = getCtx();\n if (c.state === 'suspended') {\n await c.resume();\n }\n}\n\nexport function closeCtx(): void {\n if (ctx && ctx.state !== 'closed') {\n ctx.close();\n }\n ctx = null;\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\nlet musicBuffer: AudioBuffer | null = null;\nlet musicSource: AudioBufferSourceNode | null = null;\nlet musicGain: GainNode | null = null;\nlet musicStarted = false;\nlet musicMuted = false;\nlet musicVolume = 0.3;\nlet visibilityHandler: (() => void) | null = null;\n\nexport async function loadMusic(url: string, opts?: { volume?: number; fadeIn?: number }): Promise<void> {\n musicVolume = opts?.volume ?? 0.3;\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n musicBuffer = await getCtx().decodeAudioData(arrayBuf);\n}\n\nexport async function playMusic(fadeIn = 2.0): Promise<void> {\n if (!musicBuffer || musicStarted) return;\n await resumeCtx();\n\n const ctx = getCtx();\n musicGain = ctx.createGain();\n musicGain.connect(ctx.destination);\n musicGain.gain.setValueAtTime(0, ctx.currentTime);\n\n musicSource = ctx.createBufferSource();\n musicSource.buffer = musicBuffer;\n musicSource.loop = true;\n musicSource.connect(musicGain);\n musicSource.start(0);\n\n const target = musicMuted ? 0 : musicVolume;\n musicGain.gain.linearRampToValueAtTime(target, ctx.currentTime + fadeIn);\n musicStarted = true;\n}\n\nexport function pauseMusic(): void {\n if (musicSource) {\n musicSource.onended = null;\n musicSource.stop();\n musicSource = null;\n }\n musicStarted = false;\n}\n\nexport function stopMusic(): void {\n pauseMusic();\n if (musicGain) {\n musicGain.disconnect();\n musicGain = null;\n }\n}\n\nexport function setMusicVolume(volume: number, rampTime = 0.5): void {\n musicVolume = volume;\n if (musicGain && !musicMuted) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(volume, ctx.currentTime + rampTime);\n }\n}\n\nexport function setMusicMuted(muted: boolean, rampTime = 0.5): void {\n musicMuted = muted;\n if (musicGain) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n if (rampTime <= 0) {\n musicGain.gain.setValueAtTime(muted ? 0 : musicVolume, ctx.currentTime);\n } else {\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(muted ? 0 : musicVolume, ctx.currentTime + rampTime);\n }\n }\n}\n\nexport function isMusicMuted(): boolean {\n return musicMuted;\n}\n\nexport function toggleMusicMute(): boolean {\n setMusicMuted(!musicMuted);\n return musicMuted;\n}\n\nexport function isMusicPlaying(): boolean {\n return musicStarted;\n}\n\nexport function onVisibilityChange(pause: boolean): void {\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n }\n visibilityHandler = () => {\n if (document.hidden && pause && musicStarted) {\n pauseMusic();\n } else if (!document.hidden && pause && musicBuffer) {\n playMusic(0);\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n}\n\nexport function destroyMusic(): void {\n stopMusic();\n musicBuffer = null;\n musicStarted = false;\n musicMuted = false;\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n visibilityHandler = null;\n }\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\ninterface SfxEntry {\n buffer: AudioBuffer;\n volume: number;\n pool: AudioBufferSourceNode[];\n poolSize: number;\n}\n\nconst sfxMap = new Map<string, SfxEntry>();\nlet sfxMuted = false;\nlet sfxGlobalVolume = 1.0;\n\nexport async function loadSfx(name: string, url: string, opts?: { volume?: number; pool?: number }): Promise<void> {\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n const buffer = await getCtx().decodeAudioData(arrayBuf);\n sfxMap.set(name, {\n buffer,\n volume: opts?.volume ?? 0.5,\n pool: [],\n poolSize: opts?.pool ?? 1,\n });\n}\n\nexport async function playSfx(name: string, opts?: { volume?: number }): Promise<void> {\n if (sfxMuted) return;\n const entry = sfxMap.get(name);\n if (!entry) return;\n await resumeCtx();\n\n const ctx = getCtx();\n const gain = ctx.createGain();\n const vol = (opts?.volume ?? entry.volume) * sfxGlobalVolume;\n gain.gain.setValueAtTime(vol, ctx.currentTime);\n gain.connect(ctx.destination);\n\n const source = ctx.createBufferSource();\n source.buffer = entry.buffer;\n source.connect(gain);\n source.start(0);\n\n source.onended = () => {\n gain.disconnect();\n const idx = entry.pool.indexOf(source);\n if (idx !== -1) entry.pool.splice(idx, 1);\n };\n\n if (entry.pool.length < entry.poolSize) {\n entry.pool.push(source);\n }\n}\n\nexport function setSfxVolume(name: string, volume: number): void {\n const entry = sfxMap.get(name);\n if (entry) entry.volume = volume;\n}\n\nexport function setSfxGlobalVolume(volume: number): void {\n sfxGlobalVolume = volume;\n}\n\nexport function setSfxMuted(muted: boolean): void {\n sfxMuted = muted;\n}\n\nexport function isSfxMuted(): boolean {\n return sfxMuted;\n}\n\nexport function toggleSfxMute(): boolean {\n sfxMuted = !sfxMuted;\n return sfxMuted;\n}\n\nexport function destroySfx(): void {\n sfxMap.clear();\n sfxMuted = false;\n sfxGlobalVolume = 1.0;\n}\n"],"mappings":"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","names":["playMusic","ctx","ctx","playMusic"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/audio-context.ts","../src/music.ts","../src/sfx.ts"],"sourcesContent":["import type { AudioManifest } from './types.js';\nimport {\n loadMusic as _loadMusic,\n playMusic as _playMusic,\n pauseMusic as _pauseMusic,\n stopMusic as _stopMusic,\n setMusicVolume as _setMusicVolume,\n setMusicMuted as _setMusicMuted,\n isMusicMuted as _isMusicMuted,\n isMusicPlaying as _isMusicPlaying,\n toggleMusicMute as _toggleMusicMute,\n onVisibilityChange,\n destroyMusic,\n} from './music.js';\nimport {\n loadSfx as _loadSfx,\n playSfx as _playSfx,\n setSfxVolume as _setSfxVolume,\n setSfxGlobalVolume as _setSfxGlobalVolume,\n setSfxMuted as _setSfxMuted,\n isSfxMuted as _isSfxMuted,\n toggleSfxMute as _toggleSfxMute,\n destroySfx,\n} from './sfx.js';\nimport { closeCtx } from './audio-context.js';\n\nlet musicFadeIn = 2.0;\n\nexport async function init(manifest: AudioManifest): Promise<void> {\n if (manifest.music) {\n await _loadMusic(manifest.music.url, {\n volume: manifest.music.volume,\n fadeIn: manifest.music.fadeIn,\n });\n musicFadeIn = manifest.music.fadeIn ?? 2.0;\n }\n\n if (manifest.sfx) {\n const entries = Object.entries(manifest.sfx);\n await Promise.all(\n entries.map(([name, cfg]) => _loadSfx(name, cfg.url, { volume: cfg.volume, pool: cfg.pool }))\n );\n }\n\n if (manifest.autoVisibility !== false) {\n onVisibilityChange(true);\n }\n}\n\nexport async function playMusic(fadeIn?: number): Promise<void> {\n await _playMusic(fadeIn ?? musicFadeIn);\n}\n\nexport function destroy(): void {\n destroyMusic();\n destroySfx();\n closeCtx();\n}\n\nexport {\n _loadMusic as loadMusic,\n _pauseMusic as pauseMusic,\n _stopMusic as stopMusic,\n _setMusicVolume as setMusicVolume,\n _setMusicMuted as setMusicMuted,\n _isMusicMuted as isMusicMuted,\n _isMusicPlaying as isMusicPlaying,\n _toggleMusicMute as toggleMusicMute,\n _loadSfx as loadSfx,\n _playSfx as playSfx,\n _setSfxVolume as setSfxVolume,\n _setSfxGlobalVolume as setSfxGlobalVolume,\n _setSfxMuted as setSfxMuted,\n _isSfxMuted as isSfxMuted,\n _toggleSfxMute as toggleSfxMute,\n onVisibilityChange as handleVisibility,\n};\n\nexport type { AudioManifest, MusicConfig, SfxConfig, VisibilityOptions } from './types.js';\n","let ctx: AudioContext | null = null;\n\nexport function getCtx(): AudioContext {\n if (!ctx) {\n ctx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return ctx;\n}\n\nexport async function resumeCtx(): Promise<void> {\n const c = getCtx();\n if (c.state === 'suspended') {\n await c.resume();\n }\n}\n\nexport function closeCtx(): void {\n if (ctx && ctx.state !== 'closed') {\n ctx.close();\n }\n ctx = null;\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\nlet musicBuffer: AudioBuffer | null = null;\nlet musicSource: AudioBufferSourceNode | null = null;\nlet musicGain: GainNode | null = null;\nlet musicStarted = false;\nlet musicMuted = false;\nlet musicVolume = 0.3;\nlet visibilityHandler: (() => void) | null = null;\n\n/**\n * Where the playhead is, so a pause can be resumed rather than restarted.\n *\n * A BufferSourceNode cannot be restarted once stopped, and it does not report\n * how far it got, so the elapsed time has to be tracked by hand: the context\n * clock is read when a source starts and again when it is stopped.\n *\n * Without this, pauseMusic() threw the position away and playMusic() started\n * the buffer at 0, so a tab-away and back replayed the track from its opening.\n * Easy to miss on a long ambient bed, very audible on a short loop.\n */\nlet musicStartedAt = 0; // ctx.currentTime when the live source began\nlet musicOffset = 0; // seconds into the buffer that source began at\n\n/**\n * Whether the visibility handler is the reason music is not playing.\n *\n * Its resume branch used to fire on any `musicBuffer`, i.e. on nothing more\n * than a track having been loaded — so a page that deliberately had not\n * started its music (a title screen, or a game that only plays during a run)\n * began playing on the first tab-return, with no playMusic() call anywhere in\n * the app. Only what this handler paused is resumed by it.\n */\nlet pausedByVisibility = false;\n\nexport async function loadMusic(url: string, opts?: { volume?: number; fadeIn?: number }): Promise<void> {\n musicVolume = opts?.volume ?? 0.3;\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n musicBuffer = await getCtx().decodeAudioData(arrayBuf);\n}\n\nexport async function playMusic(fadeIn = 2.0): Promise<void> {\n if (!musicBuffer || musicStarted) return;\n await resumeCtx();\n\n const ctx = getCtx();\n musicGain = ctx.createGain();\n musicGain.connect(ctx.destination);\n musicGain.gain.setValueAtTime(0, ctx.currentTime);\n\n musicSource = ctx.createBufferSource();\n musicSource.buffer = musicBuffer;\n musicSource.loop = true;\n musicSource.connect(musicGain);\n // Resume where the last pause left off. `loop` means the offset must be\n // wrapped: start() throws nothing for an offset past the buffer, it simply\n // clamps, which would silently pin a resumed loop to the final sample.\n musicOffset = musicBuffer.duration > 0 ? musicOffset % musicBuffer.duration : 0;\n musicSource.start(0, musicOffset);\n musicStartedAt = ctx.currentTime;\n\n const target = musicMuted ? 0 : musicVolume;\n musicGain.gain.linearRampToValueAtTime(target, ctx.currentTime + fadeIn);\n musicStarted = true;\n}\n\n/** Stop playback, remembering the playhead so playMusic() resumes from it. */\nexport function pauseMusic(): void {\n if (musicSource) {\n // Banked before the node goes, since nothing can be read from it after.\n musicOffset += getCtx().currentTime - musicStartedAt;\n musicSource.onended = null;\n musicSource.stop();\n musicSource = null;\n }\n musicStarted = false;\n}\n\n/** End playback. Unlike pauseMusic(), the next play starts from the top. */\nexport function stopMusic(): void {\n pauseMusic();\n musicOffset = 0;\n pausedByVisibility = false;\n if (musicGain) {\n musicGain.disconnect();\n musicGain = null;\n }\n}\n\nexport function setMusicVolume(volume: number, rampTime = 0.5): void {\n musicVolume = volume;\n if (musicGain && !musicMuted) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(volume, ctx.currentTime + rampTime);\n }\n}\n\nexport function setMusicMuted(muted: boolean, rampTime = 0.5): void {\n musicMuted = muted;\n if (musicGain) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n if (rampTime <= 0) {\n musicGain.gain.setValueAtTime(muted ? 0 : musicVolume, ctx.currentTime);\n } else {\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(muted ? 0 : musicVolume, ctx.currentTime + rampTime);\n }\n }\n}\n\nexport function isMusicMuted(): boolean {\n return musicMuted;\n}\n\nexport function toggleMusicMute(): boolean {\n setMusicMuted(!musicMuted);\n return musicMuted;\n}\n\nexport function isMusicPlaying(): boolean {\n return musicStarted;\n}\n\nexport function onVisibilityChange(pause: boolean): void {\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n }\n visibilityHandler = () => {\n if (!pause) return;\n if (document.hidden) {\n if (musicStarted) {\n pausedByVisibility = true;\n pauseMusic();\n }\n } else if (pausedByVisibility) {\n pausedByVisibility = false;\n void playMusic(0);\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n}\n\nexport function destroyMusic(): void {\n stopMusic();\n musicBuffer = null;\n musicStarted = false;\n musicMuted = false;\n musicOffset = 0;\n musicStartedAt = 0;\n pausedByVisibility = false;\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n visibilityHandler = null;\n }\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\ninterface SfxEntry {\n buffer: AudioBuffer;\n volume: number;\n pool: AudioBufferSourceNode[];\n poolSize: number;\n}\n\nconst sfxMap = new Map<string, SfxEntry>();\nlet sfxMuted = false;\nlet sfxGlobalVolume = 1.0;\n\nexport async function loadSfx(name: string, url: string, opts?: { volume?: number; pool?: number }): Promise<void> {\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n const buffer = await getCtx().decodeAudioData(arrayBuf);\n sfxMap.set(name, {\n buffer,\n volume: opts?.volume ?? 0.5,\n pool: [],\n poolSize: opts?.pool ?? 1,\n });\n}\n\nexport async function playSfx(name: string, opts?: { volume?: number }): Promise<void> {\n if (sfxMuted) return;\n const entry = sfxMap.get(name);\n if (!entry) return;\n await resumeCtx();\n\n const ctx = getCtx();\n const gain = ctx.createGain();\n const vol = (opts?.volume ?? entry.volume) * sfxGlobalVolume;\n gain.gain.setValueAtTime(vol, ctx.currentTime);\n gain.connect(ctx.destination);\n\n const source = ctx.createBufferSource();\n source.buffer = entry.buffer;\n source.connect(gain);\n source.start(0);\n\n source.onended = () => {\n gain.disconnect();\n const idx = entry.pool.indexOf(source);\n if (idx !== -1) entry.pool.splice(idx, 1);\n };\n\n if (entry.pool.length < entry.poolSize) {\n entry.pool.push(source);\n }\n}\n\nexport function setSfxVolume(name: string, volume: number): void {\n const entry = sfxMap.get(name);\n if (entry) entry.volume = volume;\n}\n\nexport function setSfxGlobalVolume(volume: number): void {\n sfxGlobalVolume = volume;\n}\n\nexport function setSfxMuted(muted: boolean): void {\n sfxMuted = muted;\n}\n\nexport function isSfxMuted(): boolean {\n return sfxMuted;\n}\n\nexport function toggleSfxMute(): boolean {\n sfxMuted = !sfxMuted;\n return sfxMuted;\n}\n\nexport function destroySfx(): void {\n sfxMap.clear();\n sfxMuted = false;\n sfxGlobalVolume = 1.0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAAA;AAAA,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,MAAI,MAA2B;AAExB,WAAS,SAAuB;AACrC,QAAI,CAAC,KAAK;AACR,YAAM,KAAK,OAAO,gBAAiB,OAAe,oBAAoB;AAAA,IACxE;AACA,WAAO;AAAA,EACT;AAEA,iBAAsB,YAA2B;AAC/C,UAAM,IAAI,OAAO;AACjB,QAAI,EAAE,UAAU,aAAa;AAC3B,YAAM,EAAE,OAAO;AAAA,IACjB;AAAA,EACF;AAEO,WAAS,WAAiB;AAC/B,QAAI,OAAO,IAAI,UAAU,UAAU;AACjC,UAAI,MAAM;AAAA,IACZ;AACA,UAAM;AAAA,EACR;;;ACnBA,MAAI,cAAkC;AACtC,MAAI,cAA4C;AAChD,MAAI,YAA6B;AACjC,MAAI,eAAe;AACnB,MAAI,aAAa;AACjB,MAAI,cAAc;AAClB,MAAI,oBAAyC;AAa7C,MAAI,iBAAiB;AACrB,MAAI,cAAc;AAWlB,MAAI,qBAAqB;AAEzB,iBAAsB,UAAU,KAAa,MAA4D;AACvG,kBAAc,MAAM,UAAU;AAC9B,UAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,UAAM,WAAW,MAAM,IAAI,YAAY;AACvC,kBAAc,MAAM,OAAO,EAAE,gBAAgB,QAAQ;AAAA,EACvD;AAEA,iBAAsB,UAAU,SAAS,GAAoB;AAC3D,QAAI,CAAC,eAAe,aAAc;AAClC,UAAM,UAAU;AAEhB,UAAMC,OAAM,OAAO;AACnB,gBAAYA,KAAI,WAAW;AAC3B,cAAU,QAAQA,KAAI,WAAW;AACjC,cAAU,KAAK,eAAe,GAAGA,KAAI,WAAW;AAEhD,kBAAcA,KAAI,mBAAmB;AACrC,gBAAY,SAAS;AACrB,gBAAY,OAAO;AACnB,gBAAY,QAAQ,SAAS;AAI7B,kBAAc,YAAY,WAAW,IAAI,cAAc,YAAY,WAAW;AAC9E,gBAAY,MAAM,GAAG,WAAW;AAChC,qBAAiBA,KAAI;AAErB,UAAM,SAAS,aAAa,IAAI;AAChC,cAAU,KAAK,wBAAwB,QAAQA,KAAI,cAAc,MAAM;AACvE,mBAAe;AAAA,EACjB;AAGO,WAAS,aAAmB;AACjC,QAAI,aAAa;AAEf,qBAAe,OAAO,EAAE,cAAc;AACtC,kBAAY,UAAU;AACtB,kBAAY,KAAK;AACjB,oBAAc;AAAA,IAChB;AACA,mBAAe;AAAA,EACjB;AAGO,WAAS,YAAkB;AAChC,eAAW;AACX,kBAAc;AACd,yBAAqB;AACrB,QAAI,WAAW;AACb,gBAAU,WAAW;AACrB,kBAAY;AAAA,IACd;AAAA,EACF;AAEO,WAAS,eAAe,QAAgB,WAAW,KAAW;AACnE,kBAAc;AACd,QAAI,aAAa,CAAC,YAAY;AAC5B,YAAMA,OAAM,OAAO;AACnB,gBAAU,KAAK,sBAAsBA,KAAI,WAAW;AACpD,gBAAU,KAAK,eAAe,UAAU,KAAK,OAAOA,KAAI,WAAW;AACnE,gBAAU,KAAK,wBAAwB,QAAQA,KAAI,cAAc,QAAQ;AAAA,IAC3E;AAAA,EACF;AAEO,WAAS,cAAc,OAAgB,WAAW,KAAW;AAClE,iBAAa;AACb,QAAI,WAAW;AACb,YAAMA,OAAM,OAAO;AACnB,gBAAU,KAAK,sBAAsBA,KAAI,WAAW;AACpD,UAAI,YAAY,GAAG;AACjB,kBAAU,KAAK,eAAe,QAAQ,IAAI,aAAaA,KAAI,WAAW;AAAA,MACxE,OAAO;AACL,kBAAU,KAAK,eAAe,UAAU,KAAK,OAAOA,KAAI,WAAW;AACnE,kBAAU,KAAK,wBAAwB,QAAQ,IAAI,aAAaA,KAAI,cAAc,QAAQ;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AAEO,WAAS,eAAwB;AACtC,WAAO;AAAA,EACT;AAEO,WAAS,kBAA2B;AACzC,kBAAc,CAAC,UAAU;AACzB,WAAO;AAAA,EACT;AAEO,WAAS,iBAA0B;AACxC,WAAO;AAAA,EACT;AAEO,WAAS,mBAAmB,OAAsB;AACvD,QAAI,mBAAmB;AACrB,eAAS,oBAAoB,oBAAoB,iBAAiB;AAAA,IACpE;AACA,wBAAoB,MAAM;AACxB,UAAI,CAAC,MAAO;AACZ,UAAI,SAAS,QAAQ;AACnB,YAAI,cAAc;AAChB,+BAAqB;AACrB,qBAAW;AAAA,QACb;AAAA,MACF,WAAW,oBAAoB;AAC7B,6BAAqB;AACrB,aAAK,UAAU,CAAC;AAAA,MAClB;AAAA,IACF;AACA,aAAS,iBAAiB,oBAAoB,iBAAiB;AAAA,EACjE;AAEO,WAAS,eAAqB;AACnC,cAAU;AACV,kBAAc;AACd,mBAAe;AACf,iBAAa;AACb,kBAAc;AACd,qBAAiB;AACjB,yBAAqB;AACrB,QAAI,mBAAmB;AACrB,eAAS,oBAAoB,oBAAoB,iBAAiB;AAClE,0BAAoB;AAAA,IACtB;AAAA,EACF;;;ACrJA,MAAM,SAAS,oBAAI,IAAsB;AACzC,MAAI,WAAW;AACf,MAAI,kBAAkB;AAEtB,iBAAsB,QAAQ,MAAc,KAAa,MAA0D;AACjH,UAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,UAAM,WAAW,MAAM,IAAI,YAAY;AACvC,UAAM,SAAS,MAAM,OAAO,EAAE,gBAAgB,QAAQ;AACtD,WAAO,IAAI,MAAM;AAAA,MACf;AAAA,MACA,QAAQ,MAAM,UAAU;AAAA,MACxB,MAAM,CAAC;AAAA,MACP,UAAU,MAAM,QAAQ;AAAA,IAC1B,CAAC;AAAA,EACH;AAEA,iBAAsB,QAAQ,MAAc,MAA2C;AACrF,QAAI,SAAU;AACd,UAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,QAAI,CAAC,MAAO;AACZ,UAAM,UAAU;AAEhB,UAAMC,OAAM,OAAO;AACnB,UAAM,OAAOA,KAAI,WAAW;AAC5B,UAAM,OAAO,MAAM,UAAU,MAAM,UAAU;AAC7C,SAAK,KAAK,eAAe,KAAKA,KAAI,WAAW;AAC7C,SAAK,QAAQA,KAAI,WAAW;AAE5B,UAAM,SAASA,KAAI,mBAAmB;AACtC,WAAO,SAAS,MAAM;AACtB,WAAO,QAAQ,IAAI;AACnB,WAAO,MAAM,CAAC;AAEd,WAAO,UAAU,MAAM;AACrB,WAAK,WAAW;AAChB,YAAM,MAAM,MAAM,KAAK,QAAQ,MAAM;AACrC,UAAI,QAAQ,GAAI,OAAM,KAAK,OAAO,KAAK,CAAC;AAAA,IAC1C;AAEA,QAAI,MAAM,KAAK,SAAS,MAAM,UAAU;AACtC,YAAM,KAAK,KAAK,MAAM;AAAA,IACxB;AAAA,EACF;AAEO,WAAS,aAAa,MAAc,QAAsB;AAC/D,UAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,QAAI,MAAO,OAAM,SAAS;AAAA,EAC5B;AAEO,WAAS,mBAAmB,QAAsB;AACvD,sBAAkB;AAAA,EACpB;AAEO,WAAS,YAAY,OAAsB;AAChD,eAAW;AAAA,EACb;AAEO,WAAS,aAAsB;AACpC,WAAO;AAAA,EACT;AAEO,WAAS,gBAAyB;AACvC,eAAW,CAAC;AACZ,WAAO;AAAA,EACT;AAEO,WAAS,aAAmB;AACjC,WAAO,MAAM;AACb,eAAW;AACX,sBAAkB;AAAA,EACpB;;;AHrDA,MAAI,cAAc;AAElB,iBAAsB,KAAK,UAAwC;AACjE,QAAI,SAAS,OAAO;AAClB,YAAM,UAAW,SAAS,MAAM,KAAK;AAAA,QACnC,QAAQ,SAAS,MAAM;AAAA,QACvB,QAAQ,SAAS,MAAM;AAAA,MACzB,CAAC;AACD,oBAAc,SAAS,MAAM,UAAU;AAAA,IACzC;AAEA,QAAI,SAAS,KAAK;AAChB,YAAM,UAAU,OAAO,QAAQ,SAAS,GAAG;AAC3C,YAAM,QAAQ;AAAA,QACZ,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,QAAS,MAAM,IAAI,KAAK,EAAE,QAAQ,IAAI,QAAQ,MAAM,IAAI,KAAK,CAAC,CAAC;AAAA,MAC9F;AAAA,IACF;AAEA,QAAI,SAAS,mBAAmB,OAAO;AACrC,yBAAmB,IAAI;AAAA,IACzB;AAAA,EACF;AAEA,iBAAsBC,WAAU,QAAgC;AAC9D,UAAM,UAAW,UAAU,WAAW;AAAA,EACxC;AAEO,WAAS,UAAgB;AAC9B,iBAAa;AACb,eAAW;AACX,aAAS;AAAA,EACX;","names":["playMusic","ctx","ctx","playMusic"]}
package/dist/index.js CHANGED
@@ -27,6 +27,9 @@ var musicStarted = false;
27
27
  var musicMuted = false;
28
28
  var musicVolume = 0.3;
29
29
  var visibilityHandler = null;
30
+ var musicStartedAt = 0;
31
+ var musicOffset = 0;
32
+ var pausedByVisibility = false;
30
33
  async function loadMusic(url, opts) {
31
34
  musicVolume = opts?.volume ?? 0.3;
32
35
  const res = await fetch(url);
@@ -44,13 +47,16 @@ async function playMusic(fadeIn = 2) {
44
47
  musicSource.buffer = musicBuffer;
45
48
  musicSource.loop = true;
46
49
  musicSource.connect(musicGain);
47
- musicSource.start(0);
50
+ musicOffset = musicBuffer.duration > 0 ? musicOffset % musicBuffer.duration : 0;
51
+ musicSource.start(0, musicOffset);
52
+ musicStartedAt = ctx2.currentTime;
48
53
  const target = musicMuted ? 0 : musicVolume;
49
54
  musicGain.gain.linearRampToValueAtTime(target, ctx2.currentTime + fadeIn);
50
55
  musicStarted = true;
51
56
  }
52
57
  function pauseMusic() {
53
58
  if (musicSource) {
59
+ musicOffset += getCtx().currentTime - musicStartedAt;
54
60
  musicSource.onended = null;
55
61
  musicSource.stop();
56
62
  musicSource = null;
@@ -59,6 +65,8 @@ function pauseMusic() {
59
65
  }
60
66
  function stopMusic() {
61
67
  pauseMusic();
68
+ musicOffset = 0;
69
+ pausedByVisibility = false;
62
70
  if (musicGain) {
63
71
  musicGain.disconnect();
64
72
  musicGain = null;
@@ -101,10 +109,15 @@ function onVisibilityChange(pause) {
101
109
  document.removeEventListener("visibilitychange", visibilityHandler);
102
110
  }
103
111
  visibilityHandler = () => {
104
- if (document.hidden && pause && musicStarted) {
105
- pauseMusic();
106
- } else if (!document.hidden && pause && musicBuffer) {
107
- playMusic(0);
112
+ if (!pause) return;
113
+ if (document.hidden) {
114
+ if (musicStarted) {
115
+ pausedByVisibility = true;
116
+ pauseMusic();
117
+ }
118
+ } else if (pausedByVisibility) {
119
+ pausedByVisibility = false;
120
+ void playMusic(0);
108
121
  }
109
122
  };
110
123
  document.addEventListener("visibilitychange", visibilityHandler);
@@ -114,6 +127,9 @@ function destroyMusic() {
114
127
  musicBuffer = null;
115
128
  musicStarted = false;
116
129
  musicMuted = false;
130
+ musicOffset = 0;
131
+ musicStartedAt = 0;
132
+ pausedByVisibility = false;
117
133
  if (visibilityHandler) {
118
134
  document.removeEventListener("visibilitychange", visibilityHandler);
119
135
  visibilityHandler = null;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/audio-context.ts","../src/music.ts","../src/sfx.ts","../src/index.ts"],"sourcesContent":["let ctx: AudioContext | null = null;\n\nexport function getCtx(): AudioContext {\n if (!ctx) {\n ctx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return ctx;\n}\n\nexport async function resumeCtx(): Promise<void> {\n const c = getCtx();\n if (c.state === 'suspended') {\n await c.resume();\n }\n}\n\nexport function closeCtx(): void {\n if (ctx && ctx.state !== 'closed') {\n ctx.close();\n }\n ctx = null;\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\nlet musicBuffer: AudioBuffer | null = null;\nlet musicSource: AudioBufferSourceNode | null = null;\nlet musicGain: GainNode | null = null;\nlet musicStarted = false;\nlet musicMuted = false;\nlet musicVolume = 0.3;\nlet visibilityHandler: (() => void) | null = null;\n\nexport async function loadMusic(url: string, opts?: { volume?: number; fadeIn?: number }): Promise<void> {\n musicVolume = opts?.volume ?? 0.3;\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n musicBuffer = await getCtx().decodeAudioData(arrayBuf);\n}\n\nexport async function playMusic(fadeIn = 2.0): Promise<void> {\n if (!musicBuffer || musicStarted) return;\n await resumeCtx();\n\n const ctx = getCtx();\n musicGain = ctx.createGain();\n musicGain.connect(ctx.destination);\n musicGain.gain.setValueAtTime(0, ctx.currentTime);\n\n musicSource = ctx.createBufferSource();\n musicSource.buffer = musicBuffer;\n musicSource.loop = true;\n musicSource.connect(musicGain);\n musicSource.start(0);\n\n const target = musicMuted ? 0 : musicVolume;\n musicGain.gain.linearRampToValueAtTime(target, ctx.currentTime + fadeIn);\n musicStarted = true;\n}\n\nexport function pauseMusic(): void {\n if (musicSource) {\n musicSource.onended = null;\n musicSource.stop();\n musicSource = null;\n }\n musicStarted = false;\n}\n\nexport function stopMusic(): void {\n pauseMusic();\n if (musicGain) {\n musicGain.disconnect();\n musicGain = null;\n }\n}\n\nexport function setMusicVolume(volume: number, rampTime = 0.5): void {\n musicVolume = volume;\n if (musicGain && !musicMuted) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(volume, ctx.currentTime + rampTime);\n }\n}\n\nexport function setMusicMuted(muted: boolean, rampTime = 0.5): void {\n musicMuted = muted;\n if (musicGain) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n if (rampTime <= 0) {\n musicGain.gain.setValueAtTime(muted ? 0 : musicVolume, ctx.currentTime);\n } else {\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(muted ? 0 : musicVolume, ctx.currentTime + rampTime);\n }\n }\n}\n\nexport function isMusicMuted(): boolean {\n return musicMuted;\n}\n\nexport function toggleMusicMute(): boolean {\n setMusicMuted(!musicMuted);\n return musicMuted;\n}\n\nexport function isMusicPlaying(): boolean {\n return musicStarted;\n}\n\nexport function onVisibilityChange(pause: boolean): void {\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n }\n visibilityHandler = () => {\n if (document.hidden && pause && musicStarted) {\n pauseMusic();\n } else if (!document.hidden && pause && musicBuffer) {\n playMusic(0);\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n}\n\nexport function destroyMusic(): void {\n stopMusic();\n musicBuffer = null;\n musicStarted = false;\n musicMuted = false;\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n visibilityHandler = null;\n }\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\ninterface SfxEntry {\n buffer: AudioBuffer;\n volume: number;\n pool: AudioBufferSourceNode[];\n poolSize: number;\n}\n\nconst sfxMap = new Map<string, SfxEntry>();\nlet sfxMuted = false;\nlet sfxGlobalVolume = 1.0;\n\nexport async function loadSfx(name: string, url: string, opts?: { volume?: number; pool?: number }): Promise<void> {\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n const buffer = await getCtx().decodeAudioData(arrayBuf);\n sfxMap.set(name, {\n buffer,\n volume: opts?.volume ?? 0.5,\n pool: [],\n poolSize: opts?.pool ?? 1,\n });\n}\n\nexport async function playSfx(name: string, opts?: { volume?: number }): Promise<void> {\n if (sfxMuted) return;\n const entry = sfxMap.get(name);\n if (!entry) return;\n await resumeCtx();\n\n const ctx = getCtx();\n const gain = ctx.createGain();\n const vol = (opts?.volume ?? entry.volume) * sfxGlobalVolume;\n gain.gain.setValueAtTime(vol, ctx.currentTime);\n gain.connect(ctx.destination);\n\n const source = ctx.createBufferSource();\n source.buffer = entry.buffer;\n source.connect(gain);\n source.start(0);\n\n source.onended = () => {\n gain.disconnect();\n const idx = entry.pool.indexOf(source);\n if (idx !== -1) entry.pool.splice(idx, 1);\n };\n\n if (entry.pool.length < entry.poolSize) {\n entry.pool.push(source);\n }\n}\n\nexport function setSfxVolume(name: string, volume: number): void {\n const entry = sfxMap.get(name);\n if (entry) entry.volume = volume;\n}\n\nexport function setSfxGlobalVolume(volume: number): void {\n sfxGlobalVolume = volume;\n}\n\nexport function setSfxMuted(muted: boolean): void {\n sfxMuted = muted;\n}\n\nexport function isSfxMuted(): boolean {\n return sfxMuted;\n}\n\nexport function toggleSfxMute(): boolean {\n sfxMuted = !sfxMuted;\n return sfxMuted;\n}\n\nexport function destroySfx(): void {\n sfxMap.clear();\n sfxMuted = false;\n sfxGlobalVolume = 1.0;\n}\n","import type { AudioManifest, VisibilityOptions } from './types.js';\nimport {\n loadMusic as _loadMusic,\n playMusic as _playMusic,\n pauseMusic as _pauseMusic,\n stopMusic as _stopMusic,\n setMusicVolume as _setMusicVolume,\n setMusicMuted as _setMusicMuted,\n isMusicMuted as _isMusicMuted,\n isMusicPlaying as _isMusicPlaying,\n toggleMusicMute as _toggleMusicMute,\n onVisibilityChange,\n destroyMusic,\n} from './music.js';\nimport {\n loadSfx as _loadSfx,\n playSfx as _playSfx,\n setSfxVolume as _setSfxVolume,\n setSfxGlobalVolume as _setSfxGlobalVolume,\n setSfxMuted as _setSfxMuted,\n isSfxMuted as _isSfxMuted,\n toggleSfxMute as _toggleSfxMute,\n destroySfx,\n} from './sfx.js';\nimport { closeCtx } from './audio-context.js';\n\nlet musicFadeIn = 2.0;\n\nexport async function init(manifest: AudioManifest): Promise<void> {\n if (manifest.music) {\n await _loadMusic(manifest.music.url, {\n volume: manifest.music.volume,\n fadeIn: manifest.music.fadeIn,\n });\n musicFadeIn = manifest.music.fadeIn ?? 2.0;\n }\n\n if (manifest.sfx) {\n const entries = Object.entries(manifest.sfx);\n await Promise.all(\n entries.map(([name, cfg]) => _loadSfx(name, cfg.url, { volume: cfg.volume, pool: cfg.pool }))\n );\n }\n\n if (manifest.autoVisibility !== false) {\n onVisibilityChange(true);\n }\n}\n\nexport async function playMusic(fadeIn?: number): Promise<void> {\n await _playMusic(fadeIn ?? musicFadeIn);\n}\n\nexport function destroy(): void {\n destroyMusic();\n destroySfx();\n closeCtx();\n}\n\nexport {\n _loadMusic as loadMusic,\n _pauseMusic as pauseMusic,\n _stopMusic as stopMusic,\n _setMusicVolume as setMusicVolume,\n _setMusicMuted as setMusicMuted,\n _isMusicMuted as isMusicMuted,\n _isMusicPlaying as isMusicPlaying,\n _toggleMusicMute as toggleMusicMute,\n _loadSfx as loadSfx,\n _playSfx as playSfx,\n _setSfxVolume as setSfxVolume,\n _setSfxGlobalVolume as setSfxGlobalVolume,\n _setSfxMuted as setSfxMuted,\n _isSfxMuted as isSfxMuted,\n _toggleSfxMute as toggleSfxMute,\n onVisibilityChange as handleVisibility,\n};\n\nexport type { AudioManifest, MusicConfig, SfxConfig, VisibilityOptions } from './types.js';\n"],"mappings":"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","names":["ctx","ctx","playMusic"]}
1
+ {"version":3,"sources":["../src/audio-context.ts","../src/music.ts","../src/sfx.ts","../src/index.ts"],"sourcesContent":["let ctx: AudioContext | null = null;\n\nexport function getCtx(): AudioContext {\n if (!ctx) {\n ctx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return ctx;\n}\n\nexport async function resumeCtx(): Promise<void> {\n const c = getCtx();\n if (c.state === 'suspended') {\n await c.resume();\n }\n}\n\nexport function closeCtx(): void {\n if (ctx && ctx.state !== 'closed') {\n ctx.close();\n }\n ctx = null;\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\nlet musicBuffer: AudioBuffer | null = null;\nlet musicSource: AudioBufferSourceNode | null = null;\nlet musicGain: GainNode | null = null;\nlet musicStarted = false;\nlet musicMuted = false;\nlet musicVolume = 0.3;\nlet visibilityHandler: (() => void) | null = null;\n\n/**\n * Where the playhead is, so a pause can be resumed rather than restarted.\n *\n * A BufferSourceNode cannot be restarted once stopped, and it does not report\n * how far it got, so the elapsed time has to be tracked by hand: the context\n * clock is read when a source starts and again when it is stopped.\n *\n * Without this, pauseMusic() threw the position away and playMusic() started\n * the buffer at 0, so a tab-away and back replayed the track from its opening.\n * Easy to miss on a long ambient bed, very audible on a short loop.\n */\nlet musicStartedAt = 0; // ctx.currentTime when the live source began\nlet musicOffset = 0; // seconds into the buffer that source began at\n\n/**\n * Whether the visibility handler is the reason music is not playing.\n *\n * Its resume branch used to fire on any `musicBuffer`, i.e. on nothing more\n * than a track having been loaded — so a page that deliberately had not\n * started its music (a title screen, or a game that only plays during a run)\n * began playing on the first tab-return, with no playMusic() call anywhere in\n * the app. Only what this handler paused is resumed by it.\n */\nlet pausedByVisibility = false;\n\nexport async function loadMusic(url: string, opts?: { volume?: number; fadeIn?: number }): Promise<void> {\n musicVolume = opts?.volume ?? 0.3;\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n musicBuffer = await getCtx().decodeAudioData(arrayBuf);\n}\n\nexport async function playMusic(fadeIn = 2.0): Promise<void> {\n if (!musicBuffer || musicStarted) return;\n await resumeCtx();\n\n const ctx = getCtx();\n musicGain = ctx.createGain();\n musicGain.connect(ctx.destination);\n musicGain.gain.setValueAtTime(0, ctx.currentTime);\n\n musicSource = ctx.createBufferSource();\n musicSource.buffer = musicBuffer;\n musicSource.loop = true;\n musicSource.connect(musicGain);\n // Resume where the last pause left off. `loop` means the offset must be\n // wrapped: start() throws nothing for an offset past the buffer, it simply\n // clamps, which would silently pin a resumed loop to the final sample.\n musicOffset = musicBuffer.duration > 0 ? musicOffset % musicBuffer.duration : 0;\n musicSource.start(0, musicOffset);\n musicStartedAt = ctx.currentTime;\n\n const target = musicMuted ? 0 : musicVolume;\n musicGain.gain.linearRampToValueAtTime(target, ctx.currentTime + fadeIn);\n musicStarted = true;\n}\n\n/** Stop playback, remembering the playhead so playMusic() resumes from it. */\nexport function pauseMusic(): void {\n if (musicSource) {\n // Banked before the node goes, since nothing can be read from it after.\n musicOffset += getCtx().currentTime - musicStartedAt;\n musicSource.onended = null;\n musicSource.stop();\n musicSource = null;\n }\n musicStarted = false;\n}\n\n/** End playback. Unlike pauseMusic(), the next play starts from the top. */\nexport function stopMusic(): void {\n pauseMusic();\n musicOffset = 0;\n pausedByVisibility = false;\n if (musicGain) {\n musicGain.disconnect();\n musicGain = null;\n }\n}\n\nexport function setMusicVolume(volume: number, rampTime = 0.5): void {\n musicVolume = volume;\n if (musicGain && !musicMuted) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(volume, ctx.currentTime + rampTime);\n }\n}\n\nexport function setMusicMuted(muted: boolean, rampTime = 0.5): void {\n musicMuted = muted;\n if (musicGain) {\n const ctx = getCtx();\n musicGain.gain.cancelScheduledValues(ctx.currentTime);\n if (rampTime <= 0) {\n musicGain.gain.setValueAtTime(muted ? 0 : musicVolume, ctx.currentTime);\n } else {\n musicGain.gain.setValueAtTime(musicGain.gain.value, ctx.currentTime);\n musicGain.gain.linearRampToValueAtTime(muted ? 0 : musicVolume, ctx.currentTime + rampTime);\n }\n }\n}\n\nexport function isMusicMuted(): boolean {\n return musicMuted;\n}\n\nexport function toggleMusicMute(): boolean {\n setMusicMuted(!musicMuted);\n return musicMuted;\n}\n\nexport function isMusicPlaying(): boolean {\n return musicStarted;\n}\n\nexport function onVisibilityChange(pause: boolean): void {\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n }\n visibilityHandler = () => {\n if (!pause) return;\n if (document.hidden) {\n if (musicStarted) {\n pausedByVisibility = true;\n pauseMusic();\n }\n } else if (pausedByVisibility) {\n pausedByVisibility = false;\n void playMusic(0);\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n}\n\nexport function destroyMusic(): void {\n stopMusic();\n musicBuffer = null;\n musicStarted = false;\n musicMuted = false;\n musicOffset = 0;\n musicStartedAt = 0;\n pausedByVisibility = false;\n if (visibilityHandler) {\n document.removeEventListener('visibilitychange', visibilityHandler);\n visibilityHandler = null;\n }\n}\n","import { getCtx, resumeCtx } from './audio-context.js';\n\ninterface SfxEntry {\n buffer: AudioBuffer;\n volume: number;\n pool: AudioBufferSourceNode[];\n poolSize: number;\n}\n\nconst sfxMap = new Map<string, SfxEntry>();\nlet sfxMuted = false;\nlet sfxGlobalVolume = 1.0;\n\nexport async function loadSfx(name: string, url: string, opts?: { volume?: number; pool?: number }): Promise<void> {\n const res = await fetch(url);\n const arrayBuf = await res.arrayBuffer();\n const buffer = await getCtx().decodeAudioData(arrayBuf);\n sfxMap.set(name, {\n buffer,\n volume: opts?.volume ?? 0.5,\n pool: [],\n poolSize: opts?.pool ?? 1,\n });\n}\n\nexport async function playSfx(name: string, opts?: { volume?: number }): Promise<void> {\n if (sfxMuted) return;\n const entry = sfxMap.get(name);\n if (!entry) return;\n await resumeCtx();\n\n const ctx = getCtx();\n const gain = ctx.createGain();\n const vol = (opts?.volume ?? entry.volume) * sfxGlobalVolume;\n gain.gain.setValueAtTime(vol, ctx.currentTime);\n gain.connect(ctx.destination);\n\n const source = ctx.createBufferSource();\n source.buffer = entry.buffer;\n source.connect(gain);\n source.start(0);\n\n source.onended = () => {\n gain.disconnect();\n const idx = entry.pool.indexOf(source);\n if (idx !== -1) entry.pool.splice(idx, 1);\n };\n\n if (entry.pool.length < entry.poolSize) {\n entry.pool.push(source);\n }\n}\n\nexport function setSfxVolume(name: string, volume: number): void {\n const entry = sfxMap.get(name);\n if (entry) entry.volume = volume;\n}\n\nexport function setSfxGlobalVolume(volume: number): void {\n sfxGlobalVolume = volume;\n}\n\nexport function setSfxMuted(muted: boolean): void {\n sfxMuted = muted;\n}\n\nexport function isSfxMuted(): boolean {\n return sfxMuted;\n}\n\nexport function toggleSfxMute(): boolean {\n sfxMuted = !sfxMuted;\n return sfxMuted;\n}\n\nexport function destroySfx(): void {\n sfxMap.clear();\n sfxMuted = false;\n sfxGlobalVolume = 1.0;\n}\n","import type { AudioManifest } from './types.js';\nimport {\n loadMusic as _loadMusic,\n playMusic as _playMusic,\n pauseMusic as _pauseMusic,\n stopMusic as _stopMusic,\n setMusicVolume as _setMusicVolume,\n setMusicMuted as _setMusicMuted,\n isMusicMuted as _isMusicMuted,\n isMusicPlaying as _isMusicPlaying,\n toggleMusicMute as _toggleMusicMute,\n onVisibilityChange,\n destroyMusic,\n} from './music.js';\nimport {\n loadSfx as _loadSfx,\n playSfx as _playSfx,\n setSfxVolume as _setSfxVolume,\n setSfxGlobalVolume as _setSfxGlobalVolume,\n setSfxMuted as _setSfxMuted,\n isSfxMuted as _isSfxMuted,\n toggleSfxMute as _toggleSfxMute,\n destroySfx,\n} from './sfx.js';\nimport { closeCtx } from './audio-context.js';\n\nlet musicFadeIn = 2.0;\n\nexport async function init(manifest: AudioManifest): Promise<void> {\n if (manifest.music) {\n await _loadMusic(manifest.music.url, {\n volume: manifest.music.volume,\n fadeIn: manifest.music.fadeIn,\n });\n musicFadeIn = manifest.music.fadeIn ?? 2.0;\n }\n\n if (manifest.sfx) {\n const entries = Object.entries(manifest.sfx);\n await Promise.all(\n entries.map(([name, cfg]) => _loadSfx(name, cfg.url, { volume: cfg.volume, pool: cfg.pool }))\n );\n }\n\n if (manifest.autoVisibility !== false) {\n onVisibilityChange(true);\n }\n}\n\nexport async function playMusic(fadeIn?: number): Promise<void> {\n await _playMusic(fadeIn ?? musicFadeIn);\n}\n\nexport function destroy(): void {\n destroyMusic();\n destroySfx();\n closeCtx();\n}\n\nexport {\n _loadMusic as loadMusic,\n _pauseMusic as pauseMusic,\n _stopMusic as stopMusic,\n _setMusicVolume as setMusicVolume,\n _setMusicMuted as setMusicMuted,\n _isMusicMuted as isMusicMuted,\n _isMusicPlaying as isMusicPlaying,\n _toggleMusicMute as toggleMusicMute,\n _loadSfx as loadSfx,\n _playSfx as playSfx,\n _setSfxVolume as setSfxVolume,\n _setSfxGlobalVolume as setSfxGlobalVolume,\n _setSfxMuted as setSfxMuted,\n _isSfxMuted as isSfxMuted,\n _toggleSfxMute as toggleSfxMute,\n onVisibilityChange as handleVisibility,\n};\n\nexport type { AudioManifest, MusicConfig, SfxConfig, VisibilityOptions } from './types.js';\n"],"mappings":"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","names":["ctx","ctx","playMusic"]}
package/dist/music.d.ts CHANGED
@@ -3,7 +3,9 @@ export declare function loadMusic(url: string, opts?: {
3
3
  fadeIn?: number;
4
4
  }): Promise<void>;
5
5
  export declare function playMusic(fadeIn?: number): Promise<void>;
6
+ /** Stop playback, remembering the playhead so playMusic() resumes from it. */
6
7
  export declare function pauseMusic(): void;
8
+ /** End playback. Unlike pauseMusic(), the next play starts from the top. */
7
9
  export declare function stopMusic(): void;
8
10
  export declare function setMusicVolume(volume: number, rampTime?: number): void;
9
11
  export declare function setMusicMuted(muted: boolean, rampTime?: number): void;
@@ -12,4 +14,3 @@ export declare function toggleMusicMute(): boolean;
12
14
  export declare function isMusicPlaying(): boolean;
13
15
  export declare function onVisibilityChange(pause: boolean): void;
14
16
  export declare function destroyMusic(): void;
15
- //# sourceMappingURL=music.d.ts.map
package/dist/sfx.d.ts CHANGED
@@ -11,4 +11,3 @@ export declare function setSfxMuted(muted: boolean): void;
11
11
  export declare function isSfxMuted(): boolean;
12
12
  export declare function toggleSfxMute(): boolean;
13
13
  export declare function destroySfx(): void;
14
- //# sourceMappingURL=sfx.d.ts.map
package/dist/types.d.ts CHANGED
@@ -17,4 +17,3 @@ export interface VisibilityOptions {
17
17
  pauseMusic?: boolean;
18
18
  muteSfxOnHidden?: boolean;
19
19
  }
20
- //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magmacrunch/adenosine-audio",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "Web Audio API engine for game music and sound effects",
6
6
  "license": "Apache-2.0",
@@ -17,23 +17,27 @@
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "https://github.com/magmacrunchmedia/adenosine",
20
+ "url": "git+https://github.com/magmacrunch-media/adenosine.git",
21
21
  "directory": "packages/audio"
22
22
  },
23
23
  "author": "magmacrunch media",
24
- "homepage": "https://github.com/magmacrunchmedia/adenosine#readme",
24
+ "homepage": "https://magmacrunch.com/ware/adenosine/",
25
25
  "main": "dist/index.js",
26
+ "unpkg": "dist/index.global.js",
27
+ "jsdelivr": "dist/index.global.js",
26
28
  "types": "dist/index.d.ts",
27
29
  "exports": {
28
30
  ".": {
29
31
  "types": "./dist/index.d.ts",
30
32
  "import": "./dist/index.js"
31
- }
33
+ },
34
+ "./global": "./dist/index.global.js"
32
35
  },
33
36
  "files": [
34
37
  "dist",
35
38
  "NOTICE",
36
- "API.md"
39
+ "API.md",
40
+ "CHANGELOG.md"
37
41
  ],
38
42
  "publishConfig": {
39
43
  "access": "public"
@@ -41,12 +45,13 @@
41
45
  "scripts": {
42
46
  "build": "tsup && tsc --emitDeclarationOnly --declaration --outDir dist",
43
47
  "typecheck": "tsc --noEmit",
44
- "test": "vitest run --passWithNoTests"
48
+ "test": "vitest run --passWithNoTests",
49
+ "test:coverage": "vitest run --coverage"
45
50
  },
46
51
  "devDependencies": {
47
52
  "tsup": "^8.5.1",
48
53
  "typescript": "^7.0.2",
49
- "vitest": "^3.2.1"
54
+ "vitest": "^4.1.11"
50
55
  },
51
56
  "sideEffects": false
52
57
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"audio-context.d.ts","sourceRoot":"","sources":["../src/audio-context.ts"],"names":[],"mappings":"AAEA,wBAAgB,MAAM,IAAI,YAAY,CAKrC;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAK/C;AAED,wBAAgB,QAAQ,IAAI,IAAI,CAK/B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAqB,MAAM,YAAY,CAAC;AACnE,OAAO,EACL,SAAS,IAAI,UAAU,EAEvB,UAAU,IAAI,WAAW,EACzB,SAAS,IAAI,UAAU,EACvB,cAAc,IAAI,eAAe,EACjC,aAAa,IAAI,cAAc,EAC/B,YAAY,IAAI,aAAa,EAC7B,cAAc,IAAI,eAAe,EACjC,eAAe,IAAI,gBAAgB,EACnC,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,OAAO,IAAI,QAAQ,EACnB,OAAO,IAAI,QAAQ,EACnB,YAAY,IAAI,aAAa,EAC7B,kBAAkB,IAAI,mBAAmB,EACzC,WAAW,IAAI,YAAY,EAC3B,UAAU,IAAI,WAAW,EACzB,aAAa,IAAI,cAAc,EAEhC,MAAM,UAAU,CAAC;AAKlB,wBAAsB,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBjE;AAED,wBAAsB,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D;AAED,wBAAgB,OAAO,IAAI,IAAI,CAI9B;AAED,OAAO,EACL,UAAU,IAAI,SAAS,EACvB,WAAW,IAAI,UAAU,EACzB,UAAU,IAAI,SAAS,EACvB,eAAe,IAAI,cAAc,EACjC,cAAc,IAAI,aAAa,EAC/B,aAAa,IAAI,YAAY,EAC7B,eAAe,IAAI,cAAc,EACjC,gBAAgB,IAAI,eAAe,EACnC,QAAQ,IAAI,OAAO,EACnB,QAAQ,IAAI,OAAO,EACnB,aAAa,IAAI,YAAY,EAC7B,mBAAmB,IAAI,kBAAkB,EACzC,YAAY,IAAI,WAAW,EAC3B,WAAW,IAAI,UAAU,EACzB,cAAc,IAAI,aAAa,EAC/B,kBAAkB,IAAI,gBAAgB,GACvC,CAAC;AAEF,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"music.d.ts","sourceRoot":"","sources":["../src/music.ts"],"names":[],"mappings":"AAUA,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvG;AAED,wBAAsB,SAAS,CAAC,MAAM,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB3D;AAED,wBAAgB,UAAU,IAAI,IAAI,CAOjC;AAED,wBAAgB,SAAS,IAAI,IAAI,CAMhC;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAM,GAAG,IAAI,CAQnE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAM,GAAG,IAAI,CAYlE;AAED,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED,wBAAgB,eAAe,IAAI,OAAO,CAGzC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAYvD;AAED,wBAAgB,YAAY,IAAI,IAAI,CASnC"}
package/dist/sfx.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"sfx.d.ts","sourceRoot":"","sources":["../src/sfx.ts"],"names":[],"mappings":"AAaA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjH;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BrF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAG/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAEhD;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAgB,aAAa,IAAI,OAAO,CAGvC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAIjC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B"}