diablo2-web/src/audio/manager.ts

95 lines
3.2 KiB
TypeScript

export class AudioManager {
private ctx: AudioContext | null = null;
private masterGain: GainNode | null = null;
private sfxGain: GainNode | null = null;
private musicGain: GainNode | null = null;
private buffers: Map<string, AudioBuffer> = new Map();
private currentMusicSource: AudioBufferSourceNode | null = null;
constructor() {
// We defer AudioContext creation until needed due to autoplay policies
}
private ensureContext() {
if (!this.ctx && typeof AudioContext !== 'undefined') {
this.ctx = new AudioContext();
this.masterGain = this.ctx.createGain();
this.sfxGain = this.ctx.createGain();
this.musicGain = this.ctx.createGain();
this.masterGain.connect(this.ctx.destination);
this.sfxGain.connect(this.masterGain);
this.musicGain.connect(this.masterGain);
}
}
/** Set the global master volume [0.0..1.0] */
setMasterVolume(value: number) {
this.ensureContext();
if (this.masterGain) this.masterGain.gain.value = value;
}
/** Set the SFX volume [0.0..1.0] */
setSfxVolume(value: number) {
this.ensureContext();
if (this.sfxGain) this.sfxGain.gain.value = value;
}
/** Set the music volume [0.0..1.0] */
setMusicVolume(value: number) {
this.ensureContext();
if (this.musicGain) this.musicGain.gain.value = value;
}
/** Decode a full WAFF/RIFF MPQ audio member and cache it by name */
async loadBuffer(name: string, data: Uint8Array) {
this.ensureContext();
if (!this.ctx) return;
// Ensure WAFF/RIFF is correctly passed. WebAudio takes an ArrayBuffer
const buffer = await this.ctx.decodeAudioData(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer);
this.buffers.set(name, buffer);
}
/** Play a sound effect one-shot */
playSfx(name: string) {
this.ensureContext();
if (!this.ctx || !this.sfxGain) return;
if (this.ctx.state === 'suspended') this.ctx.resume().catch(() => {});
const buffer = this.buffers.get(name);
if (!buffer) return;
const source = this.ctx.createBufferSource();
source.buffer = buffer;
source.connect(this.sfxGain);
source.start();
}
/** Play music (loops, switches without crossfade) */
playMusic(name: string) {
this.ensureContext();
if (!this.ctx || !this.musicGain) return;
if (this.ctx.state === 'suspended') this.ctx.resume().catch(() => {});
const buffer = this.buffers.get(name);
if (!buffer) return;
// Stop current music
if (this.currentMusicSource) {
this.currentMusicSource.stop();
this.currentMusicSource.disconnect();
this.currentMusicSource = null;
}
const source = this.ctx.createBufferSource();
source.buffer = buffer;
source.loop = true;
source.connect(this.musicGain);
source.start();
this.currentMusicSource = source;
}
}