Demo: music

Click on the image below to lock mouse cursor to demo. Open demo in new window.

Source

grammar("JavaScript");
var height = 8,
    width = 4,
    numButtons = width * height,
    midWidth = (width - 1) / 2,
    midHeight = (height - 1) / 2,
    colorOn = 0xffffff,
    colorOff = 0xff0000,
    colorPlay = 0xffff00,
    boards = [],
    t = 0,
    measure = 0,
    lastMeasure = null,
    padSize = 0.22,
    padDepth = 0.02,
    textSize = 0.05,
    skipOne = false,
    perMeasure = 2 / height,
    DIST = v3(),
    env = new Primrose.BrowserEnvironment({
      groundTexture: "../shared_assets/images/deck.png",
      font: "../shared_assets/fonts/helvetiker_regular.typeface.json",
      backgroundColor: 0x000000,
      useFog: true,
      drawDistance: 10,
      fullScreenButtonContainer: "#fullScreenButtonContainer"
    });

function text(size, text) {
  size = size || 1;
  return text3D(size * textSize, text).center();
}

function Board(type){
  this.object = hub();
  this.title = text3D(0.15, type)
      .center()
      .colored(colorPlay)
      .addTo(this.object)
      .named("text" + type)
      .at(0, 0.7, -1.3);
  this.btns = [];
  this.btnState = [];
  this.type = type;
  range(numButtons, function(i){
    var x = Math.floor(i / height),
      y = i % height,
      lon = (midWidth - x) * 10,
      lat = (y - midHeight) * 10,
      btn = box(padSize, padSize, padDepth)
        .colored(colorOff)
        .named("btn" + i)
        .latLng(lat, lon);

    box(padSize * 1.1, padSize * 1.1, padDepth * 0.9)
      .colored(colorPlay)
      .named("bevel" + i)
      .addTo(btn);

    btn.addEventListener("select", this.select.bind(this, i));
    btn.addEventListener("enter", this.play.bind(this, i, 0));
    this.object.add(btn);
    this.btns.push(btn);
  }.bind(this));
}

Board.prototype.play = function(i, dt) {
  this.highlight(i, colorPlay);
  this.object.getWorldDirection(DIST);
  var duration = perMeasure * 0.85;
  env.music.play(this.type, 25 - numButtons + i * 3, 0.25, duration, dt)
    .at(this.object.x, this.object.y, this.object.z,
        DIST.x, DIST.y, DIST.z);
  setTimeout(this.highlight.bind(this, i), duration * 1000);
}

Board.prototype.update = function() {
  if(measure !== lastMeasure){
    var time = env.audio.context.currentTime,
      measureTime = perMeasure * Math.ceil(time / perMeasure),
      dt = measureTime - time;
    for(var y = 0; y < width; ++y){
      var i = y * height + measure;
      if(this.btnState[i]){
        this.play(i, dt);
      }
    }
  }
  DIST.set(this.object.position.x, env.head.position.y, this.object.position.z);
  this.object.position.lerp(DIST, 0.01);
};


Board.prototype.highlight = function(i, color) {
  color = color || this.btnState[i] && colorOn || colorOff;
  var btn = this.btns[i]
  btn.colored(color).named(btn.name);
};

Board.prototype.select = function(i, evt) {
  if(!env.hasMouse || evt.pointer.name === "MousePointer") {
    this.btnState[i] = !this.btnState[i];
    this.highlight(i);
  }
};

env.addEventListener("ready", function () {
  env.audio.ready.then(function() {
    var types = Primrose.Audio.Music.TYPES,
      nTypes = types.length;
    types.forEach(function(type, t) {
      var board = new Board(type);
      boards.push(board);
      env.scene.add(board.object);
      board.object.latLng(0, (t - (nTypes - 1) / 2) * 100 / nTypes);
    });
  });

  Preloader.hide();
});

env.addEventListener("update", function(){
  if(!skipOne){
    t += env.deltaTime;
    if(t > perMeasure){
      t -= perMeasure;
      measure = (measure + 1) % height;
    }
    boards.forEach(function (board) {
      board.update();
    });
    lastMeasure = measure;
  }
  skipOne = false;
});

window.addEventListener("focus", function() {
  skipOne = true;
});