Merge pull request #1972 from mzgoddard/sound-bank-over-sprite

Sound bank over sprite
This commit is contained in:
Michael "Z" Goddard 2019-03-04 12:51:10 -05:00 committed by GitHub
commit 33f4482127
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 22 deletions

View file

@ -8,10 +8,10 @@ const log = require('../util/log');
* @property {Buffer} data - sound data will be written here once loaded. * @property {Buffer} data - sound data will be written here once loaded.
* @param {!Asset} soundAsset - the asset loaded from storage. * @param {!Asset} soundAsset - the asset loaded from storage.
* @param {!Runtime} runtime - Scratch runtime, used to access the storage module. * @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. * @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; sound.assetId = soundAsset.assetId;
if (!runtime.audioEngine) { if (!runtime.audioEngine) {
log.error('No audio engine present; cannot load sound asset: ', sound.md5); 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.rate = soundBuffer.sampleRate;
sound.sampleCount = soundBuffer.length; sound.sampleCount = soundBuffer.length;
if (sprite.soundBank !== null) { if (soundBank !== null) {
sprite.soundBank.addSoundPlayer(soundPlayer); soundBank.addSoundPlayer(soundPlayer);
} }
return sound; 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 {string} md5 - the MD5 and extension of the sound to be loaded.
* @property {Buffer} data - sound data will be written here once loaded. * @property {Buffer} data - sound data will be written here once loaded.
* @param {!Runtime} runtime - Scratch runtime, used to access the storage module. * @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. * @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) { if (!runtime.storage) {
log.error('No storage module present; cannot load sound asset: ', sound.md5); log.error('No storage module present; cannot load sound asset: ', sound.md5);
return Promise.resolve(sound); return Promise.resolve(sound);
@ -61,7 +61,7 @@ const loadSound = function (sound, runtime, sprite) {
runtime.storage.load(runtime.storage.AssetType.Sound, md5, ext) runtime.storage.load(runtime.storage.AssetType.Sound, md5, ext)
).then(soundAsset => { ).then(soundAsset => {
sound.asset = soundAsset; sound.asset = soundAsset;
return loadSoundFromAsset(sound, soundAsset, runtime, sprite); return loadSoundFromAsset(sound, soundAsset, runtime, soundBank);
}); });
}; };

View file

@ -399,9 +399,10 @@ const parseMonitorObject = (object, runtime, targets, extensions) => {
* @param {!Runtime} runtime - Runtime object to load all structures into. * @param {!Runtime} runtime - Runtime object to load all structures into.
* @param {boolean} topLevel - Whether this is the top-level object (stage). * @param {boolean} topLevel - Whether this is the top-level object (stage).
* @param {?object} zip - Optional zipped assets for local file import * @param {?object} zip - Optional zipped assets for local file import
* @return {?{costumePromises:Array.<Promise>,soundPromises:Array.<Promise>,children:object}} * @return {?{costumePromises:Array.<Promise>,soundPromises:Array.<Promise>,soundBank:SoundBank,children:object}}
* Object of arrays of promises and child objects for asset objects used in * 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) { const parseScratchAssets = function (object, runtime, topLevel, zip) {
if (!object.hasOwnProperty('objName')) { if (!object.hasOwnProperty('objName')) {
@ -409,7 +410,12 @@ const parseScratchAssets = function (object, runtime, topLevel, zip) {
return null; return null;
} }
const assets = {costumePromises: [], soundPromises: [], children: []}; const assets = {
costumePromises: [],
soundPromises: [],
soundBank: runtime.audioEngine && runtime.audioEngine.createBank(),
children: []
};
// Costumes from JSON. // Costumes from JSON.
const costumePromises = assets.costumePromises; const costumePromises = assets.costumePromises;
@ -457,7 +463,7 @@ const parseScratchAssets = function (object, runtime, topLevel, zip) {
} }
} }
// Sounds from JSON // Sounds from JSON
const soundPromises = assets.soundPromises; const {soundBank, soundPromises} = assets;
if (object.hasOwnProperty('sounds')) { if (object.hasOwnProperty('sounds')) {
for (let s = 0; s < object.sounds.length; s++) { for (let s = 0; s < object.sounds.length; s++) {
const soundSource = object.sounds[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) // the file name of the sound should be the soundID (provided from the project.json)
// followed by the file ext // followed by the file ext
const assetFileName = `${soundSource.soundID}.${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. // Costumes from JSON.
const costumePromises = assets.costumePromises; const costumePromises = assets.costumePromises;
// Sounds from JSON // Sounds from JSON
const soundPromises = assets.soundPromises; const {soundBank, soundPromises} = assets;
for (let s = 0; s < soundPromises.length; s++) {
soundPromises[s] = soundPromises[s]
.then(sound => loadSound(sound, runtime, sprite));
}
// Create the first clone, and load its run-state from JSON. // Create the first clone, and load its run-state from JSON.
const target = sprite.createClone(topLevel ? StageLayering.BACKGROUND_LAYER : StageLayering.SPRITE_LAYER); 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 => { Promise.all(soundPromises).then(sounds => {
sprite.sounds = 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. // The stage will have child objects; recursively process them.

View file

@ -914,7 +914,7 @@ const parseScratchObject = function (object, runtime, extensions, zip) {
// any translation that needs to happen will happen in the process // any translation that needs to happen will happen in the process
// of building up the costume object into an sb3 format // of building up the costume object into an sb3 format
return deserializeSound(sound, runtime, zip) 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 // Only attempt to load the sound after the deserialization
// process has been completed. // process has been completed.
}); });

View file

@ -153,7 +153,7 @@ class Sprite {
newSprite.sounds = this.sounds.map(sound => { newSprite.sounds = this.sounds.map(sound => {
const newSound = Object.assign({}, sound); const newSound = Object.assign({}, sound);
const soundAsset = sound.asset; const soundAsset = sound.asset;
assetPromises.push(loadSoundFromAsset(newSound, soundAsset, this.runtime, newSprite)); assetPromises.push(loadSoundFromAsset(newSound, soundAsset, this.runtime, newSprite.soundBank));
return newSound; return newSound;
}); });

View file

@ -678,7 +678,7 @@ class VirtualMachine extends EventEmitter {
duplicateSound (soundIndex) { duplicateSound (soundIndex) {
const originalSound = this.editingTarget.getSounds()[soundIndex]; const originalSound = this.editingTarget.getSounds()[soundIndex];
const clone = Object.assign({}, originalSound); 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.editingTarget.addSound(clone, soundIndex + 1);
this.emitTargetsUpdate(); this.emitTargetsUpdate();
}); });
@ -723,7 +723,7 @@ class VirtualMachine extends EventEmitter {
const target = optTargetId ? this.runtime.getTargetById(optTargetId) : const target = optTargetId ? this.runtime.getTargetById(optTargetId) :
this.editingTarget; this.editingTarget;
if (target) { if (target) {
return loadSound(soundObject, this.runtime, target.sprite).then(() => { return loadSound(soundObject, this.runtime, target.sprite.soundBank).then(() => {
target.addSound(soundObject); target.addSound(soundObject);
this.emitTargetsUpdate(); this.emitTargetsUpdate();
}); });
@ -1250,7 +1250,7 @@ class VirtualMachine extends EventEmitter {
const originalSound = this.editingTarget.getSounds()[soundIndex]; const originalSound = this.editingTarget.getSounds()[soundIndex];
const clone = Object.assign({}, originalSound); const clone = Object.assign({}, originalSound);
const target = this.runtime.getTargetById(targetId); 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) { if (target) {
target.addSound(clone); target.addSound(clone);
this.emitTargetsUpdate(); this.emitTargetsUpdate();