Files
Hommie_RPG_Game/js/game/SoundManager.js
Maxim Dolgolyov fb5f09212b Initial commit: 3D Hommie RPG game
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:09 +03:00

210 lines
7.3 KiB
JavaScript

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;
}
}