UNPKG

scratch-audio

Version:
1,421 lines (1,195 loc) 109 kB
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