1 | const log = require('./log');
|
2 |
|
3 | /**
|
4 | * A symbol indicating all targets are to be effected.
|
5 | * @const {string}
|
6 | */
|
7 | const ALL_TARGETS = '*';
|
8 |
|
9 | class SoundBank {
|
10 | /**
|
11 | * A bank of sounds that can be played.
|
12 | * @constructor
|
13 | * @param {AudioEngine} audioEngine - related AudioEngine
|
14 | * @param {EffectChain} effectChainPrime - original EffectChain cloned for
|
15 | * playing sounds
|
16 | */
|
17 | constructor (audioEngine, effectChainPrime) {
|
18 | /**
|
19 | * AudioEngine this SoundBank is related to.
|
20 | * @type {AudioEngine}
|
21 | */
|
22 | this.audioEngine = audioEngine;
|
23 |
|
24 | /**
|
25 | * Map of ids to soundPlayers.
|
26 | * @type {object<SoundPlayer>}
|
27 | */
|
28 | this.soundPlayers = {};
|
29 |
|
30 | /**
|
31 | * Map of targets by sound id.
|
32 | * @type {Map<string, Target>}
|
33 | */
|
34 | this.playerTargets = new Map();
|
35 |
|
36 | /**
|
37 | * Map of effect chains by sound id.
|
38 | * @type {Map<string, EffectChain}
|
39 | */
|
40 | this.soundEffects = new Map();
|
41 |
|
42 | /**
|
43 | * Original EffectChain cloned for every playing sound.
|
44 | * @type {EffectChain}
|
45 | */
|
46 | this.effectChainPrime = effectChainPrime;
|
47 | }
|
48 |
|
49 | /**
|
50 | * Add a sound player instance likely from AudioEngine.decodeSoundPlayer
|
51 | * @param {SoundPlayer} soundPlayer - SoundPlayer to add
|
52 | */
|
53 | addSoundPlayer (soundPlayer) {
|
54 | this.soundPlayers[soundPlayer.id] = soundPlayer;
|
55 | }
|
56 |
|
57 | /**
|
58 | * Get a sound player by id.
|
59 | * @param {string} soundId - sound to look for
|
60 | * @returns {SoundPlayer} instance of sound player for the id
|
61 | */
|
62 | getSoundPlayer (soundId) {
|
63 | if (!this.soundPlayers[soundId]) {
|
64 | log.error(`SoundBank.getSoundPlayer(${soundId}): called missing sound in bank`);
|
65 | }
|
66 |
|
67 | return this.soundPlayers[soundId];
|
68 | }
|
69 |
|
70 | /**
|
71 | * Get a sound EffectChain by id.
|
72 | * @param {string} sound - sound to look for an EffectChain
|
73 | * @returns {EffectChain} available EffectChain for this id
|
74 | */
|
75 | getSoundEffects (sound) {
|
76 | if (!this.soundEffects.has(sound)) {
|
77 | this.soundEffects.set(sound, this.effectChainPrime.clone());
|
78 | }
|
79 |
|
80 | return this.soundEffects.get(sound);
|
81 | }
|
82 |
|
83 | /**
|
84 | * Play a sound.
|
85 | * @param {Target} target - Target to play for
|
86 | * @param {string} soundId - id of sound to play
|
87 | * @returns {Promise} promise that resolves when the sound finishes playback
|
88 | */
|
89 | playSound (target, soundId) {
|
90 | const effects = this.getSoundEffects(soundId);
|
91 | const player = this.getSoundPlayer(soundId);
|
92 |
|
93 | if (this.playerTargets.get(soundId) !== target) {
|
94 | // make sure to stop the old sound, effectively "forking" the output
|
95 | // when the target switches before we adjust it's effects
|
96 | player.stop();
|
97 | }
|
98 |
|
99 | this.playerTargets.set(soundId, target);
|
100 | effects.addSoundPlayer(player);
|
101 | effects.setEffectsFromTarget(target);
|
102 | player.connect(effects);
|
103 |
|
104 | player.play();
|
105 |
|
106 | return player.finished();
|
107 | }
|
108 |
|
109 | /**
|
110 | * Set the effects (pan, pitch, and volume) from values on the given target.
|
111 | * @param {Target} target - target to set values from
|
112 | */
|
113 | setEffects (target) {
|
114 | this.playerTargets.forEach((playerTarget, key) => {
|
115 | if (playerTarget === target) {
|
116 | this.getSoundEffects(key).setEffectsFromTarget(target);
|
117 | }
|
118 | });
|
119 | }
|
120 |
|
121 | /**
|
122 | * Stop playback of sound by id if was lasted played by the target.
|
123 | * @param {Target} target - target to check if it last played the sound
|
124 | * @param {string} soundId - id of the sound to stop
|
125 | */
|
126 | stop (target, soundId) {
|
127 | if (this.playerTargets.get(soundId) === target) {
|
128 | this.soundPlayers[soundId].stop();
|
129 | }
|
130 | }
|
131 |
|
132 | /**
|
133 | * Stop all sounds for all targets or a specific target.
|
134 | * @param {Target|string} target - a symbol for all targets or the target
|
135 | * to stop sounds for
|
136 | */
|
137 | stopAllSounds (target = ALL_TARGETS) {
|
138 | this.playerTargets.forEach((playerTarget, key) => {
|
139 | if (target === ALL_TARGETS || playerTarget === target) {
|
140 | this.getSoundPlayer(key).stop();
|
141 | }
|
142 | });
|
143 | }
|
144 |
|
145 | /**
|
146 | * Dispose of all EffectChains and SoundPlayers.
|
147 | */
|
148 | dispose () {
|
149 | this.playerTargets.clear();
|
150 | this.soundEffects.forEach(effects => effects.dispose());
|
151 | this.soundEffects.clear();
|
152 | for (const soundId in this.soundPlayers) {
|
153 | if (Object.prototype.hasOwnProperty.call(this.soundPlayers, soundId)) {
|
154 | this.soundPlayers[soundId].dispose();
|
155 | }
|
156 | }
|
157 | this.soundPlayers = {};
|
158 | }
|
159 |
|
160 | }
|
161 |
|
162 | module.exports = SoundBank;
|