From c087cf326a7866d89d42486a9b23a609c4292d69 Mon Sep 17 00:00:00 2001
From: Corey Frang <gnarf37@gmail.com>
Date: Fri, 15 Jun 2018 12:22:49 -0400
Subject: [PATCH] soundbank tests

---
 src/blocks/scratch3_sound.js   | 47 +++++++++++++++-------------------
 src/sprites/rendered-target.js | 40 ++++++++++++++++-------------
 src/sprites/sprite.js          | 14 +++++++++-
 test/unit/blocks_sounds.js     |  8 +++---
 4 files changed, 60 insertions(+), 49 deletions(-)

diff --git a/src/blocks/scratch3_sound.js b/src/blocks/scratch3_sound.js
index a599b314c..16b041bf8 100644
--- a/src/blocks/scratch3_sound.js
+++ b/src/blocks/scratch3_sound.js
@@ -88,6 +88,7 @@ class Scratch3SoundBlocks {
         if (!soundState) {
             soundState = Clone.simple(Scratch3SoundBlocks.DEFAULT_SOUND_STATE);
             target.setCustomState(Scratch3SoundBlocks.STATE_KEY, soundState);
+            target.soundEffects = soundState.effects;
         }
         return soundState;
     }
@@ -139,20 +140,19 @@ class Scratch3SoundBlocks {
     }
 
     playSound (args, util) {
-        const index = this._getSoundIndex(args.SOUND_MENU, util);
-        if (index >= 0) {
-            const soundId = util.target.sprite.sounds[index].soundId;
-            if (util.target.audioPlayer === null) return;
-            util.target.audioPlayer.playSound(soundId);
-        }
+        // Don't return the promise, it's the only difference for AndWait
+        this.playSoundAndWait(args, util);
     }
 
     playSoundAndWait (args, util) {
         const index = this._getSoundIndex(args.SOUND_MENU, util);
         if (index >= 0) {
-            const soundId = util.target.sprite.sounds[index].soundId;
-            if (util.target.audioPlayer === null) return;
-            return util.target.audioPlayer.playSound(soundId);
+            const {target} = util;
+            const {sprite} = target;
+            const {soundId} = sprite.sounds[index];
+            if (sprite.soundBank) {
+                return sprite.soundBank.playSound(target, soundId);
+            }
         }
     }
 
@@ -199,8 +199,9 @@ class Scratch3SoundBlocks {
     }
 
     _stopAllSoundsForTarget (target) {
-        if (target.audioPlayer === null) return;
-        target.audioPlayer.stopAllSounds();
+        if (target.sprite.soundBank) {
+            target.sprite.soundBank.stopAllSounds(target);
+        }
     }
 
     setEffect (args, util) {
@@ -224,23 +225,19 @@ class Scratch3SoundBlocks {
             soundState.effects[effect] = value;
         }
 
-        const effectRange = Scratch3SoundBlocks.EFFECT_RANGE[effect];
-        soundState.effects[effect] = MathUtil.clamp(soundState.effects[effect], effectRange.min, effectRange.max);
-
-        if (util.target.audioPlayer === null) return;
-        util.target.audioPlayer.setEffect(effect, soundState.effects[effect]);
+        const {min, max} = Scratch3SoundBlocks.EFFECT_RANGE[effect];
+        soundState.effects[effect] = MathUtil.clamp(soundState.effects[effect], min, max);
 
+        this._syncEffectsForTarget(util.target);
         // Yield until the next tick.
         return Promise.resolve();
     }
 
     _syncEffectsForTarget (target) {
-        if (!target || !target.audioPlayer) return;
-        const soundState = this._getSoundState(target);
-        for (const effect in soundState.effects) {
-            if (!soundState.effects.hasOwnProperty(effect)) continue;
-            target.audioPlayer.setEffect(effect, soundState.effects[effect]);
-        }
+        if (!target || !target.sprite.soundBank) return;
+        target.soundEffects = this._getSoundState(target).effects;
+
+        target.sprite.soundBank.setEffects(target);
     }
 
     clearEffects (args, util) {
@@ -253,8 +250,7 @@ class Scratch3SoundBlocks {
             if (!soundState.effects.hasOwnProperty(effect)) continue;
             soundState.effects[effect] = 0;
         }
-        if (target.audioPlayer === null) return;
-        target.audioPlayer.clearEffects();
+        this._syncEffectsForTarget(target);
     }
 
     _clearEffectsForAllTargets () {
@@ -278,8 +274,7 @@ class Scratch3SoundBlocks {
     _updateVolume (volume, util) {
         volume = MathUtil.clamp(volume, 0, 100);
         util.target.volume = volume;
-        if (util.target.audioPlayer === null) return;
-        util.target.audioPlayer.setVolume(util.target.volume);
+        this._syncEffectsForTarget(util.target);
 
         // Yield until the next tick.
         return Promise.resolve();
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index aac0b059a..293666acf 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -170,21 +170,30 @@ class RenderedTarget extends Target {
         }
     }
 
+    get audioPlayer () {
+        /* eslint-disable no-console */
+        console.warn('get audioPlayer deprecated, please update to use .sprite.soundBank methods');
+        console.warn(new Error('stack for debug').stack);
+        /* eslint-enable no-console */
+        const bank = this.sprite.soundBank;
+        const audioPlayerProxy = {
+            playSound: soundId => bank.play(this, soundId)
+        };
+
+        Object.defineProperty(this, 'audioPlayer', {
+            configurable: false,
+            enumerable: true,
+            writable: false,
+            value: audioPlayerProxy
+        });
+
+        return audioPlayerProxy;
+    }
+
     /**
      * Initialize the audio player for this sprite or clone.
      */
     initAudio () {
-        this.audioPlayer = null;
-        if (this.runtime && this.runtime.audioEngine) {
-            this.audioPlayer = this.runtime.audioEngine.createPlayer();
-            // If this is a clone, it gets a reference to its parent's activeSoundPlayers object.
-            if (!this.isOriginal) {
-                const parent = this.sprite.clones[0];
-                if (parent && parent.audioPlayer) {
-                    this.audioPlayer.activeSoundPlayers = parent.audioPlayer.activeSoundPlayers;
-                }
-            }
-        }
     }
 
     /**
@@ -1034,9 +1043,8 @@ class RenderedTarget extends Target {
      */
     onStopAll () {
         this.clearEffects();
-        if (this.audioPlayer) {
-            this.audioPlayer.stopAllSounds();
-            this.audioPlayer.clearEffects();
+        if (this.sprite.soundBank) {
+            this.sprite.soundBank.stopAllSounds(this);
         }
     }
 
@@ -1132,10 +1140,6 @@ class RenderedTarget extends Target {
                 this.runtime.requestRedraw();
             }
         }
-        if (this.audioPlayer) {
-            this.audioPlayer.stopAllSounds();
-            this.audioPlayer.dispose();
-        }
     }
 }
 
diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js
index de735ecc3..c7c35c9e0 100644
--- a/src/sprites/sprite.js
+++ b/src/sprites/sprite.js
@@ -8,7 +8,8 @@ const StageLayering = require('../engine/stage-layering');
 class Sprite {
     /**
      * Sprite to be used on the Scratch stage.
-     * All clones of a sprite have shared blocks, shared costumes, shared variables.
+     * All clones of a sprite have shared blocks, shared costumes, shared variables,
+     * shared sounds, etc.
      * @param {?Blocks} blocks Shared blocks object for all clones of sprite.
      * @param {Runtime} runtime Reference to the runtime.
      * @constructor
@@ -47,6 +48,11 @@ class Sprite {
          * @type {Array.<!RenderedTarget>}
          */
         this.clones = [];
+
+        this.soundBank = null;
+        if (this.runtime && this.runtime.audioEngine) {
+            this.soundBank = this.runtime.audioEngine.createBank();
+        }
     }
 
     /**
@@ -155,6 +161,12 @@ class Sprite {
 
         return Promise.all(assetPromises).then(() => newSprite);
     }
+
+    dispose () {
+        if (this.soundBank) {
+            this.soundBank.dispose();
+        }
+    }
 }
 
 module.exports = Sprite;
diff --git a/test/unit/blocks_sounds.js b/test/unit/blocks_sounds.js
index 9a44e7f9f..53acc8aff 100644
--- a/test/unit/blocks_sounds.js
+++ b/test/unit/blocks_sounds.js
@@ -11,10 +11,10 @@ const util = {
                 {name: 'second name', soundId: 'second soundId'},
                 {name: 'third name', soundId: 'third soundId'},
                 {name: '6', soundId: 'fourth soundId'}
-            ]
-        },
-        audioPlayer: {
-            playSound: soundId => (playedSound = soundId)
+            ],
+            soundBank: {
+                playSound: (target, soundId) => (playedSound = soundId)
+            }
         }
     }
 };