Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save DJStompZone/b0e3c3d4516ca17ff2f74caaf736fccf to your computer and use it in GitHub Desktop.
Save DJStompZone/b0e3c3d4516ca17ff2f74caaf736fccf to your computer and use it in GitHub Desktop.
Audio Visualizer based on Three.js

Audio Visualizer based on Three.js

I wanted to use the Web Audio API, so made this music visualizer. The basic idea came from a pen I found here on codepen itself ( will link to it as soon as I find it ).

To use> Just upload any mp3 file and watch the bubble do weird things!

( Let me know what you think in the comments - good/bad/ugly )

A Pen by Prakhar Bhardwaj on CodePen.

License.

<div id="content">
<label for="thefile" class="file"> Choose an audio file
<input type="file" id="thefile" accept="audio/*" />
</label>
<audio id="audio" controls></audio>
<div id="out"></div>
</div>
//initialise simplex noise instance
var noise = new SimplexNoise();
// the main visualiser function
var vizInit = function (){
var file = document.getElementById("thefile");
var audio = document.getElementById("audio");
var fileLabel = document.querySelector("label.file");
document.onload = function(e){
console.log(e);
audio.play();
play();
}
file.onchange = function(){
fileLabel.classList.add('normal');
audio.classList.add('active');
var files = this.files;
audio.src = URL.createObjectURL(files[0]);
audio.load();
audio.play();
play();
}
function play() {
var context = new AudioContext();
var src = context.createMediaElementSource(audio);
var analyser = context.createAnalyser();
src.connect(analyser);
analyser.connect(context.destination);
analyser.fftSize = 512;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
//here comes the webgl
var scene = new THREE.Scene();
var group = new THREE.Group();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0,0,100);
camera.lookAt(scene.position);
scene.add(camera);
var renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
var planeGeometry = new THREE.PlaneGeometry(800, 800, 20, 20);
var planeMaterial = new THREE.MeshLambertMaterial({
color: 0x6904ce,
side: THREE.DoubleSide,
wireframe: true
});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.set(0, 30, 0);
group.add(plane);
var plane2 = new THREE.Mesh(planeGeometry, planeMaterial);
plane2.rotation.x = -0.5 * Math.PI;
plane2.position.set(0, -30, 0);
group.add(plane2);
var icosahedronGeometry = new THREE.IcosahedronGeometry(10, 4);
var lambertMaterial = new THREE.MeshLambertMaterial({
color: 0xff00ee,
wireframe: true
});
var ball = new THREE.Mesh(icosahedronGeometry, lambertMaterial);
ball.position.set(0, 0, 0);
group.add(ball);
var ambientLight = new THREE.AmbientLight(0xaaaaaa);
scene.add(ambientLight);
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.intensity = 0.9;
spotLight.position.set(-10, 40, 20);
spotLight.lookAt(ball);
spotLight.castShadow = true;
scene.add(spotLight);
// var orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
// orbitControls.autoRotate = true;
scene.add(group);
document.getElementById('out').appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
render();
function render() {
analyser.getByteFrequencyData(dataArray);
var lowerHalfArray = dataArray.slice(0, (dataArray.length/2) - 1);
var upperHalfArray = dataArray.slice((dataArray.length/2) - 1, dataArray.length - 1);
var overallAvg = avg(dataArray);
var lowerMax = max(lowerHalfArray);
var lowerAvg = avg(lowerHalfArray);
var upperMax = max(upperHalfArray);
var upperAvg = avg(upperHalfArray);
var lowerMaxFr = lowerMax / lowerHalfArray.length;
var lowerAvgFr = lowerAvg / lowerHalfArray.length;
var upperMaxFr = upperMax / upperHalfArray.length;
var upperAvgFr = upperAvg / upperHalfArray.length;
makeRoughGround(plane, modulate(upperAvgFr, 0, 1, 0.5, 4));
makeRoughGround(plane2, modulate(lowerMaxFr, 0, 1, 0.5, 4));
makeRoughBall(ball, modulate(Math.pow(lowerMaxFr, 0.8), 0, 1, 0, 8), modulate(upperAvgFr, 0, 1, 0, 4));
group.rotation.y += 0.005;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function makeRoughBall(mesh, bassFr, treFr) {
mesh.geometry.vertices.forEach(function (vertex, i) {
var offset = mesh.geometry.parameters.radius;
var amp = 7;
var time = window.performance.now();
vertex.normalize();
var rf = 0.00001;
var distance = (offset + bassFr ) + noise.noise3D(vertex.x + time *rf*7, vertex.y + time*rf*8, vertex.z + time*rf*9) * amp * treFr;
vertex.multiplyScalar(distance);
});
mesh.geometry.verticesNeedUpdate = true;
mesh.geometry.normalsNeedUpdate = true;
mesh.geometry.computeVertexNormals();
mesh.geometry.computeFaceNormals();
}
function makeRoughGround(mesh, distortionFr) {
mesh.geometry.vertices.forEach(function (vertex, i) {
var amp = 2;
var time = Date.now();
var distance = (noise.noise2D(vertex.x + time * 0.0003, vertex.y + time * 0.0001) + 0) * distortionFr * amp;
vertex.z = distance;
});
mesh.geometry.verticesNeedUpdate = true;
mesh.geometry.normalsNeedUpdate = true;
mesh.geometry.computeVertexNormals();
mesh.geometry.computeFaceNormals();
}
audio.play();
};
}
window.onload = vizInit();
document.body.addEventListener('touchend', function(ev) { context.resume(); });
//some helper functions here
function fractionate(val, minVal, maxVal) {
return (val - minVal)/(maxVal - minVal);
}
function modulate(val, minVal, maxVal, outMin, outMax) {
var fr = fractionate(val, minVal, maxVal);
var delta = outMax - outMin;
return outMin + (fr * delta);
}
function avg(arr){
var total = arr.reduce(function(sum, b) { return sum + b; });
return (total / arr.length);
}
function max(arr){
return arr.reduce(function(a, b){ return Math.max(a, b); })
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.3/dat.gui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.3.0/simplex-noise.min.js"></script>
:root{
--bgColor : hsla(242, 86%, 6%, 1);
--bgColorLight : hsla(242, 86%, 24%, 1);
--textColor : hsla(242, 86%, 88%, 1);
--textColorDark : hsla(242, 36%, 0%, 1);
--paperColor: hsla(242, 86%, 44%, 1);
--paperColorDark: hsla(242, 86%, 34%, 1);
--shadowColorFaint: hsla(0, 0%, 0%, 0.2);
}
::selected{
color: var(--textColorDark);
}
html, body{
margin: 0;
padding: 0;
overflow: hidden;
height: 100vh;
width: 100vw;
background: var(--bgColor);
background: linear-gradient(135deg, var(--bgColor), var(--bgColorLight));
color: var(--textColor);
font-family: 'Saira', sans-serif;
position: relative;
}
*{
box-sizing: border-box;
transition: all 0.12s cubic-bezier(0.42, 0.54, 0.22, 1.26);
}
#canvas {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
audio {
position: fixed;
left: 10px;
bottom: -10px;
width: calc(100% - 20px);
}
audio.active{
bottom: 10px;
}
#thefile{
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: 1;
}
label.file{
display: inline-block;
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
padding: 1rem 2rem;
border-radius: 4px;
background: var(--paperColor);
color: var(--textColor);
font-size: 1.25em;
font-weight: 700;
box-shadow: 0 20px 60px var(--shadowColorFaint);
cursor: pointer;
}
label.file:hover{
background: var(--paperColorDark);
transform: translate3d(-50%, -55%, 0);
}
label.file:active{
background: var(--paperColorDark);
transform: translate3d(-50%, -45%, 0);
}
label.file.normal{
transform: translate3d(10%, 50%, 0);
padding: 0.2rem 2rem;
font-size: 1rem;
top: 0;
left: 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment