import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import ddf.minim.spi.*; 
import ddf.minim.signals.*; 
import ddf.minim.*; 
import ddf.minim.analysis.*; 
import ddf.minim.ugens.*; 
import ddf.minim.effects.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class gluster_graffathon_2015 extends PApplet {








abstract class Scene {
  public float duration; // scene duration in beats
  
  public Scene(float duration) {
      this.duration = duration;
  }
  
  public void setup() {}
  public void draw(float beats) {}
}

class Timeline {
  public Minim minim;
  public AudioPlayer song;
  public int currentScene; // index of current scene
  public float currentStartTime;
  public float currentEndTime;
  public ArrayList<Scene> scenes;
  
  public Timeline(Object processing, String songPath) {
    this.minim = new Minim(processing);
    this.song = minim.loadFile(songPath);

    this.currentScene = -1;
    this.scenes = new ArrayList<Scene>();
  }
  
  public void addScene(Scene scene) {
    this.scenes.add(scene);
  }
  
  public void drawScene() {
    float beats = 0.001f * song.position() / BEAT_DURATION;

    Scene scene = null;
    boolean sceneChanged = false;
    
    if(this.currentScene >= 0 && this.currentStartTime <= beats && beats < this.currentEndTime) {
      scene = this.scenes.get(this.currentScene);
    }
    
    boolean terminated = false;
    float accumStartTime = 0.0f;
    for(int i = 0; i < this.scenes.size(); i++) {
      scene = this.scenes.get(i);
      float endTime = accumStartTime + scene.duration;
      if(accumStartTime <= beats && beats < endTime) {
          terminated = true;
          sceneChanged = i != this.currentScene;
          
          this.currentScene = i;
          this.currentStartTime = accumStartTime;
          this.currentEndTime = endTime;
          
          break;
      }
      accumStartTime += scene.duration;
    }
    
    if(!terminated) {
      this.currentScene = -1;      
    }
    
    if(this.currentScene < 0) {
      background(0);
      return; 
    }
    
    if(sceneChanged) {
      // Scene changed, set up the new one
      scene.setup();
    }
    
    scene.draw(beats - this.currentStartTime);
  }
}

public float beatsToSecs(float beats) {
  return beats * BEAT_DURATION;
}

// Scenes

class TestScene extends Scene {
  public boolean green;
  
  public TestScene(float duration, boolean green) {
    super(duration);
    this.green = green;
  }
  
  public void setup() {
    resetShader();
    textSize(32);
    noStroke();
    fill(255);    
  }
  
  public void draw(float beats) {
    if(this.green) {
      background(0, 255, 0);      
    } else {
      background(255, 0, 0);
    }
    text("" + beats, 10, 0.5f * CANVAS_HEIGHT);
  }
}

class GreezScene extends Scene {
  PShader sprunge;
  
  public GreezScene(float duration) {
    super(duration);
    sprunge = loadShader("sprunge.glsl");
    sprunge.set("iResolution", (float)width, (float)height);
    
  }
  
  public void setup() {
    rectMode(CORNER);
    stroke(0);
  }
  
  public void draw(float beats) {
    clear();
    shader(sprunge);
    
    String[] groups = {"Peisik", "REN", "Epoch", "pants^", "Paraguay", "Mercury", "firebug\nsooda", "DOT"};
    int i = floor(0.25f * beats);
    
    boolean showHeart = 0.0f <= beats && beats < 2.0f;
    boolean showGroup = beats % 4.0f >= 3.0f; 
    
    if(showGroup) {
      sprunge.set("iSaturation", 0.0f);
    } else {
      sprunge.set("iSaturation", 1.0f);      
    }

    sprunge.set("iGlobalTime", beatsToSecs(beats));
    rect(0, 0, width, height);
    resetShader();  
    
    float timePerText = duration/groups.length;
    float timePassed = 0.0f;
    
    fill(255, 0, 100);
    textSize(32);
    textAlign(CENTER, CENTER);
      
    String msg = "";
    if(showGroup) {
      msg = groups[i];
    } else if(showHeart) {
      msg = "<3";
      fill(200);
    }
    text(msg, width/2.0f, height/2.0f, 0);
  }
}

class BlankScene extends Scene {
  public BlankScene(float duration) {
    super(duration);
  }  
  
  public void setup() {
    background(0);    
  }
}

class CylinderScene extends Scene {
  PShader stardust;
  PShape cylinder;
  PImage texture;
  boolean shakeIt;
  
  public CylinderScene(float duration, boolean shakeIt) {
    super(duration);
    stardust = loadShader("stardust.glsl");
    stardust.set("iResolution", (float)width, (float)height);
    cylinder = loadShape("AbstractSylinder.obj");
    texture = loadImage("sheetmetal.jpg");
    this.shakeIt = shakeIt;
  } 
  
  public void setup() {
    
    noStroke();      
    textureWrap(REPEAT);
    textureMode(NORMAL);    
  }
  
  public void draw(float beats) {
      float secs = beatsToSecs(beats);
      pushMatrix();
      clear();
      rectMode(CORNER);
      shader(stardust);
      stardust.set("iGlobalTime", secs);
      hint(DISABLE_DEPTH_TEST);
      rect(0, 0, width, height);
      hint(ENABLE_DEPTH_TEST);
      resetShader();
      
      beginCamera();
        camera(0, 0, 14, 0, 0, 0, 0, 1, 0);
        float fov = PI/3.0f;
        float cameraZ = (height/2.0f) / tan(fov/2.0f);
        perspective(fov, PApplet.parseFloat(width)/PApplet.parseFloat(height), cameraZ/100.0f, cameraZ*10.0f);
        
        directionalLight(126, 126, 126, 0, 0, -1);
        ambientLight(102, 102, 102);
          
          pushMatrix();
            if(shakeIt){
              translate(0, sin(secs*150)*0.1f, 0);
            }
            if(shakeIt && secs>5.0f && secs<6.0f)
              translate(sin(secs*200)*0.5f, 0, 0);
            if(shakeIt && secs>10 && secs<12)
              translate(sin(secs*200)*0.5f, 0, sin(secs*200)*0.25f);
            if(shakeIt && secs>12)
              translate(sin(secs*200)*0.7f, sin(secs*200)*0.5f, sin(secs*200)*0.3f);
      
            rotateZ(PI/2.0f);
            rotateX(-5.8f);
            rotateY(secs * 0.5f);
            
            beginShape();
            texture(texture);
            tint(0, 153, 204);
            shape(cylinder);
            endShape();
          popMatrix();
      endCamera();
      popMatrix();
      
      if(!shakeIt) {
        float fade = max(0.0f, min(1.0f, 1.0f - beats / 8.0f));
        fill(0, 0, 0, fade*255.0f);
        rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
      }
    }
}

class ShadertoyScene extends Scene {
  public PShader shader;
  
  public ShadertoyScene(float duration, String shaderPath) {
    super(duration);
    this.shader = loadShader(shaderPath);
    shader(this.shader);
  }
  
  public void setup() {    
    fill(255);
    rectMode(CORNER);
  }
  
  public void draw(float beats) {
    clear();
    shader(this.shader);
    this.shader.set("iResolution", PApplet.parseFloat(CANVAS_WIDTH), PApplet.parseFloat(CANVAS_HEIGHT));
    this.shader.set("iBeats", beats);
    this.shader.set("iGlobalTime", beatsToSecs(beats));
    rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  }
}

class RobotikScene extends ShadertoyScene {
  public boolean rotate;
  
  public RobotikScene(float duration, boolean rotate) {
    super(duration, "data/robotik.frag");
    this.rotate = rotate;
  }
  
  public void draw(float beats) {
    //background(0);
    clear();
    shader(this.shader);
    this.shader.set("iResolution", PApplet.parseFloat(CANVAS_WIDTH), PApplet.parseFloat(CANVAS_HEIGHT));
    this.shader.set("iBeats", beats);
    this.shader.set("iGlobalTime", beatsToSecs(beats));
    this.shader.set("iRotate", this.rotate);
    rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    resetShader();
  }
}

class CreditsScene extends Scene {
  public PShader shader;
  
  public CreditsScene(float duration) {
    super(duration);
    resetShader();
    this.shader = loadShader("noise.glsl");
    this.shader.set("iResolution", PApplet.parseFloat(CANVAS_WIDTH), PApplet.parseFloat(CANVAS_HEIGHT));
  }
  
  public void setup() {
    noStroke();
    fill(255);
    smooth();
  }
  
  public void effectMatrix(int time) {
    String texts[] = {"Credits", "", "code", "{", "Kitai", "ahihi", "Lumian", "}", "", "music", "{", "ahihi", "}", "", "@ Graffathon 2015"};
    float speeds[] = {1, 0, 1.5f, 1.25f, 1, 1.5f, 1, 1.25f, 0, 1.5f, 1.25f, 1.5f, 1.25f, 1, 1};
    
    pushMatrix();
      scale(0.003f);
      rotateX(radians(180));
      fill(255, 255, 255, 80 + 50*sin(time*0.001f));
      noStroke();
      textSize(40);
  
      for (int i = 0; i < texts.length; i++) {
        for (int j = 0; j < texts[i].length(); j++) {
          float randomNumber = random(10);
          
          if (randomNumber < 7) {
            text(texts[i].charAt(j), i*50 + sin(time*0.001f), j*40 -300 + 300*sin(time*0.0001f*speeds[i]), 0);
          }
          else {
            text((char) PApplet.parseInt(random(33, 127)), i*50 + sin(time*0.001f), j*40 -300 + 300*sin(time*0.0001f*speeds[i]), 0);
          }
        }
      }
      
      popMatrix();
  }
  
  public void draw(float beats) {
    clear();
    
    pushMatrix();
      translate(0,CANVAS_HEIGHT/2.0f,-800); // needed in 3D mode
      scale(1.5f*(CANVAS_WIDTH/2.0f)/ASPECT_RATIO, -CANVAS_HEIGHT/2.0f);
      effectMatrix((int)(beats * 1000));
    
      this.shader.set("iGlobalTime", beatsToSecs(beats));
      
      
      shader(shader);
      rectMode(CENTER);
      rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
      resetShader();
    popMatrix();
  }
}

class SnowflakeScene extends Scene {
  PShader shader;
  boolean reverse;
  
  public SnowflakeScene(float duration, boolean reverse) {
    super(duration);
    this.reverse = reverse;
    shader = loadShader("clouds.glsl");
    shader.set("iResolution", (float)CANVAS_WIDTH, (float)CANVAS_HEIGHT);
  }
  
  public void setup() {
    noStroke();
  }
  
  public void tree(int time, int level, int branches, float x, float y, float angle, boolean firstRun) {
    if (level < 0){ 
      return; 
    }
    
    //vasen
    float x2 = x + (level * 0.1f * sin(angle) * (1.0f + 0.1f*sin(time*0.0005f*(level+1))));
    float y2 = y + (level * 0.1f * cos(angle) * (1.0f + 0.1f*sin(time*0.0005f*(level+1))));
    
    if (!firstRun) {
      stroke(255, 255, 255, 20 + 10*sin(time*0.001f));
      strokeWeight(level/5.0f + 0.1f);
      line(x,y,x2,y2);
      
      stroke(255, 255, 255, 30 + 30*sin(time*0.001f));
      strokeWeight(level/15.0f + 0.1f);
      line(x,y,x2,y2);
      
      stroke(255, 255, 255, 100 + 50*sin(time*0.001f));
      strokeWeight(level/30.0f + 0.1f);
      line(x,y,x2,y2);
    }
    
    if (!firstRun) {
      tree(time, level-1, branches, x2, y2, radians(0)+angle, false);      
      tree(time, level-1, branches, x2, y2, radians(40)+angle, false);      
      tree(time, level-1, branches, x2, y2, radians(-40)+angle, false);
    }
    else {
      for (int i = 0; i < branches; i++) {
        tree(time, level-1, branches, x2, y2, radians(i * 360/branches)+angle, false);
      }
    }
  }

  public void effectTree(int time, int level, float angle) {
      if (level == 0){ 
        return; 
      }
      
      pushMatrix();
      
      fill(255, 255, 255, 80 + 50*sin(time*0.001f));
      
      float scaleValue = 0.25f;
      scale(scaleValue);
      
      stroke(255);
      strokeWeight(1);

      createFallingSnowFlake(time, level, 5, 0, -1, 0, true);
      
      createFallingSnowFlake(time, level - 1, 6, -2, 0, 0, true);
      
      createFallingSnowFlake(time, level,     5, 1, 3, 0, true);
        
      createFallingSnowFlake(time, level - 1, 6, 5, -3, 0, true);
      
      createFallingSnowFlake(time, level - 1, 6, -4, 1, 0, true);
    
      createFallingSnowFlake(time, level - 1, 6, 3.5f, -1, 0, true);
      
      createFallingSnowFlake(time, level - 1,     5, 2, 5, 0, true);
      
      createFallingSnowFlake(time, level - 1,     5, -1, -3, 0, true);
      
      createFallingSnowFlake(time, level,     6, -1, -1.5f, 0, true);
      
      createFallingSnowFlake(time, level - 2,     6, -2, 4, 0, true);
    
      scale(0.03f);
      translate(0, -5, 0);
      rotateX(radians(180));
    
      popMatrix();
  }
  
  public void createFallingSnowFlake(int time, int level, int branches, float x, float y, float angle, boolean isFirstCall) {
      pushMatrix();
      float yTranslate = (reverse ? 1.0f : -1.0f)*( 20 - (time*(0.0005f + 0.0005f * abs(y)/2.0f) % (40)));
      
      translate(x -1 + 2*sin(time*0.0005f + x), yTranslate);
      rotateZ(radians(time*0.005f));
      
      tree(time, level, branches, x, y, angle, true);
      
      popMatrix();
  }
  
  public void draw(float beats) {
    rectMode(CORNER);
    pushMatrix();
    translate(CANVAS_WIDTH/2.0f, CANVAS_HEIGHT/2, -800); // needed in 3D mode
    scale((CANVAS_WIDTH/2.0f)/ASPECT_RATIO, -CANVAS_HEIGHT/2.0f);
    clear();

    background(0);
    
    float time = beats * 1000;
    effectTree((int)time, 5, 0);
    
    popMatrix();
    
    float clouds_fade = 0.0f;
    if(!reverse) {
      clouds_fade = pow(min(1.0f, 1.0f - beats / 32.0f), 2.0f);
    }
    shader.set("iFade", clouds_fade);
    shader.set("iBeats", beats);
    shader.set("iGlobalTime", beats);
    shader(shader);
    fill(100, 100, 100, 0.5f);
    rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    resetShader();
    
    float fade = 0.0f;
    if(!reverse) {
      fade = max(0.0f, min(1.0f, beats < 16.0f ? 1.0f - beats / 16.0f : (beats - 56.0f) / 8.0f));
      
    } else {
      fade = max(0.0f, min(1.0f, (beats - 24.0f) / 40.0f));
    }
    fill(0, 0, 0, 255.0f * fade);
    rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  }
}

class StairsScene extends Scene {
  PShader shader;
  
  public StairsScene(float duration) {
    super(duration);

    shader = loadShader("clouds.glsl");
    shader.set("iResolution", (float)CANVAS_WIDTH, (float)CANVAS_HEIGHT);
    shader.set("iFade", 0.0f);
  }
  
  public void setup() {
    noStroke();
    background(10, 2, 12);
  }
  
  public void draw(float beats) {
    clear();
    
    int time = round(beatsToSecs(beats) * 1000.0f);
    
    shader.set("iGlobalTime", (float)sin(time*0.001f));

    pushMatrix();
    translate(CANVAS_WIDTH/2, CANVAS_HEIGHT/2,-0.3f*CANVAS_WIDTH); // needed in 3D mode
    lights();
    rotateY(-time * 0.0005f);
    translate(0, time * 0.1f, 0);
    fill(36, 36, 67);
    float towerWidth = CANVAS_WIDTH/4;
    int towerHeight = 5*CANVAS_HEIGHT;
    translate(0, -1.5f*CANVAS_HEIGHT, 0);
    
    //filter(shader);
    
    shader(shader);
    box(towerWidth, towerHeight, towerWidth);
    resetShader();
    
    
    int amountOfStairs = 5;
    float stairWidth = towerWidth/amountOfStairs;
    float stairHeight = CANVAS_WIDTH/100;
    float stairDepth = towerWidth/10;
    //float stairDepth = stairWidth;
    float heightDifferenceBetweenSteps = (int) (1.7f * stairHeight);
    translate(-0.5f*towerWidth, 1.1f*CANVAS_HEIGHT, (towerWidth + stairDepth)/2);
    
    for (int k = 0; k < 25; k++) {
      for (int i = 0; i < amountOfStairs; i++) {
        fill(10, 2, 12);
        
        if((beats % 2.0f < 1.0f) && ((i % 3.0f * time*0.001f) < 1.0f)) {
          fill(152, 146, 193);
          fill(36, 36, 67);
          fill(1.5f*20, 1.5f*4, 1.5f*24);
        }
        if ((beats % 4.0f < 1.0f) && (((i*k+i) * 0.1f*beats) % 2.0f < 1.0f)) {
          fill(191, 189, 191);
          //fill(89, 54, 67);
          fill(152, 146, 193);
        }
        
        translate(stairWidth, -heightDifferenceBetweenSteps, 0);
        box(stairWidth, stairHeight, stairDepth);
      }
      
      rotateY(PI/2);
      
      translate((stairDepth)/2, 0, (stairDepth)/2);
    }

    popMatrix();
    
    resetShader();
  }
}

class StairsScene2 extends Scene {
  PShader shader;
  PShader shader2;
  
  public StairsScene2(float duration) {
    super(duration);

    shader = loadShader("lines2.glsl");
    shader.set("iResolution", (float)CANVAS_WIDTH, (float)CANVAS_HEIGHT);
    
    shader2 = loadShader("lines3.glsl");
    shader2.set("iResolution", (float)CANVAS_WIDTH, (float)CANVAS_HEIGHT);
  }
  
  public void setup() {
    resetShader();
    noStroke();
  }
  
  public void draw(float beats) {
    clear();
    
    int time = round(beatsToSecs(beats) * 1000.0f);
    
    shader.set("iGlobalTime", (float)(time*0.001f));
    shader2.set("iGlobalTime", (float)(time*0.001f));

    pushMatrix();
    translate(CANVAS_WIDTH/2, CANVAS_HEIGHT/2,-0.3f*CANVAS_WIDTH); // needed in 3D mode
    lights();
    rotateY(1.5f*PI/2 + -time * 0.0005f);
    translate(0, time * 0.1f, 0);
    fill(36, 36, 67);
    float towerWidth = CANVAS_WIDTH/3;
    int towerHeight = 4*CANVAS_HEIGHT;
    translate(0, -2.0f*CANVAS_HEIGHT, 0);
    
    //filter(shader);
    
    //shader(shader);
    //box(towerWidth, towerHeight, towerWidth);
    //resetShader();
    
    
    int amountOfStairs = 5;
    float stairWidth = towerWidth/amountOfStairs;
    float stairHeight = CANVAS_WIDTH/100;
    float stairDepth = stairWidth;
    //float stairDepth = stairWidth;
    float heightDifferenceBetweenSteps = (int) (3.0f * stairHeight);
    translate(-0.5f*towerWidth, 1.1f*CANVAS_HEIGHT, (towerWidth + stairDepth)/2);
    
    for (int k = 0; k < 30; k++) {
      for (int i = 0; i < amountOfStairs; i++) {
        fill(36, 36, 67);
        
        if(beats % 2.0f < 1.0f && (i+k+time*0.001f) % 3.0f < 1.0f) {
          fill(152, 146, 193);
        }
        
        translate(stairWidth, -heightDifferenceBetweenSteps, 0);
        box(stairWidth, stairHeight, stairDepth);
      }
      
      rotateY(PI/2);
      
      //translate((stairDepth)/2, 0, (stairDepth)/2);
    }

    popMatrix();
    
    shader(shader);
    fill(100, 100, 100, 0.5f);
    //rectMode(CENTER);
    rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    
    shader(shader2);
    fill(100, 100, 100, 0.5f);
    rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    
    resetShader();
  }
}

class RotatingObjectScene extends Scene {
  PShader shader;
  PShader shader2;
  PShape metalObject;
  
  public RotatingObjectScene(float duration) {
    super(duration);
    
    resetShader();
    
    metalObject = loadShape("metalObject.obj");

    shader = loadShader("lines.glsl");
    shader2 = loadShader("shaderLumian.glsl");
    shader.set("iResolution", (float)CANVAS_WIDTH, (float)CANVAS_HEIGHT);
    shader2.set("iResolution", (float)CANVAS_WIDTH, (float)CANVAS_HEIGHT);
  }
  
  public void setup() {
    noStroke();
  }
  
  public void draw(float beats) {
    clear();
    
    int time = round(beatsToSecs(beats) * 1000.0f);

    pushMatrix();

    translate(CANVAS_WIDTH/2, CANVAS_HEIGHT/2,-0.5f*CANVAS_WIDTH); // needed in 3D mode
    lights();
    
    if (beats%2 < 1.0f) {
      scale(24.0f);
    }
    else {
      scale(20.0f);
    }
    
    this.shader.set("iGlobalTime", beatsToSecs(beats));
    this.shader2.set("iGlobalTime", beatsToSecs(beats));
    
    rotateX(sin(time*0.001f));
    rotateY(cos(time*0.0001f));
    rotateZ(sin(time*0.0001f)*cos(time*0.001f));
    translate(30*sin(time*0.001f), 30*cos(time*0.001f), 0.7f*sin(time*0.01f));
    shader(shader2);
    shape(metalObject, 0, 0);
    resetShader();

    popMatrix();
    
    shader(shader);
    fill(100, 100, 100, 0.5f);
    rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    
    resetShader();
  }
}

// Constants
int CANVAS_WIDTH = 1280;
int CANVAS_HEIGHT = 720;
float ASPECT_RATIO = (float)CANVAS_WIDTH/CANVAS_HEIGHT;
float TEMPO = 123.0f; // beats/minute
float BEAT_DURATION = 60.0f / TEMPO; // seconds 
int SKIP_DURATION = round(4.0f * 1000.0f * BEAT_DURATION); // milliseconds
float PREDELAY_DURATION = 0.0f; // seconds

// Global state
Timeline timeline;
boolean predelay = true; // are we still in the pre-delay period?

public void setup() {
  size(CANVAS_WIDTH, CANVAS_HEIGHT, P3D);

  timeline = new Timeline(this, "data/Vector Space Odyssey.mp3");
  timeline.addScene(new SnowflakeScene(64.0f, false));
  timeline.addScene(new CylinderScene(32.0f, false));
  timeline.addScene(new StairsScene2(32.0f));
  timeline.addScene(new ShadertoyScene(64.0f, "data/tunnel.frag")); // start at 128
  timeline.addScene(new RobotikScene(32.0f, false));
  timeline.addScene(new RotatingObjectScene(36.0f));
  timeline.addScene(new RobotikScene(32.0f, true));
  timeline.addScene(new CylinderScene(32.0f, true));
  timeline.addScene(new GreezScene(32.0f));
  timeline.addScene(new CreditsScene(32.0f));
  timeline.addScene(new SnowflakeScene(64.0f, true));
  
  frameRate(60);
  background(0);
  fill(255);
  smooth();
}

public void keyPressed() {
  if(predelay) {
    return;
  }
  
  if(key == CODED) {
    // Left/right arrow keys: seek song
    boolean isLeft = keyCode == LEFT;
    boolean isRight = keyCode == RIGHT;
    if(isLeft || isRight) {
      timeline.song.skip((isLeft ? -1 : 1) * SKIP_DURATION);  
    }
  } else if(key == ' ') {
    // Space: play/pause
    if(timeline.song.isPlaying()) {
      timeline.song.pause();
    } else {
      timeline.song.play();
    }
  } /*else if(key == ENTER) {
    // Enter: spit out the current position (for syncing)
    println("" + getBeats() + " b / " + getSeconds() + " s");
  }*/
}

public void draw() {
  if(predelay) {
    if(0.001f * millis() < PREDELAY_DURATION) {
      return;      
    }
    
    // Predelay ended, start the song
    predelay = false;
    float offset = 0.0f;
    timeline.song.play(round(offset * 1000.0f * BEAT_DURATION));
  }
  
  timeline.drawScene();
}


  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "--full-screen", "--bgcolor=#666666", "--hide-stop", "gluster_graffathon_2015" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
