let audioCtx: AudioContext | null = null; function getCtx(): AudioContext { if (!audioCtx) { audioCtx = new AudioContext(); } return audioCtx; } type OscillatorConfig = { freq: number; type: OscillatorType; duration: number; gain?: number; }; function playTone(config: OscillatorConfig) { try { const ctx = getCtx(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = config.type; osc.frequency.setValueAtTime(config.freq, ctx.currentTime); gain.gain.setValueAtTime(config.gain ?? 0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + config.duration); osc.connect(gain); gain.connect(ctx.destination); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + config.duration); } catch { // Audio not supported } } function playNoise(duration: number, gain = 0.15) { try { const ctx = getCtx(); const bufferSize = Math.floor(ctx.sampleRate * duration); const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) { data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2); } const source = ctx.createBufferSource(); source.buffer = buffer; const gainNode = ctx.createGain(); gainNode.gain.setValueAtTime(gain, ctx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration); source.connect(gainNode); gainNode.connect(ctx.destination); source.start(); } catch { // Audio not supported } } export function playHonk() { playTone({ freq: 800, type: "square", duration: 0.08, gain: 0.2 }); setTimeout(() => playTone({ freq: 600, type: "square", duration: 0.12, gain: 0.2 }), 80); } export function playReactorAlarm() { for (let i = 0; i < 4; i++) { setTimeout(() => { playTone({ freq: 440 + i * 110, type: "sawtooth", duration: 0.2, gain: 0.15 }); setTimeout(() => playTone({ freq: 330 + i * 80, type: "sawtooth", duration: 0.2, gain: 0.15 }), 100); }, i * 300); } } export function playHullBreach() { playTone({ freq: 100, type: "sine", duration: 0.5, gain: 0.4 }); setTimeout(() => playTone({ freq: 80, type: "sine", duration: 0.5, gain: 0.3 }), 200); playNoise(0.5, 0.1); } export function playExplosion() { playNoise(0.6, 0.4); playTone({ freq: 60, type: "sine", duration: 0.5, gain: 0.5 }); setTimeout(() => playTone({ freq: 40, type: "sine", duration: 0.3, gain: 0.3 }), 200); } export function playMonster() { playTone({ freq: 80, type: "sawtooth", duration: 0.4, gain: 0.2 }); setTimeout(() => playTone({ freq: 60, type: "sawtooth", duration: 0.4, gain: 0.15 }), 200); setTimeout(() => playTone({ freq: 50, type: "square", duration: 0.5, gain: 0.2 }), 400); } export function playDeath() { playTone({ freq: 400, type: "sine", duration: 0.15, gain: 0.2 }); setTimeout(() => playTone({ freq: 300, type: "sine", duration: 0.15, gain: 0.2 }), 150); setTimeout(() => playTone({ freq: 200, type: "sine", duration: 0.3, gain: 0.2 }), 300); } export function playBingo() { const notes = [523, 659, 784, 1047]; notes.forEach((freq, i) => { setTimeout(() => playTone({ freq, type: "square", duration: 0.2, gain: 0.2 }), i * 120); }); setTimeout(() => playNoise(0.3, 0.1), 480); } export function playChaosRiser() { const pitches = [200, 250, 300, 400, 500, 600, 800, 1000]; pitches.forEach((freq, i) => { setTimeout(() => playTone({ freq, type: "sawtooth", duration: 0.1, gain: 0.08 }), i * 50); }); } export function playSoundUrl(url: string) { try { const audio = new Audio(url); audio.volume = 0.4; audio.play().catch(() => {}); } catch {} } export function playSound(category: string, soundUrl?: string | null) { if (soundUrl) { playSoundUrl(soundUrl); return; } switch (category) { case "horn": playHonk(); break; case "alarm": playReactorAlarm(); break; case "flood": playHullBreach(); break; case "explosion": playExplosion(); break; case "monster": playMonster(); break; case "death": playDeath(); break; case "bingo": playBingo(); break; case "chaos": playChaosRiser(); break; default: playHonk(); break; } }