// Web Audio API Synthesizer for magical escape sounds class SoundFX { constructor() { this.ctx = null; this.muted = localStorage.getItem('escape_sound_muted') === 'true'; } init() { if (!this.ctx) { const AudioContext = window.AudioContext || window.webkitAudioContext; this.ctx = new AudioContext(); } if (this.ctx && this.ctx.state === 'suspended') { this.ctx.resume(); } } toggleMute() { this.muted = !this.muted; localStorage.setItem('escape_sound_muted', this.muted); return !this.muted; } isMuted() { return this.muted; } // Whoosh of flying broomstick playWhoosh() { if (this.muted) return; this.init(); const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); const filter = this.ctx.createBiquadFilter(); osc.type = 'sawtooth'; filter.type = 'lowpass'; filter.frequency.setValueAtTime(300, this.ctx.currentTime); filter.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.15); filter.frequency.exponentialRampToValueAtTime(200, this.ctx.currentTime + 0.35); gain.gain.setValueAtTime(0.15, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.35); osc.connect(filter); filter.connect(gain); gain.connect(this.ctx.destination); osc.start(); osc.stop(this.ctx.currentTime + 0.36); } // Key caught / fluttering chime playKeyChime() { if (this.muted) return; this.init(); const freqs = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6 freqs.forEach((freq, idx) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.value = freq; const startTime = this.ctx.currentTime + idx * 0.05; gain.gain.setValueAtTime(0.1, startTime); gain.gain.exponentialRampToValueAtTime(0.001, startTime + 0.4); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(startTime); osc.stop(startTime + 0.41); }); } // Clink of glass bottle playBottleClink() { if (this.muted) return; this.init(); const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'triangle'; osc.frequency.setValueAtTime(1200, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.12); gain.gain.setValueAtTime(0.2, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(); osc.stop(this.ctx.currentTime + 0.16); } // Success magical harmony playMagicSuccess() { if (this.muted) return; this.init(); const chords = [440, 554.37, 659.25, 880, 1108.73]; // A Major triumph chords.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.06); gain.gain.setValueAtTime(0.12, this.ctx.currentTime + i * 0.06); gain.gain.exponentialRampToValueAtTime(0.0001, this.ctx.currentTime + i * 0.06 + 1.2); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(this.ctx.currentTime + i * 0.06); osc.stop(this.ctx.currentTime + i * 0.06 + 1.25); }); } // Poison fizzle playPoisonSizzle() { if (this.muted) return; this.init(); const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(180, this.ctx.currentTime); osc.frequency.linearRampToValueAtTime(90, this.ctx.currentTime + 0.5); gain.gain.setValueAtTime(0.25, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.5); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(); osc.stop(this.ctx.currentTime + 0.51); } // Stone slide playStoneSlide() { if (this.muted) return; this.init(); const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'square'; osc.frequency.setValueAtTime(80, this.ctx.currentTime); gain.gain.setValueAtTime(0.08, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(); osc.stop(this.ctx.currentTime + 0.21); } } window.soundFX = new SoundFX(); function updateAudioButtonUI() { const btn = document.getElementById('audioToggleBtn'); const bgm = document.getElementById('bgm'); if (!btn) return; const isMuted = window.soundFX.isMuted(); btn.innerHTML = isMuted ? '🔇 Musik & Ton: Aus' : '🔊 Musik & Ton: An'; if (bgm) { bgm.volume = 0.45; if (isMuted && !bgm.paused) { bgm.pause(); } else if (!isMuted && bgm.paused) { bgm.play().catch(() => {}); } } } function toggleSound() { window.soundFX.toggleMute(); updateAudioButtonUI(); } document.addEventListener('DOMContentLoaded', () => { updateAudioButtonUI(); document.body.addEventListener('click', () => { window.soundFX.init(); const bgm = document.getElementById('bgm'); if (bgm && !window.soundFX.isMuted() && bgm.paused) { bgm.volume = 0.45; bgm.play().catch(() => {}); } }, { once: true }); });