mirror of
https://github.com/scratchfoundation/scratch-audio.git
synced 2024-12-22 14:02:29 -05:00
162 lines
4.7 KiB
JavaScript
162 lines
4.7 KiB
JavaScript
const log = require('./log');
|
|
|
|
/**
|
|
* A symbol indicating all targets are to be effected.
|
|
* @const {string}
|
|
*/
|
|
const ALL_TARGETS = '*';
|
|
|
|
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) {
|
|
/**
|
|
* AudioEngine this SoundBank is related to.
|
|
* @type {AudioEngine}
|
|
*/
|
|
this.audioEngine = audioEngine;
|
|
|
|
/**
|
|
* Map of ids to soundPlayers.
|
|
* @type {object<SoundPlayer>}
|
|
*/
|
|
this.soundPlayers = {};
|
|
|
|
/**
|
|
* Map of targets by sound id.
|
|
* @type {Map<string, Target>}
|
|
*/
|
|
this.playerTargets = new Map();
|
|
|
|
/**
|
|
* Map of effect chains by sound id.
|
|
* @type {Map<string, EffectChain}
|
|
*/
|
|
this.soundEffects = new Map();
|
|
|
|
/**
|
|
* Original EffectChain cloned for every playing sound.
|
|
* @type {EffectChain}
|
|
*/
|
|
this.effectChainPrime = effectChainPrime;
|
|
}
|
|
|
|
/**
|
|
* Add a sound player instance likely from AudioEngine.decodeSoundPlayer
|
|
* @param {SoundPlayer} soundPlayer - SoundPlayer to add
|
|
*/
|
|
addSoundPlayer (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) {
|
|
if (!this.soundPlayers[soundId]) {
|
|
log.error(`SoundBank.getSoundPlayer(${soundId}): called missing sound in bank`);
|
|
}
|
|
|
|
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) {
|
|
if (!this.soundEffects.has(sound)) {
|
|
this.soundEffects.set(sound, this.effectChainPrime.clone());
|
|
}
|
|
|
|
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) {
|
|
const effects = this.getSoundEffects(soundId);
|
|
const player = this.getSoundPlayer(soundId);
|
|
|
|
if (this.playerTargets.get(soundId) !== target) {
|
|
// make sure to stop the old sound, effectively "forking" the output
|
|
// when the target switches before we adjust it's effects
|
|
player.stop();
|
|
}
|
|
|
|
this.playerTargets.set(soundId, target);
|
|
effects.addSoundPlayer(player);
|
|
effects.setEffectsFromTarget(target);
|
|
player.connect(effects);
|
|
|
|
player.play();
|
|
|
|
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) {
|
|
this.playerTargets.forEach((playerTarget, key) => {
|
|
if (playerTarget === target) {
|
|
this.getSoundEffects(key).setEffectsFromTarget(target);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
if (this.playerTargets.get(soundId) === target) {
|
|
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) {
|
|
this.playerTargets.forEach((playerTarget, key) => {
|
|
if (target === ALL_TARGETS || playerTarget === target) {
|
|
this.getSoundPlayer(key).stop();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Dispose of all EffectChains and SoundPlayers.
|
|
*/
|
|
dispose () {
|
|
this.playerTargets.clear();
|
|
this.soundEffects.forEach(effects => effects.dispose());
|
|
this.soundEffects.clear();
|
|
for (const soundId in this.soundPlayers) {
|
|
if (Object.prototype.hasOwnProperty.call(this.soundPlayers, soundId)) {
|
|
this.soundPlayers[soundId].dispose();
|
|
}
|
|
}
|
|
this.soundPlayers = {};
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = SoundBank;
|