Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yportne8/98f24967b1c979e632525ecf04ef655e to your computer and use it in GitHub Desktop.
Save yportne8/98f24967b1c979e632525ecf04ef655e to your computer and use it in GitHub Desktop.
#Codevember 20 - Three.js audio visualizer
audio.audio
.container
.title
.controls
.btn.prev
i.material-icons skip_previous
.btn.play
i.material-icons play_arrow
.btn.next
i.material-icons skip_next
.progress-bar
.loader
p.loading-text Contacting Outer Space...
.spinner
.ring
.ring
.ring
a.make-google-happy-button Click to Start
a.codepen-link(href='https://www.codepen.io/seanfree' target='_blank')
const { PI } = Math;
const clamp = (n, min, max) => Math.min(Math.max(n, min), max);
const APP_DEFAULTS = {
dimensions: {
x: 0,
y: 0
},
camera: {
fov: 70,
nearPlane: 0.1,
farPlane: 10000,
aspectRatio: 0.7
}
};
class App {
constructor() {
window.onresize = () => {
this.setSize();
};
this.tick = 0;
this.props = Object.assign({}, APP_DEFAULTS);
this.initCamera();
this.initScene();
this.initLights();
this.initUI();
this.initAudio();
this.btnMakeGoogleHappy = document.querySelector(".make-google-happy-button");
this.btnMakeGoogleHappy.addEventListener("click", () => {
this.audioCtx.resume();
this.loadAudio();
this.btnMakeGoogleHappy.classList.add("hidden");
});
this.build();
this.render();
}
setSize() {
this.props.dimensions.x = window.innerWidth;
this.props.dimensions.y = window.innerHeight;
this.renderer.setSize(this.props.dimensions.x, this.props.dimensions.y);
this.camera.aspect = this.props.camera.aspectRatio =
this.props.dimensions.x / this.props.dimensions.y;
this.camera.updateProjectionMatrix();
}
initCamera() {
this.camera = new THREE.PerspectiveCamera(
this.props.camera.fov,
this.props.camera.aspectRatio,
this.props.camera.nearPlane,
this.props.camera.farPlane
);
this.camera.position.z = 200;
}
initScene() {
this.scene = new THREE.Scene();
this.scene.add(this.camera);
this.renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
});
this.setSize();
this.container = document.querySelector(".container");
this.container.appendChild(this.renderer.domElement);
}
initLights() {
this.mainLight = new THREE.HemisphereLight(0x000000, 0xffffff, 0.5);
this.mainLight.position.set(-100, -200, 0);
this.scene.add(this.mainLight);
this.ambientLight = new THREE.AmbientLight(0xefefef, 0.5);
this.ambientLight.position.set(0, -100, 0);
this.scene.add(this.ambientLight);
}
initUI() {
this.orbitControls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.title = document.querySelector(".title");
this.loader = document.querySelector(".loader");
this.progressBar = document.querySelector(".progress-bar");
this.controls = {
container: document.querySelector(".controls"),
prev: document.querySelector(".prev"),
play: document.querySelector(".play"),
next: document.querySelector(".next")
};
this.playIcon = this.controls.play.firstElementChild;
this.controls.play.onclick = () => {
if (this.playing && this.audioReady) {
this.playIcon.innerHTML = "play_arrow";
this.audio.pause();
} else if (!this.playing && this.audioReady) {
this.playIcon.innerHTML = "pause";
this.audio.play();
}
this.playing = !this.playing;
};
this.controls.prev.onclick = () => {
this.skipPrev();
};
this.controls.next.onclick = () => {
this.skipNext();
};
}
skipPrev() {
this.currentSong = this.currentSong > 1
? this.currentSong - 1
: this.files.length;
this.loadAudio();
}
skipNext() {
this.currentSong = this.currentSong < this.files.length
? this.currentSong + 1
: 1;
this.loadAudio();
}
initAudio() {
this.currentSong = 1;
this.playing = false;
this.audioReady = false;
this.URL = "https://res.cloudinary.com/sf-cloudinary/video/upload/v1525440296/";
this.files = [
"Washed_Out_-_Feel_it_all_around.mp3",
"Com_Truise_-_Colorvision.mp3",
"HOME_-_Resonance.mp3",
"Vulfpeck_-_Back_Pocket.mp3"
];
this.titles = this.files.map(fileName => {
return fileName.replace(/_/g, " ").substr(0, fileName.length - 4);
});
this.audio = document.querySelector(".audio");
this.audio.addEventListener("ended", () => {
this.audio.currentTime = 0;
this.skipNext();
});
this.audio.addEventListener("timeupdate", () => {
this.progressBar.style = `transform: scaleX(${this.audio.currentTime / this.audio.duration})`;
});
this.audioCtx = new AudioContext();
this.analyser = this.audioCtx.createAnalyser();
this.analyser.smoothingTimeConstant = 0.92;
this.analyser.minDecibels = -120;
this.analyser.maxDecibels = -5;
this.analyser.fftSize = 2048;
this.source = this.audioCtx.createMediaElementSource(this.audio);
this.source.connect(this.analyser);
this.analyser.connect(this.audioCtx.destination);
this.floatData = new Float32Array(this.analyser.frequencyBinCount);
}
loadAudio() {
let request = new XMLHttpRequest();
this.loader.classList.add("loading");
this.controls.container.classList.remove("ready");
this.playIcon.innerHTML = "play_arrow";
request.responseType = "blob";
request.open("GET", this.URL + this.files[this.currentSong - 1], true);
request.onprogress = () => {
if (request.response) {
this.audioReady = true;
this.playing = true;
this.playIcon.innerHTML = "pause";
this.loader.classList.remove("loading");
this.controls.container.classList.add("ready");
this.title.innerHTML = this.titles[this.currentSong - 1];
this.audio.src = window.URL.createObjectURL(request.response);
this.audio.play();
}
};
request.send();
}
build() {
this.cubes = new THREE.Group();
let rows = 26, cols = rows, dim = 8, spacing = 10;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
let geom = new THREE.CubeGeometry(dim, dim, dim),
mat = new THREE.MeshPhongMaterial({
color: 0xffffff
}),
cube = new THREE.Mesh(geom, mat),
posX = i * spacing - spacing * rows / 2,
posZ = j * spacing - spacing * cols / 2;
cube.position.set(posX, 0, posZ);
this.cubes.add(cube);
}
}
this.scene.add(this.cubes);
this.cubes.rotation.set(0, PI / -2, 0);
this.camera.position.set(0, 150, 250);
this.camera.lookAt(this.cubes.position);
}
render() {
this.update();
this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(this.render.bind(this));
}
update() {
this.tick++;
this.analyser.getFloatFrequencyData(this.floatData);
for (let i = 0, len = this.cubes.children.length; i < len; i++) {
let cube = this.cubes.children[i];
let val = (clamp(this.floatData[i], -120, -5) + 120) / 125,
scale = val * 30 + 0.1,
color = new THREE.Color(val + 0.1, (0.5 * val) + 0.1, 0.1);
cube.scale.y = scale;
cube.material.color = color;
}
this.cubes.rotation.y += 0.0015;
}
}
window.requestAnimationFrame = (() => {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
}
);
})();
window.onload = () => {
let app = new App();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@55ac3f233ed300da711f12d50e10f8e2f96a8f0b/examples/js/controls/OrbitControls.js"></script>
$blue: #2196f3
.container canvas
background: #060606
position: fixed
top: 0
left: 0
z-index: 0
width: 100vw
height: 100vh
.title
position: absolute
top: 30px
left: 30px
font-family: 'Josefin Sans', sans-serif
font-size: 1.2em
color: $blue
pointer-events: none
.controls
position: absolute
bottom: 30px
left: 30px
height: 30px
display: flex
align-items: center
justify-content: center
&.ready
.btn
cursor: pointer
pointer-events: auto
i
color: $blue
&:active
transform: scale(0.9)
.btn
height: 30px
width: 30px
margin-right: 10px
pointer-events: none
i
display: block
height: 100%
width: 100%
font-size: 30px
color: #afafaf
.progress-bar
position: absolute
left: 0
bottom: 0
width: 100%
height: 4px
background: $blue
transform: scaleX(0)
transform-origin: center left
transition: transform 0.5s linear
.loader
opacity: 0
transition: opacity 2s
&.loading
opacity: 1
.loading-text
animation: loading-text-animation 2s infinite
.ring
animation: spinner-animation 3s linear infinite
.loading-text
position: absolute
top: 50%
left: 50%
z-index: 1
font-family: 'Josefin Sans', sans-serif
font-size: 2em
color: $blue
cursor: default
transform: translateX(-50%) translateY(-50%)
.spinner
position: absolute
top: 30px
right: 30px
height: 40px
width: 40px
.ring
position: relative
top: 2.5%
left: 2.5%
height: 95%
width: 95%
border-radius: 50%
border: 2px solid
border-color: $blue transparent
box-sizing: border-box
.make-google-happy-button
position: absolute
padding: 1rem 1rem 0.8rem 1rem
top: 50%
left: 50%
transform: translateX(-50%) translateY(-50%)
z-index: 4
border: 1px solid $blue
color: $blue
font-family: 'Josefin Sans', sans-serif
font-size: 1.2em
cursor: pointer
transition: all 0.5s
&:hover
background: $blue
color: white
&.hidden
opacity: 0
transform: translateX(-50%) translateY(-50%) scale(0)
.codepen-link
position: absolute
bottom: 30px
right: 30px
height: 40px
width: 40px
z-index: 10
border-radius: 50%
box-sizing: border-box
background-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/544318/logo.jpg')
background-position: center center
background-size: cover
opacity: 0.4
transition: all 0.25s
&:hover
opacity: 0.8
box-shadow: 0 0 6px #efefef
@keyframes spinner-animation
to
transform: rotate(360deg)
@keyframes loading-text-animation
50%
opacity: 0.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment