Source: modules/core/vowelworm.game.js

/**
 * @param {Object=} options Configuration options
 * @param {VowelWorm.instance|Array.<VowelWorm.instance>} options.worms Any
 * VowelWorm instances to begin with
 * @param {number=} [options.width=700] The width of the game board
 * @param {number=} [options.height=500] The height of the game board
 * @param {number=} [options.background=0xFFFFFF] The background color of the game
 * @param {HTMLElement=} [options.element=document.body] What to append the graph to
 * @constructor
 * @name VowelWorm.Game
 */
window.VowelWorm.Game = function( options ) {
  "use strict";

  var game = this;
  game.width = options.width || 700;
  game.height = options.height || 500;
  game.x1 = -1;
  game.x2 = 2;
  game.y1 = -1;
  game.y2 = 3;
  
  game.minHz = 300;
  game.maxHz = 8000;
  game.fb = 10;

  /**
   * Represents the threshold in dB that VowelWorm's audio should be at in
   * order to to plot anything.
   * @see {@link isSilent}
   * @memberof VowelWorm.Game
   * @name silence
   * @type number
   */
  game.silence = -70;

  /**
   * Contains all instances of worms for this game
   * @type Array.<Object>
   * @private
   */
  var worms = [];

  /**
   * You can change this with game.ipa = true/false
   * @type boolean
   * @memberof VowelWorm.Game
   * @name ipa
   */
  var ipaEnabled = true;
 
  var ipaChart = new PIXI.DisplayObjectContainer();  

  /**
   * Begins animation of worms
   * @memberof VowelWorm.Game
   * @name play
   */
  game.play = function(){
    game.drawWorm();
    window.requestAnimationFrame(game.play);
  };

  /**
   * Inserts a worm into the ever-increasing frenzy of VowelWorm.
   * @param {window.VowelWorm.instance} worm
   * @memberof VowelWorm.Game
   * @name addWorm
   */
  game.addWorm = function(worm) {
   var container = {};
   container.worm = worm;
   container.circles = [];
   worms.push(container);
  };

  /**
   * @private
   */
  game.drawWorm = function(){
    var current_color = 0x00FF00;
    worms.forEach(function(container) {
      var worm = container.worm,
          circles = container.circles;

      var coords = getCoords(worm);

      if(coords!==null){
        var doRender = true;
        
        var x = coords.x;
        var y = coords.y;

        var circle = new PIXI.Sprite.fromImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZBAMAAAA2x5hQAAAAJ1BMVEUAAAD///////////////// //////////////////////////////+uPUo5AAAADHRSTlMAB+jh0bmoiU41HivIpyZzAAAAeklE QVQY02MAAsbpQYfCJwIZYE7LGSA40gjhLTsDBscWgDjcNmcgwBrEW3wGCg4DJRlzzsBAIgMDxxk4 OMHAIILgHRFgmHMGASYw1CDxChhikHgBDDpIPAWGM0jgAKocqj5UM1HtQ3ULijtR/YDqPwy/I8IF PcxQwxMAviHDkWPqCWAAAAAASUVORK5CYII=");
        circle.position.x = x;
        circle.position.y = y;
        circle.tint = current_color;

        circles.push(circle);
        
        game._stage.addChild(circle);
      }
      current_color = getNextColor(current_color);  
    });
    fadeOldCircles();
    game._renderer.render(game._stage);
  };

  Object.defineProperties(game, {
    ipa: {
      enumerable: true,
      get: function() {
        return ipaEnabled;
      },
      set: function(val) {
        var bool = !!val;
        if(ipaEnabled === bool) {
          return;
        }
        ipaEnabled = bool;
        
        if(ipaEnabled) {
          game._stage.addChild(ipaChart);
        }
        else
        {
          game._stage.removeChild(ipaChart);
        }
        window.requestAnimationFrame(game._renderer.render);
      }
    }
  });
  
  var getCoords = function(worm){
    var buffer = worm.getFFT();

    if(isSilent(buffer)) {
      return null;
    }

    var position = worm.getMFCCs({
      minFreq: game.minHz,
      maxFreq: game.maxHz,
      filterBanks: game.fb,
      fft: buffer
    });
    
    if(position.length) {
      var x = position[1];
      var y = position[2];

      //Pass in coords flipped 90 degrees
      var coords = adjustXAndY(y,-x);
      return coords;
    }else{
      return null;
    }
  };
  
  var adjustXAndY = function(x,y){    
    var xStart = game.x1;
    var xEnd = game.x2;
    var yStart = game.y1;
    var yEnd = game.y2;

    var xDist = game.width/(xEnd-xStart);
    var yDist = game.height/(yEnd-yStart);

    var adjustedX = (x-xStart)*xDist;
    var adjustedY = game.height-(y-yStart)*yDist;
    
    return {x:adjustedX,y:adjustedY};
  };

  /**
   * Determines whether, for plotting purposes, the audio data is silent or not
   * Compares against the threshold given for {@link game.silence}.
   * @param {Array.<number>|Float32Array} data - An array containing dB values
   * @return {boolean} Whether or not the data is essentially 'silent'
   */
  var isSilent = function(data) {
    for(var i = 0; i<data.length; i++) {
      if(data[i] > game.silence) {
        return false;
      }
    }
    return true;
  };

  var fadeOldCircles = function(){
    worms.forEach(function(container) {
      var circles = container.circles;
      for(var i=0; i<circles.length; i++){
        var obj = circles[i];
        
        obj.alpha = obj.alpha - .2;
        
        if(obj.alpha <= 0){
          game._stage.removeChild(obj);
          circles.splice(i, 1);
          i--;
        }
      }
    });
  };

  //Color Functions
  //Converts an integer representing a color to an integer representing a color 45 degrees away
  var getNextColor = function(old_color){
    if(typeof old_color == 'number'){
      old_color = old_color.toString(16);
      //Pad with 0's if necessary
      while(old_color.length<6){
        old_color = "0" + old_color;
      }
    }

    old_color = new tinycolor(old_color);
    var new_color = old_color.spin(45).toHex();
    new_color = parseInt(new_color,16);
    return new_color;
  };
 
  /**
   * Fills the IPA Chart. A constructor helper method.
   */
  var drawVowels = function() {
    if(!ipaChart.children.length) {
      var letters = [
        ["e",221.28871891963863,252.35519027188354],
        ["i",169.01833799969594,171.97765003235634],
        ["a",317.6219414250667,337.00896411883406],
        ["o",384.5714404194302,284.96641792056766],
        ["u",412.17314090483404,231.94657762575406]
      ];
      for(var i=0; i<letters.length; i++){
        var letter = new PIXI.Text(letters[i][0],{font: "35px sans-serif", fill: "black", align: "center"});
        letter.position.x = letters[i][1];
        letter.position.y = letters[i][2];
        ipaChart.addChild(letter);
      }
    }
  };

  // CREATE GAME
  var bgColor = options.background !== undefined ? options.background : 0xFFFFFF;
  game._stage = new PIXI.Stage(bgColor);
  game._renderer = PIXI.autoDetectRenderer(game.width, game.height);
  try{
    options.element.appendChild(game._renderer.view);
  }catch(e){
    document.body.appendChild(game._renderer.view);
  }
  drawVowels();
  if(ipaEnabled) {
    game._stage.addChild(ipaChart);
  }
  
  if(options.worms) {
    if(options.worms instanceof Array) {
      options.worms.forEach(function(worm) {
        game.addWorm(worm);
      });
    }
    else
    {
      game.addWorm(options.worms);
    }
  }
  game._renderer.render(game._stage);
  game.play();
};