Audio Visualizer like from AM album by Arctic Monkeys using Threejs.

About

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