game_actor_sound_sound_emitter.js
import Actor from "../actor.js";
import Vec2 from "../../vector/vec2.js";
import Util from "../../../util/misc/util.js";
/**
* Sound emitter that can emit sounds in 3D space.
* @class
* @extends Actor
*/
class SoundEmitter extends Actor {
/**
* Create a new SoundEmitter.
* @param {Vec2} pos The position of the SoundEmitter, defaults to (0, 0)
* @param {string|null} sound The identifier of the sound to emit, defaults to null
* @param {number} volume The volume of the sound, defaults to 1
* @param {number} speed The playback speed of the sound, defaults to 1
* @param {boolean} repeat Whether to repeat sound upon completion
* @param {string|Sprite|null} sprite Sprite for visual rendering (optional)
* @param {number} scale Scale of object (only relevant with sprites)
* @param {boolean} temp Whether emitter should be deleted once playback has finished, defaults to true
* @param {boolean} play Whether to play upon initialization, defaults to true
* @param {Object.<string, ActorComponent>} components Components to add
* @constructor
*/
constructor({
pos = new Vec2(),
sound = null,
volume = 1,
speed = 1,
repeat = false,
sprite = null,
scale = 1,
temp = true,
play = true,
components = {}
} = {}) {
super({
pos: pos,
sprite: sprite,
scale: scale,
components: components
});
this._soundID = sound;
this._resources = null;
this._volume = volume;
this._speed = speed;
this._repeat = repeat;
this._temp = temp;
this._play = play;
this.debugPrefix = "[Sound] ID: ";
}
init() {
super.init();
if (this._play)
this.play();
}
delete() {
this._release();
super.delete();
}
//
// Getters
//
/**
* Get the identifier of the sound.
* @returns {string|null} Global identifier of sound
*/
get sound() {
return this._soundID;
}
//
// Updating
//
/**
* Update the position of the sound emitter relative to the space's camera.
* @param {number} elapsed Time since last update cycle
*/
update(elapsed) {
super.update(elapsed);
if (this._resources === null || this.space === null)
return;
if (!this._repeat && this._resources.context.currentTime * this._speed >= this._resources.sound.duration) {
this.delete();
return;
}
const cam = this.space.camera;
if (cam === null)
return;
this._resources.sound.playbackRate = this._speed * $$.flags.timeScale;
const pos = cam.getOffset().sub(this.globalPos).addv($$.width / 2, $$.height / 2);
const div = 32;
this._resources.panner.positionX.setValueAtTime(-pos.x / div, this._resources.context.currentTime);
this._resources.panner.positionY.setValueAtTime(-pos.y / div, this._resources.context.currentTime);
let secs = Math.floor(this._resources.context.currentTime * this._speed);
this.debugPrefix = `[Sound (${Util.timeFormat(secs)})] ID: `;
}
// Initialization
/**
* Setup 3D audio upon first play() call.
* @private
*/
_setup() {
this._resources = $$.reg.sound.getInstance(this._soundID);
this._resources.panner.positionX.setValueAtTime(0, this._resources.context.currentTime);
this._resources.panner.positionY.setValueAtTime(0, this._resources.context.currentTime);
this._resources.panner.positionZ.setValueAtTime(0, this._resources.context.currentTime);
}
_release() {
if (this._resources !== null) {
$$.reg.sound.releaseInstance(this._soundID, this._resources);
this._resources = null;
}
}
// Controls
/**
* Play the audio from the current time, or the start.
* @param {boolean} fromStart Whether to play audio from beginning
*/
play(fromStart = false) {
if (this._resources === null)
this._setup();
this._resources.sound.currentTime = 0;
this._resources.sound.volume = this._volume;
this._resources.sound.play();
}
/**
* Pause the audio at the current time.
*/
pause() {
if (this._resources !== null) {
this._resources.sound.pause();
}
}
/**
* Stop the audio and reset back to the beginning.
*/
stop() {
if (this._resources !== null) {
this._resources.sound.pause();
this._resources.sound.currentTime = 0;
this._release();
}
}
}
export default SoundEmitter;