comment SoundBank and EffectChain

This commit is contained in:
Michael "Z" Goddard 2018-06-18 16:22:36 -04:00 committed by Corey Frang
parent 7cef4e60a8
commit c12a1a4766
2 changed files with 147 additions and 4 deletions

View file

@ -1,21 +1,64 @@
const log = require('./log'); const log = require('./log');
/**
* A symbol indicating all targets are to be effected.
* @const {string}
*/
const ALL_TARGETS = '*'; const ALL_TARGETS = '*';
class SoundBank { class SoundBank {
/**
* A bank of sounds that can be played.
* @constructor
* @param {AudioEngine} audioEngine - related AudioEngine
* @param {EffectChain} effectChainPrime - original EffectChain cloned for
* playing sounds
*/
constructor (audioEngine, effectChainPrime) { constructor (audioEngine, effectChainPrime) {
/**
* AudioEngine this SoundBank is related to.
* @type {AudioEngine}
*/
this.audioEngine = audioEngine; this.audioEngine = audioEngine;
/**
* Map of ids to soundPlayers.
* @type {object<SoundPlayer>}
*/
this.soundPlayers = {}; this.soundPlayers = {};
/**
* Map of targets by sound id.
* @type {Map<string, Target>}
*/
this.playerTargets = new Map(); this.playerTargets = new Map();
/**
* Map of effect chains by sound id.
* @type {Map<string, EffectChain}
*/
this.soundEffects = new Map(); this.soundEffects = new Map();
/**
* Original EffectChain cloned for every playing sound.
* @type {EffectChain}
*/
this.effectChainPrime = effectChainPrime; this.effectChainPrime = effectChainPrime;
} }
/**
* Add a sound player instance likely from AudioEngine.decodeSoundPlayer
* @param {SoundPlayer} soundPlayer - SoundPlayer to add
*/
addSoundPlayer (soundPlayer) { addSoundPlayer (soundPlayer) {
this.soundPlayers[soundPlayer.id] = soundPlayer; this.soundPlayers[soundPlayer.id] = soundPlayer;
} }
/**
* Get a sound player by id.
* @param {string} soundId - sound to look for
* @returns {SoundPlayer} instance of sound player for the id
*/
getSoundPlayer (soundId) { getSoundPlayer (soundId) {
if (!this.soundPlayers[soundId]) { if (!this.soundPlayers[soundId]) {
log.error(`SoundBank.getSoundPlayer(${soundId}): called missing sound in bank`); log.error(`SoundBank.getSoundPlayer(${soundId}): called missing sound in bank`);
@ -24,6 +67,11 @@ class SoundBank {
return this.soundPlayers[soundId]; return this.soundPlayers[soundId];
} }
/**
* Get a sound EffectChain by id.
* @param {string} sound - sound to look for an EffectChain
* @returns {EffectChain} available EffectChain for this id
*/
getSoundEffects (sound) { getSoundEffects (sound) {
if (!this.soundEffects.has(sound)) { if (!this.soundEffects.has(sound)) {
this.soundEffects.set(sound, this.effectChainPrime.clone()); this.soundEffects.set(sound, this.effectChainPrime.clone());
@ -32,7 +80,12 @@ class SoundBank {
return this.soundEffects.get(sound); return this.soundEffects.get(sound);
} }
/**
* Play a sound.
* @param {Target} target - Target to play for
* @param {string} soundId - id of sound to play
* @returns {Promise} promise that resolves when the sound finishes playback
*/
playSound (target, soundId) { playSound (target, soundId) {
const effects = this.getSoundEffects(soundId); const effects = this.getSoundEffects(soundId);
const player = this.getSoundPlayer(soundId); const player = this.getSoundPlayer(soundId);
@ -47,6 +100,10 @@ class SoundBank {
return player.finished(); return player.finished();
} }
/**
* Set the effects (pan, pitch, and volume) from values on the given target.
* @param {Target} target - target to set values from
*/
setEffects (target) { setEffects (target) {
this.playerTargets.forEach((playerTarget, key) => { this.playerTargets.forEach((playerTarget, key) => {
if (playerTarget === target) { if (playerTarget === target) {
@ -55,12 +112,22 @@ class SoundBank {
}); });
} }
/**
* Stop playback of sound by id if was lasted played by the target.
* @param {Target} target - target to check if it last played the sound
* @param {string} soundId - id of the sound to stop
*/
stop (target, soundId) { stop (target, soundId) {
if (this.playerTargets.get(soundId) === target) { if (this.playerTargets.get(soundId) === target) {
this.soundPlayers[soundId].stop(); this.soundPlayers[soundId].stop();
} }
} }
/**
* Stop all sounds for all targets or a specific target.
* @param {Target|string} target - a symbol for all targets or the target
* to stop sounds for
*/
stopAllSounds (target = ALL_TARGETS) { stopAllSounds (target = ALL_TARGETS) {
this.playerTargets.forEach((playerTarget, key) => { this.playerTargets.forEach((playerTarget, key) => {
if (target === ALL_TARGETS || playerTarget === target) { if (target === ALL_TARGETS || playerTarget === target) {
@ -69,6 +136,9 @@ class SoundBank {
}); });
} }
/**
* Dispose of all EffectChains and SoundPlayers.
*/
dispose () { dispose () {
this.playerTargets.clear(); this.playerTargets.clear();
this.soundEffects.forEach(effects => effects.dispose()); this.soundEffects.forEach(effects => effects.dispose());

View file

@ -1,15 +1,37 @@
class EffectChain { class EffectChain {
/**
* Chain of effects that can be applied to a group of SoundPlayers.
* @param {AudioEngine} audioEngine - engine whose effects these belong to
* @param {Array<Effect>} effects - array of Effect classes to construct
*/
constructor (audioEngine, effects) { constructor (audioEngine, effects) {
/**
* AudioEngine whose effects these belong to.
* @type {AudioEngine}
*/
this.audioEngine = audioEngine; this.audioEngine = audioEngine;
/**
* Node incoming connections will attach to. This node than connects to
* the items in the chain which finally connect to some output.
* @type {AudioNode}
*/
this.inputNode = this.audioEngine.audioContext.createGain(); this.inputNode = this.audioEngine.audioContext.createGain();
/**
* List of Effect types to create.
* @type {Array<Effect>}
*/
this.effects = effects; this.effects = effects;
// Effects are instantiate in reverse so that the first refers to the // Effects are instantiated in reverse so that the first refers to the
// second, the second refers to the third, etc and the last refers to // second, the second refers to the third, etc and the last refers to
// null. // null.
let lastEffect = null; let lastEffect = null;
/**
* List of instantiated Effects.
* @type {Array<Effect>}
*/
this._effects = effects this._effects = effects
.reverse() .reverse()
.map(Effect => { .map(Effect => {
@ -20,12 +42,28 @@ class EffectChain {
}) })
.reverse(); .reverse();
/**
* First effect of this chain.
* @type {Effect}
*/
this.firstEffect = this._effects[0]; this.firstEffect = this._effects[0];
/**
* Last effect of this chain.
* @type {Effect}
*/
this.lastEffect = this._effects[this._effects.length - 1]; this.lastEffect = this._effects[this._effects.length - 1];
/**
* A set of players this chain is managing.
*/
this._soundPlayers = new Set(); this._soundPlayers = new Set();
} }
/**
* Create a clone of the EffectChain.
* @returns {EffectChain} a clone of this EffectChain
*/
clone () { clone () {
const chain = new EffectChain(this.audioEngine, this.effects); const chain = new EffectChain(this.audioEngine, this.effects);
if (this.target) { if (this.target) {
@ -34,6 +72,10 @@ class EffectChain {
return chain; return chain;
} }
/**
* Add a sound player.
* @param {SoundPlayer} soundPlayer - a sound player to manage
*/
addSoundPlayer (soundPlayer) { addSoundPlayer (soundPlayer) {
if (!this._soundPlayers.has(soundPlayer)) { if (!this._soundPlayers.has(soundPlayer)) {
this._soundPlayers.add(soundPlayer); this._soundPlayers.add(soundPlayer);
@ -41,16 +83,24 @@ class EffectChain {
} }
} }
/**
* Remove a sound player.
* @param {SoundPlayer} soundPlayer - a sound player to stop managing
*/
removeSoundPlayer (soundPlayer) { removeSoundPlayer (soundPlayer) {
this._soundPlayers.remove(soundPlayer); this._soundPlayers.remove(soundPlayer);
} }
/**
* Get the audio input node.
* @returns {AudioNode} audio node the upstream can connect to
*/
getInputNode () { getInputNode () {
return this.inputNode; return this.inputNode;
} }
/** /**
* Connnect this player's output to another audio node * Connnect this player's output to another audio node.
* @param {object} target - target whose node to should be connected * @param {object} target - target whose node to should be connected
*/ */
connect (target) { connect (target) {
@ -70,11 +120,19 @@ class EffectChain {
firstEffect.connect(target); firstEffect.connect(target);
} }
/**
* Array of SoundPlayers managed by this EffectChain.
* @returns {Array<SoundPlayer>} sound players managed by this chain
*/
getSoundPlayers () { getSoundPlayers () {
return [...this._soundPlayers]; return [...this._soundPlayers];
} }
/**
* Set Effect values with named values on target.soundEffects if it exist
* and then from target itself.
* @param {Target} target - target to set values from
*/
setEffectsFromTarget (target) { setEffectsFromTarget (target) {
this._effects.forEach(effect => { this._effects.forEach(effect => {
if ('soundEffects' in target && effect.name in target.soundEffects) { if ('soundEffects' in target && effect.name in target.soundEffects) {
@ -85,20 +143,35 @@ class EffectChain {
}); });
} }
/**
* Set an effect value by its name.
* @param {string} effect - effect name to change
* @param {number} value - value to set effect to
*/
set (effect, value) { set (effect, value) {
if (effect in this) { if (effect in this) {
this[effect].set(value); this[effect].set(value);
} }
} }
/**
* Update managed sound players with the effects on this chain.
*/
update () { update () {
this._effects.forEach(effect => effect.update()); this._effects.forEach(effect => effect.update());
} }
/**
* Clear all effects to their default values.
*/
clear () { clear () {
this._effects.forEach(effect => effect.clear()); this._effects.forEach(effect => effect.clear());
} }
/**
* Dispose of all effects in this chain. Nothing is done to managed
* SoundPlayers.
*/
dispose () { dispose () {
this._soundPlayers = null; this._soundPlayers = null;
this._effects.forEach(effect => effect.dispose()); this._effects.forEach(effect => effect.dispose());