mirror of
https://github.com/scratchfoundation/scratch-audio.git
synced 2024-12-22 14:02:29 -05:00
comment SoundBank and EffectChain
This commit is contained in:
parent
7cef4e60a8
commit
c12a1a4766
2 changed files with 147 additions and 4 deletions
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in a new issue