"use strict"; function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, 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); return Constructor; } var CircularAudioWave = /*#__PURE__*/function () { function CircularAudioWave(elem) { var _this = this; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, CircularAudioWave); this.wiggleColorOne = '#d88ee8'; this.wiggleColorTwo = 'cyan'; this.circleColor = 'transparent'; this.innerCircleCenterColor = '#d88ee8'; this.innerCircleOuterColor = 'white'; this.opts = opts; this.lastMaxR = 0; this.maxChartValue = 240; this.minChartValue = 100; this.chart = echarts.init(elem); this.playing = false; this.lineColorOffset = 0; this.tick = 0; var bgColor = '#2E2733'; this.defaultChartOption = { angleAxis: { type: 'value', clockwise: false, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } }, radiusAxis: { min: 0, max: this.maxChartValue + 50, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } }, polar: { radius: '100%' }, series: [{ coordinateSystem: 'polar', name: 'line', type: 'line', showSymbol: false, lineStyle: { color: { colorStops: [{ offset: 0.7, color: this.wiggleColorOne }, { offset: 0.3, color: this.wiggleColorTwo }] }, shadowColor: 'blue', shadowBlur: 10 }, zlevel: 2, data: Array.apply(null, { length: 361 }).map(Function.call, function (i) { return [_this.minChartValue, i]; }), silent: true, hoverAnimation: false }, { coordinateSystem: 'polar', name: 'maxbar', type: 'line', showSymbol: false, lineStyle: { color: this.circleColor, shadowColor: this.circleColor, shadowBlur: 10 }, data: Array.apply(null, { length: 361 }).map(Function.call, function (i) { return [_this.minChartValue, i]; }), silent: true, hoverAnimation: false }, { coordinateSystem: 'polar', name: 'interior', type: 'effectScatter', showSymbol: false, data: [0], symbolSize: 100, rippleEffect: { period: 3.5, scale: 3 }, itemStyle: { color: { type: 'radial', colorStops: [{ offset: 0, color: this.innerCircleCenterColor }, { offset: 1, color: this.innerCircleOuterColor }] } }, silent: true, hoverAnimation: false, animation: false }] }; // check if the default naming is enabled, if not use the chrome one. window.AudioContext = window.AudioContext || window.webkitAudioContext; window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext; if (!window.AudioContext) { alert('Your browser does not support Web Audio API'); } else { this.context = new AudioContext(); this.offlineContext = new OfflineAudioContext(2, 30 * 44100, 44100); this.sourceNode = this.context.createBufferSource(); this.offlineSource = this.offlineContext.createBufferSource(); this.sourceNode.loop = !!this.opts.loop; this.analyser = this.context.createAnalyser(); } if (this.opts.mode === 'sunburst') { // if (true) { var colors = ['#FFAE57', '#FF7853', '#EA5151', '#CC3F57', '#9A2555']; var data = [{ children: [{ children: [] }] }, { children: [{ children: [] }] }]; for (var i = 0; i < 5; i++) { data[0].children[0].children.push({ name: '-', children: [{ name: '' }] }); data[1].children[0].children.push({ name: '-', children: [{ name: '' }] }); } // loop to the bottom children data.forEach(function (level0) { level0.children.forEach(function (level1) { level1.children.forEach(function (item) { item.children[0].value = 1; }); }); }); this.defaultChartOption = { backgroundColor: bgColor, color: colors, series: [{ type: 'sunburst', center: ['50%', '48%'], data: data, nodeClick: false, sort: function sort(a, b) { if (a.depth === 1) { return b.getValue() - a.getValue(); } else { return a.dataIndex - b.dataIndex; } }, itemStyle: { borderColor: bgColor, borderWidth: 2 }, levels: [{}, { r0: 0, r: 40 }, { r0: 40, r: 105 }, { r0: 115, r: 140, itemStyle: { shadowBlur: 2, shadowColor: colors[2], color: 'transparent' }, label: { rotate: 'tangential', fontSize: 10, color: colors[0] } }, { r0: 140, r: 145, itemStyle: { shadowBlur: 80, shadowColor: colors[0], color: colors[0] }, label: { position: 'outside', textShadowBlur: 5, textShadowColor: '#333', backgroundColor: colors[0] } }] }] }; } this.chartOption = JSON.parse(JSON.stringify(this.defaultChartOption)); } _createClass(CircularAudioWave, [{ key: "loadAudio", value: function loadAudio(filePath) { var _this2 = this; console.log(filePath); this.filePath = filePath; this._setupAudioNodes(); this._setupOfflineContext(); var request = new XMLHttpRequest(); request.open('GET', filePath, true); request.responseType = 'arraybuffer'; request.send(); return new Promise(function (resolve, reject) { request.onload = function () { // Preprocess buffer for bpm _this2.offlineContext.decodeAudioData(request.response, function (buffer) { _this2.sourceNode.buffer = buffer; _this2.offlineSource.buffer = buffer; _this2.offlineSource.start(0); _this2.offlineContext.startRendering(); }); _this2.offlineContext.oncomplete = function (e) { var buffer = e.renderedBuffer; _this2.bpm = _this2._getBPM([buffer.getChannelData(0), buffer.getChannelData(1)]); _this2._init(); resolve(); }; }; }); } }, { key: "_init", value: function _init() { this.chart.setOption(this.chartOption, true); this._debouncedDraw = this._debounce(this._drawAnimation.bind(this), 25); } }, { key: "presetOption", value: function presetOption() { if (this.opts.mode !== 'sunburst') { this.chartOption.series[0].animation = false; this.chartOption.series[2].rippleEffect.period = 150 / this.bpm; } } }, { key: "play", value: function play() { var _this3 = this; if (!this.sourceNode || !this.sourceNode.buffer) { setTimeout(function () { return _this3.play(); }, 300); return; } this.playing = true; this.presetOption(); this.sourceNode.start(0); this._debouncedDraw(); } // TODO }, { key: "pause", value: function pause() {} }, { key: "destroy", value: function destroy() { this.chart.dispose(); } }, { key: "reset", value: function reset() { this.tick = 0; this.chartOption = JSON.parse(JSON.stringify(this.defaultChartOption)); this._init(); } // TODO: Allow callback }, { key: "onended", value: function onended() { if (!this.opts.loop) { this.playing = false; this.context.close(); this.sourceNode.buffer = null; this.offlineSource.buffer = null; this.reset(); this.context = new AudioContext(); this.offlineContext = new OfflineAudioContext(2, 30 * 44100, 44100); this.sourceNode = this.context.createBufferSource(); this.offlineSource = this.offlineContext.createBufferSource(); this.analyser = this.context.createAnalyser(); this.loadAudio(this.filePath); } } }, { key: "_setupAudioNodes", value: function _setupAudioNodes() { this.analyser.smoothingTimeConstant = 0.3; this.analyser.fftSize = 2048; this.sourceNode.connect(this.analyser); this.sourceNode.connect(this.context.destination); this.sourceNode.onended = this.onended.bind(this); } }, { key: "_setupOfflineContext", value: function _setupOfflineContext() { // Beats generally occur around the 100 to 150 hz range. var lowpass = this.offlineContext.createBiquadFilter(); lowpass.type = "lowpass"; lowpass.settar; lowpass.frequency.setValueAtTime(150, 0); lowpass.Q.setValueAtTime(1, 0); this.offlineSource.connect(lowpass); var highpass = this.offlineContext.createBiquadFilter(); highpass.type = "highpass"; highpass.frequency.setValueAtTime(100, 0); highpass.Q.setValueAtTime(1, 0); lowpass.connect(highpass); highpass.connect(this.offlineContext.destination); } }, { key: "_drawAnimation", value: function _drawAnimation() { var freqData = new Uint8Array(this.analyser.frequencyBinCount); this.analyser.getByteFrequencyData(freqData); this._draw(freqData); requestAnimationFrame(this._debouncedDraw.bind(this)); } }, { key: "_draw", value: function _draw(freqData) { var _this4 = this; if (this.playing) { var waveData = this._generateWaveData(freqData); this.chartOption.series[0].data = waveData.data; if (waveData.maxR > this.lastMaxR) { this.lastMaxR = waveData.maxR + 4; } else if (this.playing) { this.lastMaxR -= 2; } else { this.lastMaxR = this.minChartValue; } if (this.opts.mode !== 'sunburst') { // maxbar this.chartOption.series[1].data = Array.apply(null, { length: 361 }).map(Function.call, function (i) { return [_this4.lastMaxR, i]; }); } this.chart.setOption(this.chartOption, true); this.tick++; } } }, { key: "_generateWaveData", value: function _generateWaveData(data) { var waveData = []; var maxR = 0; if (this.opts.mode !== 'sunburst') { for (var i = 0; i <= 360; i++) { // (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin var freq = data[i]; var r = (freq - 0) * (this.maxChartValue - this.minChartValue) / (255 - 0) + this.minChartValue; if (r > maxR) { maxR = r; } waveData.push([r, i]); } waveData.push([waveData[0][0], 360]); } else { waveData = JSON.parse(JSON.stringify(this.chartOption.series[0].data)); ; var index = 0; waveData.forEach(function (level0) { level0.children.forEach(function (level1) { level1.children.forEach(function (item) { var freq = data[index]; var r = (freq - 0) * (40 - 0) / (255 - 0) + 0; item.children[0].name = Array.apply(null, { length: r }).map(Function.call, function (i) { return ''; }).join(' '); index++; }); }); }); } return { maxR: maxR, data: waveData }; } }, { key: "_getBPM", value: function _getBPM(data) { var partSize = 22050, parts = data[0].length / partSize, peaks = []; for (var i = 0; i < parts; i++) { var max = 0; for (var j = i * partSize; j < (i + 1) * partSize; j++) { var volume = Math.max(Math.abs(data[0][j]), Math.abs(data[1][j])); if (!max || volume > max.volume) { max = { position: j, volume: volume }; } } peaks.push(max); } peaks.sort(function (a, b) { return b.volume - a.volume; }); peaks = peaks.splice(0, peaks.length * 0.5); peaks.sort(function (a, b) { return a.position - b.position; }); var groups = []; peaks.forEach(function (peak, index) { var _loop = function _loop(_i) { var group = { bpm: 60 * 44100 / (peaks[index + _i].position - peak.position), count: 1 }; while (group.bpm < 90) { group.bpm *= 2; } while (group.bpm > 180) { group.bpm /= 2; } group.bpm = Math.round(group.bpm); if (!groups.some(function (interval) { return interval.bpm === group.bpm ? interval.count++ : 0; })) { groups.push(group); } }; for (var _i = 1; index + _i < peaks.length && _i < 10; _i++) { _loop(_i); } }); var bpm = groups.sort(function (intA, intB) { return intB.count - intA.count; })[0].bpm; console.log('bpm:', bpm); return bpm; } }, { key: "_debounce", value: function _debounce(func, wait, immediate) { var timeout; return function () { var context = this, args = arguments; var later = function later() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } }]); return CircularAudioWave; }();