/**
* Минимальная обёртка WebAudio: загрузка сэмплов в буферы и проигрывание.
* Музыку и сложные графы можно достроить позже на этой же базе.
*/
export class AudioManager {
private ctx: AudioContext | null = null;
private buffers = new Map<string, AudioBuffer>();
private urls = new Map<string, string>();
constructor(private resolveUrl: (key: string) => string) {}
/** Расблокировать аудио — вызвать из обработчика пользовательского ввода. */
async unlock(): Promise<void> {
if (!this.ctx) {
this.ctx = new AudioContext();
}
if (this.ctx.state === 'suspended') {
await this.ctx.resume();
}
}
async load(key: string): Promise<void> {
this.urls.set(key, this.resolveUrl(key));
}
/** Проиграть (если звук не загружен — тихо ничего не делает). */
async play(key: string, volume = 1): Promise<void> {
if (!this.ctx) return;
let buf = this.buffers.get(key);
const url = this.urls.get(key);
if (!buf && url) {
const res = await fetch(url);
buf = await this.ctx.decodeAudioData(await res.arrayBuffer());
this.buffers.set(key, buf);
}
if (!buf) return;
const source = this.ctx.createBufferSource();
source.buffer = buf;
const gain = this.ctx.createGain();
gain.gain.value = volume;
source.connect(gain).connect(this.ctx.destination);
source.start();
}
}