template/packages/audio/src/sound.ts

133 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { logger } from '@motajs/common';
import { IAudioVolumeEffect, IMotaAudioContext, ISoundPlayer } from './types';
type LocationArray = [number, number, number];
export class SoundPlayer<
T extends string = SoundIds
> implements ISoundPlayer<T> {
/** 每个音效的唯一标识符 */
private num: number = 0;
/** 每个音效的数据 */
readonly buffer: Map<T, AudioBuffer> = new Map();
/** 所有正在播放的音乐 */
readonly playing: Set<number> = new Set();
/** 音量节点 */
readonly gain: IAudioVolumeEffect;
/** 是否已经启用 */
enabled: boolean = true;
constructor(public readonly ac: IMotaAudioContext) {
this.gain = ac.createVolumeEffect();
}
/**
* 设置是否启用音效
* @param enabled 是否启用音效
*/
setEnabled(enabled: boolean) {
if (!enabled) this.stopAllSounds();
this.enabled = enabled;
}
/**
* 设置音量大小
* @param volume 音量大小
*/
setVolume(volume: number) {
this.gain.setVolume(volume);
}
/**
* 获取音量大小
*/
getVolume() {
return this.gain.getVolume();
}
/**
* 添加一个音效
* @param id 音效名称
* @param data 音效的Uint8Array数据
*/
async add(id: T, data: Uint8Array | AudioBuffer) {
if (data instanceof Uint8Array) {
const buffer = await this.ac.decodeToAudioBuffer(data);
if (!buffer) {
logger.warn(51, id);
return;
}
this.buffer.set(id, buffer);
} else {
this.buffer.set(id, data);
}
}
/**
* 播放一个音效
* @param id 音效名称
* @param position 音频位置,[0, 0, 0]表示正中心,x轴指向水平向右,y轴指向水平向上,z轴指向竖直向上
* @param orientation 音频朝向,[0, 1, 0]表示朝向前方
*/
play(
id: T,
position: LocationArray = [0, 0, 0],
orientation: LocationArray = [1, 0, 0]
) {
if (!this.enabled) return -1;
const buffer = this.buffer.get(id);
if (!buffer) {
logger.warn(52, id);
return -1;
}
const soundNum = this.num++;
const source = this.ac.createBufferSource();
source.setBuffer(buffer);
const route = this.ac.createRoute(source);
const stereo = this.ac.createStereoEffect();
stereo.setPosition(position[0], position[1], position[2]);
stereo.setOrientation(orientation[0], orientation[1], orientation[2]);
route.addEffect([stereo, this.gain]);
this.ac.addRoute(`sounds.${soundNum}`, route);
route.play();
// 清理垃圾
source.output.addEventListener('ended', () => {
this.playing.delete(soundNum);
this.ac.removeRoute(`sounds.${soundNum}`);
});
this.playing.add(soundNum);
return soundNum;
}
/**
* 停止一个音效
* @param num 音效的唯一 id
*/
stop(num: number) {
const id = `sounds.${num}`;
const route = this.ac.getRoute(id);
if (route) {
route.stop();
this.ac.removeRoute(id);
this.playing.delete(num);
}
}
/**
* 停止播放所有音效
*/
stopAllSounds() {
this.playing.forEach(v => {
const id = `sounds.${v}`;
const route = this.ac.getRoute(id);
if (route) {
route.stop();
this.ac.removeRoute(id);
}
});
this.playing.clear();
}
}