I really like AM album by Arctic Monkeys and I decided to create audio visualizer in its cover style.
Script parse audio file to byte array and analyze its frequency.
Using the resulting value, I render the wave using the basic trigonometric formulas sin and cos.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Audio visualizer</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="wrapper"></div>
<script src="threejs/node_modules/three/build/three.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float scale;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = scale * (350.0 / - mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color;
void main() {
if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.475) discard;
gl_FragColor = vec4(color, 1.0);
}
</script>
<script src="js/script.js"></script>
</body>
</html>
const space = 10;
const particlesNum = 300;
const windowHalfX = window.innerWidth / 2;
const windowHalfY = window.innerHeight / 2;
const parent = document.querySelector('.wrapper');
var camera, scene, renderer, particles;
var speed = 0, step = 0.1;
var playing = false;
var song, listener, sound, analyser, audioLoader;
class Song {
init () {
listener = new THREE.AudioListener();
// create an Audio source
sound = new THREE.Audio(listener);
// create an AudioAnalyser, passing in the sound and desired fftSize
analyser = new THREE.AudioAnalyser(sound, 32);
}
play () {
// load a sound and set it as the Audio object's buffer
audioLoader = new THREE.AudioLoader();
audioLoader.load('Arctic Monkeys - Do I Wanna Know.mp3', function(buffer) {
sound.setBuffer(buffer);
sound.setLoop(true);
sound.setVolume(0.5);
sound.play();
});
}
}
init();
animate();
function init() {
var pov = 75;
if (window.innerWidth <= 768)
pov = 120;
camera = new THREE.PerspectiveCamera(pov, window.innerWidth / window.innerHeight, 1, 800);
camera.position.x = 800;
scene = new THREE.Scene();
scene.background = new THREE.Color(0x141414);
var positions = new Float32Array(particlesNum * 3);
var scales = new Float32Array(particlesNum);
var i = 0, j = 0;
for (var iy = 0; iy < particlesNum; iy++)
{
positions[i] = space - (space / 2); // x
positions[i + 1] = 0; // y
positions[i + 2] = iy * space - ((particlesNum * space) / 2); // z
scales[j] = 15;
i += 3;
j++;
}
var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('scale', new THREE.BufferAttribute(scales, 1));
var material = new THREE.ShaderMaterial({
uniforms: {
color: { value: new THREE.Color(0xffffff) },
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent
});
particles = new THREE.Points(geometry, material);
scene.add(particles);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
parent.appendChild(renderer.domElement);
song = new Song();
song.init();
}
function animate () {
requestAnimationFrame(animate);
render();
}
function render() {
camera.lookAt(scene.position);
var positions = particles.geometry.attributes.position.array;
var scales = particles.geometry.attributes.scale.array;
var i = 0, j = 0;
// get the average frequency of the sound
var frequency = analyser.getAverageFrequency() * 1.5;
if (!playing || frequency == 0)
frequency = 30;
var segmentParticles = particlesNum / 6;
var amplitudeRate = frequency / segmentParticles;
var amplitude = 0;
if (playing && step < 0.2) {
step += 0.0005;
speed -= 0.5;
}
for (var iy = 0; iy < particlesNum; iy++)
{
if (iy < 100 || iy > 200) {
positions[i + 1] = 0;
} else if (iy >= 100 && iy <= 150) {
positions[i + 1] = Math.sin((iy + speed) * step) * (amplitude += amplitudeRate);
} else if (iy > 150 && iy <= 200) {
positions[i + 1] = Math.sin((iy + speed) * step) * (amplitude -= amplitudeRate);
} else {
positions[i + 1] = Math.sin((iy + speed) * step) * segmentParticles;
}
scales[ j ] = 15;
i += 3;
j ++;
}
particles.geometry.attributes.position.needsUpdate = true;
particles.geometry.attributes.scale.needsUpdate = true;
renderer.render(scene, camera);
speed += 0.5;
}
class Button {
init () {
var button = document.createElement('button');
button.id = 'button';
parent.appendChild(button);
button.addEventListener('click', () => {
event.target.style.display = 'none';
playing = true;
song.play();
});
}
}
new Button().init();