557 lines
15 KiB
JavaScript
557 lines
15 KiB
JavaScript
"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;
|
|
}(); |