diff --git a/src/import/load-sound.js b/src/import/load-sound.js index 548eb3276..639fcc2af 100644 --- a/src/import/load-sound.js +++ b/src/import/load-sound.js @@ -8,10 +8,10 @@ const log = require('../util/log'); * @property {Buffer} data - sound data will be written here once loaded. * @param {!Asset} soundAsset - the asset loaded from storage. * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. - * @param {Sprite} sprite - Scratch sprite to add sounds to. + * @param {SoundBank} soundBank - Scratch Audio SoundBank to add sounds to. * @returns {!Promise} - a promise which will resolve to the sound when ready. */ -const loadSoundFromAsset = function (sound, soundAsset, runtime, sprite) { +const loadSoundFromAsset = function (sound, soundAsset, runtime, soundBank) { sound.assetId = soundAsset.assetId; if (!runtime.audioEngine) { log.error('No audio engine present; cannot load sound asset: ', sound.md5); @@ -30,8 +30,8 @@ const loadSoundFromAsset = function (sound, soundAsset, runtime, sprite) { sound.rate = soundBuffer.sampleRate; sound.sampleCount = soundBuffer.length; - if (sprite.soundBank !== null) { - sprite.soundBank.addSoundPlayer(soundPlayer); + if (soundBank !== null) { + soundBank.addSoundPlayer(soundPlayer); } return sound; @@ -44,10 +44,10 @@ const loadSoundFromAsset = function (sound, soundAsset, runtime, sprite) { * @property {string} md5 - the MD5 and extension of the sound to be loaded. * @property {Buffer} data - sound data will be written here once loaded. * @param {!Runtime} runtime - Scratch runtime, used to access the storage module. - * @param {Sprite} sprite - Scratch sprite to add sounds to. + * @param {SoundBank} soundBank - Scratch Audio SoundBank to add sounds to. * @returns {!Promise} - a promise which will resolve to the sound when ready. */ -const loadSound = function (sound, runtime, sprite) { +const loadSound = function (sound, runtime, soundBank) { if (!runtime.storage) { log.error('No storage module present; cannot load sound asset: ', sound.md5); return Promise.resolve(sound); @@ -61,7 +61,7 @@ const loadSound = function (sound, runtime, sprite) { runtime.storage.load(runtime.storage.AssetType.Sound, md5, ext) ).then(soundAsset => { sound.asset = soundAsset; - return loadSoundFromAsset(sound, soundAsset, runtime, sprite); + return loadSoundFromAsset(sound, soundAsset, runtime, soundBank); }); }; diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 42a612b4c..b3dc19eef 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -399,9 +399,10 @@ const parseMonitorObject = (object, runtime, targets, extensions) => { * @param {!Runtime} runtime - Runtime object to load all structures into. * @param {boolean} topLevel - Whether this is the top-level object (stage). * @param {?object} zip - Optional zipped assets for local file import - * @return {?{costumePromises:Array.,soundPromises:Array.,children:object}} + * @return {?{costumePromises:Array.,soundPromises:Array.,soundBank:SoundBank,children:object}} * Object of arrays of promises and child objects for asset objects used in - * Sprites. null for unsupported objects. + * Sprites. As well as a SoundBank for the sound assets. null for unsupported + * objects. */ const parseScratchAssets = function (object, runtime, topLevel, zip) { if (!object.hasOwnProperty('objName')) { @@ -409,7 +410,12 @@ const parseScratchAssets = function (object, runtime, topLevel, zip) { return null; } - const assets = {costumePromises: [], soundPromises: [], children: []}; + const assets = { + costumePromises: [], + soundPromises: [], + soundBank: runtime.audioEngine && runtime.audioEngine.createBank(), + children: [] + }; // Costumes from JSON. const costumePromises = assets.costumePromises; @@ -457,7 +463,7 @@ const parseScratchAssets = function (object, runtime, topLevel, zip) { } } // Sounds from JSON - const soundPromises = assets.soundPromises; + const {soundBank, soundPromises} = assets; if (object.hasOwnProperty('sounds')) { for (let s = 0; s < object.sounds.length; s++) { const soundSource = object.sounds[s]; @@ -486,7 +492,10 @@ const parseScratchAssets = function (object, runtime, topLevel, zip) { // the file name of the sound should be the soundID (provided from the project.json) // followed by the file ext const assetFileName = `${soundSource.soundID}.${ext}`; - soundPromises.push(deserializeSound(sound, runtime, zip, assetFileName).then(() => sound)); + soundPromises.push( + deserializeSound(sound, runtime, zip, assetFileName) + .then(() => loadSound(sound, runtime, soundBank)) + ); } } @@ -546,11 +555,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip, // Costumes from JSON. const costumePromises = assets.costumePromises; // Sounds from JSON - const soundPromises = assets.soundPromises; - for (let s = 0; s < soundPromises.length; s++) { - soundPromises[s] = soundPromises[s] - .then(sound => loadSound(sound, runtime, sprite)); - } + const {soundBank, soundPromises} = assets; // Create the first clone, and load its run-state from JSON. const target = sprite.createClone(topLevel ? StageLayering.BACKGROUND_LAYER : StageLayering.SPRITE_LAYER); @@ -738,6 +743,8 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip, Promise.all(soundPromises).then(sounds => { sprite.sounds = sounds; + // Make sure if soundBank is undefined, sprite.soundBank is then null. + sprite.soundBank = soundBank || null; }); // The stage will have child objects; recursively process them. diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 6aa592c73..2cafa5920 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -914,7 +914,7 @@ const parseScratchObject = function (object, runtime, extensions, zip) { // any translation that needs to happen will happen in the process // of building up the costume object into an sb3 format return deserializeSound(sound, runtime, zip) - .then(() => loadSound(sound, runtime, sprite)); + .then(() => loadSound(sound, runtime, sprite.soundBank)); // Only attempt to load the sound after the deserialization // process has been completed. }); diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js index 6874f91bf..07b94673c 100644 --- a/src/sprites/sprite.js +++ b/src/sprites/sprite.js @@ -153,7 +153,7 @@ class Sprite { newSprite.sounds = this.sounds.map(sound => { const newSound = Object.assign({}, sound); const soundAsset = sound.asset; - assetPromises.push(loadSoundFromAsset(newSound, soundAsset, this.runtime, newSprite)); + assetPromises.push(loadSoundFromAsset(newSound, soundAsset, this.runtime, newSprite.soundBank)); return newSound; }); diff --git a/src/virtual-machine.js b/src/virtual-machine.js index e762ce196..b610d193e 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -678,7 +678,7 @@ class VirtualMachine extends EventEmitter { duplicateSound (soundIndex) { const originalSound = this.editingTarget.getSounds()[soundIndex]; const clone = Object.assign({}, originalSound); - return loadSound(clone, this.runtime, this.editingTarget.sprite).then(() => { + return loadSound(clone, this.runtime, this.editingTarget.sprite.soundBank).then(() => { this.editingTarget.addSound(clone, soundIndex + 1); this.emitTargetsUpdate(); }); @@ -723,7 +723,7 @@ class VirtualMachine extends EventEmitter { const target = optTargetId ? this.runtime.getTargetById(optTargetId) : this.editingTarget; if (target) { - return loadSound(soundObject, this.runtime, target.sprite).then(() => { + return loadSound(soundObject, this.runtime, target.sprite.soundBank).then(() => { target.addSound(soundObject); this.emitTargetsUpdate(); }); @@ -1250,7 +1250,7 @@ class VirtualMachine extends EventEmitter { const originalSound = this.editingTarget.getSounds()[soundIndex]; const clone = Object.assign({}, originalSound); const target = this.runtime.getTargetById(targetId); - return loadSound(clone, this.runtime, target.sprite).then(() => { + return loadSound(clone, this.runtime, target.sprite.soundBank).then(() => { if (target) { target.addSound(clone); this.emitTargetsUpdate();