210 lines
7.3 KiB
JavaScript
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;
|
|
}
|
|
}
|