From 961749815c63a4756890590f903cc0da99eaf72d Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Tue, 12 Jun 2018 09:21:03 -0400 Subject: [PATCH 1/3] feat: use GreenPlayer in AudioPlayer --- src/AudioPlayer.js | 66 ++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/src/AudioPlayer.js b/src/AudioPlayer.js index 1619e4c..e6513c8 100644 --- a/src/AudioPlayer.js +++ b/src/AudioPlayer.js @@ -2,7 +2,7 @@ const PanEffect = require('./effects/PanEffect'); const PitchEffect = require('./effects/PitchEffect'); const VolumeEffect = require('./effects/VolumeEffect'); -const SoundPlayer = require('./SoundPlayer'); +const SoundPlayer = require('./GreenPlayer'); class AudioPlayer { /** @@ -17,7 +17,7 @@ class AudioPlayer { this.outputNode = this.audioEngine.audioContext.createGain(); - // Create the audio effects + // Create the audio effects. const volumeEffect = new VolumeEffect(this.audioEngine, this, null); const pitchEffect = new PitchEffect(this.audioEngine, this, volumeEffect); const panEffect = new PanEffect(this.audioEngine, this, pitchEffect); @@ -33,12 +33,11 @@ class AudioPlayer { pitchEffect.connect(panEffect); volumeEffect.connect(pitchEffect); - // reset effects to their default parameters + // Reset effects to their default parameters. this.clearEffects(); - // sound players that are currently playing, indexed by the sound's - // soundId - this.activeSoundPlayers = {}; + // SoundPlayers mapped by sound id. + this.soundPlayers = {}; } /** @@ -55,7 +54,19 @@ class AudioPlayer { * players */ getSoundPlayers () { - return this.activeSoundPlayers; + return this.soundPlayers; + } + + /** + * Add a SoundPlayer instance to soundPlayers map. + * @param {SoundPlayer} soundPlayer - SoundPlayer instance to add + */ + addSoundPlayer (soundPlayer) { + this.soundPlayers[soundPlayer.id] = soundPlayer; + + for (const effectName in this.effects) { + this.effects[effectName].update(); + } } /** @@ -64,37 +75,16 @@ class AudioPlayer { * @return {Promise} a Promise that resolves when the sound finishes playing */ playSound (soundId) { - // if this sound is not in the audio engine, return - if (!this.audioEngine.audioBuffers[soundId]) { - return; - } - - // if this sprite or clone is already playing this sound, stop it first - if (this.activeSoundPlayers[soundId]) { - this.activeSoundPlayers[soundId].stop(); - } - // create a new soundplayer to play the sound - const player = new SoundPlayer(this.audioEngine.audioContext); - player.setBuffer(this.audioEngine.audioBuffers[soundId]); - player.connect(this.outputNode); - player.start(); - - // add it to the list of active sound players - this.activeSoundPlayers[soundId] = player; - for (const effectName in this.effects) { - this.effects[effectName].update(); - } - - // remove sounds that are not playing from the active sound players - // array - for (const id in this.activeSoundPlayers) { - if (this.activeSoundPlayers.hasOwnProperty(id)) { - if (!this.activeSoundPlayers[id].isPlaying) { - delete this.activeSoundPlayers[id]; - } - } + if (!this.soundPlayers[soundId]) { + this.addSoundPlayer(new SoundPlayer( + this.audioEngine, + {id: soundId, buffer: this.audioEngine.audioBuffers[soundId]} + )); } + const player = this.soundPlayers[soundId]; + player.connect(this); + player.play(); return player.finished(); } @@ -104,8 +94,8 @@ class AudioPlayer { */ stopAllSounds () { // stop all active sound players - for (const soundId in this.activeSoundPlayers) { - this.activeSoundPlayers[soundId].stop(); + for (const soundId in this.soundPlayers) { + this.soundPlayers[soundId].stop(); } } From 331f083f5ae33c217b903c06015340a6f6527e4a Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Mon, 18 Jun 2018 14:47:38 -0400 Subject: [PATCH 2/3] add GreenPlayer.isStarting promise Self-resolving promise removes itself once it resolves. While it is on a player the player may consider itself to not yet have the time to actually start sound playback. While playback has not started, the player can shortcut out of starting playback. --- src/GreenPlayer.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/GreenPlayer.js b/src/GreenPlayer.js index fb70e2e..8e53252 100644 --- a/src/GreenPlayer.js +++ b/src/GreenPlayer.js @@ -31,6 +31,7 @@ class SoundPlayer extends EventEmitter { this.initialized = false; this.isPlaying = false; + this.isStarting = null; this.playbackRate = 1; } @@ -146,6 +147,7 @@ class SoundPlayer extends EventEmitter { const taken = new SoundPlayer(this.audioEngine, this); taken.playbackRate = this.playbackRate; if (this.isPlaying) { + taken.isStarting = this.isStarting; taken.isPlaying = this.isPlaying; taken.initialize(); taken.outputNode.disconnect(); @@ -168,6 +170,7 @@ class SoundPlayer extends EventEmitter { } this.volumeEffect = null; this.initialized = false; + this.isStarting = null; this.isPlaying = false; return taken; @@ -180,6 +183,10 @@ class SoundPlayer extends EventEmitter { * out. */ play () { + if (this.isStarting) { + return; + } + if (this.isPlaying) { // Spawn a Player with the current buffer source, and play for a // short period until its volume is 0 and release it to be @@ -198,6 +205,13 @@ class SoundPlayer extends EventEmitter { this.isPlaying = true; + const isStarting = this.isStarting = Promise.resolve() + .then(() => { + if (this.isStarting === isStarting) { + this.isStarting = null; + } + }); + this.emit('play'); } @@ -213,6 +227,7 @@ class SoundPlayer extends EventEmitter { this.outputNode.stop(this.audioEngine.audioContext.currentTime + this.audioEngine.DECAY_TIME); this.isPlaying = false; + this.isStarting = null; this.emit('stop'); } @@ -228,6 +243,7 @@ class SoundPlayer extends EventEmitter { this.outputNode.stop(); this.isPlaying = false; + this.isStarting = null; this.emit('stop'); } From 2126761dc368ce7aee6ef97c5eddcb31140f1791 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Thu, 21 Jun 2018 13:44:41 -0400 Subject: [PATCH 3/3] use AudioContext.currentTime + DECAY_TIME to debounce sound start Debounce when the sound starts to keep from playing many copies of the same sound layered on itself. Use AudioEngine's currentTime in seconds plus some value in milliseconds to make sure the sound has a chance to start before starting a second playback. --- src/GreenPlayer.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/GreenPlayer.js b/src/GreenPlayer.js index 8e53252..e73954a 100644 --- a/src/GreenPlayer.js +++ b/src/GreenPlayer.js @@ -31,10 +31,18 @@ class SoundPlayer extends EventEmitter { this.initialized = false; this.isPlaying = false; - this.isStarting = null; + this.startingUntil = 0; this.playbackRate = 1; } + /** + * Is plaback currently starting? + * @type {boolean} + */ + get isStarting () { + return this.isPlaying && this.startingUntil > this.audioEngine.audioContext.currentTime; + } + /** * Handle any event we have told the output node to listen for. * @param {Event} event - dom event to handle @@ -147,7 +155,7 @@ class SoundPlayer extends EventEmitter { const taken = new SoundPlayer(this.audioEngine, this); taken.playbackRate = this.playbackRate; if (this.isPlaying) { - taken.isStarting = this.isStarting; + taken.startingUntil = this.startingUntil; taken.isPlaying = this.isPlaying; taken.initialize(); taken.outputNode.disconnect(); @@ -170,7 +178,7 @@ class SoundPlayer extends EventEmitter { } this.volumeEffect = null; this.initialized = false; - this.isStarting = null; + this.startingUntil = 0; this.isPlaying = false; return taken; @@ -205,12 +213,7 @@ class SoundPlayer extends EventEmitter { this.isPlaying = true; - const isStarting = this.isStarting = Promise.resolve() - .then(() => { - if (this.isStarting === isStarting) { - this.isStarting = null; - } - }); + this.startingUntil = this.audioEngine.audioContext.currentTime + this.audioEngine.DECAY_TIME; this.emit('play'); } @@ -227,7 +230,7 @@ class SoundPlayer extends EventEmitter { this.outputNode.stop(this.audioEngine.audioContext.currentTime + this.audioEngine.DECAY_TIME); this.isPlaying = false; - this.isStarting = null; + this.startingUntil = 0; this.emit('stop'); } @@ -243,7 +246,7 @@ class SoundPlayer extends EventEmitter { this.outputNode.stop(); this.isPlaying = false; - this.isStarting = null; + this.startingUntil = 0; this.emit('stop'); }