From c5daccef7c3691aa949ac65d653f068289658c14 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 22 Mar 2017 18:08:44 -0400 Subject: [PATCH 1/4] Rename to ADPCMSoundDecoder, and only decode Scratch Storage now handles asset loading, so this class is now just the decoder --- ...PCMSoundLoader.js => ADPCMSoundDecoder.js} | 100 ++++++++---------- src/index.js | 2 +- 2 files changed, 45 insertions(+), 57 deletions(-) rename src/{ADPCMSoundLoader.js => ADPCMSoundDecoder.js} (59%) diff --git a/src/ADPCMSoundLoader.js b/src/ADPCMSoundDecoder.js similarity index 59% rename from src/ADPCMSoundLoader.js rename to src/ADPCMSoundDecoder.js index 3b27dd9..63d4baf 100644 --- a/src/ADPCMSoundLoader.js +++ b/src/ADPCMSoundDecoder.js @@ -3,78 +3,66 @@ var Tone = require('tone'); var log = require('./log'); /** - * Load wav audio files that have been compressed with the ADPCM format. + * Decode wav audio files that have been compressed with the ADPCM format. * This is necessary because, while web browsers have native decoders for many audio * formats, ADPCM is a non-standard format used by Scratch since its early days. * This decoder is based on code from Scratch-Flash: * https://github.com/LLK/scratch-flash/blob/master/src/sound/WAVFile.as * @constructor */ -function ADPCMSoundLoader () { +function ADPCMSoundDecoder () { } /** - * Load an ADPCM sound file from a URL, decode it, and return a promise - * with the audio buffer. - * @param {string} url - a url pointing to the ADPCM wav file + * Decode an ADPCM sound stored in an ArrayBuffer and return a promise + * with the decoded audio buffer. + * @param {ArrayBuffer} audioData - containing ADPCM encoded wav audio * @return {Tone.Buffer} */ -ADPCMSoundLoader.prototype.load = function (url) { +ADPCMSoundDecoder.prototype.decode = function (audioData) { return new Promise(function (resolve, reject) { + var stream = new ArrayBufferStream(audioData); - var request = new XMLHttpRequest(); - request.open('GET', url, true); - request.responseType = 'arraybuffer'; + var riffStr = stream.readUint8String(4); + if (riffStr != 'RIFF') { + log.warn('incorrect adpcm wav header'); + reject(); + } - request.onload = function () { - var audioData = request.response; - var stream = new ArrayBufferStream(audioData); + var lengthInHeader = stream.readInt32(); + if ((lengthInHeader + 8) != audioData.byteLength) { + log.warn('adpcm wav length in header: ' + lengthInHeader + ' is incorrect'); + } - var riffStr = stream.readUint8String(4); - if (riffStr != 'RIFF') { - log.warn('incorrect adpcm wav header'); - reject(); - } + var wavStr = stream.readUint8String(4); + if (wavStr != 'WAVE') { + log.warn('incorrect adpcm wav header'); + reject(); + } - var lengthInHeader = stream.readInt32(); - if ((lengthInHeader + 8) != audioData.byteLength) { - log.warn('adpcm wav length in header: ' + lengthInHeader + ' is incorrect'); - } + var formatChunk = this.extractChunk('fmt ', stream); + this.encoding = formatChunk.readUint16(); + this.channels = formatChunk.readUint16(); + this.samplesPerSecond = formatChunk.readUint32(); + this.bytesPerSecond = formatChunk.readUint32(); + this.blockAlignment = formatChunk.readUint16(); + this.bitsPerSample = formatChunk.readUint16(); + formatChunk.position += 2; // skip extra header byte count + this.samplesPerBlock = formatChunk.readUint16(); + this.adpcmBlockSize = ((this.samplesPerBlock - 1) / 2) + 4; // block size in bytes - var wavStr = stream.readUint8String(4); - if (wavStr != 'WAVE') { - log.warn('incorrect adpcm wav header'); - reject(); - } + var samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize); - var formatChunk = this.extractChunk('fmt ', stream); - this.encoding = formatChunk.readUint16(); - this.channels = formatChunk.readUint16(); - this.samplesPerSecond = formatChunk.readUint32(); - this.bytesPerSecond = formatChunk.readUint32(); - this.blockAlignment = formatChunk.readUint16(); - this.bitsPerSample = formatChunk.readUint16(); - formatChunk.position += 2; // skip extra header byte count - this.samplesPerBlock = formatChunk.readUint16(); - this.adpcmBlockSize = ((this.samplesPerBlock - 1) / 2) + 4; // block size in bytes + // todo: this line is the only place Tone is used here, should be possible to remove + var buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond); - var samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize); - - // todo: this line is the only place Tone is used here, should be possible to remove - var buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond); - - // todo: optimize this? e.g. replace the divide by storing 1/32768 and multiply? - for (var i=0; i Date: Wed, 22 Mar 2017 18:09:42 -0400 Subject: [PATCH 2/4] Decode sounds in audio engine Remove logic for loading sounds, since that is handled by Scratch Storage --- src/index.js | 54 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/index.js b/src/index.js index f841f29..d3c2cb7 100644 --- a/src/index.js +++ b/src/index.js @@ -56,37 +56,37 @@ function AudioEngine () { } /** - * Load all sounds for a sprite and store them in the audioBuffers dictionary, indexed by md5 - * @param {Object} sounds - an array of objects containing metadata for sound files of a sprite + * Decode a sound, decompressing it into audio samples. + * Store a reference to it the sound in the audioBuffers dictionary, indexed by md5 + * @param {Object} sound - an object containing audio data and metadata for a sound */ -AudioEngine.prototype.loadSounds = function (sounds) { +AudioEngine.prototype.decodeSound = function (sound) { + var storedContext = this; - for (var i=0; i Date: Mon, 27 Mar 2017 11:51:01 -0400 Subject: [PATCH 3/4] Tidy up sound decoding --- src/index.js | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/index.js b/src/index.js index d3c2cb7..68450a5 100644 --- a/src/index.js +++ b/src/index.js @@ -62,34 +62,33 @@ function AudioEngine () { */ AudioEngine.prototype.decodeSound = function (sound) { + var loaderPromise = null; + + switch (sound.format) { + case '': + loaderPromise = Tone.context.decodeAudioData(sound.data.buffer); + break; + case 'adpcm': + loaderPromise = (new ADPCMSoundDecoder()).decode(sound.data.buffer); + break; + default: + return log.warn('unknown sound format', sound.format); + } + var storedContext = this; - // if the format string is empty, assume the sound is a wav file and use the native decoder - if (sound.format === '') { - Tone.context.decodeAudioData(sound.data.buffer).then( - function (decodedAudio) { - storedContext.audioBuffers[sound.md5] = new Tone.Buffer(decodedAudio); - }, - function (error) { - log.warn('audio data could not be decoded', error); - } - ); - } - - // if the format is adpcm, we use a custom decoder - if (sound.format === 'adpcm') { - var loader = new ADPCMSoundDecoder(); - loader.decode(sound.data.buffer).then( - function (decodedAudio) { - storedContext.audioBuffers[sound.md5] = new Tone.Buffer(decodedAudio); - }, - function (error) { - log.warn('adpcm audio data could not be decoded', error); - } - ); - } + loaderPromise.then( + function (decodedAudio) { + storedContext.audioBuffers[sound.md5] = new Tone.Buffer(decodedAudio); + }, + function (error) { + log.warn('audio data could not be decoded', error); + } + ); }; + + /** * Play a note for a duration on an instrument with a volume * @param {number} note - a MIDI note number From 04467673b32c9ca8b5cd1dc63018dc35ff0c4da3 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Fri, 31 Mar 2017 10:50:21 -0400 Subject: [PATCH 4/4] Add stub for loadSounds function with warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … so that the gui can call it without breaking, until it is updated. --- src/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 68450a5..a5db0e7 100644 --- a/src/index.js +++ b/src/index.js @@ -87,7 +87,14 @@ AudioEngine.prototype.decodeSound = function (sound) { ); }; - +/** + * An older version of the AudioEngine had this function to load all sounds + * This is a stub to provide a warning when it is called + * @todo remove this + */ +AudioEngine.prototype.loadSounds = function () { + log.warn('The loadSounds function is no longer available. Please use Scratch Storage.'); +}; /** * Play a note for a duration on an instrument with a volume