Skip to content

Instantly share code, notes, and snippets.

@srajagop
Created August 6, 2015 20:17
Show Gist options
  • Select an option

  • Save srajagop/5d6690601f3bcae65df7 to your computer and use it in GitHub Desktop.

Select an option

Save srajagop/5d6690601f3bcae65df7 to your computer and use it in GitHub Desktop.

Revisions

  1. srajagop created this gist Aug 6, 2015.
    11 changes: 11 additions & 0 deletions Prism music visualizer.markdown
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    Prism music visualizer
    ----------------------
    My first music visualizer! Made using DOM with a lot of customization ability. Play with the controls and have some fun!

    For solid colors (including animating it through the spectrum), the darks are the lows and the light ones are the highs. For the rainbow configuration, the red is low and pink is high.

    Based on https://24.media.tumblr.com/cca3fc8094b89dc1a597e039a413c0f4/tumblr_n4euxkZcjo1sns7veo1_500.gif

    A [Pen](http://codepen.io/Zeaklous/pen/zGePqa) by [Zach Saucier](http://codepen.io/Zeaklous) on [CodePen](http://codepen.io/).

    [License](http://codepen.io/Zeaklous/pen/zGePqa/license).
    15 changes: 15 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    .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
    344 changes: 344 additions & 0 deletions script.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,344 @@
    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);
    }
    };
    1 change: 1 addition & 0 deletions scripts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    <script src="http://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
    99 changes: 99 additions & 0 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,99 @@
    // 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;
    }
    }