diff --git a/package.json b/package.json index afc2613ea..9f7505a01 100644 --- a/package.json +++ b/package.json @@ -33,12 +33,14 @@ "babel-eslint": "^7.1.1", "babel-loader": "^7.0.0", "babel-preset-es2015": "^6.24.1", + "buffer-loader": "0.0.1", "copy-webpack-plugin": "4.2.1", "decode-html": "2.0.0", "escape-html": "1.0.3", "eslint": "^4.5.0", "eslint-config-scratch": "^5.0.0", "expose-loader": "0.7.4", + "file-loader": "^1.1.6", "format-message": "5.2.1", "format-message-cli": "5.2.1", "gh-pages": "^1.1.0", diff --git a/src/extensions/scratch3_music/index.js b/src/extensions/scratch3_music/index.js index 6d391bb9e..386c2c4a6 100644 --- a/src/extensions/scratch3_music/index.js +++ b/src/extensions/scratch3_music/index.js @@ -5,6 +5,17 @@ const Cast = require('../../util/cast'); const MathUtil = require('../../util/math-util'); const Timer = require('../../util/timer'); +/** + * The instrument and drum sounds, loaded as static assets. + * @type {object} + */ +let assetData = {}; +try { + assetData = require('./manifest'); +} catch (e) { + // Non-webpack environment, don't worry about assets. +} + /** * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI. * @type {string} @@ -73,21 +84,20 @@ class Scratch3MusicBlocks { } /** - * Download and decode the full set of drum and instrument sounds, and - * store the audio buffers in arrays. + * Decode the full set of drum and instrument sounds, and store the audio buffers in arrays. */ _loadAllSounds () { const loadingPromises = []; this.DRUM_INFO.forEach((drumInfo, index) => { - const fileName = `drums/${drumInfo.fileName}`; - const promise = this._loadSound(fileName, index, this._drumBuffers); + const filePath = `drums/${drumInfo.fileName}`; + const promise = this._storeSound(filePath, index, this._drumBuffers); loadingPromises.push(promise); }); this.INSTRUMENT_INFO.forEach((instrumentInfo, instrumentIndex) => { this._instrumentBufferArrays[instrumentIndex] = []; instrumentInfo.samples.forEach((sample, noteIndex) => { - const fileName = `instruments/${instrumentInfo.dirName}/${sample}`; - const promise = this._loadSound(fileName, noteIndex, this._instrumentBufferArrays[instrumentIndex]); + const filePath = `instruments/${instrumentInfo.dirName}/${sample}`; + const promise = this._storeSound(filePath, noteIndex, this._instrumentBufferArrays[instrumentIndex]); loadingPromises.push(promise); }); }); @@ -97,35 +107,48 @@ class Scratch3MusicBlocks { } /** - * Download and decode a sound, and store the buffer in an array. - * @param {string} fileName - the audio file name. + * Decode a sound and store the buffer in an array. + * @param {string} filePath - the audio file name. * @param {number} index - the index at which to store the audio buffer. * @param {array} bufferArray - the array of buffers in which to store it. - * @return {Promise} - a promise which will resolve once the sound has loaded. + * @return {Promise} - a promise which will resolve once the sound has been stored. */ - _loadSound (fileName, index, bufferArray) { - if (!this.runtime.storage) return; + _storeSound (filePath, index, bufferArray) { + const fullPath = `${filePath}.mp3`; + + if (!assetData[fullPath]) return; + + // The sound buffer has already been downloaded via the manifest file required above. + const soundBuffer = assetData[fullPath].buffer; + + return this._decodeSound(soundBuffer).then(buffer => { + bufferArray[index] = buffer; + }); + } + + /** + * Decode a sound and return a promise with the audio buffer. + * @param {ArrayBuffer} soundBuffer - a buffer containing the encoded audio. + * @return {Promise} - a promise which will resolve once the sound has decoded. + */ + _decodeSound (soundBuffer) { if (!this.runtime.audioEngine) return; if (!this.runtime.audioEngine.audioContext) return; - return this.runtime.storage.load(this.runtime.storage.AssetType.Sound, fileName, 'mp3') - .then(soundAsset => { - const context = this.runtime.audioEngine.audioContext; - // Check for newer promise-based API - if (context.decodeAudioData.length === 1) { - return context.decodeAudioData(soundAsset.data.buffer); - } else { // eslint-disable-line no-else-return - // Fall back to callback API - return new Promise((resolve, reject) => - context.decodeAudioData(soundAsset.data.buffer, - buffer => resolve(buffer), - error => reject(error) - ) - ); - } - }) - .then(buffer => { - bufferArray[index] = buffer; - }); + + const context = this.runtime.audioEngine.audioContext; + + // Check for newer promise-based API + if (context.decodeAudioData.length === 1) { + return context.decodeAudioData(soundBuffer); + } else { // eslint-disable-line no-else-return + // Fall back to callback API + return new Promise((resolve, reject) => + context.decodeAudioData(soundBuffer, + buffer => resolve(buffer), + error => reject(error) + ) + ); + } } /** diff --git a/src/extensions/scratch3_music/manifest.js b/src/extensions/scratch3_music/manifest.js new file mode 100644 index 000000000..017da03b9 --- /dev/null +++ b/src/extensions/scratch3_music/manifest.js @@ -0,0 +1,63 @@ +module.exports = { + 'drums/1-snare.mp3': require('!buffer-loader!./assets/drums/1-snare.mp3'), + 'drums/2-bass-drum.mp3': require('!buffer-loader!./assets/drums/2-bass-drum.mp3'), + 'drums/3-side-stick.mp3': require('!buffer-loader!./assets/drums/3-side-stick.mp3'), + 'drums/4-crash-cymbal.mp3': require('!buffer-loader!./assets/drums/4-crash-cymbal.mp3'), + 'drums/5-open-hi-hat.mp3': require('!buffer-loader!./assets/drums/5-open-hi-hat.mp3'), + 'drums/6-closed-hi-hat.mp3': require('!buffer-loader!./assets/drums/6-closed-hi-hat.mp3'), + 'drums/7-tambourine.mp3': require('!buffer-loader!./assets/drums/7-tambourine.mp3'), + 'drums/8-hand-clap.mp3': require('!buffer-loader!./assets/drums/8-hand-clap.mp3'), + 'drums/9-claves.mp3': require('!buffer-loader!./assets/drums/9-claves.mp3'), + 'drums/10-wood-block.mp3': require('!buffer-loader!./assets/drums/10-wood-block.mp3'), + 'drums/11-cowbell.mp3': require('!buffer-loader!./assets/drums/11-cowbell.mp3'), + 'drums/12-triangle.mp3': require('!buffer-loader!./assets/drums/12-triangle.mp3'), + 'drums/13-bongo.mp3': require('!buffer-loader!./assets/drums/13-bongo.mp3'), + 'drums/14-conga.mp3': require('!buffer-loader!./assets/drums/14-conga.mp3'), + 'drums/15-cabasa.mp3': require('!buffer-loader!./assets/drums/15-cabasa.mp3'), + 'drums/16-guiro.mp3': require('!buffer-loader!./assets/drums/16-guiro.mp3'), + 'drums/17-vibraslap.mp3': require('!buffer-loader!./assets/drums/17-vibraslap.mp3'), + 'drums/18-cuica.mp3': require('!buffer-loader!./assets/drums/18-cuica.mp3'), + 'instruments/1-piano/24.mp3': require('!buffer-loader!./assets/instruments/1-piano/24.mp3'), + 'instruments/1-piano/36.mp3': require('!buffer-loader!./assets/instruments/1-piano/36.mp3'), + 'instruments/1-piano/48.mp3': require('!buffer-loader!./assets/instruments/1-piano/48.mp3'), + 'instruments/1-piano/60.mp3': require('!buffer-loader!./assets/instruments/1-piano/60.mp3'), + 'instruments/1-piano/72.mp3': require('!buffer-loader!./assets/instruments/1-piano/72.mp3'), + 'instruments/1-piano/84.mp3': require('!buffer-loader!./assets/instruments/1-piano/84.mp3'), + 'instruments/1-piano/96.mp3': require('!buffer-loader!./assets/instruments/1-piano/96.mp3'), + 'instruments/1-piano/108.mp3': require('!buffer-loader!./assets/instruments/1-piano/108.mp3'), + 'instruments/2-electric-piano/60.mp3': require('!buffer-loader!./assets/instruments/2-electric-piano/60.mp3'), + 'instruments/3-organ/60.mp3': require('!buffer-loader!./assets/instruments/3-organ/60.mp3'), + 'instruments/4-guitar/60.mp3': require('!buffer-loader!./assets/instruments/4-guitar/60.mp3'), + 'instruments/5-electric-guitar/60.mp3': require('!buffer-loader!./assets/instruments/5-electric-guitar/60.mp3'), + 'instruments/6-bass/36.mp3': require('!buffer-loader!./assets/instruments/6-bass/36.mp3'), + 'instruments/6-bass/48.mp3': require('!buffer-loader!./assets/instruments/6-bass/48.mp3'), + 'instruments/7-pizzicato/60.mp3': require('!buffer-loader!./assets/instruments/7-pizzicato/60.mp3'), + 'instruments/8-cello/36.mp3': require('!buffer-loader!./assets/instruments/8-cello/36.mp3'), + 'instruments/8-cello/48.mp3': require('!buffer-loader!./assets/instruments/8-cello/48.mp3'), + 'instruments/8-cello/60.mp3': require('!buffer-loader!./assets/instruments/8-cello/60.mp3'), + 'instruments/9-trombone/36.mp3': require('!buffer-loader!./assets/instruments/9-trombone/36.mp3'), + 'instruments/9-trombone/48.mp3': require('!buffer-loader!./assets/instruments/9-trombone/48.mp3'), + 'instruments/9-trombone/60.mp3': require('!buffer-loader!./assets/instruments/9-trombone/60.mp3'), + 'instruments/10-clarinet/48.mp3': require('!buffer-loader!./assets/instruments/10-clarinet/48.mp3'), + 'instruments/10-clarinet/60.mp3': require('!buffer-loader!./assets/instruments/10-clarinet/60.mp3'), + 'instruments/11-saxophone/36.mp3': require('!buffer-loader!./assets/instruments/11-saxophone/36.mp3'), + 'instruments/11-saxophone/60.mp3': require('!buffer-loader!./assets/instruments/11-saxophone/60.mp3'), + 'instruments/11-saxophone/84.mp3': require('!buffer-loader!./assets/instruments/11-saxophone/84.mp3'), + 'instruments/12-flute/60.mp3': require('!buffer-loader!./assets/instruments/12-flute/60.mp3'), + 'instruments/12-flute/72.mp3': require('!buffer-loader!./assets/instruments/12-flute/72.mp3'), + 'instruments/13-wooden-flute/60.mp3': require('!buffer-loader!./assets/instruments/13-wooden-flute/60.mp3'), + 'instruments/13-wooden-flute/72.mp3': require('!buffer-loader!./assets/instruments/13-wooden-flute/72.mp3'), + 'instruments/14-bassoon/36.mp3': require('!buffer-loader!./assets/instruments/14-bassoon/36.mp3'), + 'instruments/14-bassoon/48.mp3': require('!buffer-loader!./assets/instruments/14-bassoon/48.mp3'), + 'instruments/14-bassoon/60.mp3': require('!buffer-loader!./assets/instruments/14-bassoon/60.mp3'), + 'instruments/15-choir/48.mp3': require('!buffer-loader!./assets/instruments/15-choir/48.mp3'), + 'instruments/15-choir/60.mp3': require('!buffer-loader!./assets/instruments/15-choir/60.mp3'), + 'instruments/15-choir/72.mp3': require('!buffer-loader!./assets/instruments/15-choir/72.mp3'), + 'instruments/16-vibraphone/60.mp3': require('!buffer-loader!./assets/instruments/16-vibraphone/60.mp3'), + 'instruments/16-vibraphone/72.mp3': require('!buffer-loader!./assets/instruments/16-vibraphone/72.mp3'), + 'instruments/17-music-box/60.mp3': require('!buffer-loader!./assets/instruments/17-music-box/60.mp3'), + 'instruments/18-steel-drum/60.mp3': require('!buffer-loader!./assets/instruments/18-steel-drum/60.mp3'), + 'instruments/19-marimba/60.mp3': require('!buffer-loader!./assets/instruments/19-marimba/60.mp3'), + 'instruments/20-synth-lead/60.mp3': require('!buffer-loader!./assets/instruments/20-synth-lead/60.mp3'), + 'instruments/21-synth-pad/60.mp3': require('!buffer-loader!./assets/instruments/21-synth-pad/60.mp3') +}; diff --git a/webpack.config.js b/webpack.config.js index e9eae32db..394b6e2a2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,6 +22,10 @@ const base = { query: { presets: ['es2015'] } + }, + { + test: /\.mp3$/, + loader: 'file-loader' }] }, plugins: process.env.NODE_ENV === 'production' ? [ @@ -62,13 +66,7 @@ module.exports = [ output: { libraryTarget: 'commonjs2', path: path.resolve('dist', 'node') - }, - plugins: base.plugins.concat([ - new CopyWebpackPlugin([{ - from: './src/extensions/scratch3_music/assets', - to: 'assets/scratch3_music' - }]) - ]) + } }), // Playground defaultsDeep({}, base, {