xxxxxxxxxx
222
const audioCtx = new AudioContext(),
audioPlayer = new Audio(),
audioSource = audioCtx.createMediaElementSource(audioPlayer),
analyser = audioCtx.createAnalyser(), // AnalyserNode.getFloatTimeDomainData is required for realtime audio visualizations
canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
audioPlayer.crossOrigin = 'anonymous';
audioSource.connect(analyser);
audioSource.connect(audioCtx.destination);
document.body.appendChild(canvas);
analyser.fftSize = 32768;
let waveform = new Float32Array(analyser.fftSize);
let dataArray = [];
// Trigger-based shake from musicvid.org
class TriggerBasedShake {
constructor(
returnSpeed = 0.1,
displacementThreshold = 4,
waveDuration = Math.PI/8,
movementAmount = 1,
maxShakeIntensity = 1,
maxShakeDisplacement = 4,
minShakeScalar = 0.9,
maxShakeScalar = 1.6,
minShakeAmount = 0.2
) {
this.returnSpeed = returnSpeed;
this.sumShakeX = 0;
this.sumShakeY = 0;
this.movementAmount = movementAmount;
this.waveSpeedX = 1;
this.waveSpeedY = 1;
this.waveFrameX = 0;
this.waveFrameY = 0;
this.waveAmplitudeX = 1;
this.waveAmplitudeY = 1;
this.trigX = Math.round(Math.random());
this.trigY = Math.round(Math.random());
this.waveDuration = waveDuration;
this.maxShakeIntensity = maxShakeIntensity;
this.maxShakeDisplacement = maxShakeDisplacement;
this.minShakeScalar = minShakeScalar;
this.maxShakeScalar = maxShakeScalar;
this.minShakeAmount = minShakeAmount;
}
shake (intensity) {
let step = this.maxShakeIntensity * intensity;
this.waveFrameX += step * this.waveSpeedX;
if (intensity <= this.minShakeAmount) {
if (Math.abs(this.sumShakeX) > this.displacementThreshold) {
const l = this.sumShakeX > 0 ? -1 : 1;
this.sumShakeX += this.returnSpeed * l;
}
if (Math.abs(this.sumShakeY) > 0) {
const l = this.sumShakeY > 0 ? -1 : 1;
this.sumShakeY += this.returnSpeed * l;
}
}
if (Math.abs(this.waveFrameX) > this.waveDuration) {
this.waveFrameX = 0;
this.waveAmplitudeX =
this.random(this.minShakeScalar, this.maxShakeScalar) *
this.direction(this.sumShakeX);
this.waveSpeedX =
this.random(this.minShakeScalar, this.maxShakeScalar) *
this.direction(this.sumShakeX);
this.trigX = Math.round(Math.random());
}
this.waveFrameY += step * this.waveSpeedY;
if (Math.abs(this.waveFrameY) > this.waveDuration) {
this.waveFrameY = 0;
this.waveAmplitudeY =
this.random(this.minShakeScalar, this.maxShakeScalar) *
this.direction(this.sumShakeY);
this.waveSpeedY =
this.random(this.minShakeScalar, this.maxShakeScalar) *
this.direction(this.sumShakeY);
this.trigY = Math.round(Math.random());
}
let trigFuncX = this.trigX === 0 ? Math.cos : Math.sin;
let trigFuncY = this.trigY === 0 ? Math.cos : Math.sin;
let dx =
trigFuncX(this.waveFrameX) *
this.maxShakeDisplacement *
this.waveAmplitudeX *
intensity *
this.movementAmount;
let dy =
trigFuncY(this.waveFrameY) *
this.maxShakeDisplacement *
this.waveAmplitudeY *
intensity *
this.movementAmount;
this.sumShakeX += dx;
this.sumShakeY += dy;
}
random(min, max) {
return Math.random() * (max - min) + min;
}
direction (cur) {
let d = cur > 0 ? 1 : -1;
const pull = d * Math.pow(Math.abs(cur), 0.08);
const dir = pull + Math.random();
return dir > 1.0 ? -1 : 1;
}
}
let cameraShake = new TriggerBasedShake();
resize();
function resize() {
const scale = devicePixelRatio;
canvas.width = innerWidth*scale;
canvas.height = innerHeight*scale;
}
addEventListener('click', () => {
if (audioCtx.state == 'suspended')
audioCtx.resume();
});
addEventListener('resize', () => {
resize();
});
const clientParameter = 'client_id=1745017edcfeb72a175c95614a1cc212';
function get(url, callback) {
let request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status === 200)
callback(request.responseText);
};
request.open("GET", url, true);
request.send(null);
}
function findTrack(trackURL) {
get("https://api.soundcloud.com/resolve.json?url=" + trackURL + "&" + clientParameter, (response) => {
let trackInfo = JSON.parse(response);
audioPlayer.src = trackInfo.stream_url + '?' + clientParameter;
audioPlayer.play();
})
}
findTrack('https://soundcloud.com/allbassnation/makzo-x-roshima-flashback-1');
realtimeVisualizer();
function realtimeVisualizer() {
requestAnimationFrame(realtimeVisualizer);
analyser.getFloatTimeDomainData(waveform);
draw();
}
// Modify here
function draw() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// After Effects-like audio spectrum for circle spectrum like Trap Nation
let audioBuffer = new Array(8820);
for (let i = 0; i < audioBuffer.length; i++) {
const waveformSize = audioBuffer.length;
const x = i * 2 / (waveformSize-1) - 1,
w = applyWindow(x, 'hamming');
audioBuffer[i] = waveform[i+(analyser.fftSize-waveformSize)] * w;
}
let result = calcSpectrum2(audioBuffer.filter((_, i) => i % 2 === 0), undefined, 'cbw', undefined, undefined, undefined, undefined, undefined, undefined, undefined, generateFreqBands(64, 20, 130, 'linear'), audioCtx.sampleRate/2);
// Bandpower spectrum with frequncy distribution similar to Wallpaper Engine
audioBuffer = new Array(2048);
for (let i = 0; i < audioBuffer.length; i++) {
const waveformSize = audioBuffer.length;
const x = i * 2 / (waveformSize-1) - 1,
w = applyWindow(x, 'hamming');
audioBuffer[i] = waveform[i+(analyser.fftSize-waveformSize+2048-8820)] * w;
}
let fftResult = calcFFT(audioBuffer);
let result2 = calcSpectrum(fftResult, 'gaps', true, false, true, 'linear', 0, 1, false, generatePiecewiseLinLogBands(64, 20, 14400, 'nth root', 0.7, 2048, audioCtx.sampleRate), 2048, audioCtx.sampleRate);
dataArray.length = result2.length;
calcSmoothingTimeConstant(dataArray, result2, 0.6);
// Actual visualizer here below
ctx.fillStyle = '#fff';
ctx.strokeStyle= '#fff';
const mirroredSpectrum = new Array(result.length*2-1);
for (let i = 0; i < mirroredSpectrum.length; i++) {
mirroredSpectrum[i] = result[getClippedIdx(i, result.length, 'mirror')];
}
const shakeIntensity = dataArray.filter((_, i) => {
return i >= 1 && i <= 6;
}).reduce((accumulator, currentValue) => accumulator + currentValue);
cameraShake.shake(shakeIntensity);
ctx.save();
ctx.translate(cameraShake.sumShakeX*(2+shakeIntensity)+canvas.width/2, cameraShake.sumShakeY*(2+shakeIntensity)+canvas.height/2);
ctx.scale(1+(shakeIntensity/2) ** 2, 1+(shakeIntensity/2) ** 2);
ctx.beginPath();
ctx.lineWidth = 1;
mirroredSpectrum.map((x, i, arr) => {
const posX = map(i, 0, arr.length-1, 0, Math.PI*2),
circleRadius = Math.min(canvas.width, canvas.height)/8*1.8;
ctx.lineTo(Math.sin(posX)*((1+x*4)*circleRadius),
-Math.cos(posX)*((1+x*4)*circleRadius));
});
ctx.fill();
ctx.restore();
ctx.lineWidth = canvas.width/result2.length/1.5;
dataArray.map((x, i, arr) => {
ctx.beginPath();
ctx.lineTo(map(i, -0.5, arr.length-0.5, 0, canvas.width), canvas.height);
ctx.lineTo(map(i, -0.5, arr.length-0.5, 0, canvas.width), map(x*2, 0, 1, canvas.height, 0));
ctx.stroke();
ctx.beginPath();
ctx.lineTo(map(i, -0.5, arr.length-0.5, canvas.width, 0), 0);
ctx.lineTo(map(i, -0.5, arr.length-0.5, canvas.width, 0), map(x*2, 0, 1, 0, canvas.height));
ctx.stroke();
});
}