UNPKG

@soundtouchjs/audio-worklet

Version:

An ES2015+ AudioWorklet implementation of the SoundTouchJS library

537 lines (508 loc) 16 kB
/* * SoundTouch Audio Worklet v0.1.17 AudioWorklet using the * SoundTouch audio processing library * * Copyright (c) Olli Parviainen * Copyright (c) Ryan Berdeen * Copyright (c) Jakub Fiala * Copyright (c) Steve 'Cutter' Blades * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } var pad = function pad(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; }; var minsSecs = function minsSecs(secs) { var mins = Math.floor(secs / 60); var seconds = secs - mins * 60; return "".concat(mins, ":").concat(pad(parseInt(seconds), 2)); }; var diffSecs = function diffSecs(ms1, ms2) { return (ms2 - ms1) / 1000; }; var createSoundTouchNode = function createSoundTouchNode(audioCtx, AudioWorkletNode, arrayBuffer, options) { var SoundTouchNode = function (_AudioWorkletNode) { _inherits(SoundTouchNode, _AudioWorkletNode); var _super = _createSuper(SoundTouchNode); function SoundTouchNode(context, arrayBuffer, options) { var _this; _classCallCheck(this, SoundTouchNode); _this = _super.call(this, context, 'soundtouch-worklet', options); _this._arrayBuffer = arrayBuffer.slice(0); _this.listeners = []; _this.port.onmessage = _this._messageProcessor.bind(_assertThisInitialized(_this)); _this.sourcePosition = 0; _this.timePlayed = 0; _this._startTime = 0; _this._pauseTime = 0; _this._playHead = 0; _this._playing = false; _this._ready = false; _this._initialPlay = true; return _this; } _createClass(SoundTouchNode, [{ key: "formattedDuration", get: function get() { return minsSecs(this.duration); } }, { key: "formattedTimePlayed", get: function get() { return minsSecs(this.timePlayed); } }, { key: "percentagePlayed", get: function get() { return 100 * this.sourcePosition / (this.duration * this.sampleRate); } , set: function set(percentage) { var duration = this.duration, sampleRate = this.sampleRate; this.sourcePosition = parseInt(duration * sampleRate * (percentage / 100)); this._updateFilterProp('sourcePosition', this.sourcePosition); this.currentTime = this.duration * percentage / 100; } }, { key: "currentTime", get: function get() { if (!this.playing) { return this._playHead; } return this._playHead + diffSecs(this._startTime, new Date().getTime()); } , set: function set(val) { this._playHead = val; } }, { key: "playing", get: function get() { return this._playing; } , set: function set(val) { this._playing = Boolean(val); } }, { key: "ready", get: function get() { return this._ready; } , set: function set(val) { this._ready = Boolean(val); } }, { key: "sampleRate", get: function get() { if (this.audioBuffer) { return this.audioBuffer.sampleRate; } return undefined; } }, { key: "duration", get: function get() { if (this.audioBuffer) { return this.audioBuffer.duration; } return undefined; } }, { key: "bufferLength", get: function get() { if (this.audioBuffer) { return this.audioBuffer.length; } return undefined; } }, { key: "numberOfChannels", get: function get() { if (this.audioBuffer) { return this.audioBuffer.numberOfChannels; } return undefined; } }, { key: "pitch", set: function set(pitch) { this._updatePipeProp('pitch', pitch); } }, { key: "pitchSemitones", set: function set(semitone) { this._updatePipeProp('pitchSemitones', semitone); } }, { key: "rate", set: function set(rate) { this._updatePipeProp('rate', rate); } }, { key: "tempo", set: function set(tempo) { this._updatePipeProp('tempo', tempo); } }, { key: "connectToBuffer", value: function connectToBuffer() { this.bufferNode = this.context.createBufferSource(); this.bufferNode.buffer = this.audioBuffer; this.bufferNode.onended = function () { return console.log('song ended'); }; this.bufferNode.connect(this); return this.bufferNode; } }, { key: "disconnectFromBuffer", value: function disconnectFromBuffer() { this.bufferNode.disconnect(); } }, { key: "handleAudioData", value: function handleAudioData(audioBuffer) { this.audioBuffer = audioBuffer; this.port.postMessage({ message: 'INITIALIZE_PROCESSOR', detail: this.createBaseArray(audioBuffer) }); } }, { key: "createBaseArray", value: function createBaseArray(audioBuffer) { return [{ sampleRate: this.sampleRate, duration: this.duration, bufferLength: this.bufferLength, numberOfChannels: this.numberOfChannels }, audioBuffer.getChannelData(0), this.numberOfChannels > 1 ? audioBuffer.getChannelData(1) : audioBuffer.getChannelData(0)]; } }, { key: "play", value: function () { var _play = _asyncToGenerator( regeneratorRuntime.mark(function _callee() { return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (this.ready) { _context.next = 2; break; } throw new Error('Your processor is not ready yet'); case 2: if (this.playing) { this.stop(true); } if (this._initialPlay) { if (this._playHead === 0) { this.percentagePlayed = 0; } this._initialPlay = false; } _context.next = 6; return this.context.resume(); case 6: this._startTime = new Date().getTime(); this.playing = true; case 8: case "end": return _context.stop(); } } }, _callee, this); })); function play() { return _play.apply(this, arguments); } return play; }() }, { key: "pause", value: function pause() { var currTime = this.currentTime; this.stop(); this.currentTime = currTime; } }, { key: "stop", value: function () { var _stop = _asyncToGenerator( regeneratorRuntime.mark(function _callee2() { return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _context2.next = 2; return this.context.suspend(); case 2: this.currentTime = 0; this._startTime = new Date().getTime(); this.playing = false; case 5: case "end": return _context2.stop(); } } }, _callee2, this); })); function stop() { return _stop.apply(this, arguments); } return stop; }() }, { key: "on", value: function on(eventName, cb) { this.listeners.push({ name: eventName, cb: cb }); this.addEventListener(eventName, function (event) { return cb(event.detail); }); } }, { key: "off", value: function off() { var _this2 = this; var eventName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var listeners = this.listeners; if (eventName) { listeners = listeners.filter(function (e) { return e.name === eventName; }); } listeners.forEach(function (e) { _this2.removeEventListener(e.name, function (event) { return e.cb(event.detail); }); }); } }, { key: "onprocessorerror", value: function onprocessorerror(err) { throw err; } }, { key: "_updatePipeProp", value: function _updatePipeProp(name, value) { this.port.postMessage({ message: 'SET_PIPE_PROP', detail: { name: name, value: value } }); } }, { key: "_updateFilterProp", value: function _updateFilterProp(name, value) { this.port.postMessage({ message: 'SET_FILTER_PROP', detail: { name: name, value: value } }); } }, { key: "_messageProcessor", value: function _messageProcessor(eventFromWorker) { var _this3 = this; var _eventFromWorker$data = eventFromWorker.data, message = _eventFromWorker$data.message, detail = _eventFromWorker$data.detail; var sampleRate = this.sampleRate, currentTime = this.timePlayed; if (message === 'SOURCEPOSITION') { this.sourcePosition = detail; var timePlayed = detail / sampleRate; if (currentTime !== timePlayed) { this.timePlayed = timePlayed; var timeEvent = new CustomEvent('play', { detail: { timePlayed: this.timePlayed, formattedTimePlayed: this.formattedTimePlayed, percentagePlayed: this.percentagePlayed } }); this.dispatchEvent(timeEvent); } } if (message === 'PROCESSOR_CONSTRUCTOR') { this.context.decodeAudioData(this._arrayBuffer, function (audioData) { return _this3.handleAudioData(audioData); }, function (err) { return console.log('[decodeAudioData ERROR] ', err); }); return; } if (message === 'PROCESSOR_READY') { this.ready = true; if (typeof this.onInitialized === 'function') { this.onInitialized(detail); return; } var init = new CustomEvent('initialized', detail); this.dispatchEvent(init); return; } if (message === 'PROCESSOR_END') { this.stop(); this.percentagePlayed = 0; var endOfPlay = new CustomEvent('end', { detail: { timePlayed: this.currentTime, formattedTimePlayed: this.formattedTimePlayed, percentagePlayed: this.percentagePlayed } }); this.dispatchEvent(endOfPlay); return; } } }]); return SoundTouchNode; }(AudioWorkletNode); return new SoundTouchNode(audioCtx, arrayBuffer, options); }; export { createSoundTouchNode as default }; //# sourceMappingURL=soundtouch-audio-node.js.map