UNPKG

waveform-playlist-nartj

Version:

Multiple track web audio editor and player with waveform preview

807 lines (716 loc) 27.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _lodash = _interopRequireDefault(require("lodash.assign")); var _lodash2 = _interopRequireDefault(require("lodash.forown")); var _uuid = require("uuid"); var _lodash3 = _interopRequireDefault(require("lodash.clonedeep")); var _h = _interopRequireDefault(require("virtual-dom/h")); var _webaudioPeaks = _interopRequireDefault(require("webaudio-peaks")); var _fadeMaker = require("fade-maker"); var _conversions = require("./utils/conversions"); var _states = _interopRequireDefault(require("./track/states")); var _CanvasHook = _interopRequireDefault(require("./render/CanvasHook")); var _FadeCanvasHook = _interopRequireDefault(require("./render/FadeCanvasHook")); var _VolumeSliderHook = _interopRequireDefault(require("./render/VolumeSliderHook")); var MAX_CANVAS_WIDTH = 1000; var _default = /*#__PURE__*/function () { function _default() { (0, _classCallCheck2["default"])(this, _default); this.name = 'Untitled'; this.id = 'i' + (0, _uuid.v4)(); // must start with a letter to be a valid css id this.taggedName = this.name; this.customClass = undefined; this.waveOutlineColor = undefined; this.gain = 1; this.fades = {}; this.peakData = { type: 'WebAudio', mono: false }; this.cueIn = 0; this.cueOut = 0; this.duration = 0; this.startTime = 0; this.endTime = 0; this.stereoPan = 0; this.src = undefined; this.duplicationNumber = 0; this.srcTrack = undefined; this.scale = window.devicePixelRatio; } (0, _createClass2["default"])(_default, [{ key: "setSrc", value: function setSrc(src) { this.src = src; } }, { key: "setSrcTrack", value: function setSrcTrack(track) { if (this.srcTrack === undefined) { this.srcTrack = track; } } }, { key: "setTaggedName", value: function setTaggedName() { if (this.duplicationNumber !== undefined && this.duplicationNumber !== 0) { this.taggedName = this.name + '#' + this.duplicationNumber; } else { this.taggedName = this.name; } } }, { key: "setEventEmitter", value: function setEventEmitter(ee) { this.ee = ee; } }, { key: "setName", value: function setName(name) { this.name = name; this.setTaggedName(); } }, { key: "setCustomClass", value: function setCustomClass(className) { this.customClass = className; } }, { key: "setWaveOutlineColor", value: function setWaveOutlineColor(color) { this.waveOutlineColor = color; } }, { key: "setCues", value: function setCues(cueIn, cueOut) { if (cueOut < cueIn) { throw new Error('cue out cannot be less than cue in'); } this.cueIn = cueIn; this.cueOut = cueOut; this.duration = this.cueOut - this.cueIn; this.endTime = this.startTime + this.duration; } /* * start, end in seconds relative to the entire playlist. */ }, { key: "trim", value: function () { var _trim = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(start, end) { var trackStart, trackEnd, offset, self, middleTrack, endTrack, trackOffset, cueIn, cueOut, peaks, undo; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: trackStart = this.getStartTime(); trackEnd = this.getEndTime(); offset = this.cueIn - trackStart; self = this; middleTrack = null; endTrack = null; trackOffset = 0; if (!(trackStart <= start && trackEnd >= start || trackStart <= end && trackEnd >= end)) { _context.next = 24; break; } cueIn = trackStart; cueOut = start < trackStart ? end : start; this.setCues(cueIn + offset, cueOut + offset); if (start > trackStart) { this.setStartTime(trackStart); } if (!(start > trackStart)) { _context.next = 18; break; } cueIn = start < trackStart ? trackStart : start; cueOut = end > trackEnd ? trackEnd : end; _context.next = 17; return this.duplicateTrack(this, start, cueIn + offset, cueOut + offset, ++trackOffset); case 17: middleTrack = _context.sent; case 18: if (!(end < trackEnd)) { _context.next = 24; break; } cueIn = end; cueOut = trackEnd; _context.next = 23; return this.duplicateTrack(this, end, cueIn + offset, cueOut + offset, ++trackOffset); case 23: endTrack = _context.sent; case 24: peaks = (0, _lodash3["default"])(this.peaks); undo = function undo() { // todo replace with playlist.load and remove peaks self.setCues(trackStart, trackEnd); self.setPeaks(peaks); playlist.removeTrack(middleTrack); playlist.removeTrack(endTrack); playlist.setTimeSelection(0, 0); playlist.adjustDuration(); playlist.draw(playlist.render()); }; return _context.abrupt("return", undo); case 27: case "end": return _context.stop(); } } }, _callee, this); })); function trim(_x, _x2) { return _trim.apply(this, arguments); } return trim; }() }, { key: "duplicateTrack", value: function () { var _duplicateTrack = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(track, start, cueIn, cueOut, trackOffset) { return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: playlist.setActiveTrack(track); // it duplicates the current active track _context2.next = 3; return playlist.load([{ track: track, src: track.src, name: track.name, start: start, states: track.states, cueIn: cueIn, cueOut: cueOut, gain: track.gain, muted: track.muted, soloed: track.soloed, selection: track.selection, peaks: track.peaks, customClass: track.customClass, waveOutlineColor: track.waveOutlineColor, stereoPan: track.stereoPan, duplicationNumber: track.duplicationNumber, trackOffset: trackOffset }]); case 3: return _context2.abrupt("return", _context2.sent[0]); case 4: case "end": return _context2.stop(); } } }, _callee2); })); function duplicateTrack(_x3, _x4, _x5, _x6, _x7) { return _duplicateTrack.apply(this, arguments); } return duplicateTrack; }() }, { key: "setStartTime", value: function setStartTime(start) { this.startTime = start; this.endTime = start + this.duration; } }, { key: "setPlayout", value: function setPlayout(playout) { this.playout = playout; } }, { key: "setOfflinePlayout", value: function setOfflinePlayout(playout) { this.offlinePlayout = playout; } }, { key: "setEnabledStates", value: function setEnabledStates() { var enabledStates = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var defaultStatesEnabled = { cursor: true, fadein: true, fadeout: true, select: true, shift: true }; this.enabledStates = (0, _lodash["default"])({}, defaultStatesEnabled, enabledStates); } }, { key: "setFadeIn", value: function setFadeIn(duration) { var shape = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'logarithmic'; if (duration > this.duration) { throw new Error('Invalid Fade In'); } var fade = { shape: shape, start: 0, end: duration }; if (this.fadeIn) { this.removeFade(this.fadeIn); this.fadeIn = undefined; } this.fadeIn = this.saveFade(_fadeMaker.FADEIN, fade.shape, fade.start, fade.end); } }, { key: "setFadeOut", value: function setFadeOut(duration) { var shape = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'logarithmic'; if (duration > this.duration) { throw new Error('Invalid Fade Out'); } var fade = { shape: shape, start: this.duration - duration, end: this.duration }; if (this.fadeOut) { this.removeFade(this.fadeOut); this.fadeOut = undefined; } this.fadeOut = this.saveFade(_fadeMaker.FADEOUT, fade.shape, fade.start, fade.end); } }, { key: "saveFade", value: function saveFade(type, shape, start, end) { var id = (0, _uuid.v4)(); this.fades[id] = { type: type, shape: shape, start: start, end: end }; return id; } }, { key: "removeFade", value: function removeFade(id) { delete this.fades[id]; } }, { key: "setBuffer", value: function setBuffer(buffer) { this.buffer = buffer; } }, { key: "setPeakData", value: function setPeakData(data) { this.peakData = data; } }, { key: "calculatePeaks", value: function calculatePeaks(samplesPerPixel, sampleRate) { var cueIn = (0, _conversions.secondsToSamples)(this.cueIn, sampleRate); var cueOut = (0, _conversions.secondsToSamples)(this.cueOut, sampleRate); this.setPeaks((0, _webaudioPeaks["default"])(this.buffer, samplesPerPixel, this.peakData.mono, cueIn, cueOut)); } }, { key: "setPeaks", value: function setPeaks(peaks) { this.peaks = peaks; } }, { key: "setState", value: function setState(state) { this.state = state; if (this.state && this.enabledStates[this.state]) { var StateClass = _states["default"][this.state]; this.stateObj = new StateClass(this); } else { this.stateObj = undefined; } } }, { key: "getStartTime", value: function getStartTime() { return this.startTime; } }, { key: "getEndTime", value: function getEndTime() { return this.endTime; } }, { key: "getDuration", value: function getDuration() { return this.duration; } }, { key: "isPlaying", value: function isPlaying() { return this.playout.isPlaying(); } }, { key: "setShouldPlay", value: function setShouldPlay(bool) { this.playout.setShouldPlay(bool); } }, { key: "setGainLevel", value: function setGainLevel(level) { this.gain = level; this.playout.setVolumeGainLevel(level); } }, { key: "setMasterGainLevel", value: function setMasterGainLevel(level) { this.playout.setMasterGainLevel(level); } }, { key: "setStereoPanValue", value: function setStereoPanValue(value) { this.stereoPan = value; this.playout.setStereoPanValue(value); } /* startTime, endTime in seconds (float). segment is for a highlighted section in the UI. returns a Promise that will resolve when the AudioBufferSource is either stopped or plays out naturally. */ }, { key: "schedulePlay", value: function schedulePlay(now, startTime, endTime, config) { var start; var duration; var when = now; var segment = endTime ? endTime - startTime : undefined; var defaultOptions = { shouldPlay: true, masterGain: 1, isOffline: false }; var options = (0, _lodash["default"])({}, defaultOptions, config); var playoutSystem = options.isOffline ? this.offlinePlayout : this.playout; // 1) track has no content to play. // 2) track does not play in this selection. if (this.endTime <= startTime || segment && startTime + segment < this.startTime) { // return a resolved promise since this track is technically "stopped". return Promise.resolve(); } // track should have something to play if it gets here. // the track starts in the future or on the cursor position if (this.startTime >= startTime) { start = 0; // schedule additional delay for this audio node. when += this.startTime - startTime; if (endTime) { segment -= this.startTime - startTime; duration = Math.min(segment, this.duration); } else { duration = this.duration; } } else { start = startTime - this.startTime; if (endTime) { duration = Math.min(segment, this.duration - start); } else { duration = this.duration - start; } } start += this.cueIn; var relPos = startTime - this.startTime; var sourcePromise = playoutSystem.setUpSource(); // param relPos: cursor position in seconds relative to this track. // can be negative if the cursor is placed before the start of this track etc. (0, _lodash2["default"])(this.fades, function (fade) { var fadeStart; var fadeDuration; // only apply fade if it's ahead of the cursor. if (relPos < fade.end) { if (relPos <= fade.start) { fadeStart = now + (fade.start - relPos); fadeDuration = fade.end - fade.start; } else if (relPos > fade.start && relPos < fade.end) { fadeStart = now - (relPos - fade.start); fadeDuration = fade.end - fade.start; } switch (fade.type) { case _fadeMaker.FADEIN: { playoutSystem.applyFadeIn(fadeStart, fadeDuration, fade.shape); break; } case _fadeMaker.FADEOUT: { playoutSystem.applyFadeOut(fadeStart, fadeDuration, fade.shape); break; } default: { throw new Error('Invalid fade type saved on track.'); } } } }); playoutSystem.setVolumeGainLevel(this.gain); playoutSystem.setShouldPlay(options.shouldPlay); playoutSystem.setMasterGainLevel(options.masterGain); playoutSystem.setStereoPanValue(this.stereoPan); playoutSystem.play(when, start, duration); return sourcePromise; } }, { key: "scheduleStop", value: function scheduleStop() { var when = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; this.playout.stop(when); } }, { key: "renderOverlay", value: function renderOverlay(data) { var _this = this; var channelPixels = (0, _conversions.secondsToPixels)(data.playlistLength, data.resolution, data.sampleRate); var config = { attributes: { style: "position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: ".concat(channelPixels, "px; z-index: 9;") } }; var overlayClass = ''; if (this.stateObj) { this.stateObj.setup(data.resolution, data.sampleRate); var StateClass = _states["default"][this.state]; var events = StateClass.getEvents(); events.forEach(function (event) { config["on".concat(event)] = _this.stateObj[event].bind(_this.stateObj); }); overlayClass = StateClass.getClass(); } // use this overlay for track event cursor position calculations. return (0, _h["default"])("div.playlist-overlay".concat(overlayClass), config); } }, { key: "renderControls", value: function renderControls(data) { var _this2 = this; var muteClass = data.muted ? '.active' : ''; var soloClass = data.soloed ? '.active' : ''; var numChan = this.peaks.data.length; return (0, _h["default"])('div.controls', { attributes: { style: "height: ".concat(numChan * data.height, "px; width: ").concat(data.controls.width, "px; position: absolute; left: 0; z-index: 10;"), id: this.id + 'Controls' } }, [(0, _h["default"])('header', [this.taggedName]), (0, _h["default"])('div.btn-group', [(0, _h["default"])("span.btn.btn-default.i.fa.fa-volume-off", { onclick: function onclick() { _this2.ee.emit('mute', _this2); } }), (0, _h["default"])("span.btn.btn-default.i.fa.fa-crosshairs", { onclick: function onclick() { _this2.ee.emit('solo', _this2); } }), (0, _h["default"])("span.btn.btn-default.i.fa.fa-chevron-up", { onclick: function onclick() { _this2.ee.emit('moveUp', _this2); } }), (0, _h["default"])("span.btn.btn-default.i.fa.fa-chevron-down", { onclick: function onclick() { _this2.ee.emit('moveDown', _this2); } }), (0, _h["default"])("span.btn.btn-default.i.fa.fa-times", { onclick: function onclick() { _this2.ee.emit('delete', _this2); } }), (0, _h["default"])("span.btn.btn-default.i.fa.fa-files-o", { onclick: function onclick() { _this2.ee.emit('duplicate', _this2); } }), (0, _h["default"])("span.btn.btn-default.i.fa.fa-compress}", { onclick: function onclick() { document.querySelector("#" + _this2.id + 'Controls').style.display = "none"; document.querySelector("#" + _this2.id + "Card").style.display = "none"; document.querySelector("#" + _this2.id + "Header").style.display = "contents"; } })]), (0, _h["default"])('label', [(0, _h["default"])('input.volume-slider', { attributes: { type: 'range', min: 0, max: 100, value: 100 }, hook: new _VolumeSliderHook["default"](this.gain), oninput: function oninput(e) { _this2.ee.emit('volumechange', e.target.value, _this2); } })])]); } }, { key: "render", value: function render(data) { var _this3 = this; var width = this.peaks.length; var playbackX = (0, _conversions.secondsToPixels)(data.playbackSeconds, data.resolution, data.sampleRate); var startX = (0, _conversions.secondsToPixels)(this.startTime, data.resolution, data.sampleRate); var endX = (0, _conversions.secondsToPixels)(this.endTime, data.resolution, data.sampleRate); var progressWidth = 0; var numChan = this.peaks.data.length; var oldScale = this.scale; this.scale = window.devicePixelRatio; if (playbackX > 0 && playbackX > startX) { if (playbackX < endX) { progressWidth = playbackX - startX; } else { progressWidth = width; } } var waveformChildren = [(0, _h["default"])('div.cursor', { attributes: { style: "position: absolute; width: 1px; margin: 0; padding: 0; top: 0; left: ".concat(playbackX, "px; bottom: 0; z-index: 5;") } })]; var channels = Object.keys(this.peaks.data).map(function (channelNum) { var channelChildren = [(0, _h["default"])('div.channel-progress', { attributes: { style: "position: absolute; width: ".concat(progressWidth, "px; height: ").concat(data.height, "px; z-index: 2;") } })]; var offset = 0; var totalWidth = width; var peaks = _this3.peaks.data[channelNum]; while (totalWidth > 0) { var currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH); var canvasColor = _this3.waveOutlineColor ? _this3.waveOutlineColor : data.colors.waveOutlineColor; channelChildren.push((0, _h["default"])('canvas', { attributes: { width: currentWidth * _this3.scale, height: data.height * _this3.scale, style: "float: left; position: relative; margin: 0; padding: 0; z-index: 3; width: ".concat(currentWidth, "px; height: ").concat(data.height, "px;") }, hook: new _CanvasHook["default"](peaks, offset, _this3.peaks.bits, canvasColor, _this3.scale, _this3.scale !== oldScale) })); totalWidth -= currentWidth; offset += MAX_CANVAS_WIDTH; } // if there are fades, display them. if (_this3.fadeIn) { var fadeIn = _this3.fades[_this3.fadeIn]; var fadeWidth = (0, _conversions.secondsToPixels)(fadeIn.end - fadeIn.start, data.resolution, data.sampleRate); channelChildren.push((0, _h["default"])('div.wp-fade.wp-fadein', { attributes: { style: "position: absolute; height: ".concat(data.height, "px; width: ").concat(fadeWidth, "px; top: 0; left: 0; z-index: 4;") } }, [(0, _h["default"])('canvas', { attributes: { width: fadeWidth, height: data.height }, hook: new _FadeCanvasHook["default"](fadeIn.type, fadeIn.shape, fadeIn.end - fadeIn.start, data.resolution) })])); } if (_this3.fadeOut) { var fadeOut = _this3.fades[_this3.fadeOut]; var _fadeWidth = (0, _conversions.secondsToPixels)(fadeOut.end - fadeOut.start, data.resolution, data.sampleRate); channelChildren.push((0, _h["default"])('div.wp-fade.wp-fadeout', { attributes: { style: "position: absolute; height: ".concat(data.height, "px; width: ").concat(_fadeWidth, "px; top: 0; right: 0; z-index: 4;") } }, [(0, _h["default"])('canvas', { attributes: { width: _fadeWidth, height: data.height }, hook: new _FadeCanvasHook["default"](fadeOut.type, fadeOut.shape, fadeOut.end - fadeOut.start, data.resolution) })])); } return (0, _h["default"])("div.channel.channel-".concat(channelNum), { attributes: { style: "height: ".concat(data.height, "px; width: ").concat(width, "px; top: ").concat(channelNum * data.height, "px; left: ").concat(startX, "px; position: absolute; margin: 0; padding: 0; z-index: 1;") } }, channelChildren); }); waveformChildren.push(channels); waveformChildren.push(this.renderOverlay(data)); // draw cursor selection on active track. if (data.isActive === true) { var cStartX = (0, _conversions.secondsToPixels)(data.timeSelection.start, data.resolution, data.sampleRate); var cEndX = (0, _conversions.secondsToPixels)(data.timeSelection.end, data.resolution, data.sampleRate); var cWidth = cEndX - cStartX + 1; var cClassName = cWidth > 1 ? '.segment' : '.point'; waveformChildren.push((0, _h["default"])("div.selection".concat(cClassName), { attributes: { style: "position: absolute; width: ".concat(cWidth, "px; bottom: 0; top: 0; left: ").concat(cStartX, "px; z-index: 4;") } })); } var waveform = (0, _h["default"])('div.waveform', { attributes: { style: "height: ".concat(numChan * data.height, "px; position: relative;") } }, waveformChildren); var channelChildren = []; var channelMargin = 0; if (data.controls.show) { channelChildren.push(this.renderControls(data)); channelMargin = data.controls.width; } channelChildren.push(waveform); var audibleClass = data.shouldPlay ? '' : '.silent'; var customClass = this.customClass === undefined ? '' : ".".concat(this.customClass); var full = (0, _h["default"])("div.channel-wrapper".concat(audibleClass).concat(customClass), { attributes: { style: "margin-left: ".concat(channelMargin, "px; height: ").concat(data.height * numChan, "px;"), id: this.id + 'Card' } }, channelChildren); var collapseButton = (0, _h["default"])("span.btn.btn-default.i.fa.fa-expand}", { onclick: function onclick() { document.querySelector("#" + _this3.id + 'Controls').style.display = "block"; document.querySelector("#" + _this3.id + "Card").style.display = "block"; document.querySelector("#" + _this3.id + "Header").style.display = "none"; } }); var collapse = (0, _h["default"])("div.controls.collapsed", { attributes: { style: "display: none; text-align: left;", id: this.id + 'Header' } }, [(0, _h["default"])('header', [this.taggedName, collapseButton])]); return (0, _h["default"])('', [collapse, full]); } }, { key: "getTrackDetails", value: function getTrackDetails() { var info = { src: this.src, start: this.startTime, end: this.endTime, name: this.name, customClass: this.customClass, cueIn: this.cueIn, cueOut: this.cueOut }; if (this.fadeIn) { var fadeIn = this.fades[this.fadeIn]; info.fadeIn = { shape: fadeIn.shape, duration: fadeIn.end - fadeIn.start }; } if (this.fadeOut) { var fadeOut = this.fades[this.fadeOut]; info.fadeOut = { shape: fadeOut.shape, duration: fadeOut.end - fadeOut.start }; } return info; } }, { key: "setDuplicationNumber", value: function setDuplicationNumber(duplicationNumber) { if (duplicationNumber === undefined) { duplicationNumber = 0; } if (this.srcTrack !== undefined) { this.srcTrack.setDuplicationNumber(duplicationNumber); } this.duplicationNumber = duplicationNumber; } }]); return _default; }(); exports["default"] = _default;