import { useStore } from './store' let ctx: AudioContext | null = null function getCtx(): AudioContext | null { if (ctx && ctx.state !== 'closed') return ctx try { ctx = new AudioContext() } catch { return null } return ctx } function isSoundEnabled(): boolean { return useStore.getState().narratorSettings.sounds } function tone(freq: number, duration: number, type: OscillatorType = 'sine', gain = 0.15) { if (!isSoundEnabled()) return const ac = getCtx() if (!ac) return if (ac.state === 'suspended') ac.resume() const osc = ac.createOscillator() const g = ac.createGain() osc.type = type osc.frequency.value = freq g.gain.value = gain g.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + duration / 1000) osc.connect(g).connect(ac.destination) osc.start() osc.stop(ac.currentTime + duration / 1000) } export function playKeyClick() { tone(1200, 50, 'sine', 0.08) } export function playDelete() { tone(400, 50, 'sine', 0.08) } export function playComplete() { if (!isSoundEnabled()) return const ac = getCtx() if (!ac) return if (ac.state === 'suspended') ac.resume() const notes = [523, 659, 784] notes.forEach((freq, i) => { const osc = ac.createOscillator() const g = ac.createGain() osc.type = 'sine' osc.frequency.value = freq g.gain.value = 0.15 const start = ac.currentTime + i * 0.1 g.gain.exponentialRampToValueAtTime(0.001, start + 0.15) osc.connect(g).connect(ac.destination) osc.start(start) osc.stop(start + 0.15) }) } export function playHint() { tone(660, 100, 'sine', 0.1) } export function playWordComplete() { if (!isSoundEnabled()) return const ac = getCtx() if (!ac) return if (ac.state === 'suspended') ac.resume() const notes = [587, 784] notes.forEach((freq, i) => { const osc = ac.createOscillator() const g = ac.createGain() osc.type = 'sine' osc.frequency.value = freq g.gain.value = 0.12 const start = ac.currentTime + i * 0.1 g.gain.exponentialRampToValueAtTime(0.001, start + 0.12) osc.connect(g).connect(ac.destination) osc.start(start) osc.stop(start + 0.12) }) }