<canvas id="c"></canvas>

<script id="shader-fs" type="x-shader/x-fragment">
  #ifdef GL_ES
  precision highp float;
  #endif
  varying vec4 vColor;

  void main(void) {
    gl_FragColor = vColor;
  }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 vertexPosition;
  attribute vec4 a_color;

  uniform mat4 modelViewMatrix;
  uniform mat4 perspectiveMatrix;

  varying   vec4 vColor;

  void main(void) {
    gl_Position = perspectiveMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);
    vColor = a_color;
  }
</script>
body{
  overflow: hidden;
  margin: 0;
  background: #000;
}
(function(w) {

    var canvas, 
        gl, 
        audio,
        audio_ctx,
        audioSrc,
        gainNode,
        audio_buffer,
        startedAt,
        pausedAt,
        analyser,
        frequencyData,
        canvas_width,
        canvas_height,
        vShader,
        fShader,
        ratio,
        vertexBuffer,
        colorBuffer,
        vertices,
        modulated,
        velocities,
        freqArr,
        colorArr,
        thetaArr,
        velThetaArr,
        velRadArr,
        boldRateArr,
        playing = false,
        fieldOfView = 30.0, 
        nearPlane = 1.0,
        farPlane = 10000.0,
        aspectRatio,
        top,
        bottom,
        right,
        left,
        uModelViewMatrix,
    perspectiveMatrix = [
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ];

    var settings = {
        numLines: 15000,
        color: { r: 255, g: 255, b: 255 },
        removeRed: 0.01,
        removeGreen: 0.02,
        removeBlue: 0.03,
        trebReact: 2,
        midReact: 1.5,
        bassReact: 1,
        invert: false,
        volume: 1
    };

    var target = [],
        randomTargetXArr = [],
        randomTargetYArr = [];

    var count = 0;
    var cn = 0;

    var audio_available = false;

    function init() {
        
        canvas = document.getElementById("c");
        gl = canvas.getContext("experimental-webgl");

        if (!gl) {
            alert("There's no WebGL context available.");
            return;
        }

        vShader = load_shader("shader-vs", gl.VERTEX_SHADER);

        if (!vShader) {
            alert("Couldn't compile the vertex shader.");
            return;
        }

        fShader = load_shader("shader-fs", gl.FRAGMENT_SHADER);

        if (!fShader) {
            alert("Couldn't compile the fragment shader.");
            return;
        }

        gl.program = gl.createProgram();
        gl.attachShader(gl.program, vShader);
        gl.attachShader(gl.program, fShader);
        gl.linkProgram(gl.program);

        if (!gl.getProgramParameter(gl.program, gl.LINK_STATUS)) {
            gl.deleteProgram(gl.program);
            gl.deleteProgram(vShader);
            gl.deleteProgram(fShader);
            alert("Unable to initialise shaders");
            return;
        }

        gl.useProgram(gl.program);

        var vertexPosition = gl.getAttribLocation(gl.program, "vertexPosition");
        var colorAttrib = gl.getAttribLocation(gl.program, "a_color");

      var uPerspectiveMatrix = gl.getUniformLocation(gl.program, "perspectiveMatrix");
      uModelViewMatrix = gl.getUniformLocation(gl.program, "modelViewMatrix");

        vertexBuffer = gl.createBuffer();
        colorBuffer = gl.createBuffer();

        setup_vertices();

        gl.uniformMatrix4fv(uPerspectiveMatrix, false, new Float32Array(perspectiveMatrix));

        gl.enableVertexAttribArray(vertexPosition);
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.vertexAttribPointer(vertexPosition, 3.0, gl.FLOAT, false, 0, 0); //28, 0
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);

        gl.enableVertexAttribArray(colorAttrib);
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.vertexAttribPointer(colorAttrib, 4.0, gl.FLOAT, false, 0, 0);
        gl.bufferData(gl.ARRAY_BUFFER, colorArr, gl.DYNAMIC_DRAW);

        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clearDepth(1.0);

        gl.enable(gl.BLEND);
        gl.disable(gl.DEPTH_TEST);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        handle_resize();

        var resize = debounce(handle_resize, 20);

        w.addEventListener("resize", resize);
        canvas.addEventListener("click", toggle);

        try {

            if (typeof(AudioContext) != "undefined") {
              audio_ctx = new AudioContext();
            }
            else if (typeof(webkitAudioContext) != "undefined") {
              audio_ctx = new webkitAudioContext();
            }
            else if (typeof(mozAudioContext) != "undefined") {
              audio_ctx = new mozAudioContext();
            }

            //audio = document.getElementById("music");

            var xmlHTTP = new XMLHttpRequest();
            
            //xmlHTTP.open('GET', "https://www.aecend.com/images/to_cotb.mp3", true);
          
          xmlHTTP.open('GET', "https://sentinelc3.s3.amazonaws.com/tcob.mp3", true);

          
            // Keys N Krates's - Are We Faded:
            //xmlHTTP.open('GET', "https://myrror.co/etc/are_we_faded.mp3", true);

            xmlHTTP.responseType = 'arraybuffer';

            xmlHTTP.onload = function(e) {
             
              analyser = audio_ctx.createAnalyser();
              //audioSrc = audio_ctx.createMediaElementSource(audio);
              
              audio_ctx.decodeAudioData(this.response, function(buffer) {

                audioSrc = audio_ctx.createBufferSource();
                gainNode = audio_ctx.createGain();
                //gainNode.gain.value = 1;
                audioSrc.buffer = buffer;
                audio_buffer = buffer;

                audioSrc.connect(gainNode);
                gainNode.connect(analyser);
                analyser.connect(audio_ctx.destination);
                analyser.fftSize = 64;

                frequencyData = new Uint8Array(analyser.frequencyBinCount);
                
                audio_available = true;
                if (playing) {
                    startedAt = pausedAt ? Date.now() - pausedAt : Date.now();
                    audioSrc.start();
                }

              });
              
            };
      
            xmlHTTP.send();

        }catch (e) {
            console.log("Couldn't load audio.");
        }

        w.onload = play;
    }

    function play() {
        playing = true;
        if (audio_available) {
            startedAt = pausedAt ? Date.now() - pausedAt : Date.now();
            audioSrc = audio_ctx.createBufferSource();
            audioSrc.buffer = audio_buffer;
            audioSrc.connect(gainNode);
            pausedAt ? audioSrc.start(0, pausedAt / 1000) : audioSrc.start();
        }
        
        animate();
    }

    function pause() {
        playing = false;
        if (audio_available) {
            pausedAt = Date.now() - startedAt;
            //console.log(frequencyData);
            //console.log(settings.color);
            //console.log("Vertices: " + (vertices.length / 3));
            //console.log(vertices[119997] + ", " + vertices[119998] + ", " + vertices[119999]);
            audioSrc.stop();
        }
    }

    function toggle() {
        playing ? pause() : play();
    }

    function handle_resize() {
        canvas_width = window.innerWidth;
        canvas_height = window.innerHeight;
        canvas.width = canvas_width;
        canvas.height = canvas_height;
        gl.viewport(0, 0, canvas.width, canvas.height);

        aspectRatio = canvas.width / canvas.height;
        top = nearPlane * Math.tan(fieldOfView * Math.PI / 360.0),
        bottom = -top,
        right = top * aspectRatio,
        left = -right;

        var a = (right + left) / (right - left);
        var b = (top + bottom) / (top - bottom);
        var c = (farPlane + nearPlane) / (farPlane - nearPlane);
        var d = (2 * farPlane * nearPlane) / (farPlane - nearPlane);
        var x = (2 * nearPlane) / (right - left);
        var y = (2 * nearPlane) / (top - bottom);

        modelViewMatrix = [
            x, 0, a, 0,
            0, y, b, 0,
            0, 0, c, d,
            0, 0, -1, 0
        ];

        gl.uniformMatrix4fv(uModelViewMatrix, false, new Float32Array(modelViewMatrix));

    }

    w.Flurry = {
        initialize: init,
        pause: pause,
        play: play,
        toggle: toggle,
        settings: settings
    }

    function load_shader(el, type) {
        var shaderScript = document.getElementById(el);
        var shader = gl.createShader(type);

        gl.shaderSource(shader, shaderScript.text);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            gl.deleteShader(shader);
            return false;
        }

        return shader;
    }

    function setup_vertices() {

        vertices = [];
        velThetaArr = [];
        velRadArr = [];
        velocities = [];
        thetaArr = [];
        freqArr = [];
        boldRateArr = [];
        colorArr = [];

        for (var ii = 0; ii < settings.numLines; ii++) {
            var rad = 0.3; //( 0.1 + .2 * Math.random() );
            var theta = Math.random() * Math.PI * 2;
            var velTheta = Math.random() * Math.PI * 2 / 30;
            var freq = Math.random() * 0.12 + 0.03;
            var boldRate = Math.random() * .04 + .01;
            //var randomPosX = (Math.random() * 2  - 1) * aspectRatio;
            //var randomPosY = Math.random() * 2 - 1;
            randomPosX = 0;
            randomPosY = 0;

            vertices.push(rad * Math.cos(theta), rad * Math.sin(theta), 1.83); // X, Y, Z
            colorArr.push(settings.color.r / 256, settings.color.g / 256, settings.color.b / 256, 1); // R, G, B, A

            vertices.push(rad * Math.cos(theta), rad * Math.sin(theta), 1.83); // X, Y, Z
            colorArr.push(settings.color.r / 256, settings.color.g / 256, settings.color.b / 256, 1); // R, G, B, A

            thetaArr.push(theta);
            velThetaArr.push(velTheta);
            velRadArr.push(rad);
            freqArr.push(freq);
            boldRateArr.push(boldRate);

            randomTargetXArr.push(randomPosX);
            randomTargetYArr.push(randomPosY);

        }

        freqArr = new Float32Array(freqArr);
        vertices = new Float32Array(vertices);
        modulated = new Float32Array(vertices);
        velocities = new Float32Array(velocities);
        colorArr = new Float32Array(colorArr);

        thetaArr = new Float32Array(thetaArr);
        velThetaArr = new Float32Array(velThetaArr);
        velRadArr = new Float32Array(velRadArr);

    }

    function animate() {
        if (!playing) return;
        requestAnimationFrame(animate);
        try {
            if (audio_available) {
                gainNode.gain.value = settings.volume;
                analyser.getByteFrequencyData(frequencyData);
            }
        }catch (e) {
            audio_available = false;
        }
        drawScene();
    }

    function drawScene() {

        var i, p, bp, cp;
        var px, py;
        var pTheta;
        var rad;
        var num;
        var rand;
        var targetX, targetY, X2, Y2;

        for (i = 0; i < settings.numLines * 2; i += 2) {
            var range, val, freq_data;

            if (audio_available) {
                range = Math.floor(analyser.frequencyBinCount * (i / (settings.numLines * 2)));

                if (i / (settings.numLines * 2) < 1/3) {
                  val = settings.bassReact;
                }else if (i / (settings.numLines * 2) > 3/5) {
                  val = settings.trebReact
                }else{
                  val = settings.midReact;
                }

              freq_data = ((frequencyData[range]) * val) / 256;

            }else{
                freq_data = 0.8;
            }
            
            bp = i * 3;
            cp = i * 4;

            vertices[bp] = vertices[bp + 3];
            vertices[bp + 1] = vertices[bp + 4];

            num = parseInt(i / 2);
            pTheta = thetaArr[num];
            rad = velRadArr[num] * (freq_data) * 5;  

            pTheta = pTheta + velThetaArr[num];
            thetaArr[num] = pTheta;

            targetX = rad * Math.cos(pTheta);
            targetY = rad * Math.sin(pTheta);

            px = vertices[bp + 3];
            rand = (Math.random() * .1 + .1);
            px += (targetX - px) * rand;
            vertices[bp + 3] = px;

            py = vertices[bp + 4];
            rand = (Math.random() * .02 + .02);
            py += (targetY - py) * rand + (freq_data * 0.01 * (Math.random() > 0.5 ? 1 : -1));
            vertices[bp + 4] = py;


      if (audio_available) {
        var value = (frequencyData[range] / 256);
                value = settings.invert ? value : 1 - value;

        let rgb;

        if (i / (settings.numLines * 2) < 1/3) {
          rgb = { r: 121, g: 240, b: 56 }; //bass
        }else if (i / (settings.numLines * 2) > 3/5) {
          rgb = { r: 255, g: 255, b: 255 }; //treb
        }else{
          rgb = { r: 240, g: 20, b: 0 }; //mid
        }

        var col = [
          Math.min(value * (rgb.r / 256)),
          Math.min(value * (rgb.g / 256)),
          Math.min(value * (rgb.b / 256))
        ];


            colorArr[cp] = col[0]; 
            colorArr[cp + 1] =  col[1];
            colorArr[cp + 2] =  col[2];
            colorArr[cp + 3] = 1;
              
            colorArr[cp + 4] = col[0]; 
            colorArr[cp + 5] =  col[1];
            colorArr[cp + 6] =  col[2];
            colorArr[cp + 7] = 1;
      }
 
        }

        gl.lineWidth(1.5);
        
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);

        if (audio_available) {
          gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
          gl.bufferData(gl.ARRAY_BUFFER, colorArr, gl.DYNAMIC_DRAW);
    }

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        gl.drawArrays(gl.LINES, 0, settings.numLines * 2); //Wasn't drawing everything...

        gl.flush();
    }

    function debounce(func, wait, immediate) {
        var timeout;

        return function() {
            var context = this, 
                args = arguments;

            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };

            var callNow = immediate && !timeout;
            clearTimeout(timeout);

            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };

}(window));

Flurry.initialize();

var gui = new dat.GUI();
var colourGUI = gui.addColor(Flurry.settings, 'color');
gui.add(Flurry.settings, 'numLines').min(1).max(15000).step(1);
gui.add(Flurry.settings, 'removeRed').min(0.00).max(1.00).step(0.01);
gui.add(Flurry.settings, 'removeGreen').min(0.00).max(1.00).step(0.01);
gui.add(Flurry.settings, 'removeBlue').min(0.00).max(1.00).step(0.01);
gui.add(Flurry.settings, 'invert');
gui.add(Flurry.settings, 'bassReact').min(0).max(2).step(0.1);
gui.add(Flurry.settings, 'midReact').min(0).max(2).step(0.1);
gui.add(Flurry.settings, 'trebReact').min(0).max(2).step(0.1);
gui.add(Flurry.settings, 'volume').min(0).max(1).step(0.1);

gui.close();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js