class SoundManager { constructor(scene) { this.scene = scene; this.musicVolume = 0.3; this.sfxVolume = 0.5; this.isMuted = false; this.currentMusic = null; this.currentAmbient = null; console.log('🎵 SoundManager: Initialized'); } playSFX(key) { if (this.isMuted) return; if (this.scene.sound.get(key)) { this.scene.sound.play(key, { volume: this.sfxVolume }); } else { // Enhanced placeholder beeps if (key === 'chop') { this.beepChop(); } else if (key === 'pickup') { this.beepPickup(); } else if (key === 'plant') { this.beepPlant(); } else if (key === 'harvest') { this.beepHarvest(); } else if (key === 'build') { this.beepBuild(); } } } beepChop() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = 150; osc.type = 'sawtooth'; gain.gain.setValueAtTime(0.15, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.15); osc.start(); osc.stop(ctx.currentTime + 0.15); } beepPickup() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(600, ctx.currentTime); osc.frequency.linearRampToValueAtTime(1200, ctx.currentTime + 0.1); osc.type = 'sine'; gain.gain.setValueAtTime(0.12, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.1); osc.start(); osc.stop(ctx.currentTime + 0.1); } beepPlant() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = 300; osc.type = 'triangle'; gain.gain.setValueAtTime(0.1, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.12); osc.start(); osc.stop(ctx.currentTime + 0.12); } beepHarvest() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc1 = ctx.createOscillator(); const gain1 = ctx.createGain(); osc1.connect(gain1); gain1.connect(ctx.destination); osc1.frequency.value = 523; osc1.type = 'sine'; gain1.gain.setValueAtTime(0.1, ctx.currentTime); gain1.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.08); osc1.start(); osc1.stop(ctx.currentTime + 0.08); const osc2 = ctx.createOscillator(); const gain2 = ctx.createGain(); osc2.connect(gain2); gain2.connect(ctx.destination); osc2.frequency.value = 659; osc2.type = 'sine'; gain2.gain.setValueAtTime(0.1, ctx.currentTime + 0.08); gain2.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.16); osc2.start(ctx.currentTime + 0.08); osc2.stop(ctx.currentTime + 0.16); } beepBuild() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = 80; osc.type = 'square'; gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2); osc.start(); osc.stop(ctx.currentTime + 0.2); } beepAttack() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(400, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(100, ctx.currentTime + 0.08); osc.type = 'sawtooth'; gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.08); osc.start(); osc.stop(ctx.currentTime + 0.08); } beepHit() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = 80; osc.type = 'square'; gain.gain.setValueAtTime(0.25, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.05); osc.start(); osc.stop(ctx.currentTime + 0.05); } beepFootstep() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = 120 + Math.random() * 20; osc.type = 'sine'; gain.gain.setValueAtTime(0.05, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.03); osc.start(); osc.stop(ctx.currentTime + 0.03); } beepDeath() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(300, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(50, ctx.currentTime + 0.5); osc.type = 'sawtooth'; gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.5); osc.start(); osc.stop(ctx.currentTime + 0.5); } startRainNoise() { if (!this.scene.sound.context || this.rainNode) return; const ctx = this.scene.sound.context; // Create noise buffer const bufferSize = 2 * ctx.sampleRate; const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate); const output = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) { output[i] = Math.random() * 2 - 1; } this.rainNode = ctx.createBufferSource(); this.rainNode.buffer = buffer; this.rainNode.loop = true; // Lowpass filter for rain sound const filter = ctx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 800; this.rainGain = ctx.createGain(); this.rainGain.gain.value = 0.05 * this.sfxVolume; this.rainNode.connect(filter); filter.connect(this.rainGain); this.rainGain.connect(ctx.destination); this.rainNode.start(); } stopRainNoise() { if (this.rainNode) { this.rainNode.stop(); this.rainNode.disconnect(); this.rainNode = null; } if (this.rainGain) { this.rainGain.disconnect(); this.rainGain = null; } } playAmbient(key, loop = true) { if (this.isMuted) return; if (this.currentAmbient) this.currentAmbient.stop(); if (!this.scene.sound.get(key)) return; this.currentAmbient = this.scene.sound.add(key, { volume: this.sfxVolume * 0.5, loop: loop }); this.currentAmbient.play(); } stopAmbient() { if (this.currentAmbient) { this.currentAmbient.stop(); this.currentAmbient = null; } } startMusic() { if (!this.scene.sound.context || this.musicInterval) return; console.log('🎵 Starting Ambient Music...'); // Simple C Minor Pentatonic: C3, Eb3, F3, G3, Bb3 const scale = [130.81, 155.56, 174.61, 196.00, 233.08, 261.63]; // Loop every 3-5 seconds play a note this.musicInterval = setInterval(() => { if (this.isMuted) return; // 40% chance to play a note if (Math.random() > 0.6) { const freq = scale[Math.floor(Math.random() * scale.length)]; this.playProceduralNote(freq); } }, 2000); } stopMusic() { if (this.musicInterval) { clearInterval(this.musicInterval); this.musicInterval = null; } } playProceduralNote(freq) { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = freq; osc.type = 'sine'; // Soft tone const now = ctx.currentTime; const duration = 2.0; // Long decay like reverb/pad gain.gain.setValueAtTime(0, now); gain.gain.linearRampToValueAtTime(0.05 * this.musicVolume, now + 0.5); // Slow attack gain.gain.exponentialRampToValueAtTime(0.001, now + duration); // Long release osc.start(now); osc.stop(now + duration); } toggleMute() { this.isMuted = !this.isMuted; this.scene.sound.mute = this.isMuted; console.log(this.isMuted ? '🔇 Muted' : '🔊 Unmuted'); } playChop() { this.playSFX('chop'); } playPlant() { this.playSFX('plant'); } playHarvest() { this.playSFX('harvest'); } playBuild() { this.playSFX('build'); } playPickup() { this.playSFX('pickup'); } playAttack() { this.beepAttack(); } playHit() { this.beepHit(); } playFootstep() { this.beepFootstep(); } playDeath() { this.beepDeath(); } playRainSound() { this.startRainNoise(); } stopRainSound() { this.stopRainNoise(); } }