interface AudioConfig { /** Default volumes per category (0..1) */ music?: number; sfx?: number; ui?: number; ambient?: number; /** Persist mute state in localStorage */ persist?: boolean; /** LocalStorage key prefix */ storageKey?: string; } type AudioCategoryName = 'music' | 'sfx' | 'ui' | 'ambient'; /** * Manages all game audio: music, SFX, UI sounds, ambient. * * Optional dependency on @pixi/sound — if not installed, AudioManager * operates as a silent no-op (graceful degradation). * * Features: * - Per-category volume control (music, sfx, ui, ambient) * - Music crossfade and looping * - Mobile audio unlock on first interaction * - Mute state persistence in localStorage * - Global mute/unmute * * @example * ```ts * const audio = new AudioManager({ music: 0.5, sfx: 0.8 }); * await audio.init(); * audio.playMusic('bg-music'); * audio.play('spin-click', 'sfx'); * ``` */ declare class AudioManager { private _soundModule; private _initialized; private _globalMuted; private _persist; private _storageKey; private _categories; private _masterGain; private _currentMusic; /** Duck factor (0..1) from duckMusic/unduckMusic. A presentation state, not a player setting. */ private _musicDuck; /** Crossfade ramp (0..1) for the track that is fading IN. 1 whenever no fade is running. */ private _musicFade; /** Generation counter so a superseded crossfade ramp stops writing over the new track's. */ private _musicFadeToken; private _unlocked; private _unlockHandler; constructor(config?: AudioConfig); /** Whether the audio system is initialized */ get initialized(): boolean; /** Whether audio is globally muted */ get muted(): boolean; /** * Initialize the audio system. * Dynamically imports @pixi/sound to keep it optional. */ init(): Promise; /** * Play a sound effect. * * @param alias - Sound alias (must be loaded via AssetManager) * @param category - Audio category (default: 'sfx') * @param options - Additional play options */ play(alias: string, category?: AudioCategoryName, options?: { volume?: number; loop?: boolean; speed?: number; }): void; /** * Play background music with optional crossfade. * * @param alias - Music alias * @param fadeDuration - Crossfade duration in ms (default: 500) */ playMusic(alias: string, fadeDuration?: number): void; /** * Stop current music. */ stopMusic(): void; /** * Stop all sounds. */ stopAll(): void; /** Global gain (0..1) folded into every category's effective volume. Driven by the shell's * 'master' settingChange. Does not affect the persisted per-category volumes. */ setMasterVolume(volume: number): void; getMasterVolume(): number; /** * Set volume for a category. */ setVolume(category: AudioCategoryName, volume: number): void; /** * Get volume for a category. */ getVolume(category: AudioCategoryName): number; /** * Mute a specific category. */ muteCategory(category: AudioCategoryName): void; /** * Unmute a specific category. */ unmuteCategory(category: AudioCategoryName): void; /** * Toggle mute for a category. */ toggleCategory(category: AudioCategoryName): boolean; /** * Mute all audio globally. */ muteAll(): void; /** * Unmute all audio globally. */ unmuteAll(): void; /** * Toggle global mute. */ toggleMute(): boolean; /** * Duck music volume (e.g., during big win presentation). * * @param factor - Volume multiplier (0..1), e.g. 0.3 = 30% of normal */ duckMusic(factor: number): void; /** * Restore music to normal volume after ducking. */ unduckMusic(): void; /** * Destroy the audio manager and free resources. */ destroy(): void; /** * Smoothly fade a sound's volume from `fromVol` to `toVol` over `durationMs`. */ private fadeVolume; /** * The SOUND-layer gain for the running music track. * * @pixi/sound resolves a playing instance as `instance × sound × global` (WebAudioInstance. * refresh). Each of those three has exactly ONE owner here, which is what keeps the mixer honest: * global — the master gain (`applyVolumes`) * sound — music: this function. sfx: untouched, left at 1. * instance — sfx: the per-call volume × the sfx category. music: always 1. * Everything that can move music volume — the player's slider, the category mute, a big-win duck, * a crossfade — is a term below, so they compose instead of overwriting each other. */ private musicGain; /** Push `musicGain()` at the current track. Safe before it starts playing and with none playing. */ private applyMusicGain; /** Current SOUND-layer volume of `alias`, or 0 when it cannot be read. */ private soundVolumeOf; /** Ramp the crossfade term 0 → 1 over `durationMs`, recomposing the gain each frame so a slider * drag or a duck landing mid-fade is honoured rather than overwritten when the fade ends. */ private rampMusicFade; private applyVolumes; private setupMobileUnlock; private removeMobileUnlock; private saveState; private restoreState; } export { AudioManager };