scratch-audio
Version:
audio engine for scratch 3.0
1,421 lines (1,195 loc) • 109 kB
JavaScript
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./node_modules/events/events.js":
/*!***************************************!*\
!*** ./node_modules/events/events.js ***!
\***************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var R = typeof Reflect === 'object' ? Reflect : null
var ReflectApply = R && typeof R.apply === 'function'
? R.apply
: function ReflectApply(target, receiver, args) {
return Function.prototype.apply.call(target, receiver, args);
}
var ReflectOwnKeys
if (R && typeof R.ownKeys === 'function') {
ReflectOwnKeys = R.ownKeys
} else if (Object.getOwnPropertySymbols) {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target)
.concat(Object.getOwnPropertySymbols(target));
};
} else {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target);
};
}
function ProcessEmitWarning(warning) {
if (console && console.warn) console.warn(warning);
}
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
return value !== value;
}
function EventEmitter() {
EventEmitter.init.call(this);
}
module.exports = EventEmitter;
module.exports.once = once;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
var defaultMaxListeners = 10;
function checkListener(listener) {
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
}
}
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
}
defaultMaxListeners = arg;
}
});
EventEmitter.init = function() {
if (this._events === undefined ||
this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
}
this._maxListeners = n;
return this;
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this);
};
EventEmitter.prototype.emit = function emit(type) {
var args = [];
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
var doError = (type === 'error');
var events = this._events;
if (events !== undefined)
doError = (doError && events.error === undefined);
else if (!doError)
return false;
// If there is no 'error' event listener then throw.
if (doError) {
var er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
// At least give some kind of context to the user
var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));
err.context = er;
throw err; // Unhandled 'error' event
}
var handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
ReflectApply(handler, this, args);
} else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
ReflectApply(listeners[i], this, args);
}
return true;
};
function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
checkListener(listener);
events = target._events;
if (events === undefined) {
events = target._events = Object.create(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
}
existing = events[type];
}
if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
var w = new Error('Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + String(type) + ' listeners ' +
'added. Use emitter.setMaxListeners() to ' +
'increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
ProcessEmitWarning(w);
}
}
return target;
}
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments);
}
}
function _onceWrap(target, type, listener) {
var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
var wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
}
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener));
return this;
};
EventEmitter.prototype.prependOnceListener =
function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
// Emits a 'removeListener' event if and only if the listener was removed.
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i, originalListener;
checkListener(listener);
events = this._events;
if (events === undefined)
return this;
list = events[type];
if (list === undefined)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
position = -1;
for (i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
originalListener = list[i].listener;
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== undefined)
this.emit('removeListener', type, originalListener || listener);
}
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
var listeners, events, i;
events = this._events;
if (events === undefined)
return this;
// not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (arguments.length === 0) {
this._events = Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else
delete events[type];
}
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
var keys = Object.keys(events);
var key;
for (i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = Object.create(null);
this._eventsCount = 0;
return this;
}
listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
function _listeners(target, type, unwrap) {
var events = target._events;
if (events === undefined)
return [];
var evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
} else {
return listenerCount.call(emitter, type);
}
};
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
var events = this._events;
if (events !== undefined) {
var evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener !== undefined) {
return evlistener.length;
}
}
return 0;
}
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};
function arrayClone(arr, n) {
var copy = new Array(n);
for (var i = 0; i < n; ++i)
copy[i] = arr[i];
return copy;
}
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1];
list.pop();
}
function unwrapListeners(arr) {
var ret = new Array(arr.length);
for (var i = 0; i < ret.length; ++i) {
ret[i] = arr[i].listener || arr[i];
}
return ret;
}
function once(emitter, name) {
return new Promise(function (resolve, reject) {
function errorListener(err) {
emitter.removeListener(name, resolver);
reject(err);
}
function resolver() {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener('error', errorListener);
}
resolve([].slice.call(arguments));
};
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
if (name !== 'error') {
addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });
}
});
}
function addErrorHandlerIfEventEmitter(emitter, handler, flags) {
if (typeof emitter.on === 'function') {
eventTargetAgnosticAddListener(emitter, 'error', handler, flags);
}
}
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
if (typeof emitter.on === 'function') {
if (flags.once) {
emitter.once(name, listener);
} else {
emitter.on(name, listener);
}
} else if (typeof emitter.addEventListener === 'function') {
// EventTarget does not have `error` event semantics like Node
// EventEmitters, we do not listen for `error` events here.
emitter.addEventListener(name, function wrapListener(arg) {
// IE does not have builtin `{ once: true }` support so we
// have to do it manually.
if (flags.once) {
emitter.removeEventListener(name, wrapListener);
}
listener(arg);
});
} else {
throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter);
}
}
/***/ }),
/***/ "./src/ADPCMSoundDecoder.js":
/*!**********************************!*\
!*** ./src/ADPCMSoundDecoder.js ***!
\**********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var ArrayBufferStream = __webpack_require__(/*! ./ArrayBufferStream */ "./src/ArrayBufferStream.js");
var log = __webpack_require__(/*! ./log */ "./src/log.js");
/**
* Data used by the decompression algorithm
* @type {Array}
*/
var STEP_TABLE = [7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767];
/**
* Data used by the decompression algorithm
* @type {Array}
*/
var INDEX_TABLE = [-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8];
var _deltaTable = null;
/**
* Build a table of deltas from the 89 possible steps and 16 codes.
* @return {Array<number>} computed delta values
*/
var deltaTable = function deltaTable() {
if (_deltaTable === null) {
var NUM_STEPS = STEP_TABLE.length;
var NUM_INDICES = INDEX_TABLE.length;
_deltaTable = new Array(NUM_STEPS * NUM_INDICES).fill(0);
var i = 0;
for (var index = 0; index < NUM_STEPS; index++) {
for (var code = 0; code < NUM_INDICES; code++) {
var step = STEP_TABLE[index];
var delta = 0;
if (code & 4) delta += step;
if (code & 2) delta += step >> 1;
if (code & 1) delta += step >> 2;
delta += step >> 3;
_deltaTable[i++] = code & 8 ? -delta : delta;
}
}
}
return _deltaTable;
};
/**
* 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
*/
var ADPCMSoundDecoder = function () {
/**
* @param {AudioContext} audioContext - a webAudio context
* @constructor
*/
function ADPCMSoundDecoder(audioContext) {
_classCallCheck(this, ADPCMSoundDecoder);
this.audioContext = audioContext;
}
/**
* Data used by the decompression algorithm
* @type {Array}
*/
_createClass(ADPCMSoundDecoder, [{
key: 'decode',
/**
* 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 {Promise.<AudioBuffer>} the decoded audio buffer
*/
value: function decode(audioData) {
var _this = this;
return new Promise(function (resolve, reject) {
var stream = new ArrayBufferStream(audioData);
var riffStr = stream.readUint8String(4);
if (riffStr !== 'RIFF') {
log.warn('incorrect adpcm wav header');
reject(new Error('incorrect adpcm wav header'));
}
var lengthInHeader = stream.readInt32();
if (lengthInHeader + 8 !== audioData.byteLength) {
log.warn('adpcm wav length in header: ' + lengthInHeader + ' is incorrect');
}
var wavStr = stream.readUint8String(4);
if (wavStr !== 'WAVE') {
log.warn('incorrect adpcm wav header');
reject(new Error('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 compressedData = _this.extractChunk('data', stream);
var sampleCount = _this.numberOfSamples(compressedData, _this.adpcmBlockSize);
var buffer = _this.audioContext.createBuffer(1, sampleCount, _this.samplesPerSecond);
_this.imaDecompress(compressedData, _this.adpcmBlockSize, buffer.getChannelData(0));
resolve(buffer);
});
}
/**
* Extract a chunk of audio data from the stream, consisting of a set of audio data bytes
* @param {string} chunkType - the type of chunk to extract. 'data' or 'fmt' (format)
* @param {ArrayBufferStream} stream - an stream containing the audio data
* @return {ArrayBufferStream} a stream containing the desired chunk
*/
}, {
key: 'extractChunk',
value: function extractChunk(chunkType, stream) {
stream.position = 12;
while (stream.position < stream.getLength() - 8) {
var typeStr = stream.readUint8String(4);
var chunkSize = stream.readInt32();
if (typeStr === chunkType) {
var chunk = stream.extract(chunkSize);
return chunk;
}
stream.position += chunkSize;
}
}
/**
* Count the exact number of samples in the compressed data.
* @param {ArrayBufferStream} compressedData - the compressed data
* @param {number} blockSize - size of each block in the data in bytes
* @return {number} number of samples in the compressed data
*/
}, {
key: 'numberOfSamples',
value: function numberOfSamples(compressedData, blockSize) {
if (!compressedData) return 0;
compressedData.position = 0;
var available = compressedData.getBytesAvailable();
var blocks = available / blockSize | 0;
// Number of samples in full blocks.
var fullBlocks = blocks * (2 * (blockSize - 4)) + 1;
// Number of samples in the last incomplete block. 0 if the last block
// is full.
var subBlock = Math.max(available % blockSize - 4, 0) * 2;
// 1 if the last block is incomplete. 0 if it is complete.
var incompleteBlock = Math.min(available % blockSize, 1);
return fullBlocks + subBlock + incompleteBlock;
}
/**
* Decompress sample data using the IMA ADPCM algorithm.
* Note: Handles only one channel, 4-bits per sample.
* @param {ArrayBufferStream} compressedData - a stream of compressed audio samples
* @param {number} blockSize - the number of bytes in the stream
* @param {Float32Array} out - the uncompressed audio samples
*/
}, {
key: 'imaDecompress',
value: function imaDecompress(compressedData, blockSize, out) {
var sample = void 0;
var code = void 0;
var delta = void 0;
var index = 0;
var lastByte = -1; // -1 indicates that there is no saved lastByte
// Bail and return no samples if we have no data
if (!compressedData) return;
compressedData.position = 0;
var size = out.length;
var samplesAfterBlockHeader = (blockSize - 4) * 2;
var DELTA_TABLE = deltaTable();
var i = 0;
while (i < size) {
// read block header
sample = compressedData.readInt16();
index = compressedData.readUint8();
compressedData.position++; // skip extra header byte
if (index > 88) index = 88;
out[i++] = sample / 32768;
var blockLength = Math.min(samplesAfterBlockHeader, size - i);
var blockStart = i;
while (i - blockStart < blockLength) {
// read 4-bit code and compute delta from previous sample
lastByte = compressedData.readUint8();
code = lastByte & 0xF;
delta = DELTA_TABLE[index * 16 + code];
// compute next index
index += INDEX_TABLE[code];
if (index > 88) index = 88;else if (index < 0) index = 0;
// compute and output sample
sample += delta;
if (sample > 32767) sample = 32767;else if (sample < -32768) sample = -32768;
out[i++] = sample / 32768;
// use 4-bit code from lastByte and compute delta from previous
// sample
code = lastByte >> 4 & 0xF;
delta = DELTA_TABLE[index * 16 + code];
// compute next index
index += INDEX_TABLE[code];
if (index > 88) index = 88;else if (index < 0) index = 0;
// compute and output sample
sample += delta;
if (sample > 32767) sample = 32767;else if (sample < -32768) sample = -32768;
out[i++] = sample / 32768;
}
}
}
}], [{
key: 'STEP_TABLE',
get: function get() {
return STEP_TABLE;
}
/**
* Data used by the decompression algorithm
* @type {Array}
*/
}, {
key: 'INDEX_TABLE',
get: function get() {
return INDEX_TABLE;
}
}]);
return ADPCMSoundDecoder;
}();
module.exports = ADPCMSoundDecoder;
/***/ }),
/***/ "./src/ArrayBufferStream.js":
/*!**********************************!*\
!*** ./src/ArrayBufferStream.js ***!
\**********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var ArrayBufferStream = function () {
/**
* ArrayBufferStream wraps the built-in javascript ArrayBuffer, adding the ability to access
* data in it like a stream, tracking its position.
* You can request to read a value from the front of the array, and it will keep track of the position
* within the byte array, so that successive reads are consecutive.
* The available types to read include:
* Uint8, Uint8String, Int16, Uint16, Int32, Uint32
* @param {ArrayBuffer} arrayBuffer - array to use as a stream
* @param {number} start - the start position in the raw buffer. position
* will be relative to the start value.
* @param {number} end - the end position in the raw buffer. length and
* bytes available will be relative to the end value.
* @param {ArrayBufferStream} parent - if passed reuses the parent's
* internal objects
* @constructor
*/
function ArrayBufferStream(arrayBuffer) {
var start = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arrayBuffer.byteLength;
var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
_ref$_uint8View = _ref._uint8View,
_uint8View = _ref$_uint8View === undefined ? new Uint8Array(arrayBuffer) : _ref$_uint8View;
_classCallCheck(this, ArrayBufferStream);
/**
* Raw data buffer for stream to read.
* @type {ArrayBufferStream}
*/
this.arrayBuffer = arrayBuffer;
/**
* Start position in arrayBuffer. Read values are relative to the start
* in the arrayBuffer.
* @type {number}
*/
this.start = start;
/**
* End position in arrayBuffer. Length and bytes available are relative
* to the start, end, and _position in the arrayBuffer;
* @type {number};
*/
this.end = end;
/**
* Cached Uint8Array view of the arrayBuffer. Heavily used for reading
* Uint8 values and Strings from the stream.
* @type {Uint8Array}
*/
this._uint8View = _uint8View;
/**
* Raw position in the arrayBuffer relative to the beginning of the
* arrayBuffer.
* @type {number}
*/
this._position = start;
}
/**
* Return a new ArrayBufferStream that is a slice of the existing one
* @param {number} length - the number of bytes of extract
* @return {ArrayBufferStream} the extracted stream
*/
_createClass(ArrayBufferStream, [{
key: 'extract',
value: function extract(length) {
return new ArrayBufferStream(this.arrayBuffer, this._position, this._position + length, this);
}
/**
* @return {number} the length of the stream in bytes
*/
}, {
key: 'getLength',
value: function getLength() {
return this.end - this.start;
}
/**
* @return {number} the number of bytes available after the current position in the stream
*/
}, {
key: 'getBytesAvailable',
value: function getBytesAvailable() {
return this.end - this._position;
}
/**
* Position relative to the start value in the arrayBuffer of this
* ArrayBufferStream.
* @type {number}
*/
}, {
key: 'readUint8',
/**
* Read an unsigned 8 bit integer from the stream
* @return {number} the next 8 bit integer in the stream
*/
value: function readUint8() {
var val = this._uint8View[this._position];
this._position += 1;
return val;
}
/**
* Read a sequence of bytes of the given length and convert to a string.
* This is a convenience method for use with short strings.
* @param {number} length - the number of bytes to convert
* @return {string} a String made by concatenating the chars in the input
*/
}, {
key: 'readUint8String',
value: function readUint8String(length) {
var arr = this._uint8View;
var str = '';
var end = this._position + length;
for (var i = this._position; i < end; i++) {
str += String.fromCharCode(arr[i]);
}
this._position += length;
return str;
}
/**
* Read a 16 bit integer from the stream
* @return {number} the next 16 bit integer in the stream
*/
}, {
key: 'readInt16',
value: function readInt16() {
var val = new Int16Array(this.arrayBuffer, this._position, 1)[0];
this._position += 2; // one 16 bit int is 2 bytes
return val;
}
/**
* Read an unsigned 16 bit integer from the stream
* @return {number} the next unsigned 16 bit integer in the stream
*/
}, {
key: 'readUint16',
value: function readUint16() {
var val = new Uint16Array(this.arrayBuffer, this._position, 1)[0];
this._position += 2; // one 16 bit int is 2 bytes
return val;
}
/**
* Read a 32 bit integer from the stream
* @return {number} the next 32 bit integer in the stream
*/
}, {
key: 'readInt32',
value: function readInt32() {
var val = void 0;
if (this._position % 4 === 0) {
val = new Int32Array(this.arrayBuffer, this._position, 1)[0];
} else {
// Cannot read Int32 directly out because offset is not multiple of 4
// Need to slice out the values first
val = new Int32Array(this.arrayBuffer.slice(this._position, this._position + 4))[0];
}
this._position += 4; // one 32 bit int is 4 bytes
return val;
}
/**
* Read an unsigned 32 bit integer from the stream
* @return {number} the next unsigned 32 bit integer in the stream
*/
}, {
key: 'readUint32',
value: function readUint32() {
var val = new Uint32Array(this.arrayBuffer, this._position, 1)[0];
this._position += 4; // one 32 bit int is 4 bytes
return val;
}
}, {
key: 'position',
get: function get() {
return this._position - this.start;
}
/**
* Set the position to read from in the arrayBuffer.
* @type {number}
* @param {number} value - new value to set position to
*/
,
set: function set(value) {
this._position = value + this.start;
}
}]);
return ArrayBufferStream;
}();
module.exports = ArrayBufferStream;
/***/ }),
/***/ "./src/AudioEngine.js":
/*!****************************!*\
!*** ./src/AudioEngine.js ***!
\****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var StartAudioContext = __webpack_require__(/*! ./StartAudioContext */ "./src/StartAudioContext.js");
var AudioContext = __webpack_require__(/*! audio-context */ "audio-context");
var log = __webpack_require__(/*! ./log */ "./src/log.js");
var uid = __webpack_require__(/*! ./uid */ "./src/uid.js");
var ADPCMSoundDecoder = __webpack_require__(/*! ./ADPCMSoundDecoder */ "./src/ADPCMSoundDecoder.js");
var Loudness = __webpack_require__(/*! ./Loudness */ "./src/Loudness.js");
var SoundPlayer = __webpack_require__(/*! ./SoundPlayer */ "./src/SoundPlayer.js");
var EffectChain = __webpack_require__(/*! ./effects/EffectChain */ "./src/effects/EffectChain.js");
var PanEffect = __webpack_require__(/*! ./effects/PanEffect */ "./src/effects/PanEffect.js");
var PitchEffect = __webpack_require__(/*! ./effects/PitchEffect */ "./src/effects/PitchEffect.js");
var VolumeEffect = __webpack_require__(/*! ./effects/VolumeEffect */ "./src/effects/VolumeEffect.js");
var SoundBank = __webpack_require__(/*! ./SoundBank */ "./src/SoundBank.js");
/**
* Wrapper to ensure that audioContext.decodeAudioData is a promise
* @param {object} audioContext The current AudioContext
* @param {ArrayBuffer} buffer Audio data buffer to decode
* @return {Promise} A promise that resolves to the decoded audio
*/
var decodeAudioData = function decodeAudioData(audioContext, buffer) {
// Check for newer promise-based API
if (audioContext.decodeAudioData.length === 1) {
return audioContext.decodeAudioData(buffer);
}
// Fall back to callback API
return new Promise(function (resolve, reject) {
audioContext.decodeAudioData(buffer, function (decodedAudio) {
return resolve(decodedAudio);
}, function (error) {
return reject(error);
});
});
};
/**
* There is a single instance of the AudioEngine. It handles global audio
* properties and effects, loads all the audio buffers for sounds belonging to
* sprites.
*/
var AudioEngine = function () {
function AudioEngine() {
var audioContext = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new AudioContext();
_classCallCheck(this, AudioEngine);
/**
* AudioContext to play and manipulate sounds with a graph of source
* and effect nodes.
* @type {AudioContext}
*/
this.audioContext = audioContext;
StartAudioContext(this.audioContext);
/**
* Master GainNode that all sounds plays through. Changing this node
* will change the volume for all sounds.
* @type {GainNode}
*/
this.inputNode = this.audioContext.createGain();
this.inputNode.connect(this.audioContext.destination);
/**
* a map of soundIds to audio buffers, holding sounds for all sprites
* @type {Object<String, ArrayBuffer>}
*/
this.audioBuffers = {};
/**
* A Loudness detector.
* @type {Loudness}
*/
this.loudness = null;
/**
* Array of effects applied in order, left to right,
* Left is closest to input, Right is closest to output
*/
this.effects = [PanEffect, PitchEffect, VolumeEffect];
}
/**
* Current time in the AudioEngine.
* @type {number}
*/
_createClass(AudioEngine, [{
key: 'getInputNode',
/**
* Get the input node.
* @return {AudioNode} - audio node that is the input for this effect
*/
value: function getInputNode() {
return this.inputNode;
}
/**
* Decode a sound, decompressing it into audio samples.
* @param {object} sound - an object containing audio data and metadata for
* a sound
* @param {Buffer} sound.data - sound data loaded from scratch-storage
* @returns {?Promise} - a promise which will resolve to the sound id and
* buffer if decoded
*/
}, {
key: '_decodeSound',
value: function _decodeSound(sound) {
var _this = this;
// Make a copy of the buffer because decoding detaches the original
// buffer
var bufferCopy1 = sound.data.buffer.slice(0);
// todo: multiple decodings of the same buffer create duplicate decoded
// copies in audioBuffers. Create a hash id of the buffer or deprecate
// audioBuffers to avoid memory issues for large audio buffers.
var soundId = uid();
// Attempt to decode the sound using the browser's native audio data
// decoder If that fails, attempt to decode as ADPCM
var decoding = decodeAudioData(this.audioContext, bufferCopy1).catch(function () {
// If the file is empty, create an empty sound
if (sound.data.length === 0) {
return _this._emptySound();
}
// The audio context failed to parse the sound data
// we gave it, so try to decode as 'adpcm'
// First we need to create another copy of our original data
var bufferCopy2 = sound.data.buffer.slice(0);
// Try decoding as adpcm
return new ADPCMSoundDecoder(_this.audioContext).decode(bufferCopy2).catch(function () {
return _this._emptySound();
});
}).then(function (buffer) {
return [soundId, buffer];
}, function (error) {
log.warn('audio data could not be decoded', error);
});
return decoding;
}
/**
* An empty sound buffer, for use when we are unable to decode a sound file.
* @returns {AudioBuffer} - an empty audio buffer.
*/
}, {
key: '_emptySound',
value: function _emptySound() {
return this.audioContext.createBuffer(1, 1, this.audioContext.sampleRate);
}
/**
* Decode a sound, decompressing it into audio samples.
*
* Store a reference to it the sound in the audioBuffers dictionary,
* indexed by soundId.
*
* @param {object} sound - an object containing audio data and metadata for
* a sound
* @param {Buffer} sound.data - sound data loaded from scratch-storage
* @returns {?Promise} - a promise which will resolve to the sound id
*/
}, {
key: 'decodeSound',
value: function decodeSound(sound) {
var _this2 = this;
return this._decodeSound(sound).then(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
id = _ref2[0],
buffer = _ref2[1];
_this2.audioBuffers[id] = buffer;
return id;
});
}
/**
* Decode a sound, decompressing it into audio samples.
*
* Create a SoundPlayer instance that can be used to play the sound and
* stop and fade out playback.
*
* @param {object} sound - an object containing audio data and metadata for
* a sound
* @param {Buffer} sound.data - sound data loaded from scratch-storage
* @returns {?Promise} - a promise which will resolve to the buffer
*/
}, {
key: 'decodeSoundPlayer',
value: function decodeSoundPlayer(sound) {
var _this3 = this;
return this._decodeSound(sound).then(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 2),
id = _ref4[0],
buffer = _ref4[1];
return new SoundPlayer(_this3, { id: id, buffer: buffer });
});
}
/**
* Get the current loudness of sound received by the microphone.
* Sound is measured in RMS and smoothed.
* @return {number} loudness scaled 0 to 100
*/
}, {
key: 'getLoudness',
value: function getLoudness() {
// The microphone has not been set up, so try to connect to it
if (!this.loudness) {
this.loudness = new Loudness(this.audioContext);
}
return this.loudness.getLoudness();
}
/**
* Create an effect chain.
* @returns {EffectChain} chain of effects defined by this AudioEngine
*/
}, {
key: 'createEffectChain',
value: function createEffectChain() {
var effects = new EffectChain(this, this.effects);
effects.connect(this);
return effects;
}
/**
* Create a sound bank and effect chain.
* @returns {SoundBank} a sound bank configured with an effect chain
* defined by this AudioEngine
*/
}, {
key: 'createBank',
value: function createBank() {
return new SoundBank(this, this.createEffectChain());
}
}, {
key: 'currentTime',
get: function get() {
return this.audioContext.currentTime;
}
/**
* Names of the audio effects.
* @enum {string}
*/
}, {
key: 'EFFECT_NAMES',
get: function get() {
return {
pitch: 'pitch',
pan: 'pan'
};
}
/**
* A short duration to transition audio prarameters.
*
* Used as a time constant for exponential transitions. A general value
* must be large enough that it does not cute off lower frequency, or bass,
* sounds. Human hearing lower limit is ~20Hz making a safe value 25
* milliseconds or 0.025 seconds, where half of a 20Hz wave will play along
* with the DECAY. Higher frequencies will play multiple waves during the
* same amount of time and avoid clipping.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioParam/setTargetAtTime}
* @const {number}
*/
}, {
key: 'DECAY_DURATION',
get: function get() {
return 0.025;
}
/**
* Some environments cannot smoothly change parameters immediately, provide