From 75cf6407d4faf2754bf4c2384ab6a7a3f9baf7f2 Mon Sep 17 00:00:00 2001
From: DD <liudi08@gmail.com>
Date: Fri, 23 Feb 2018 10:42:29 -0500
Subject: [PATCH 1/4] Duplicate costume

---
 src/sprites/rendered-target.js | 22 ++++++++++++++++++++++
 src/virtual-machine.js         | 30 +++++++++++++++++++++++++++++-
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index 2a6a0f7d3..aa1963480 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -408,6 +408,17 @@ class RenderedTarget extends Target {
         this.sprite.costumes.push(costumeObject);
     }
 
+    /**
+     * Add a costume at the given index, taking care to avoid duplicate names.
+     * @param {!object} costumeObject Object representing the costume.
+     * @param {!int} index Index at which to add costume
+     */
+    addCostumeAt (costumeObject, index) {
+        const usedNames = this.sprite.costumes.map(costume => costume.name);
+        costumeObject.name = StringUtil.unusedName(costumeObject.name, usedNames);
+        this.sprite.costumes.splice(index, 0, costumeObject);
+    }
+
     /**
      * Rename a costume, taking care to avoid duplicate names.
      * @param {int} costumeIndex - the index of the costume to be renamed.
@@ -458,6 +469,17 @@ class RenderedTarget extends Target {
         this.runtime.requestTargetsUpdate(this);
     }
 
+    /**
+     * Add a sound, taking care to avoid duplicate names.
+     * @param {!object} soundObject Object representing the sound.
+     * @param {!int} index Index at which to add costume
+     */
+    addSoundAt (soundObject, index) {
+        const usedNames = this.sprite.sounds.map(sound => sound.name);
+        soundObject.name = StringUtil.unusedName(soundObject.name, usedNames);
+        this.sprite.sounds.splice(index, 0, soundObject);
+    }
+
     /**
      * Add a sound, taking care to avoid duplicate names.
      * @param {!object} soundObject Object representing the sound.
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index d930ad534..6fdb1d0ec 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -322,11 +322,39 @@ class VirtualMachine extends EventEmitter {
         return loadCostume(md5ext, costumeObject, this.runtime).then(() => {
             this.editingTarget.addCostume(costumeObject);
             this.editingTarget.setCostume(
-                this.editingTarget.sprite.costumes.length - 1
+                this.editingTarget.getCostumes().length - 1
             );
         });
     }
 
+    /**
+     * Duplicate the costume at the given index. Add it at that index + 1.
+     * @param {!int} costumeIndex Index of costume to duplicate
+     */
+    duplicateCostume (costumeIndex) {
+        const originalCostume = this.editingTarget.getCostumes()[costumeIndex];
+        const clone = Object.assign({}, originalCostume);
+        const md5ext = `${clone.assetId}.${clone.dataFormat}`;
+        loadCostume(md5ext, clone, this.runtime).then(() => {
+            this.editingTarget.addCostumeAt(clone, costumeIndex + 1);
+            this.editingTarget.setCostume(costumeIndex + 1);
+            this.emitTargetsUpdate();
+        });
+    }
+
+    /**
+     * Duplicate the sound at the given index. Add it at that index + 1.
+     * @param {!int} soundIndex Index of sound to duplicate
+     */
+    duplicateSound (soundIndex) {
+        const originalSound = this.editingTarget.getSounds()[soundIndex];
+        const clone = Object.assign({}, originalSound);
+        loadSound(clone, this.runtime).then(() => {
+            this.editingTarget.addSoundAt(clone, soundIndex + 1);
+            this.emitTargetsUpdate();
+        });
+    }
+
     /**
      * Rename a costume on the current editing target.
      * @param {int} costumeIndex - the index of the costume to be renamed.

From 2239d1b92bb087446eb7a5a199cabb84e7b31734 Mon Sep 17 00:00:00 2001
From: DD <liudi08@gmail.com>
Date: Fri, 23 Feb 2018 11:09:19 -0500
Subject: [PATCH 2/4] Use promises

---
 src/virtual-machine.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 6fdb1d0ec..4e0a2bb11 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -330,12 +330,13 @@ class VirtualMachine extends EventEmitter {
     /**
      * Duplicate the costume at the given index. Add it at that index + 1.
      * @param {!int} costumeIndex Index of costume to duplicate
+     * @returns {?Promise} - a promise that resolves when the costume has been decoded and added
      */
     duplicateCostume (costumeIndex) {
         const originalCostume = this.editingTarget.getCostumes()[costumeIndex];
         const clone = Object.assign({}, originalCostume);
         const md5ext = `${clone.assetId}.${clone.dataFormat}`;
-        loadCostume(md5ext, clone, this.runtime).then(() => {
+        return loadCostume(md5ext, clone, this.runtime).then(() => {
             this.editingTarget.addCostumeAt(clone, costumeIndex + 1);
             this.editingTarget.setCostume(costumeIndex + 1);
             this.emitTargetsUpdate();
@@ -345,11 +346,12 @@ class VirtualMachine extends EventEmitter {
     /**
      * Duplicate the sound at the given index. Add it at that index + 1.
      * @param {!int} soundIndex Index of sound to duplicate
+     * @returns {?Promise} - a promise that resolves when the sound has been decoded and added
      */
     duplicateSound (soundIndex) {
         const originalSound = this.editingTarget.getSounds()[soundIndex];
         const clone = Object.assign({}, originalSound);
-        loadSound(clone, this.runtime).then(() => {
+        return loadSound(clone, this.runtime).then(() => {
             this.editingTarget.addSoundAt(clone, soundIndex + 1);
             this.emitTargetsUpdate();
         });

From 10789cd779ba684f9da1af8dd1dc9b8a1302e66a Mon Sep 17 00:00:00 2001
From: DD <liudi08@gmail.com>
Date: Fri, 23 Feb 2018 16:21:07 -0500
Subject: [PATCH 3/4] Bring in Karishmas changes from save-load to ensure the
 sound gets updated in storage when edited

---
 src/virtual-machine.js | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 4e0a2bb11..e2b18f2a3 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -414,12 +414,34 @@ class VirtualMachine extends EventEmitter {
      * Update a sound buffer.
      * @param {int} soundIndex - the index of the sound to be updated.
      * @param {AudioBuffer} newBuffer - new audio buffer for the audio engine.
+     * @param {ArrayBuffer} soundEncoding - the new (wav) encoded sound to be stored
      */
-    updateSoundBuffer (soundIndex, newBuffer) {
-        const id = this.editingTarget.sprite.sounds[soundIndex].soundId;
+    updateSoundBuffer (soundIndex, newBuffer, soundEncoding) {
+        const sound = this.editingTarget.sprite.sounds[soundIndex];
+        const id = sound ? sound.soundId : null;
         if (id && this.runtime && this.runtime.audioEngine) {
             this.runtime.audioEngine.updateSoundBuffer(id, newBuffer);
         }
+        // Update sound in runtime
+        if (soundEncoding) {
+            // Now that we updated the sound, the format should also be updated
+            // so that the sound can eventually be decoded the right way.
+            // Sounds that were formerly 'adpcm', but were updated in sound editor
+            // will not get decoded by the audio engine correctly unless the format
+            // is updated as below.
+            sound.format = '';
+            const storage = this.runtime.storage;
+            sound.assetId = storage.builtinHelper.cache(
+                storage.AssetType.Sound,
+                storage.DataFormat.WAV,
+                soundEncoding
+            );
+            sound.md5 = `${sound.assetId}.${sound.dataFormat}`;
+        }
+        // If soundEncoding is null, it's because gui had a problem
+        // encoding the updated sound. We don't want to store anything in this
+        // case, and gui should have logged an error.
+
         this.emitTargetsUpdate();
     }
 

From 9a65df4c12c619cff3e997c14a079bd3eb610c22 Mon Sep 17 00:00:00 2001
From: DD <liudi08@gmail.com>
Date: Fri, 23 Feb 2018 16:24:18 -0500
Subject: [PATCH 4/4] Make index optional

---
 src/sprites/rendered-target.js | 40 ++++++++++++----------------------
 src/virtual-machine.js         |  4 ++--
 2 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index aa1963480..ae02b72bd 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -401,22 +401,16 @@ class RenderedTarget extends Target {
     /**
      * Add a costume, taking care to avoid duplicate names.
      * @param {!object} costumeObject Object representing the costume.
+     * @param {?int} index Index at which to add costume
      */
-    addCostume (costumeObject) {
+    addCostume (costumeObject, index) {
         const usedNames = this.sprite.costumes.map(costume => costume.name);
         costumeObject.name = StringUtil.unusedName(costumeObject.name, usedNames);
-        this.sprite.costumes.push(costumeObject);
-    }
-
-    /**
-     * Add a costume at the given index, taking care to avoid duplicate names.
-     * @param {!object} costumeObject Object representing the costume.
-     * @param {!int} index Index at which to add costume
-     */
-    addCostumeAt (costumeObject, index) {
-        const usedNames = this.sprite.costumes.map(costume => costume.name);
-        costumeObject.name = StringUtil.unusedName(costumeObject.name, usedNames);
-        this.sprite.costumes.splice(index, 0, costumeObject);
+        if (index) {
+            this.sprite.costumes.splice(index, 0, costumeObject);
+        } else {
+            this.sprite.costumes.push(costumeObject);
+        }
     }
 
     /**
@@ -472,22 +466,16 @@ class RenderedTarget extends Target {
     /**
      * Add a sound, taking care to avoid duplicate names.
      * @param {!object} soundObject Object representing the sound.
-     * @param {!int} index Index at which to add costume
+     * @param {?int} index Index at which to add costume
      */
-    addSoundAt (soundObject, index) {
+    addSound (soundObject, index) {
         const usedNames = this.sprite.sounds.map(sound => sound.name);
         soundObject.name = StringUtil.unusedName(soundObject.name, usedNames);
-        this.sprite.sounds.splice(index, 0, soundObject);
-    }
-
-    /**
-     * Add a sound, taking care to avoid duplicate names.
-     * @param {!object} soundObject Object representing the sound.
-     */
-    addSound (soundObject) {
-        const usedNames = this.sprite.sounds.map(sound => sound.name);
-        soundObject.name = StringUtil.unusedName(soundObject.name, usedNames);
-        this.sprite.sounds.push(soundObject);
+        if (index) {
+            this.sprite.sounds.splice(index, 0, soundObject);
+        } else {
+            this.sprite.sounds.push(soundObject);
+        }
     }
 
     /**
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index e2b18f2a3..f9fa45be3 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -337,7 +337,7 @@ class VirtualMachine extends EventEmitter {
         const clone = Object.assign({}, originalCostume);
         const md5ext = `${clone.assetId}.${clone.dataFormat}`;
         return loadCostume(md5ext, clone, this.runtime).then(() => {
-            this.editingTarget.addCostumeAt(clone, costumeIndex + 1);
+            this.editingTarget.addCostume(clone, costumeIndex + 1);
             this.editingTarget.setCostume(costumeIndex + 1);
             this.emitTargetsUpdate();
         });
@@ -352,7 +352,7 @@ class VirtualMachine extends EventEmitter {
         const originalSound = this.editingTarget.getSounds()[soundIndex];
         const clone = Object.assign({}, originalSound);
         return loadSound(clone, this.runtime).then(() => {
-            this.editingTarget.addSoundAt(clone, soundIndex + 1);
+            this.editingTarget.addSound(clone, soundIndex + 1);
             this.emitTargetsUpdate();
         });
     }