export class SoundManager { constructor(game) { this.game = game; this.ctx = null; this.masterGain = null; this.enabled = true; this.initialized = false; this.ambientSource = null; this.currentAmbient = null; } init() { if (this.initialized) return; try { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); this.masterGain = this.ctx.createGain(); this.masterGain.gain.value = 0.3; this.masterGain.connect(this.ctx.destination); this.initialized = true; } catch (e) { this.enabled = false; } } resume() { if (this.ctx && this.ctx.state === 'suspended') { this.ctx.resume(); } } // Шаги playStep() { if (!this.enabled || !this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'triangle'; osc.frequency.value = 80 + Math.random() * 40; gain.gain.setValueAtTime(0.05, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.1); osc.connect(gain); gain.connect(this.masterGain); osc.start(); osc.stop(this.ctx.currentTime + 0.1); } // Подбор предмета playPickup() { if (!this.enabled || !this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(400, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.15); gain.gain.setValueAtTime(0.1, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2); osc.connect(gain); gain.connect(this.masterGain); osc.start(); osc.stop(this.ctx.currentTime + 0.2); } // Квест выполнен playQuestComplete() { if (!this.enabled || !this.ctx) return; const notes = [523, 659, 784]; // C5, E5, G5 notes.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.value = freq; gain.gain.setValueAtTime(0.08, this.ctx.currentTime + i * 0.12); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + i * 0.12 + 0.4); osc.connect(gain); gain.connect(this.masterGain); osc.start(this.ctx.currentTime + i * 0.12); osc.stop(this.ctx.currentTime + i * 0.12 + 0.4); }); } // Урон playHurt() { if (!this.enabled || !this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(200, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(80, this.ctx.currentTime + 0.3); gain.gain.setValueAtTime(0.1, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3); osc.connect(gain); gain.connect(this.masterGain); osc.start(); osc.stop(this.ctx.currentTime + 0.3); } // Монеты playCoin() { if (!this.enabled || !this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'square'; osc.frequency.setValueAtTime(1200, this.ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(1800, this.ctx.currentTime + 0.08); gain.gain.setValueAtTime(0.04, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15); osc.connect(gain); gain.connect(this.masterGain); osc.start(); osc.stop(this.ctx.currentTime + 0.15); } // Еда playEat() { if (!this.enabled || !this.ctx) return; for (let i = 0; i < 3; i++) { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'triangle'; osc.frequency.value = 300 + Math.random() * 100; const t = this.ctx.currentTime + i * 0.1; gain.gain.setValueAtTime(0.06, t); gain.gain.exponentialRampToValueAtTime(0.001, t + 0.08); osc.connect(gain); gain.connect(this.masterGain); osc.start(t); osc.stop(t + 0.08); } } // Диалог playDialogOpen() { if (!this.enabled || !this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.value = 600; gain.gain.setValueAtTime(0.06, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2); osc.connect(gain); gain.connect(this.masterGain); osc.start(); osc.stop(this.ctx.currentTime + 0.2); } // Амбиент ночной — тихий гул playAmbient(type) { if (!this.enabled || !this.ctx) return; if (this.currentAmbient === type) return; this.stopAmbient(); this.currentAmbient = type; if (type === 'night') { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); const filter = this.ctx.createBiquadFilter(); osc.type = 'sawtooth'; osc.frequency.value = 60; filter.type = 'lowpass'; filter.frequency.value = 100; gain.gain.value = 0.02; osc.connect(filter); filter.connect(gain); gain.connect(this.masterGain); osc.start(); this.ambientSource = { osc, gain }; } else if (type === 'rain') { // Белый шум через буфер const bufferSize = this.ctx.sampleRate * 2; const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) { data[i] = (Math.random() * 2 - 1) * 0.5; } const src = this.ctx.createBufferSource(); src.buffer = buffer; src.loop = true; const gain = this.ctx.createGain(); const filter = this.ctx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 800; gain.gain.value = 0.04; src.connect(filter); filter.connect(gain); gain.connect(this.masterGain); src.start(); this.ambientSource = { osc: src, gain }; } } stopAmbient() { if (this.ambientSource) { try { this.ambientSource.osc.stop(); } catch (e) {} this.ambientSource = null; this.currentAmbient = null; } } setVolume(v) { if (this.masterGain) { this.masterGain.gain.value = Math.max(0, Math.min(1, v)); } } toggle() { this.enabled = !this.enabled; if (!this.enabled) this.stopAmbient(); return this.enabled; } }