<!-- Particles -->
<script type = "x-shader/x-vertex" id = "vertex">
      
    attribute float size;
    uniform float bass;
          
    void main() {
              
        vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
      
        gl_PointSize = size * bass * (100.0 / length(mvPosition.xyz));
      
        gl_Position = projectionMatrix * mvPosition;
    
    }
    
</script>
    
<script type = "x-shader/x-fragment" id = "fragment">
    
    uniform vec3 color;
    uniform sampler2D texture;
    uniform float opacity;
    
    void main() {
    
        vec4 outColor = texture2D(texture, gl_PointCoord);
    
        gl_FragColor = outColor * vec4(color, opacity);
            
    }
    
</script>

<body>
    
    <div class = "play">Play</div>
  
</body>
@import url(https://fonts.googleapis.com/css?family=Carrois+Gothic);

*, *:after, *:before {
  
  box-sizing: border-box; 
  
}

html {

  width: 100%;
  height: 100%;
    
  overflow: hidden;

}

.play {
  
  font-family: 'Carrois Gothic', sans-serif;
  font-size: 1.4em;
    
  text-align: center; 
  text-transform: uppercase;
  
}

.play {
  
  color: #ffffff;

  position: absolute;
  
  width: 80px;
  height: 30px;
  
  top: 90%;
  left: 50%;
  
  margin: -15px 0 0 -45px;
  
  border: medium solid rgba(255, 255, 255, 0.3);
  
  z-index: 2;
    
  -webkit-transition: border 1s;
     -moz-transition: border 1s;
      -ms-transition: border 1s;
       -o-transition: border 1s;
          transition: border 1s;
  
}

.play:hover {
  
  border: medium solid rgba(255, 255, 255, 1.0);
  
  cursor: pointer;
        
}
/*
 * Copyright MIT © <2013> <Francesco Trillini>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

var self = window;

(function(self) { 
  
  var container, scene, camera, renderer, stats, cubes = [], mouse = { x: 0, y: 0 }, group, material = 0, particles, uniforms, mouseDown = start = play = false, lastTransition = Date.now();
      
  // Mouse wheel and leap settings    
  var cameraZ = 3000, min = cameraZ - 500, max = min * 3;
      
  // Audio data     
  var context, buffer, source, stream, analyser, frequency = [], playButton, createObjectURL, codec = ['.ogg', '.mp3'], mimeType = ['audio/ogg', 'audio/mpeg'], URL = 'http://francescotrillini.it/assets/Soundwaves_Remix';    
        
  // Dat GUI default value
  var showStats = false;      
      
  /*
   * List colors.
   */
    
  var colors = {
    
    lightRed: [
      
      '#ffffff',
      '#e50000'
      
    ],
    
    lightViolet: [
      
      '#ffffff',
      '#ee82ee'
            
    ],
    
    lightBlue: [
    
      '#ffffff',
      '#00bcff'
    
    ]
  
  };
  
  /*
   * Settings.
   */
    
  var Settings = function() {
      
    this.showStats = false;
    
    this.enableStats = function(value) {
      
      showStats = value;
      
      showStats ? stats.domElement.style.visibility = 'visible' : stats.domElement.style.visibility = 'hidden';
      
    };
    
    this.fullScreen = function() {
    
      var container, fullscreen;

      container = document.documentElement;
      fullscreen = (container.webkitRequestFullscreen || container.mozRequestFullScreen || container.msRequestFullscreen || container.requestFullscreen);

      fullscreen.call(document.documentElement);
      
    };
          
  };  
  
  window.addEventListener ? window.addEventListener('load', init, false) : window.onload = init;
  
    /*
   * Init.
   */

  function init() {
    
    if(!window.WebGLRenderingContext) { 
        
      console.error("Sorry, your browser doesn't support WebGL."); 
          
      return; 
          
    } 
        
    initAudio();    
        
    var settings = new Settings();
    var GUI = new dat.GUI();
            
    // Dat GUI main
    GUI.add(settings, 'showStats').onChange(settings.enableStats);
    GUI.add(settings, 'fullScreen');
      
    var body = document.querySelector('body');
  
    container = document.createElement('div');
    
    container.width = innerWidth;
    container.height = innerHeight;
    
    container.style.position = 'absolute';
    container.style.top = 0;
    container.style.bottom = 0;
    container.style.left = 0;
    container.style.right = 0;
    container.style.zIndex = -1;
    container.style.overflow = 'hidden';
    
    container.style.background = '-webkit-radial-gradient(#ffcc99, #ff9933)';
    container.style.background = '-moz-radial-gradient(#ffcc99, #ff9933)';
    container.style.background = '-ms-radial-gradient(#ffcc99, #ff9933)';
    container.style.background = '-o-radial-gradient(#ffcc99, #ff9933)';
    container.style.background = 'radial-gradient(#ffcc99, #ff9933)';
      
    body.appendChild(container);
    
    // Setup
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.z = cameraZ;
    
    scene = new THREE.Scene();
    
    // Lights
    hemisphereLight = new THREE.HemisphereLight(0xffffff, 100);
    scene.add(hemisphereLight);
    
    pointLight = new THREE.PointLight(0xffffff, 3.0);
    scene.add(pointLight);
    
    group = new THREE.Object3D();
  
    for(var cube = 0, len = 80; cube < len; cube++) {
      
      var redMaterial = new THREE.MeshPhongMaterial({
          
        color: colors.lightRed[cube % colors.lightRed.length],  
          
        shading: THREE.FlatShading, 
        blending: THREE.NormalBlending, 
        
        depthTest: true,
        transparent: false
            
      });
                                      
      var violetMaterial = new THREE.MeshPhongMaterial({

        color: colors.lightViolet[cube % colors.lightViolet.length], 
            
        shading: THREE.FlatShading, 
        blending: THREE.NormalBlending, 
        
        depthTest: true,
        transparent: false
            
      });
      
      var blueMaterial = new THREE.MeshPhongMaterial({

        color: colors.lightBlue[cube % colors.lightBlue.length], 
            
        shading: THREE.FlatShading, 
        blending: THREE.NormalBlending, 
        
        depthTest: true,
        transparent: false    
            
      });
            
      var geometry = new THREE.TetrahedronGeometry(50, 1);
      geometry.dynamic = true;
      
      var mesh = new THREE.Mesh(geometry, violetMaterial);
          
      mesh.position.x = Math.random() * 2000 - 1000;
      mesh.position.y = Math.random() * 2000 - 1000;
      mesh.position.z = Math.random() * 2000 - 1000;
      mesh.rotation.x = Math.random() * 2 * Math.PI;
      mesh.rotation.y = Math.random() * 2 * Math.PI;
      mesh.rotation.z = Math.random() * 2 * Math.PI;
                    
      cubes.push({
          
        mesh: mesh, 
        
        materials: [
            
          redMaterial,  
          violetMaterial, 
          blueMaterial
          
        ],    
        
        band: Math.floor(Math.random(128)),
        scale: 0,
        level: ~~(Math.random() * (7 - 2 + 1) + 2)
          
      });
      
      group.add(mesh);

    }

    scene.add(group);
    
    // Init particles and mesh
    loadParticles();
    updateTransitions(0);
    changeMaterial(~~(Math.random() * 3));

    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    
    container.appendChild(renderer.domElement);

    // Stats
    stats = new Stats();
    
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.top = '0px';
    
    container.appendChild(stats.domElement);
    
    // Hide stats
    settings.enableStats();
    
    // Listeners
    container.addEventListener('mousedown', onMouseDown, false);
    container.addEventListener('touchstart', onTouchStart, false);
  
    document.addEventListener('drop', onDrop, false);
    document.addEventListener('dragover', onDragOver, false);
    
    document.addEventListener('mousewheel', onMouseWheel, false );
    document.addEventListener('DOMMouseScroll', onMouseWheel, false);

    playButton = document.querySelector('.play'); 
    playButton.addEventListener('click', onClick, false);
    
    enableLeap();
    render();
  
    window.onresize = onResize;

  }
  
  /*
   * Mouse down event.
   */

  function onMouseDown(event) {

    event.preventDefault();

    if(play)
    
      changeMaterial();
    
  }
  
  /*
   * Touch start event.
   */

  function onTouchStart(event) {

    event.preventDefault();

    if(play)
    
      changeMaterial();
    
  }
  
  /*
   * On drag over event.
   */
  
  function onDragOver(event) {
  
    event.stopPropagation();
    event.preventDefault();
    
    return false;
    
  }
  
  /*
   * On drop event.
   */
   
  function onDrop(event) {
  
    event.stopPropagation();
    event.preventDefault();
      
    // Read data as URL
    loadAudio(createObjectURL(event.dataTransfer.files[0]));
    
  }
  
  /*
   * On mouse wheel event.
   */
  
  function onMouseWheel(event) {
    
    if(cameraZ < min || cameraZ > max) 
    
      cameraZ = THREE.Math.clamp(cameraZ, min, max);
  
    // WebKit
    if(play && event.wheelDeltaY)
          
      cameraZ -=event.wheelDeltaY * 2;
    
    // Opera / Explorer 9
    else if(play && event.wheelDelta)
    
      cameraZ -= event.wheelDelta * 2;

    // Firefox
    else if(play && event.detail) 

      cameraZ -= event.detail * 2;
  
  }
  
  /*
   * On click event.
   */
  
  function onClick(event) {
              
    var triggeredEvent = event !== null ? this : playButton;
      
    document.querySelector('body').removeChild(triggeredEvent);
        
    connectSource();    
        
    if(source !== undefined) {
    
      play = true;
          
      container.style.cursor = 'pointer';
          
      source.play();
    
    }   
      
  }
  
  /*
   * Enable leap device if detected.
   */
  
  function enableLeap() {
  
    var controller = new Leap.Controller({ enableGestures: true });
    
    controller.on('frame', function(data) {
          
      if(cameraZ < min || cameraZ > max) 
    
        cameraZ = THREE.Math.clamp(cameraZ, min, max);  
        
      // Palm position  
      if(data.hands.length > 0) {
                       
        var hand = data.hands[0];
                        
        cameraZ += ~~(-hand.palmPosition[2]);
          
        }
      
      for(var i = 0; i < data.gestures.length; i++) {
        
        var gesture = data.gestures[i];
        
            var type = gesture.type;
            
        switch(type) {
            
                case 'screenTap':
          
            !play ? onClick(null) : changeMaterial();
                  
              break;
                  
            }
    
        }
    
    });
          
    // Connect controller   
    controller.connect();
  
  }

  /*
   * On resize event.
   */

  function onResize(event) {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    
    renderer.setSize(window.innerWidth, window.innerHeight);

  }
  
  /*
   * Init audio from a source.
   */
  
  function initAudio() {
    
    createObjectURL = (window.URL || window.webkitURL || {}).createObjectURL || function(){};
    
    if(!typeof document.createElement('audio').canPlayType === 'function') {
    
      console.error("Sorry, your browser doesn't support Web Audio API."); 
    
      return;
    
    }
    
    context = new (window.AudioContext || window.webkitAudioContext)();
    
    source = document.createElement('audio');   
    
    for(var fallback = 0; fallback < (source.canPlayType('audio/mpeg') === '' ? 1 : 2); fallback++)
     
      new Audio(fallback);
  
  }
  
  /*
   * Create an audio object from an AJAX stream.
     */
 
  function Audio(fallback) {
    
    var request = new XMLHttpRequest();
        
    request.open('GET', URL + codec[fallback], true);
    request.responseType = 'blob';
    
    request.onload = function(event) {
  
      if(this.readyState === 4 && this.status === 200) {
      
        stream = document.createElement('source');
        
        stream.src = createObjectURL(this.response);
        stream.type = mimeType[fallback];
    
        source.appendChild(stream);
        
      }
      
    };
    
    request.send();
    
  }
  
  /*
   * Load audio from a source (drag & drop).
   */
  
  function loadAudio(data) {
    
    var streams = source.childNodes.length;
    
    while(streams--)
    
      // Manually disconnect the source
      source.removeChild(source.childNodes[streams]);
    
        stream.src = data;      
    
    source.appendChild(stream);   
  
  }
  
  /*
   * Connect the source to analyzer.
   */
  
  function connectSource() {
    
    buffer = context.createMediaElementSource(source);
    
    analyser = context.createAnalyser();
        
    // Fast smoothing
    analyser.smoothingTimeConstant = 0.3;
    analyser.fftSize = 512;
    
    buffer.connect(analyser);
    analyser.connect(context.destination);
    
    frequency = new Uint8Array(analyser.frequencyBinCount);
            
  }
  
  /*
   * Load the particles.
   */
  
  function loadParticles() {
    
    // See: https://github.com/mrdoob/three.js/issues/687
    THREE.ImageUtils.crossOrigin = '';
            
    var texture = THREE.ImageUtils.loadTexture('http://francescotrillini.it/assets/particle.png');
    
    var attributes = {
    
      size: { type: 'f', value: [] }
          
    };
    
    uniforms = {
    
      color: { type: 'c', value: new THREE.Color(0x454545) },
      texture: { type: 't', value: texture },
      bass: {type: 'f', value: 0.0 },
      opacity: { type: 'f', value: 0.0 },
    
    };
    
    var shaderMaterial = new THREE.ShaderMaterial( {
    
      uniforms: uniforms,
      attributes: attributes,
      
      vertexShader: document.getElementById('vertex').textContent,
      fragmentShader: document.getElementById('fragment').textContent,
    
      blending: THREE.AdditiveBlending,
      transparent: true,
              
    });
    
    var geometry = new THREE.Geometry();
    geometry.verticesNeedUpdate = true;
        
    // Position
    for(particle = 0, len = 100; particle < len; particle++) 
              
      geometry.vertices.push(new THREE.Vector3(Math.random() * 10000 - 5000, Math.random() * 10000 - 5000, Math.random() * 10000 - 5000));
          
    // Size
    for(var index = 0, len = geometry.vertices.length; index < len; index++)
              
      attributes.size.value[index] = 100 + Math.random() * 100;
    
    particles = new THREE.PointCloud(geometry, shaderMaterial);
    scene.add(particles);
  
  }
  
  /*
   * Change the current material with the next one.
   */
  
  function changeMaterial(seed) {
  
    var cube = cubes.length;
    
    material += (seed !== undefined ? seed : 1);  
      
    while(cube--) {
      
      var object = cubes[cube];
      
      object.mesh.material = object.materials[material % object.materials.length];
      
    }
  
  }
  
  /*
   * Update the transitions (position, rotation).
   */
  
  function updateTransitions(schedule) {
    
    var size = schedule;
    
    // Random size before play
    if(size === 0) {
    
      var cube = cubes.length;
  
      while(cube--) {
        
        var object = cubes[cube];
        
        new TWEEN.Tween(object.mesh.scale).to({

          x: object.level / 1.35,
          y: object.level / 1.35,
          z: object.level / 1.35 
            
        }, 500).start();

      }
    
    }
    
    // Change position and rotation every two secs
    if((Date.now() - lastTransition > 0 && Date.now() - lastTransition < 1000 && start) || schedule !== undefined) {
    
      start = false;
      
      var cube = cubes.length;
      
      while(cube--) {
    
        var object = cubes[cube];
        
        // Random position
        new TWEEN.Tween(object.mesh.position).to({

          x: Math.random() * 2000 - 1000,
          y: Math.random() * 2000 - 1000,
          z: Math.random() * 2000 - 1000 
            
        }, 3000).start();
          
        // Random rotation        
        new TWEEN.Tween(object.mesh.rotation).to({
        
          x: Math.random() * 2 * Math.PI,
          y: Math.random() * 2 * Math.PI,
          z: Math.random() * 2 * Math.PI 
            
        }, 3000).start();

      }
    
    }
    
    // Reset 'em all
    if(Date.now() - lastTransition > 2000) {
    
      lastTransition = Date.now();
      
      start = true;
      
    }
    
  }

  /*
   * Render the animation.
   */

  function render() {

    requestAnimationFrame(render);  
          
    updateTransitions();
    TWEEN.update();
    
    if(!play) { 
      
      group.rotation.x += 0.006;
      group.rotation.y += 0.006;
      
    }
           
    if(play && frequency.length > 0) {
          
      // Zoom   
      camera.position.z += (THREE.Math.clamp(cameraZ, min, max) - camera.position.z) * 0.1;
      
      var cube = cubes.length;
      
      while(cube--) {
        
        var object = cubes[cube];
     
        var currentLevel, band, rotation;
        
        // Frequency
        currentLevel = (frequency[object.band] / 256) * object.level;
        band = currentLevel < 1 ? 1 : currentLevel;
      
        object.scale += (band - object.scale) * 0.2;
                
        group.rotation.x += object.scale * 0.00012;
        group.rotation.y += object.scale * 0.00012;
        
        particles.rotation.copy(group.rotation);
        
        particles.rotation.x *= 0.4;
        particles.rotation.y *= 0.4;
        
        uniforms.bass.value += (object.scale / 2.7 - uniforms.bass.value) * 0.5;
        uniforms.opacity.value = Math.max(0.0, currentLevel / Math.min(12, currentLevel));
        
        // Scale fov            
        camera.fov += (75 - object.scale * 3 - camera.fov) * 0.008;
        camera.updateProjectionMatrix();
        
      }
      
      analyser.getByteFrequencyData(frequency);

    }
    
    camera.lookAt(scene.position);
            
    stats.update();
                    
    renderer.render(scene, camera);
    
  }

})(self);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/r68/three.min.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16395/tween.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/stats.js/r11/Stats.js
  4. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js
  5. https://js.leapmotion.com/0.2.1/leap.min.js