95 lines
3.2 KiB
TypeScript
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;
|
|
}
|
|
}
|