diff --git a/src/blocks/scratch3_sound.js b/src/blocks/scratch3_sound.js
index 7e1e466a0..aa227c513 100644
--- a/src/blocks/scratch3_sound.js
+++ b/src/blocks/scratch3_sound.js
@@ -1,5 +1,6 @@
 var MathUtil = require('../util/math-util');
 var Cast = require('../util/cast');
+var Clone = require('../util/clone');
 
 var Scratch3SoundBlocks = function (runtime) {
     /**
@@ -9,6 +10,43 @@ var Scratch3SoundBlocks = function (runtime) {
     this.runtime = runtime;
 };
 
+/**
+ * The key to load & store a target's sound-related state.
+ * @type {string}
+ */
+Scratch3SoundBlocks.STATE_KEY = 'Scratch.sound';
+
+/**
+ * The default sound-related state, to be used when a target has no existing sound state.
+ * @type {SoundState}
+ */
+Scratch3SoundBlocks.DEFAULT_SOUND_STATE = {
+    volume: 100,
+    currentInstrument: 0,
+    effects: {
+        pitch: 0,
+        pan: 0,
+        echo: 0,
+        reverb: 0,
+        fuzz: 0,
+        robot: 0
+    }
+};
+
+/**
+ * @param {Target} target - collect sound state for this target.
+ * @returns {SoundState} the mutable sound state associated with that target. This will be created if necessary.
+ * @private
+ */
+Scratch3SoundBlocks.prototype._getSoundState = function (target) {
+    var soundState = target.getCustomState(Scratch3SoundBlocks.STATE_KEY);
+    if (!soundState) {
+        soundState = Clone.simple(Scratch3SoundBlocks.DEFAULT_SOUND_STATE);
+        target.setCustomState(Scratch3SoundBlocks.STATE_KEY, soundState);
+    }
+    return soundState;
+};
+
 /**
  * Retrieve the block primitives implemented by this package.
  * @return {object.<string, Function>} Mapping of opcode to Function.
@@ -30,6 +68,7 @@ Scratch3SoundBlocks.prototype.getPrimitives = function () {
         sound_effects_menu: this.effectsMenu,
         sound_setvolumeto: this.setVolume,
         sound_changevolumeby: this.changeVolume,
+        sound_volume: this.getVolume,
         sound_settempotobpm: this.setTempo,
         sound_changetempoby: this.changeTempo,
         sound_tempo: this.getTempo
@@ -38,31 +77,50 @@ Scratch3SoundBlocks.prototype.getPrimitives = function () {
 
 Scratch3SoundBlocks.prototype.playSound = function (args, util) {
     var index = this._getSoundIndex(args.SOUND_MENU, util);
-    util.target.audioPlayer.playSound(index);
+    if (index >= 0) {
+        var md5 = util.target.sprite.sounds[index].md5;
+        util.target.audioPlayer.playSound(md5);
+    }
 };
 
 Scratch3SoundBlocks.prototype.playSoundAndWait = function (args, util) {
     var index = this._getSoundIndex(args.SOUND_MENU, util);
-    return util.target.audioPlayer.playSound(index);
+    if (index >= 0) {
+        var md5 = util.target.sprite.sounds[index].md5;
+        return util.target.audioPlayer.playSound(md5);
+    }
 };
 
 Scratch3SoundBlocks.prototype._getSoundIndex = function (soundName, util) {
-    if (util.target.sprite.sounds.length === 0) {
-        return 0;
+    // if the sprite has no sounds, return -1
+    var len = util.target.sprite.sounds.length;
+    if (len === 0) {
+        return -1;
     }
+
     var index;
 
-    if (Number(soundName)) {
-        soundName = Number(soundName);
-        var len = util.target.sprite.sounds.length;
-        index = MathUtil.wrapClamp(soundName, 1, len) - 1;
-    } else {
-        index = util.target.getSoundIndexByName(soundName);
-        if (index === -1) {
-            index = 0;
+    // try to convert to a number and use that as an index
+    var num = parseInt(soundName, 10);
+    if (!isNaN(num)) {
+        index = MathUtil.wrapClamp(num, 0, len - 1);
+        return index;
+    }
+
+    // return the index for the sound of that name
+    index = this.getSoundIndexByName(soundName, util);
+    return index;
+};
+
+Scratch3SoundBlocks.prototype.getSoundIndexByName = function (soundName, util) {
+    var sounds = util.target.sprite.sounds;
+    for (var i = 0; i < sounds.length; i++) {
+        if (sounds[i].name === soundName) {
+            return i;
         }
     }
-    return index;
+    // if there is no sound by that name, return -1
+    return -1;
 };
 
 Scratch3SoundBlocks.prototype.stopAllSounds = function (args, util) {
@@ -70,62 +128,100 @@ Scratch3SoundBlocks.prototype.stopAllSounds = function (args, util) {
 };
 
 Scratch3SoundBlocks.prototype.playNoteForBeats = function (args, util) {
-    return util.target.audioPlayer.playNoteForBeats(args.NOTE, args.BEATS);
+    var note = Cast.toNumber(args.NOTE);
+    var beats = Cast.toNumber(args.BEATS);
+    var soundState = this._getSoundState(util.target);
+    var inst = soundState.currentInstrument;
+    return this.runtime.audioEngine.playNoteForBeatsWithInst(note, beats, inst);
 };
 
 Scratch3SoundBlocks.prototype.playDrumForBeats = function (args, util) {
-    return util.target.audioPlayer.playDrumForBeats(args.DRUM, args.BEATS);
+    var drum = Cast.toNumber(args.DRUM);
+    drum -= 1; // drums are one-indexed
+    drum = MathUtil.wrapClamp(drum, 0, this.runtime.audioEngine.numDrums);
+    var beats = Cast.toNumber(args.BEATS);
+    return util.target.audioPlayer.playDrumForBeats(drum, beats);
 };
 
-Scratch3SoundBlocks.prototype.restForBeats = function (args, util) {
-    return util.target.audioPlayer.waitForBeats(args.BEATS);
+Scratch3SoundBlocks.prototype.restForBeats = function (args) {
+    var beats = Cast.toNumber(args.BEATS);
+    return this.runtime.audioEngine.waitForBeats(beats);
 };
 
 Scratch3SoundBlocks.prototype.setInstrument = function (args, util) {
+    var soundState = this._getSoundState(util.target);
     var instNum = Cast.toNumber(args.INSTRUMENT);
-    return util.target.audioPlayer.setInstrument(instNum);
+    instNum -= 1; // instruments are one-indexed
+    instNum = MathUtil.wrapClamp(instNum, 0, this.runtime.audioEngine.numInstruments);
+    soundState.currentInstrument = instNum;
+    return this.runtime.audioEngine.instrumentPlayer.loadInstrument(soundState.currentInstrument);
 };
 
 Scratch3SoundBlocks.prototype.setEffect = function (args, util) {
+    var effect = Cast.toString(args.EFFECT).toLowerCase();
     var value = Cast.toNumber(args.VALUE);
-    util.target.audioPlayer.setEffect(args.EFFECT, value);
+
+    var soundState = this._getSoundState(util.target);
+    if (!soundState.effects.hasOwnProperty(effect)) return;
+
+    soundState.effects[effect] = value;
+    util.target.audioPlayer.setEffect(effect, soundState.effects[effect]);
 };
 
 Scratch3SoundBlocks.prototype.changeEffect = function (args, util) {
+    var effect = Cast.toString(args.EFFECT).toLowerCase();
     var value = Cast.toNumber(args.VALUE);
-    util.target.audioPlayer.changeEffect(args.EFFECT, value);
+
+    var soundState = this._getSoundState(util.target);
+    if (!soundState.effects.hasOwnProperty(effect)) return;
+
+    soundState.effects[effect] += value;
+    util.target.audioPlayer.setEffect(effect, soundState.effects[effect]);
 };
 
 Scratch3SoundBlocks.prototype.clearEffects = function (args, util) {
+    var soundState = this._getSoundState(util.target);
+    for (var effect in soundState.effects) {
+        soundState.effects[effect] = 0;
+    }
     util.target.audioPlayer.clearEffects();
 };
 
 Scratch3SoundBlocks.prototype.setVolume = function (args, util) {
-    var value = Cast.toNumber(args.VOLUME);
-    util.target.audioPlayer.setVolume(value);
+    var volume = Cast.toNumber(args.VOLUME);
+    this._updateVolume(volume, util);
 };
 
 Scratch3SoundBlocks.prototype.changeVolume = function (args, util) {
-    var value = Cast.toNumber(args.VOLUME);
-    util.target.audioPlayer.changeVolume(value);
+    var soundState = this._getSoundState(util.target);
+    var volume = Cast.toNumber(args.VOLUME) + soundState.volume;
+    this._updateVolume(volume, util);
+};
+
+Scratch3SoundBlocks.prototype._updateVolume = function (volume, util) {
+    var soundState = this._getSoundState(util.target);
+    volume = MathUtil.clamp(volume, 0, 100);
+    soundState.volume = volume;
+    util.target.audioPlayer.setVolume(soundState.volume);
 };
 
 Scratch3SoundBlocks.prototype.getVolume = function (args, util) {
-    return util.target.audioPlayer.currentVolume;
+    var soundState = this._getSoundState(util.target);
+    return soundState.volume;
 };
 
-Scratch3SoundBlocks.prototype.setTempo = function (args, util) {
+Scratch3SoundBlocks.prototype.setTempo = function (args) {
     var value = Cast.toNumber(args.TEMPO);
-    util.target.audioPlayer.setTempo(value);
+    this.runtime.audioEngine.setTempo(value);
 };
 
-Scratch3SoundBlocks.prototype.changeTempo = function (args, util) {
+Scratch3SoundBlocks.prototype.changeTempo = function (args) {
     var value = Cast.toNumber(args.TEMPO);
-    util.target.audioPlayer.changeTempo(value);
+    this.runtime.audioEngine.changeTempo(value);
 };
 
-Scratch3SoundBlocks.prototype.getTempo = function (args, util) {
-    return util.target.audioPlayer.currentTempo;
+Scratch3SoundBlocks.prototype.getTempo = function () {
+    return this.runtime.audioEngine.currentTempo;
 };
 
 Scratch3SoundBlocks.prototype.soundsMenu = function (args) {
diff --git a/src/import/sb2import.js b/src/import/sb2import.js
index 40c17e309..20669466e 100644
--- a/src/import/sb2import.js
+++ b/src/import/sb2import.js
@@ -61,7 +61,8 @@ var parseScratchObject = function (object, runtime, topLevel) {
                 rate: sound.rate,
                 sampleCount: sound.sampleCount,
                 soundID: sound.soundID,
-                name: sound.soundName
+                name: sound.soundName,
+                md5: sound.md5
             });
         }
     }
diff --git a/src/import/sb2specmap.js b/src/import/sb2specmap.js
index 95aad5019..5ff494059 100644
--- a/src/import/sb2specmap.js
+++ b/src/import/sb2specmap.js
@@ -408,7 +408,7 @@ var specMap = {
             {
                 type: 'input',
                 inputOp: 'math_number',
-                inputName: 'DRUMTYPE'
+                inputName: 'DRUM'
             },
             {
                 type: 'input',
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index b82afa846..9bbc20b23 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -69,10 +69,9 @@ RenderedTarget.prototype.initDrawable = function () {
     this.audioPlayer = null;
     if (this.runtime && this.runtime.audioEngine) {
         if (this.isOriginal) {
-            this.sprite.audioPlayer = this.runtime.audioEngine.createPlayer();
-            this.sprite.audioPlayer.loadSounds(this.sprite.sounds);
+            this.runtime.audioEngine.loadSounds(this.sprite.sounds);
         }
-        this.audioPlayer = this.sprite.audioPlayer;
+        this.audioPlayer = this.runtime.audioEngine.createPlayer();
     }
 };
 
@@ -399,20 +398,6 @@ RenderedTarget.prototype.getCostumeIndexByName = function (costumeName) {
     return -1;
 };
 
-/**
- * Get a sound index of this rendered target, by name of the sound.
- * @param {?string} soundName Name of a sound.
- * @return {number} Index of the named sound, or -1 if not present.
- */
-RenderedTarget.prototype.getSoundIndexByName = function (soundName) {
-    for (var i = 0; i < this.sprite.sounds.length; i++) {
-        if (this.sprite.sounds[i].name === soundName) {
-            return i;
-        }
-    }
-    return -1;
-};
-
 /**
  * Get a costume of this rendered target by id.
  * @return {object} current costume