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 if (key === 'chop' && this.scene.sound.get('wood_chop')) { // Priority for real wood chop sound this.scene.sound.play('wood_chop', { 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(); } else if (key === 'dig') { this.beepDig(); } } } playMusic(key, loop = true) { if (this.isMuted) return; // Stop current music if (this.currentMusic) { this.currentMusic.stop(); } if (this.scene.sound.get(key)) { this.currentMusic = this.scene.sound.add(key, { volume: this.musicVolume, loop: loop }); this.currentMusic.play(); } } playForestAmbient() { this.playMusic('forest_ambient', true); } 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() { this.playFootstep('grass'); } playFootstep(surface = 'grass') { if (this.isMuted) return; const key = `footstep_${surface}`; if (this.scene.sound.get(key)) { this.scene.sound.play(key, { volume: this.sfxVolume * 0.4 }); } else { // Fallback beep 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); } setAmbientModulation(factor) { // factor 0 to 1 (proximity to pond) this.ambientModFactor = factor; // Optionally adjust existing music pitch or filter if (this.currentMusic && this.currentMusic.isPlaying) { this.currentMusic.setRate(1.0 - (factor * 0.1)); // Slightly slow down } } startHighMusic() { if (!this.scene.sound.context || this.highInterval) return; console.log('🌈 Starting High Ambient Music...'); // Warped/Low scales for high effect const scale = [65.41, 77.78, 87.31, 98.00, 116.54, 130.81]; this.highInterval = setInterval(() => { if (this.isMuted) return; if (Math.random() > 0.4) { const freq = scale[Math.floor(Math.random() * scale.length)]; this.playProceduralNote(freq, 4.0, 0.1, 'sawtooth'); // More buzzy/long } }, 3000); } stopHighMusic() { if (this.highInterval) { clearInterval(this.highInterval); this.highInterval = null; } } stopMusic() { if (this.musicInterval) { clearInterval(this.musicInterval); this.musicInterval = null; } } playProceduralNote(freq, duration = 2.0, volumeMod = 0.05, type = 'sine') { 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 = type; const now = ctx.currentTime; gain.gain.setValueAtTime(0, now); gain.gain.linearRampToValueAtTime(volumeMod * 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() { if (this.scene.sound.get('wood_chop')) { this.scene.sound.play('wood_chop', { volume: this.sfxVolume }); } else { this.playSFX('chop'); } } playPlant() { this.playSFX('plant'); } playHarvest() { this.playSFX('harvest'); } playBuild() { this.playSFX('build'); } playPickup() { this.playSFX('pickup'); } playDig() { this.playSFX('dig'); } playUIClick() { this.beepUIClick(); } // UI click sound playAttack() { this.beepAttack(); } playHit() { this.beepHit(); } playFootstep() { this.beepFootstep(); } playDeath() { this.beepDeath(); } playRainSound() { this.startRainNoise(); } stopRainSound() { this.stopRainNoise(); } playHighAmbient() { this.startHighMusic(); } stopHighAmbient() { this.stopHighMusic(); } playCraftSound() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; // Hammering rhythm for (let i = 0; i < 3; i++) { const time = ctx.currentTime + (i * 0.3); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(200 - (i * 20), time); osc.type = 'square'; gain.gain.setValueAtTime(0.1, time); gain.gain.exponentialRampToValueAtTime(0.01, time + 0.1); osc.start(time); osc.stop(time + 0.1); } } playSuccessSound() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6 notes.forEach((freq, i) => { const time = ctx.currentTime + (i * 0.1); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = freq; osc.type = 'sine'; gain.gain.setValueAtTime(0, time); gain.gain.linearRampToValueAtTime(0.1, time + 0.05); gain.gain.exponentialRampToValueAtTime(0.001, time + 0.3); osc.start(time); osc.stop(time + 0.3); }); } playSmokeSound() { if (!this.scene.sound.context) return; const ctx = this.scene.sound.context; // Inhale (White noise sweep) const bufferSize = ctx.sampleRate * 0.5; 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; const source = ctx.createBufferSource(); source.buffer = buffer; const filter = ctx.createBiquadFilter(); const gain = ctx.createGain(); source.connect(filter); filter.connect(gain); gain.connect(ctx.destination); filter.type = 'lowpass'; filter.frequency.setValueAtTime(100, ctx.currentTime); filter.frequency.exponentialRampToValueAtTime(2000, ctx.currentTime + 0.5); gain.gain.setValueAtTime(0, ctx.currentTime); gain.gain.linearRampToValueAtTime(0.1, ctx.currentTime + 0.1); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.5); source.start(); } beepUIClick() { 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); // Quick, pleasant click sound osc.frequency.value = 800; osc.type = 'sine'; gain.gain.setValueAtTime(0.08, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.05); osc.start(); osc.stop(ctx.currentTime + 0.05); } beepDig() { 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); // Low "thud" for dirt interaction osc.frequency.setValueAtTime(80, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(10, ctx.currentTime + 0.15); osc.type = 'triangle'; gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.15); osc.start(); osc.stop(ctx.currentTime + 0.15); } }