d609934b73
CI / build-and-push (push) Successful in 1m17s
Synthesized audio system with 9 distinct SFX (click, success, warning, danger, purchase, achievement, era transition, info) mapped to all game notifications, plus generative ambient background music with chord progressions. Adds SFX volume slider to settings alongside existing music volume control. No audio files or npm dependencies needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import { AudioManager } from './AudioManager';
|
|
import type { GameSettings } from '@token-empire/shared';
|
|
import { useGameStore } from '@/store';
|
|
|
|
export { AudioManager } from './AudioManager';
|
|
export { triggerNotificationSound, playUISound } from './sounds';
|
|
export type { SoundId } from './synthesizer';
|
|
|
|
function applyVolumes(audio: AudioManager, settings: GameSettings): void {
|
|
audio.setSoundEnabled(settings.soundEnabled);
|
|
audio.setMusicVolume(settings.musicVolume);
|
|
audio.setSfxVolume(settings.sfxVolume ?? 0.5);
|
|
}
|
|
|
|
function shouldPlayMusic(settings: GameSettings): boolean {
|
|
return settings.soundEnabled && settings.musicVolume > 0;
|
|
}
|
|
|
|
export function initAudioSystem(): () => void {
|
|
const audio = AudioManager.getInstance();
|
|
let gestureListenerActive = false;
|
|
|
|
const settings = useGameStore.getState().meta.settings;
|
|
applyVolumes(audio, settings);
|
|
|
|
// Music requires a user gesture to start (browser autoplay policy).
|
|
// We register a one-shot listener that starts music on first click/key.
|
|
const startOnGesture = () => {
|
|
const s = useGameStore.getState().meta.settings;
|
|
if (shouldPlayMusic(s)) {
|
|
audio.startMusic();
|
|
}
|
|
document.removeEventListener('click', startOnGesture);
|
|
document.removeEventListener('keydown', startOnGesture);
|
|
gestureListenerActive = false;
|
|
};
|
|
|
|
if (shouldPlayMusic(settings)) {
|
|
document.addEventListener('click', startOnGesture);
|
|
document.addEventListener('keydown', startOnGesture);
|
|
gestureListenerActive = true;
|
|
}
|
|
|
|
const unsub = useGameStore.subscribe((state) => {
|
|
const next = state.meta.settings;
|
|
applyVolumes(audio, next);
|
|
|
|
if (shouldPlayMusic(next)) {
|
|
if (!audio.isMusicPlaying) {
|
|
if (audio.isContextActive) {
|
|
audio.startMusic();
|
|
} else if (!gestureListenerActive) {
|
|
document.addEventListener('click', startOnGesture);
|
|
document.addEventListener('keydown', startOnGesture);
|
|
gestureListenerActive = true;
|
|
}
|
|
}
|
|
} else {
|
|
audio.stopMusic();
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
unsub();
|
|
document.removeEventListener('click', startOnGesture);
|
|
document.removeEventListener('keydown', startOnGesture);
|
|
audio.dispose();
|
|
};
|
|
}
|