star-audio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ # Third-Party Software Notices and Information
2
+
3
+ This package contains third-party software components governed by the licenses indicated below.
4
+
5
+ ---
6
+
7
+ ## howler.js
8
+
9
+ - **Website:** [https://howlerjs.com/](https://howlerjs.com/)
10
+ - **License:** MIT License
11
+
12
+ Copyright (c) 2013-2020 James Simpson and GoldFire Studios, Inc.
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining
15
+ a copy of this software and associated documentation files (the
16
+ "Software"), to deal in the Software without restriction, including
17
+ without limitation the rights to use, copy, modify, merge, publish,
18
+ distribute, sublicense, and/or sell copies of the Software, and to
19
+ permit persons to whom the Software is furnished to do so, subject to
20
+ the following conditions:
21
+
22
+ The above copyright notice and this permission notice shall be
23
+ included in all copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Star Audio
2
+
3
+ A simple sound manager for web games that "just works" on mobile.
4
+
5
+ `star-audio` solves the 90% use case for game audio: SFX playback and BGM crossfades, with a mobile-first design that handles browser audio unlocking automatically. It's designed to be predictable, easy to use, and LLM-friendly.
6
+
7
+ **Powered by the battle-tested [Howler.js](https://howlerjs.com/) core.**
8
+
9
+ This package is part of the **Star SDK**.
10
+
11
+ ## Features
12
+
13
+ - **Bulletproof:** Missing audio files won't crash your game - errors are logged but never thrown.
14
+ - **Mobile-Safe by Default:** Automatically handles audio context unlocking on user interaction. Plays sound even when the device's silent/ringer switch is on.
15
+ - **Tiny API Surface:** A small, guessable API (`play`, `music.crossfadeTo`, `setMute`). No try/catch needed.
16
+ - **Two-Bus Mixer:** Simple `music` and `sfx` groups for easy volume control.
17
+ - **Seamless Music Crossfades:** Built-in helpers for smooth background music transitions.
18
+ - **SSR-Safe:** Can be imported in server-side environments without errors.
19
+ - **Single Dependency:** Powered by Howler.js for maximum reliability.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ yarn add star-audio
25
+ # or
26
+ npm install star-audio
27
+ ```
28
+
29
+ ## Quick Starts
30
+
31
+ ### 1. Vanilla JS (60 seconds)
32
+
33
+ This is the fastest way to get started. Serve an `index.html` file and add the following.
34
+
35
+ ```html
36
+ <div id="root" style="height:100vh; display: grid; place-items: center;">Tap anywhere</div>
37
+ <script type="module">
38
+ import createAudio from 'star-audio';
39
+
40
+ // Create an audio manager.
41
+ // 'auto' unlocks audio on the first user interaction.
42
+ // 'autostart' will play 'music.theme' once unlocked.
43
+ const audio = createAudio({
44
+ unlockWith: 'auto',
45
+ autostart: { id: 'music.theme', crossfadeSec: 1 }
46
+ });
47
+
48
+ // Preload your audio assets.
49
+ await audio.preload({
50
+ 'music.theme': ['/audio/theme.m4a', '/audio/theme.mp3'],
51
+ 'sfx.tap': ['/audio/tap.mp3']
52
+ });
53
+
54
+ // Play a sound effect on user interaction.
55
+ document.getElementById('root').addEventListener('pointerdown', () => {
56
+ audio.play('sfx.tap', { volume: 0.9 }); // alias: audio.playSound('sfx.tap')
57
+ });
58
+ </script>
59
+ ```
60
+
61
+ ### 2. Next.js / React (Client Component)
62
+
63
+ In a React component, it's best to create the audio instance once with `useMemo`.
64
+
65
+ ```tsx
66
+ /* app/game/page.tsx */
67
+ "use client";
68
+ import { useEffect, useMemo } from 'react';
69
+ import { createStarAudio } from 'star-audio';
70
+
71
+ export default function Game() {
72
+ const audio = useMemo(() => createStarAudio({
73
+ unlockWith: 'auto',
74
+ autostart: { id: 'music.theme', crossfadeSec: 1 },
75
+ suspendOnHidden: true, // Pauses audio when tab is not visible
76
+ }), []);
77
+
78
+ useEffect(() => {
79
+ // Preload assets when the component mounts.
80
+ (async () => {
81
+ await audio.preload({
82
+ 'music.theme': ['/audio/theme.m4a', '/audio/theme.mp3'],
83
+ 'sfx.tap': ['/audio/tap.mp3']
84
+ });
85
+ })();
86
+
87
+ // Clean up the audio instance when the component unmounts.
88
+ return () => audio.destroy();
89
+ }, [audio]);
90
+
91
+ return (
92
+ <div
93
+ className="h-screen w-screen"
94
+ onPointerDown={() => audio.play('sfx.tap', { volume: 0.9 })}
95
+ >
96
+ Tap to play
97
+ </div>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ## Common Recipes
103
+
104
+ ### Crossfade Background Music
105
+
106
+ ```ts
107
+ await audio.preload({ 'bgm.title': ['/music/title.m4a'] });
108
+
109
+ // Fade from the current track (or silence) to the title music over 1.5 seconds.
110
+ await audio.music.crossfadeTo('bgm.title', { duration: 1.5 });
111
+ ```
112
+
113
+ ### Volumes & Mute
114
+
115
+ The volume and mute state are automatically persisted to `localStorage`.
116
+
117
+ ```ts
118
+ // Set music volume to 60% with a 300ms fade.
119
+ audio.setMusicVolume(0.6, { durationMs: 300 });
120
+
121
+ // Set SFX volume to 80% instantly.
122
+ audio.setSfxVolume(0.8);
123
+
124
+ // Toggle mute for all audio.
125
+ audio.toggleMute();
126
+ ```
127
+
128
+ ### Pause/Resume (e.g., for a pause menu)
129
+
130
+ ```ts
131
+ // Suspend the audio context.
132
+ await audio.pause();
133
+
134
+ // Resume the audio context.
135
+ await audio.resume();
136
+ ```
137
+
138
+ ### Limit SFX Overlap
139
+
140
+ To prevent audio clipping from too many overlapping sounds, you can limit the number of concurrent SFX voices.
141
+
142
+ ```ts
143
+ // Only allow a maximum of 16 sound effects to play at once.
144
+ const audio = createAudio({ maxSfxVoices: 16 });
145
+ ```
146
+
147
+ ## Error Handling Philosophy
148
+
149
+ Star Audio is designed to **never crash your game**. All loading methods (`preload()`, `load()`) gracefully handle missing or corrupt audio files:
150
+
151
+ ```ts
152
+ // ✅ This never throws - even if the file doesn't exist!
153
+ await audio.preload({
154
+ 'sfx.coin': '/missing.mp3', // ⚠️ Logs warning, continues
155
+ 'sfx.jump': '/audio/jump.mp3' // ✅ Loads successfully
156
+ });
157
+
158
+ // Game continues working with the sounds that did load
159
+ audio.play('sfx.jump'); // ✅ Works
160
+ audio.play('sfx.coin'); // ⚠️ Silently fails (already warned during preload)
161
+ ```
162
+
163
+ **No try/catch needed.** Failed audio loads are logged to the console with clear warnings, but your game keeps running.
164
+
165
+ If you need programmatic error handling, listen to the `'error'` event:
166
+
167
+ ```ts
168
+ audio.on('error', () => {
169
+ console.log('An audio file failed to load, but the game is still running!');
170
+ });
171
+ ```
172
+
173
+ ## Known Limitations
174
+
175
+ `star-audio` is designed to solve the 90% use case for web game audio. The current implementation (v1.x) has the following limitations:
176
+
177
+ - **No SFX voice limiting**: The `maxSfxVoices` option is accepted but not enforced. In practice, browsers limit concurrent audio to ~30-50 voices.
178
+ - **No micro-ducking**: Background music does not automatically reduce volume when sound effects play.
179
+ - **No volume ramping**: The `durationMs` parameter in `setMusicVolume()` and `setSfxVolume()` is accepted but ignored. Volume changes are instant.
180
+ - **No scheduled playback**: The `when`, `offset`, and `duration` parameters in `PlayOptions` are not currently implemented.
181
+ - **Pause/Resume behavior**: Only music is automatically resumed when calling `resume()` after `pause()`. Sound effects that were playing will not resume.
182
+
183
+ These limitations reflect the current Howler.js wrapper implementation. If you need any of these features, please open an issue on GitHub to help us prioritize future development.
184
+
185
+ ## Acknowledgements
186
+
187
+ The reliability and robustness of `star-audio`, especially on mobile devices, is made possible by the fantastic work of the developers behind [Howler.js](https://howlerjs.com/). We use the Howler core to handle the complexities of cross-browser audio, allowing us to provide a simpler, game-focused API on top of a battle-tested foundation.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Star Audio SDK version
3
+ */
4
+ declare const VERSION = "0.1.0";
5
+ /**
6
+ * The possible states of the audio context.
7
+ * - `locked`: The audio context is waiting for a user gesture to start.
8
+ * - `running`: The audio context is active and playing sound.
9
+ * - `suspended`: The audio context is paused.
10
+ */
11
+ type AudioState = 'locked' | 'running' | 'suspended';
12
+ /**
13
+ * Predefined procedural sound presets.
14
+ */
15
+ type SynthPreset = 'beep' | 'coin' | 'pickup' | 'jump' | 'hurt' | 'explosion' | 'powerup' | 'shoot' | 'laser' | 'error' | 'click' | 'success' | 'bonus' | 'select' | 'unlock' | 'swoosh' | 'hit';
16
+ /**
17
+ * Custom procedural sound definition using Web Audio API oscillators.
18
+ */
19
+ interface SynthDefinition {
20
+ waveform?: 'sine' | 'square' | 'sawtooth' | 'triangle';
21
+ frequency?: number | number[];
22
+ duration?: number;
23
+ volume?: number;
24
+ envelope?: 'percussive' | 'sustained';
25
+ }
26
+ /**
27
+ * A manifest of audio assets to load.
28
+ * The key is the asset ID, and the value can be:
29
+ * - A URL string (file-based audio)
30
+ * - An array of URLs (multiple formats)
31
+ * - A SynthPreset string (procedural preset sound)
32
+ * - A SynthDefinition object (custom procedural sound)
33
+ * - An object with `src`/`url`/`synth` and optional `group` properties
34
+ */
35
+ type Manifest = Record<string, string | string[] | SynthPreset | SynthDefinition | {
36
+ src?: string | string[];
37
+ url?: string | string[];
38
+ synth?: SynthPreset | SynthDefinition;
39
+ group?: 'music' | 'sfx';
40
+ volume?: number;
41
+ }>;
42
+ /**
43
+ * Options for playing a sound.
44
+ */
45
+ interface PlayOptions {
46
+ src?: string | string[];
47
+ url?: string | string[];
48
+ volume?: number;
49
+ loop?: boolean;
50
+ rate?: number;
51
+ detune?: number;
52
+ when?: number;
53
+ offset?: number;
54
+ duration?: number;
55
+ group?: 'music' | 'sfx';
56
+ }
57
+ /**
58
+ * A handle to a playing sound, allowing for control over its playback.
59
+ */
60
+ interface SoundHandle {
61
+ id: string;
62
+ stop(at?: number): void;
63
+ setVolume(v: number, nowPlusSec?: number): void;
64
+ readonly playing: boolean;
65
+ }
66
+ /**
67
+ * Options for creating a StarAudio instance.
68
+ */
69
+ interface StarAudioOptions {
70
+ unlockWith?: 'auto' | HTMLElement | Document | Window | false;
71
+ autostart?: string | {
72
+ id: string;
73
+ crossfadeSec?: number;
74
+ loop?: boolean;
75
+ };
76
+ autoplay?: StarAudioOptions['autostart'];
77
+ maxSfxVoices?: number;
78
+ initialMute?: boolean;
79
+ initialVolumes?: {
80
+ music?: number;
81
+ sfx?: number;
82
+ };
83
+ suspendOnHidden?: boolean;
84
+ persistKey?: string;
85
+ ducking?: boolean | {
86
+ amount?: number;
87
+ holdMs?: number;
88
+ releaseMs?: number;
89
+ };
90
+ latencyHint?: 'interactive' | 'balanced' | 'playback';
91
+ }
92
+ /**
93
+ * The main interface for the Star Audio manager.
94
+ */
95
+ interface StarAudio {
96
+ readonly state: AudioState;
97
+ readonly ready: Promise<void>;
98
+ preload(m: Manifest): Promise<void>;
99
+ load(o: {
100
+ id: string;
101
+ src?: string | string[];
102
+ url?: string | string[];
103
+ group?: 'music' | 'sfx';
104
+ }): Promise<void>;
105
+ play(id: string, opts?: PlayOptions): SoundHandle | null;
106
+ playSound: StarAudio['play'];
107
+ music: {
108
+ crossfadeTo(id: string, o?: {
109
+ duration?: number;
110
+ loop?: boolean;
111
+ }): Promise<void>;
112
+ stop(fadeSec?: number): void;
113
+ fadeTo: StarAudio['music']['crossfadeTo'];
114
+ switchTo: StarAudio['music']['crossfadeTo'];
115
+ };
116
+ musicFadeTo: StarAudio['music']['crossfadeTo'];
117
+ setMusicVolume(v: number, o?: {
118
+ durationMs?: number;
119
+ }): void;
120
+ setSfxVolume(v: number, o?: {
121
+ durationMs?: number;
122
+ }): void;
123
+ setMusic: StarAudio['setMusicVolume'];
124
+ setSfx: StarAudio['setSfxVolume'];
125
+ setMute(muted: boolean): void;
126
+ mute: StarAudio['setMute'];
127
+ toggleMute(): void;
128
+ isMuted(): boolean;
129
+ pause(): Promise<void>;
130
+ resume(): Promise<void>;
131
+ unlock(): Promise<void>;
132
+ attachUnlock(target?: HTMLElement | Document | Window): void;
133
+ on(evt: 'unlocked' | 'suspended' | 'resumed' | 'error', cb: () => void): void;
134
+ off(evt: 'unlocked' | 'suspended' | 'resumed' | 'error', cb: () => void): void;
135
+ destroy(): void;
136
+ }
137
+ /**
138
+ * Creates a new StarAudio instance.
139
+ * @param opts - A set of options for configuring the audio manager.
140
+ * @returns A new `StarAudio` instance.
141
+ */
142
+ declare function createStarAudio(opts?: StarAudioOptions): StarAudio;
143
+
144
+ export { type AudioState, type Manifest, type PlayOptions, type SoundHandle, type StarAudio, type StarAudioOptions, type SynthDefinition, type SynthPreset, VERSION, createStarAudio, createStarAudio as default };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Star Audio SDK version
3
+ */
4
+ declare const VERSION = "0.1.0";
5
+ /**
6
+ * The possible states of the audio context.
7
+ * - `locked`: The audio context is waiting for a user gesture to start.
8
+ * - `running`: The audio context is active and playing sound.
9
+ * - `suspended`: The audio context is paused.
10
+ */
11
+ type AudioState = 'locked' | 'running' | 'suspended';
12
+ /**
13
+ * Predefined procedural sound presets.
14
+ */
15
+ type SynthPreset = 'beep' | 'coin' | 'pickup' | 'jump' | 'hurt' | 'explosion' | 'powerup' | 'shoot' | 'laser' | 'error' | 'click' | 'success' | 'bonus' | 'select' | 'unlock' | 'swoosh' | 'hit';
16
+ /**
17
+ * Custom procedural sound definition using Web Audio API oscillators.
18
+ */
19
+ interface SynthDefinition {
20
+ waveform?: 'sine' | 'square' | 'sawtooth' | 'triangle';
21
+ frequency?: number | number[];
22
+ duration?: number;
23
+ volume?: number;
24
+ envelope?: 'percussive' | 'sustained';
25
+ }
26
+ /**
27
+ * A manifest of audio assets to load.
28
+ * The key is the asset ID, and the value can be:
29
+ * - A URL string (file-based audio)
30
+ * - An array of URLs (multiple formats)
31
+ * - A SynthPreset string (procedural preset sound)
32
+ * - A SynthDefinition object (custom procedural sound)
33
+ * - An object with `src`/`url`/`synth` and optional `group` properties
34
+ */
35
+ type Manifest = Record<string, string | string[] | SynthPreset | SynthDefinition | {
36
+ src?: string | string[];
37
+ url?: string | string[];
38
+ synth?: SynthPreset | SynthDefinition;
39
+ group?: 'music' | 'sfx';
40
+ volume?: number;
41
+ }>;
42
+ /**
43
+ * Options for playing a sound.
44
+ */
45
+ interface PlayOptions {
46
+ src?: string | string[];
47
+ url?: string | string[];
48
+ volume?: number;
49
+ loop?: boolean;
50
+ rate?: number;
51
+ detune?: number;
52
+ when?: number;
53
+ offset?: number;
54
+ duration?: number;
55
+ group?: 'music' | 'sfx';
56
+ }
57
+ /**
58
+ * A handle to a playing sound, allowing for control over its playback.
59
+ */
60
+ interface SoundHandle {
61
+ id: string;
62
+ stop(at?: number): void;
63
+ setVolume(v: number, nowPlusSec?: number): void;
64
+ readonly playing: boolean;
65
+ }
66
+ /**
67
+ * Options for creating a StarAudio instance.
68
+ */
69
+ interface StarAudioOptions {
70
+ unlockWith?: 'auto' | HTMLElement | Document | Window | false;
71
+ autostart?: string | {
72
+ id: string;
73
+ crossfadeSec?: number;
74
+ loop?: boolean;
75
+ };
76
+ autoplay?: StarAudioOptions['autostart'];
77
+ maxSfxVoices?: number;
78
+ initialMute?: boolean;
79
+ initialVolumes?: {
80
+ music?: number;
81
+ sfx?: number;
82
+ };
83
+ suspendOnHidden?: boolean;
84
+ persistKey?: string;
85
+ ducking?: boolean | {
86
+ amount?: number;
87
+ holdMs?: number;
88
+ releaseMs?: number;
89
+ };
90
+ latencyHint?: 'interactive' | 'balanced' | 'playback';
91
+ }
92
+ /**
93
+ * The main interface for the Star Audio manager.
94
+ */
95
+ interface StarAudio {
96
+ readonly state: AudioState;
97
+ readonly ready: Promise<void>;
98
+ preload(m: Manifest): Promise<void>;
99
+ load(o: {
100
+ id: string;
101
+ src?: string | string[];
102
+ url?: string | string[];
103
+ group?: 'music' | 'sfx';
104
+ }): Promise<void>;
105
+ play(id: string, opts?: PlayOptions): SoundHandle | null;
106
+ playSound: StarAudio['play'];
107
+ music: {
108
+ crossfadeTo(id: string, o?: {
109
+ duration?: number;
110
+ loop?: boolean;
111
+ }): Promise<void>;
112
+ stop(fadeSec?: number): void;
113
+ fadeTo: StarAudio['music']['crossfadeTo'];
114
+ switchTo: StarAudio['music']['crossfadeTo'];
115
+ };
116
+ musicFadeTo: StarAudio['music']['crossfadeTo'];
117
+ setMusicVolume(v: number, o?: {
118
+ durationMs?: number;
119
+ }): void;
120
+ setSfxVolume(v: number, o?: {
121
+ durationMs?: number;
122
+ }): void;
123
+ setMusic: StarAudio['setMusicVolume'];
124
+ setSfx: StarAudio['setSfxVolume'];
125
+ setMute(muted: boolean): void;
126
+ mute: StarAudio['setMute'];
127
+ toggleMute(): void;
128
+ isMuted(): boolean;
129
+ pause(): Promise<void>;
130
+ resume(): Promise<void>;
131
+ unlock(): Promise<void>;
132
+ attachUnlock(target?: HTMLElement | Document | Window): void;
133
+ on(evt: 'unlocked' | 'suspended' | 'resumed' | 'error', cb: () => void): void;
134
+ off(evt: 'unlocked' | 'suspended' | 'resumed' | 'error', cb: () => void): void;
135
+ destroy(): void;
136
+ }
137
+ /**
138
+ * Creates a new StarAudio instance.
139
+ * @param opts - A set of options for configuring the audio manager.
140
+ * @returns A new `StarAudio` instance.
141
+ */
142
+ declare function createStarAudio(opts?: StarAudioOptions): StarAudio;
143
+
144
+ export { type AudioState, type Manifest, type PlayOptions, type SoundHandle, type StarAudio, type StarAudioOptions, type SynthDefinition, type SynthPreset, VERSION, createStarAudio, createStarAudio as default };