diff --git a/src/ADPCMSoundLoader.js b/src/ADPCMSoundLoader.js new file mode 100644 index 0000000..b4ea9ba --- /dev/null +++ b/src/ADPCMSoundLoader.js @@ -0,0 +1,145 @@ +/* + +ADPCMSoundLoader loads wav files that have been compressed with the ADPCM format + +based on code from Scratch-Flash: +https://github.com/LLK/scratch-flash/blob/master/src/sound/WAVFile.as + +*/ + +var ArrayBufferStream = require('./ArrayBufferStream'); +var Tone = require('tone'); +var log = require('./log'); + +function ADPCMSoundLoader (url) { + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + + request.onload = function () { + var audioData = request.response; + var stream = new ArrayBufferStream(audioData); + + var riffStr = stream.readUint8String(4); + if (riffStr != 'RIFF') { + log.warn('incorrect adpcm wav header'); + } + + var lengthInHeader = stream.readInt32(); + if ((lengthInHeader + 8) != audioData.byteLength) { + log.warn('adpcm wav length in header: ' + length + 'is incorrect'); + } + + var wavStr = stream.readUint8String(4); + if (wavStr != 'WAVE') { + log.warn('incorrect adpcm wav header'); + } + + 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 samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize); + var buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond); + + // todo: optimize this? + for (var i=0; i 88) index = 88; + out.push(sample); + } else { + // read 4-bit code and compute delta from previous sample + if (lastByte < 0) { + if (compressedData.getBytesAvailable() == 0) break; + lastByte = compressedData.readUint8(); + code = lastByte & 0xF; + } else { + code = (lastByte >> 4) & 0xF; + lastByte = -1; + } + step = this.stepTable[index]; + delta = 0; + if (code & 4) delta += step; + if (code & 2) delta += step >> 1; + if (code & 1) delta += step >> 2; + delta += step >> 3; + // compute next index + index += this.indexTable[code]; + if (index > 88) index = 88; + if (index < 0) index = 0; + // compute and output sample + sample += (code & 8) ? -delta : delta; + if (sample > 32767) sample = 32767; + if (sample < -32768) sample = -32768; + out.push(sample); + } + } + var samples = Int16Array.from(out); + return samples; +}; + +module.exports = ADPCMSoundLoader; diff --git a/src/ArrayBufferStream.js b/src/ArrayBufferStream.js new file mode 100644 index 0000000..df50782 --- /dev/null +++ b/src/ArrayBufferStream.js @@ -0,0 +1,71 @@ +/* + +ArrayBufferStream wraps the built-in javascript ArrayBuffer, adding the ability to access +data in it like a stream. You can request to read a value from the front of the array, +such as an 8 bit unsigned int, a 16 bit int, etc, and it will keep track of the position +within the byte array, so that successive reads are consecutive. + +*/ +function ArrayBufferStream (arrayBuffer) { + this.arrayBuffer = arrayBuffer; + this.position = 0; +} + +// return a new ArrayBufferStream that is a slice of the existing one +ArrayBufferStream.prototype.extract = function (length) { + var slicedArrayBuffer = this.arrayBuffer.slice(this.position, this.position+length); + var newStream = new ArrayBufferStream(slicedArrayBuffer); + return newStream; +}; + +ArrayBufferStream.prototype.getLength = function () { + return this.arrayBuffer.byteLength; +}; + +ArrayBufferStream.prototype.getBytesAvailable = function () { + return (this.arrayBuffer.byteLength - this.position); +}; + +ArrayBufferStream.prototype.readUint8 = function () { + var val = new Uint8Array(this.arrayBuffer, this.position, 1)[0]; + this.position += 1; + return val; +}; + +// convert a sequence of bytes of the given length to a string +// for small length strings only +ArrayBufferStream.prototype.readUint8String = function (length) { + var arr = new Uint8Array(this.arrayBuffer, this.position, length); + this.position += length; + var str = ''; + for (var i=0; i