/* ═══════════════════════════════════════════════════════════════ AUDIO SYSTEM - Web Audio API Sound Effects ═══════════════════════════════════════════════════════════════ */ const AudioSystem = { ctx: null, masterVolume: 0.3, sounds: {}, muted: false, init() { try { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); this.createSounds(); } catch (e) { console.log('Audio not supported'); } }, createSounds() { // Jump sound - rising pitch this.sounds.jump = () => { if (!this.ctx || this.muted) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'sine'; osc.frequency.setValueAtTime(200, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(600, this.ctx.currentTime + 0.1); gain.gain.setValueAtTime(this.masterVolume * 0.5, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15); osc.start(); osc.stop(this.ctx.currentTime + 0.15); }; // Double jump sound - higher pitch this.sounds.doubleJump = () => { if (!this.ctx || this.muted) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'sine'; osc.frequency.setValueAtTime(400, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.1); gain.gain.setValueAtTime(this.masterVolume * 0.4, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.12); osc.start(); osc.stop(this.ctx.currentTime + 0.12); }; // Coin collect - bright chime this.sounds.coin = () => { if (!this.ctx || this.muted) return; const frequencies = [880, 1100, 1320]; frequencies.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'sine'; osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.05); gain.gain.setValueAtTime(0, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(this.masterVolume * 0.3, this.ctx.currentTime + i * 0.05); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2 + i * 0.05); osc.start(this.ctx.currentTime + i * 0.05); osc.stop(this.ctx.currentTime + 0.25 + i * 0.05); }); }; // Damage/hurt - low thud this.sounds.hurt = () => { if (!this.ctx || this.muted) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(150, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(50, this.ctx.currentTime + 0.2); gain.gain.setValueAtTime(this.masterVolume * 0.4, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2); osc.start(); osc.stop(this.ctx.currentTime + 0.2); }; // Power-up collected - magical chime this.sounds.powerUp = () => { if (!this.ctx || this.muted) return; const frequencies = [523, 659, 784, 1047]; frequencies.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'triangle'; osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.08); gain.gain.setValueAtTime(0, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(this.masterVolume * 0.25, this.ctx.currentTime + i * 0.08); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3 + i * 0.08); osc.start(this.ctx.currentTime + i * 0.08); osc.stop(this.ctx.currentTime + 0.4 + i * 0.08); }); }; // Checkpoint - celebratory sound this.sounds.checkpoint = () => { if (!this.ctx || this.muted) return; const frequencies = [392, 523, 659, 784]; frequencies.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'sine'; osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.1); gain.gain.setValueAtTime(0, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(this.masterVolume * 0.3, this.ctx.currentTime + i * 0.1); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3 + i * 0.1); osc.start(this.ctx.currentTime + i * 0.1); osc.stop(this.ctx.currentTime + 0.5 + i * 0.1); }); }; // Level complete - victory fanfare this.sounds.levelComplete = () => { if (!this.ctx || this.muted) return; const notes = [523, 587, 659, 698, 784, 880, 988, 1047]; notes.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'square'; osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.12); gain.gain.setValueAtTime(0, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(this.masterVolume * 0.15, this.ctx.currentTime + i * 0.12 + 0.02); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4 + i * 0.12); osc.start(this.ctx.currentTime + i * 0.12); osc.stop(this.ctx.currentTime + 0.5 + i * 0.12); }); }; // Game over - sad descending this.sounds.gameOver = () => { if (!this.ctx || this.muted) return; const frequencies = [440, 415, 392, 370, 349, 330, 311, 294]; frequencies.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.15); gain.gain.setValueAtTime(0, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(this.masterVolume * 0.2, this.ctx.currentTime + i * 0.15); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3 + i * 0.15); osc.start(this.ctx.currentTime + i * 0.15); osc.stop(this.ctx.currentTime + 0.4 + i * 0.15); }); }; // Step/land sound this.sounds.land = () => { if (!this.ctx || this.muted) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination); osc.type = 'sine'; osc.frequency.setValueAtTime(80, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(40, this.ctx.currentTime + 0.05); gain.gain.setValueAtTime(this.masterVolume * 0.2, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.05); osc.start(); osc.stop(this.ctx.currentTime + 0.05); }; }, play(soundName) { if (this.sounds[soundName]) { // Resume audio context if suspended (browser autoplay policy) if (this.ctx && this.ctx.state === 'suspended') { this.ctx.resume(); } this.sounds[soundName](); } }, toggleMute() { this.muted = !this.muted; return this.muted; }, setVolume(vol) { this.masterVolume = Math.max(0, Math.min(1, vol)); } }; // Auto-initialize on first user interaction document.addEventListener('click', () => { if (!AudioSystem.ctx) { AudioSystem.init(); } }, { once: true }); document.addEventListener('keydown', () => { if (!AudioSystem.ctx) { AudioSystem.init(); } }, { once: true });