Files
novafarma/EMERGENCY_SYSTEMS_RECOVERY/SoundManager.js
2026-01-16 02:43:46 +01:00

507 lines
17 KiB
JavaScript

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;
// SAFE CHECK: Check cache instead of active instances
if (this.scene.cache.audio.exists(key)) {
try {
this.scene.sound.play(key, { volume: this.sfxVolume });
} catch (e) {
console.warn(`🔊 Failed to play SFX ${key}:`, e);
// Fallback to beep if playback fails
this.playFallbackBeep(key);
}
} else if (key === 'chop' && this.scene.cache.audio.exists('wood_chop')) {
// Priority for real wood chop sound
this.scene.sound.play('wood_chop', { volume: this.sfxVolume });
} else {
// Enhanced placeholder beeps
this.playFallbackBeep(key);
}
}
playFallbackBeep(key) {
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.cache.audio.exists(key)) {
try {
this.currentMusic = this.scene.sound.add(key, { volume: this.musicVolume, loop: loop });
this.currentMusic.play();
} catch (e) {
console.warn(`🎵 Failed to play music ${key}:`, e);
}
} else {
console.warn(`🎵 Music key not found: ${key}`);
}
}
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.cache.audio.exists(key)) return;
try {
this.currentAmbient = this.scene.sound.add(key, { volume: this.sfxVolume * 0.5, loop: loop });
this.currentAmbient.play();
} catch (e) {
console.warn(`🍃 Failed to play ambient ${key}:`, e);
}
}
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);
}
}