static.wiai.de/weihnachtslied-2020/circular-audio-wave.js
2023-01-10 11:40:39 +01:00

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;
}();