Skip to content

Instantly share code, notes, and snippets.

@srajagop
Created August 6, 2015 20:17
Show Gist options
  • Save srajagop/5d6690601f3bcae65df7 to your computer and use it in GitHub Desktop.
Save srajagop/5d6690601f3bcae65df7 to your computer and use it in GitHub Desktop.
Prism music visualizer
.musicControls
input#audiofile(type='file')
//- button.listenButton ...or listen to one of mine // Silly CORS
button.playPauseButton ▶
label#loading(for="audiofile") Select an audio file...
// This should match the values in the js
- var maxSideNum = 24
- var maxRectangleNum = 24
.prism
- for (var x = 0; x < maxSideNum; x++)
.side.hide
- for (var y = 0; y < maxRectangleNum; y++)
.rectangle.hide
var maxSideNum = 24,
maxRectangleNum = 24;
// Dat.gui setup
var Options = function() {
this.height = 400;
this.radius = 185;
this.sideCount = 12;
this.rectangleCount = 12;
this.rectangleWidth = 80;
this.vertMargin = 10;
this.borderWidth = 3;
this.color = 200;
this.solidBG = false;
this.rainbowMode = false;
this.animateThroughSpectrum = false;
this.fade = false;
};
window.onload = function() {
// dat.gui setup
var myOptions = new Options(),
gui = new dat.GUI(),
f1 = gui.addFolder('Prism Controls'),
f2 = gui.addFolder('Rectangle Controls'),
f3 = gui.addFolder('Color Controls'),
mySideCount = f1.add(myOptions, 'sideCount', 3, maxSideNum).step(1),
myRadius = f1.add(myOptions, 'radius', 30, 600).step(15),
myHeight = f1.add(myOptions, 'height', 50, 750).step(50),
myRectangleCount = f2.add(myOptions, 'rectangleCount', 3, maxRectangleNum).step(1),
myRectangleWidth = f2.add(myOptions, 'rectangleWidth', 1, 100).step(5),
myVertMargin = f2.add(myOptions, 'vertMargin', 0, 15).step(1),
myBorderWidth = f2.add(myOptions, 'borderWidth', 0, 15).step(1),
myColor = f3.add(myOptions, 'color', 0, 360).step(1),
mySolidBG = f3.add(myOptions, 'solidBG'),
myRainbow = f3.add(myOptions, 'rainbowMode'),
myAnimateThroughSpectrum = f3.add(myOptions, 'animateThroughSpectrum'),
myFade = f3.add(myOptions, 'fade');
f2.open();
var audio,
analyser,
audioctx,
sourceNode,
stream;
var audioInput = document.getElementById('audiofile'),
listenButton = document.querySelector(".listenButton"),
playPauseButton = document.querySelector(".playPauseButton");
var c = 0, // Used to change color over time
paused = true;
/*var myMusic = [
"http://zachsaucier.com/music/Initiation.mp3",
"http://zachsaucier.com/music/High%20Tide.mp3",
"http://zachsaucier.com/music/Dolphin%20Style.mp3",
"http://zachsaucier.com/music/King.mp3"
];*/
var prism = document.querySelector(".prism"),
sides = document.querySelectorAll(".side"),
rectangleArray = [maxSideNum],
lastTime = Date.now(),
timeGap = 50;
function rectangleSetup() {
for(var i = 0; i < maxSideNum; i++) {
rectangleArray[i] = sides[i].querySelectorAll(".rectangle");
}
}
rectangleSetup();
// dat.gui listeners
// f1 listeners
function sideCountChange(newCount) {
[].forEach.call(sides, function(elem, i) {
if(i < myOptions.sideCount) {
// The circle is inscribed inside of the prism, so we can use this formula to calculate the side length
var sideLength = 2 * (myOptions.radius) * Math.tan(Math.PI / newCount);
prism.style.width = sideLength + "px";
prism.style.left = "calc(50% - " + sideLength / 2 + "px)";
sides[i].style.transform = "rotateY(" + i * (360 / newCount) + "deg) translateZ(" + myOptions.radius + "px) rotateX(180deg)";
sides[i].classList.remove("hide");
} else {
sides[i].classList.add("hide");
}
});
}
mySideCount.onFinishChange(sideCountChange);
sideCountChange(myOptions.sideCount);
function radiusChange(newRadius) {
sideCountChange(myOptions.sideCount);
}
myRadius.onFinishChange(radiusChange);
radiusChange(myOptions.radius);
function heightChange(newHeight) {
prism.style.height = newHeight + "px";
prism.style.top = "calc(50% - " + newHeight / 2 + "px)"
rectangleCountChange(myOptions.rectangleCount);
}
myHeight.onFinishChange(heightChange);
heightChange(myOptions.height);
// f2 listeners
function rectangleCountChange(newCount) {
[].forEach.call(rectangleArray, function(side, i) {
[].forEach.call(side, function(rect, i) {
if(i < myOptions.rectangleCount) {
rect.style.height = (myOptions.height - myOptions.vertMargin) / newCount - myOptions.vertMargin + "px";
rect.classList.remove("hide");
} else {
rect.classList.add("hide");
}
});
});
}
myRectangleCount.onFinishChange(rectangleCountChange);
rectangleCountChange(myOptions.rectangleCount);
function rectangleWidthChange(newWidth) {
[].forEach.call(rectangleArray, function(side, i) {
[].forEach.call(side, function(rect, i) {
rect.style.width = newWidth + "%";
});
});
}
myRectangleWidth.onFinishChange(rectangleWidthChange);
rectangleWidthChange(myOptions.rectangleWidth);
function vertMarginChange(newMargin) {
[].forEach.call(rectangleArray, function(side, i) {
[].forEach.call(side, function(rect, i) {
rect.style.margin = newMargin + "px auto";
});
});
rectangleCountChange(myOptions.rectangleCount);
}
myVertMargin.onFinishChange(vertMarginChange);
vertMarginChange(myOptions.vertMargin);
function borderWidthChange(newWidth) {
[].forEach.call(rectangleArray, function(side, i) {
[].forEach.call(side, function(rect, i) {
rect.style.borderWidth = newWidth + "px";
});
});
}
myBorderWidth.onFinishChange(borderWidthChange);
borderWidthChange(myOptions.borderWidthChange);
// f3 listeners
function colorChange(value) {
if(!myOptions.rainbowMode)
[].forEach.call(sides, function(elem, i) {
sides[i].style.color = "hsl(" + value + ", 55%, " + (20 + (i / myOptions.sideCount) * 40) + "%)";
});
}
myColor.onFinishChange(colorChange);
colorChange(myOptions.color);
mySolidBG.onFinishChange(function(value) {
if(value === true)
prism.classList.add("solid");
else
prism.classList.remove("solid");
});
function goRainbowMode(value) {
[].forEach.call(sides, function(elem, i) {
if(value === true)
sides[i].style.color = "hsl(" + 360 * (i / myOptions.sideCount) + ", 80%, 55%)";
else
colorChange(myOptions.color);
});
}
myRainbow.onFinishChange(goRainbowMode);
function checkAnimateThroughSpectrum() {
if(myOptions.animateThroughSpectrum)
[].forEach.call(sides, function(elem, i) {
sides[i].style.color = "hsl(" + c + ", 80%, " + (20 + (i / myOptions.sideCount) * 40) + "%)";
});
else if(myOptions.rainbowMode)
goRainbowMode(true);
else
colorChange(myOptions.color);
}
// The music player listeners
audioInput.addEventListener('change', function(event) {
if(event.target.files[0]) {
// No error checking of file here, could be added
stream = URL.createObjectURL(event.target.files[0]);
loadSong(stream);
}
}, false);
if(listenButton)
listenButton.addEventListener('click', chooseOneOfMine, false);
playPauseButton.addEventListener('click', togglePlayPause, false);
// The music functions
function setup() {
// Stop the previous song if there is one
if(audio)
togglePlayPause();
audio = new Audio();
audioctx = new AudioContext();
analyser = audioctx.createAnalyser();
analyser.smoothingTimeConstant = 0.75;
analyser.fftSize = 512;
audio.addEventListener('ended', songEnded, false);
sourceNode = audioctx.createMediaElementSource(audio);
sourceNode.connect(analyser);
sourceNode.connect(audioctx.destination);
}
function loadSong(stream) {
setup();
audio.src = stream;
togglePlayPause();
document.body.classList.add('loaded');
update();
}
function songEnded() {
document.body.classList.remove('loaded');
togglePlayPause();
}
function togglePlayPause() {
if(paused) {
document.body.classList.add('loaded');
audio.play();
playPauseButton.innerText = "▮▮";
} else if(!audio.paused && !audio.ended) {
audio.pause();
playPauseButton.innerText = "▶";
}
paused = !paused;
}
function chooseOneOfMine() {
var num = Math.round(Math.random() * (myMusic.length - 1)) + 1;
loadSong(myMusic[num]);
}
// The drawing functions
function drawSide(freqSequence, freqPercent) {
// Get the number of rectangles based on the freqValue
drawRectangles(freqSequence, Math.floor(freqPercent * myOptions.rectangleCount / 100))
}
function drawRectangles(sideNum, numRectanglesShowing) {
for(var i = 0; i < myOptions.rectangleCount; i++) {
var cl = rectangleArray[sideNum][i].classList;
if(i <= numRectanglesShowing) {
cl.remove("hide");
cl.remove("faded");
} else {
if(!myOptions.fade)
cl.add("hide");
else
cl.add("faded");
}
}
}
var sectionsAveraged = [maxSideNum],
countSinceLast = [maxSideNum];
function update() {
var currTime = Date.now();
var freqArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(freqArray);
// Find the average of the values near to each other (grouping)
var average = 0,
count = 0,
numPerSection = 256 / (myOptions.sideCount + 1),
nextSection = numPerSection;
for (var i = 0; i < freqArray.length; i++) {
var v = freqArray[i];
average += Math.abs(128 - v); // 128 is essentially 0
count++;
if(i > nextSection) {
var currentSection = Math.floor(i / numPerSection - 1);
sectionsAveraged[currentSection] += average / count;
countSinceLast[currentSection]++;
average = 0;
count = 0;
nextSection += numPerSection;
}
}
// Find the average of the values since the last time checked per section (smoothing)
if(currTime - lastTime > timeGap) {
for (var i = 0; i < myOptions.sideCount; i++) {
drawSide(i, (sectionsAveraged[i] / countSinceLast[i]), c);
sectionsAveraged[i] = 0;
countSinceLast[i] = 0;
}
lastTime = currTime;
}
checkAnimateThroughSpectrum();
c += 0.5;
requestAnimationFrame(update);
}
};
<script src="http://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
// All of the commented out things are from the static, pre-dat.gui version that are no longer necessary
//$sideNum: 12;
//$rectNum: 12;
//$width: 100px;
//$margin: 1% * 7.3;
$border: 3px solid #4DA16F; /* Default all same color */
* { box-sizing: border-box; }
html, body { height:100%; }
body {
background: rgb(30,30,30);
font-family: 'Helvatica', sans-serif;
color: #FFF;
position:relative;
perspective: 1000px;
perspective-origin: 50% 50%;
}
.hide { display:none; }
.faded { opacity: 0; }
.prism {
position:absolute;
//top:calc(50% - #{$width * 2});
//left:calc(50% - #{$width * 0.5});
//width:$width;
//height:$width * 4;
transform-style: preserve-3d;
animation:rotate 8s linear infinite;
}
.side {
width:100%;
height:100%;
border-top: $border;
border-bottom: $border;
border-color:currentColor;
position:absolute;
}
.rectangle {
//height: (100% / $rectNum) - 2%;
//width:80%;
//margin:10px auto;
border: $border;
border-color:currentColor;
transition: opacity 150ms;
}
.solid .rectangle {
background:currentColor;
}
//@for $i from 1 through $sideNum {
// .side:nth-child(#{$i}) {
// color: hsl(144, 55%, 20% + ($i / $sideNum) * 40%);
//
// transform: rotateY($i * (360 / $sideNum) + deg) translateZ($width * 1.85) rotateX(180deg);
// }
// .side.rainbow:nth-child(#{$i}) {
// color: hsl(360*($i / $sideNum), 80%, 55%);
// }
//}
@keyframes rotate {
from { transform:rotateY(0); }
to { transform:rotateY(-360deg); }
}
.musicControls, label {
position: absolute;
border-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 10px;
}
.musicControls {
top: 20px;
left: 20px;
}
label {
left: 20px;
top: 100px;
font-style:italic;
}
body.loaded {
#loading {
display: none;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment