SCPattern moved out of GLucose
[SugarCubes.git] / _Internals.pde
index f872d9b7a733086578108e39160096e6da27b36d..100607a97d4cecee33689184b736ed26efbee064 100644 (file)
@@ -4,7 +4,7 @@
  *         //\\   //\\                 //\\   //\\  
  *        ///\\\ ///\\\               ///\\\ ///\\\
  *        \\\/// \\\///               \\\/// \\\///
  *         //\\   //\\                 //\\   //\\  
  *        ///\\\ ///\\\               ///\\\ ///\\\
  *        \\\/// \\\///               \\\/// \\\///
- *         \\//   \\//                 \\//   \\//
+ *         \\//   \\//                 \\//   \\//H
  *
  *        EXPERTS ONLY!!              EXPERTS ONLY!!
  *
  *
  *        EXPERTS ONLY!!              EXPERTS ONLY!!
  *
  */
 
 import glucose.*;
  */
 
 import glucose.*;
-import glucose.control.*;
-import glucose.effect.*;
-import glucose.model.*;
-import glucose.pattern.*;
-import glucose.transform.*;
-import glucose.transition.*;
 import heronarts.lx.*;
 import heronarts.lx.*;
-import heronarts.lx.control.*;
 import heronarts.lx.effect.*;
 import heronarts.lx.effect.*;
+import heronarts.lx.model.*;
 import heronarts.lx.modulator.*;
 import heronarts.lx.modulator.*;
+import heronarts.lx.parameter.*;
 import heronarts.lx.pattern.*;
 import heronarts.lx.pattern.*;
+import heronarts.lx.transform.*;
 import heronarts.lx.transition.*;
 import heronarts.lx.transition.*;
+import heronarts.lx.ui.*;
+import heronarts.lx.ui.component.*;
+import heronarts.lx.ui.control.*;
 import ddf.minim.*;
 import ddf.minim.analysis.*;
 import processing.opengl.*;
 import rwmidi.*;
 import ddf.minim.*;
 import ddf.minim.analysis.*;
 import processing.opengl.*;
 import rwmidi.*;
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 final int VIEWPORT_WIDTH = 900;
 final int VIEWPORT_HEIGHT = 700;
 
 // The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
 
 final int VIEWPORT_WIDTH = 900;
 final int VIEWPORT_HEIGHT = 700;
 
 // The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
-final float TRAILER_WIDTH = 240;
-final float TRAILER_DEPTH = 97;
+final float TRAILER_WIDTH = 192;
+final float TRAILER_DEPTH = 192;
 final float TRAILER_HEIGHT = 33;
 
 int targetFramerate = 60;
 final float TRAILER_HEIGHT = 33;
 
 int targetFramerate = 60;
@@ -44,43 +47,72 @@ int startMillis, lastMillis;
 
 // Core engine variables
 GLucose glucose;
 
 // Core engine variables
 GLucose glucose;
-HeronLX lx;
+LX lx;
+Model model;
 LXPattern[] patterns;
 LXPattern[] patterns;
+Effects effects;
 MappingTool mappingTool;
 MappingTool mappingTool;
-PandaDriver[] pandaBoards;
-MidiListener midiQwertyKeys;
-MidiListener midiQwertyAPC;
+GrizzlyOutput[] grizzlies;
+PresetManager presetManager;
+MidiEngine midiEngine;
 
 // Display configuration mode
 boolean mappingMode = false;
 boolean debugMode = false;
 
 // Display configuration mode
 boolean mappingMode = false;
 boolean debugMode = false;
-DebugUI debugUI;
-boolean uiOn = true;
+boolean simulationOn = true;
+boolean diagnosticsOn = false;
 LXPattern restoreToPattern = null;
 PImage logo;
 LXPattern restoreToPattern = null;
 PImage logo;
+float[] hsb = new float[3];
 
 // Handles to UI objects
 
 // Handles to UI objects
-UIContext[] overlays;
 UIPatternDeck uiPatternA;
 UICrossfader uiCrossfader;
 UIMidi uiMidi;
 UIMapping uiMapping;
 UIDebugText uiDebugText;
 UIPatternDeck uiPatternA;
 UICrossfader uiCrossfader;
 UIMidi uiMidi;
 UIMapping uiMapping;
 UIDebugText uiDebugText;
-
-// Camera variables
-float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
+UISpeed uiSpeed;
 
 /**
  * Engine construction and initialization.
  */
 
 /**
  * Engine construction and initialization.
  */
-LXPattern[] _patterns(GLucose glucose) {
-  LXPattern[] patterns = patterns(glucose);
+
+LXTransition _transition(LX lx) {
+  return new DissolveTransition(lx).setDuration(1000);
+}
+
+LXPattern[] _leftPatterns(LX lx) {
+  LXPattern[] patterns = patterns(lx);
   for (LXPattern p : patterns) {
   for (LXPattern p : patterns) {
-    p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
+    p.setTransition(_transition(lx));
   }
   return patterns;
 }
 
   }
   return patterns;
 }
 
+LXPattern[] _rightPatterns(LX lx) {
+  LXPattern[] patterns = _leftPatterns(lx);
+  LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
+  int i = 0;
+  rightPatterns[i++] = new BlankPattern(lx).setTransition(_transition(lx));
+  for (LXPattern p : patterns) {
+    rightPatterns[i++] = p;
+  }
+  return rightPatterns;
+}
+
+LXEffect[] _effectsArray(Effects effects) {
+  List<LXEffect> effectList = new ArrayList<LXEffect>();
+  for (Field f : effects.getClass().getDeclaredFields()) {
+    try {
+      Object val = f.get(effects);
+      if (val instanceof LXEffect) {
+        effectList.add((LXEffect)val);
+      }
+    } catch (IllegalAccessException iax) {}
+  }
+  return effectList.toArray(new LXEffect[]{});
+} 
+
 void logTime(String evt) {
   int now = millis();
   println(evt + ": " + (now - lastMillis) + "ms");
 void logTime(String evt) {
   int now = millis();
   println(evt + ": " + (now - lastMillis) + "ms");
@@ -98,499 +130,369 @@ void setup() {
   logTime("Created viewport");
 
   // Create the GLucose engine to run the cubes
   logTime("Created viewport");
 
   // Create the GLucose engine to run the cubes
-  glucose = new GLucose(this, buildModel());
+  glucose = new GLucose(this, model = buildModel());
   lx = glucose.lx;
   lx.enableKeyboardTempo();
   logTime("Built GLucose engine");
   lx = glucose.lx;
   lx.enableKeyboardTempo();
   logTime("Built GLucose engine");
-  
+    
   // Set the patterns
   // Set the patterns
-  Engine engine = lx.engine;
-  engine.setPatterns(patterns = _patterns(glucose));
-  engine.addDeck(_patterns(glucose));
+  LXEngine engine = lx.engine;
+  engine.setPatterns(patterns = _leftPatterns(lx));
+  engine.addDeck(_rightPatterns(lx));
   logTime("Built patterns");
   logTime("Built patterns");
-  glucose.setTransitions(transitions(glucose));
+  glucose.setTransitions(transitions(lx));
   logTime("Built transitions");
   logTime("Built transitions");
-  glucose.lx.addEffects(effects(glucose));
+  glucose.lx.addEffects(_effectsArray(effects = new Effects()));
   logTime("Built effects");
   logTime("Built effects");
-    
-  // Build output driver
-  PandaMapping[] pandaMappings = buildPandaList();
-  pandaBoards = new PandaDriver[pandaMappings.length];
-  int pbi = 0;
-  for (PandaMapping pm : pandaMappings) {
-    pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
-  }
-  mappingTool = new MappingTool(glucose, pandaMappings);
-  logTime("Built PandaDriver");
+
+  // Preset manager
+  presetManager = new PresetManager();
+  logTime("Loaded presets");
 
   // MIDI devices
 
   // MIDI devices
-  List<MidiListener> midiListeners = new ArrayList<MidiListener>();
-  midiListeners.add(midiQwertyKeys = new MidiListener(MidiListener.KEYS));
-  midiListeners.add(midiQwertyAPC = new MidiListener(MidiListener.APC));
-  for (MidiInputDevice device : RWMidi.getInputDevices()) {
-    boolean enableDevice = device.getName().contains("APC");
-    midiListeners.add(new MidiListener(device).setEnabled(enableDevice));
-  }
-  SCMidiDevices.initializeStandardDevices(glucose);
+  midiEngine = new MidiEngine();
   logTime("Setup MIDI devices");
 
   logTime("Setup MIDI devices");
 
+  // Build output driver
+  grizzlies = new GrizzlyOutput[]{};
+  try {
+    grizzlies = buildGrizzlies();
+    for (LXOutput output : grizzlies) {
+      lx.addOutput(output);
+    }
+  } catch (Exception x) {
+    x.printStackTrace();
+  }
+  logTime("Built Grizzly Outputs");
+  
+  // Mapping tools
+  mappingTool = new MappingTool(lx);
+  
   // Build overlay UI
   // Build overlay UI
-  debugUI = new DebugUI(pandaMappings);
-  overlays = new UIContext[] {
-    uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
+  UILayer[] layers = new UILayer[] {
+    // Camera layer
+    new UICameraLayer(lx.ui)
+      .setCenter(model.cx, model.cy, model.cz)
+      .setRadius(290).addComponent(new UICubesLayer()),
+    
+    // Left controls
+    uiPatternA = new UIPatternDeck(lx.ui, lx.engine.getDeck(GLucose.LEFT_DECK), "PATTERN A", 4, 4, 140, 324),
     new UIBlendMode(4, 332, 140, 86),
     new UIEffects(4, 422, 140, 144),
     new UITempo(4, 570, 140, 50),
     new UIBlendMode(4, 332, 140, 86),
     new UIEffects(4, 422, 140, 144),
     new UITempo(4, 570, 140, 50),
-    new UISpeed(4, 624, 140, 50),
+    uiSpeed = new UISpeed(4, 624, 140, 50),
         
         
-    new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
-    uiMidi = new UIMidi(midiListeners, width-144, 332, 140, 158),
-    new UIOutput(width-144, 494, 140, 106),
+    // Right controls
+    new UIPatternDeck(lx.ui, lx.engine.getDeck(GLucose.RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324),
+    uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
+    new UIOutput(grizzlies, width-144, 494, 140, 106),
     
     
+    // Crossfader
     uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
     
     uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
     
+    // Overlays
     uiDebugText = new UIDebugText(148, height-138, width-304, 44),
     uiDebugText = new UIDebugText(148, height-138, width-304, 44),
-    uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
+    uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324)
   };
   };
-  uiMapping.setVisible(false);
-  logTime("Built overlay UI");
+  uiMapping.setVisible(false);  
+  for (UILayer layer : layers) {
+    lx.ui.addLayer(layer);
+  }
+  logTime("Built UI");
 
   // Load logo image
   logo = loadImage("data/logo.png");
 
   // Load logo image
   logo = loadImage("data/logo.png");
-  
-  // Setup camera
-  midX = TRAILER_WIDTH/2.;
-  midY = glucose.model.yMax/2;
-  midZ = TRAILER_DEPTH/2.;
-  eyeR = -290;
-  eyeA = .15;
-  eyeY = midY + 70;
-  eyeX = midX + eyeR*sin(eyeA);
-  eyeZ = midZ + eyeR*cos(eyeA);
-  addMouseWheelListener(new java.awt.event.MouseWheelListener() { 
-    public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) { 
-      mouseWheel(mwe.getWheelRotation());
-  }}); 
-  
+  logTime("Loaded logo image");
+      
   println("Total setup: " + (millis() - startMillis) + "ms");
   println("Total setup: " + (millis() - startMillis) + "ms");
-  println("Hit the 'p' key to toggle Panda Board output");
+  println("Hit the 'o' key to toggle live output");
 }
 
 }
 
-public class MidiListener extends AbstractScrollItem {
-  
-  public static final int MIDI = 0;
-  public static final int KEYS = 1;
-  public static final int APC = 2;
-  
-  private boolean enabled = false;
-  private final String name;
-  
-  MidiListener(MidiInputDevice d) {
-    mode = MIDI;
-    d.createInput(this);
-    name = d.getName();
-  }
-  
-  class NoteMeta {
-    int channel;
-    int number;
-    NoteMeta(int channel, int number) {
-      this.channel = channel;
-      this.number = number;
-    }
-  }
-  
-  final Map<Character, NoteMeta> keyToNote = new HashMap<Character, NoteMeta>();
-  
-  private final int mode;
-  private int octaveShift = 0;
-  
-  MidiListener(int mode) {
-    this.mode = mode;
-    switch (mode) {
-      case APC:
-        name = "QWERTY (APC Mode)";
-        mapAPC();
-        break;
-      default:
-      case KEYS:
-        name = "QWERTY (Key Mode)";
-        mapKeys();
-        break;
-    }
-  }
-  
-  private void mapAPC() {
-    mapNote('1', 0, 53);
-    mapNote('2', 1, 53);
-    mapNote('3', 2, 53);
-    mapNote('4', 3, 53);
-    mapNote('5', 4, 53);
-    mapNote('6', 5, 53);
-    mapNote('q', 0, 54);
-    mapNote('w', 1, 54);
-    mapNote('e', 2, 54);
-    mapNote('r', 3, 54);
-    mapNote('t', 4, 54);
-    mapNote('y', 5, 54);
-    mapNote('a', 0, 55);
-    mapNote('s', 1, 55);
-    mapNote('d', 2, 55);
-    mapNote('f', 3, 55);
-    mapNote('g', 4, 55);
-    mapNote('h', 5, 55);
-    mapNote('z', 0, 56);
-    mapNote('x', 1, 56);
-    mapNote('c', 2, 56);
-    mapNote('v', 3, 56);
-    mapNote('b', 4, 56);
-    mapNote('n', 5, 56);
-    registerKeyEvent(this);
-  }
-  
-  private void mapKeys() {
-    int note = 48;
-    mapNote('a', 1, note++);
-    mapNote('w', 1, note++);
-    mapNote('s', 1, note++);
-    mapNote('e', 1, note++);
-    mapNote('d', 1, note++);
-    mapNote('f', 1, note++);
-    mapNote('t', 1, note++);
-    mapNote('g', 1, note++);
-    mapNote('y', 1, note++);
-    mapNote('h', 1, note++);
-    mapNote('u', 1, note++);
-    mapNote('j', 1, note++);
-    mapNote('k', 1, note++);
-    mapNote('o', 1, note++);
-    mapNote('l', 1, note++);
-    registerKeyEvent(this);
-  }
-  
-  void mapNote(char ch, int channel, int number) {
-    keyToNote.put(ch, new NoteMeta(channel, number));
-  }
-  
-  public String getLabel() {
-    return name;
-  }
-  
-  public void keyEvent(KeyEvent e) {
-    if (!enabled) {
-      return;
-    }
-    char c = Character.toLowerCase(e.getKeyChar());
-    NoteMeta nm = keyToNote.get(c);
-    if (nm != null) {
-      switch (e.getID()) {
-        case KeyEvent.KEY_PRESSED:
-          noteOnReceived(new Note(Note.NOTE_ON, nm.channel, nm.number + octaveShift*12, 127));
-          break;
-        case KeyEvent.KEY_RELEASED:
-          noteOffReceived(new Note(Note.NOTE_OFF, nm.channel, nm.number + octaveShift*12, 0));
-          break;
-      }
-    }
-    if ((mode == KEYS) && (e.getID() == KeyEvent.KEY_PRESSED)) {
-      switch (c) {
-        case 'z':
-          octaveShift = constrain(octaveShift-1, -4, 4);
-          break;
-        case 'x':
-          octaveShift = constrain(octaveShift+1, -4, 4);
-          break;
-      }
-    }
-  }
-  
-  public boolean isEnabled() {
-    return enabled;
-  }
-  
-  public boolean isSelected() {
-    return enabled;
-  }
-  
-  public void onMousePressed() {
-    setEnabled(!enabled);
-  }
-  
-  public MidiListener setEnabled(boolean enabled) {
-    if (enabled != this.enabled) {
-      this.enabled = enabled;
-      uiMidi.redraw();
-    }
-    return this;
-  }
-  
-  private SCPattern getFocusedPattern() {
-    return (SCPattern) uiMidi.getFocusedDeck().getActivePattern();
-  }
-  
-  void programChangeReceived(ProgramChange pc) {
-    if (!enabled) {
-      return;
-    }
-    if (uiMidi.logMidi()) {
-      println(getLabel() + " :: Program Change :: " + pc.getNumber());
-    }
-  }
-  
-  void controllerChangeReceived(rwmidi.Controller cc) {
-    if (!enabled) {
-      return;
-    }
-    if (uiMidi.logMidi()) {
-      println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue());
-    }
-    getFocusedPattern().controllerChangeReceived(cc);
-  }
-
-  void noteOnReceived(Note note) {
-    if (!enabled) {
-      return;
-    }
-    if (uiMidi.logMidi()) {
-      println(getLabel() + " :: Note On  :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
-    }
-    getFocusedPattern().noteOnReceived(note);
-  }
+public SCPattern getPattern() {
+  return (SCPattern) lx.getPattern();
+}      
 
 
-  void noteOffReceived(Note note) {
-    if (!enabled) {
-      return;
-    }
-    if (uiMidi.logMidi()) {
-      println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
+/**
+ * Subclass of LXPattern specific to sugar cubes. These patterns
+ * get access to the glucose state and geometry, and have some
+ * little helpers for interacting with the model.
+ */
+public static abstract class SCPattern extends LXPattern {
+               
+  protected SCPattern(LX lx) {
+    super(lx);
+  }
+       
+  /**
+   * Reset this pattern to its default state.
+   */
+  public final void reset() {
+    for (LXParameter parameter : getParameters()) {
+      parameter.reset();
     }
     }
-    getFocusedPattern().noteOffReceived(note);
+    onReset();
+  }
+       
+  /**
+   * Subclasses may override to add additional reset functionality.
+   */
+  protected /*abstract*/ void onReset() {}
+       
+  /**
+   * Invoked by the engine when a grid controller button press occurs
+   * 
+   * @param row Row index on the gird
+   * @param col Column index on the grid
+   * @return True if the event was consumed, false otherwise
+   */
+  public boolean gridPressed(int row, int col) {
+    return false;
+  }
+       
+  /**
+   * Invoked by the engine when a grid controller button release occurs
+   * 
+   * @param row Row index on the gird
+   * @param col Column index on the grid
+   * @return True if the event was consumed, false otherwise
+   */
+  public boolean gridReleased(int row, int col) {
+    return false;
+  }
+       
+  /**
+   * Invoked by engine when this pattern is focused an a midi note is received.  
+   * 
+   * @param note
+   * @return True if the pattern has consumed this note, false if the top-level
+   *         may handle it
+   */
+  public boolean noteOn(rwmidi.Note note) {
+    return false;
+  }
+       
+  /**
+   * Invoked by engine when this pattern is focused an a midi note off is received.  
+   * 
+   * @param note
+   * @return True if the pattern has consumed this note, false if the top-level
+   *         may handle it
+   */
+  public boolean noteOff(rwmidi.Note note) {
+    return false;
+  }
+       
+  /**
+   * Invoked by engine when this pattern is focused an a controller is received  
+   * 
+   * @param note
+   * @return True if the pattern has consumed this controller, false if the top-level
+   *         may handle it
+   */
+  public boolean controllerChange(rwmidi.Controller controller) {
+    return false;
   }
   }
-
 }
 
 }
 
+long simulationNanos = 0;
+
 /**
  * Core render loop and drawing functionality.
  */
 void draw() {
 /**
  * Core render loop and drawing functionality.
  */
 void draw() {
-  // Draws the simulation and the 2D UI overlay
+  long drawStart = System.nanoTime();
+  
+  // Set background
   background(40);
   background(40);
-  color[] colors = glucose.getColors();
-
-  String displayMode = uiCrossfader.getDisplayMode();
-  if (displayMode == "A") {
-    colors = lx.engine.getDeck(0).getColors();
-  } else if (displayMode == "B") {
-    colors = lx.engine.getDeck(1).getColors();
-  }
-  if (debugMode) {
-    debugUI.maskColors(colors);
+  
+  // Send colors
+  color[] sendColors = glucose.getColors();  
+  long gammaStart = System.nanoTime();
+  // Gamma correction here. Apply a cubic to the brightness
+  // for better representation of dynamic range
+  for (int i = 0; i < sendColors.length; ++i) {
+    lx.RGBtoHSB(sendColors[i], hsb);
+    float b = hsb[2];
+    sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
   }
   }
+  long gammaNanos = System.nanoTime() - gammaStart;
 
 
-  camera(
-    eyeX, eyeY, eyeZ,
-    midX, midY, midZ,
-    0, -1, 0
-  );
+  // Always draw FPS meter
+  drawFPS();
 
 
-  translate(0, 40, 0);
+  // TODO(mcslee): fix
+  long drawNanos = System.nanoTime() - drawStart;
+  long uiNanos = 0;
 
 
-  noStroke();
-  fill(#141414);
-  drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
-  fill(#070707);
-  stroke(#222222);
-  beginShape();
-  vertex(0, 0, 0);
-  vertex(TRAILER_WIDTH, 0, 0);
-  vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
-  vertex(0, 0, TRAILER_DEPTH);
-  endShape();
-
-  // Draw the logo on the front of platform  
-  pushMatrix();
-  translate(0, 0, -1);
-  float s = .07;
-  scale(s, -s, s);
-  image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
-  popMatrix();
-  
-  noStroke();
-//  drawBassBox(glucose.model.bassBox);
-//  for (Speaker s : glucose.model.speakers) {
-//    drawSpeaker(s);
-//  }
-  for (Cube c : glucose.model.cubes) {
-    drawCube(c);
-  }
+  if (diagnosticsOn) {
+    drawDiagnostics(drawNanos, simulationNanos, uiNanos, gammaNanos);
+  }  
+}
 
 
-  noFill();
-  strokeWeight(2);
-  beginShape(POINTS);
-  // TODO(mcslee): restore when bassBox/speakers are right again
-  // for (Point p : glucose.model.points) {
-  for (Cube cube : glucose.model.cubes) {
-    for (Point p : cube.points) {
-      stroke(colors[p.index]);
-      vertex(p.fx, p.fy, p.fz);
+class UICubesLayer extends UICameraComponent {
+  void onDraw(UI ui) {
+    color[] simulationColors = glucose.getColors();
+    String displayMode = uiCrossfader.getDisplayMode();
+    if (displayMode == "A") {
+      simulationColors = lx.engine.getDeck(GLucose.LEFT_DECK).getColors();
+    } else if (displayMode == "B") {
+      simulationColors = lx.engine.getDeck(GLucose.RIGHT_DECK).getColors();
+    }
+
+    long simulationStart = System.nanoTime();
+    if (simulationOn) {
+      drawSimulation(simulationColors);
     }
     }
+    simulationNanos = System.nanoTime() - simulationStart;
+    
+    camera();
+    javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
+    gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
+    ((PGraphicsOpenGL)g).endGL();
+    strokeWeight(1);
   }
   }
-  endShape();
   
   
-  // 2D Overlay UI
-  drawUI();
+  void drawSimulation(color[] simulationColors) {
+    translate(0, 30, 0);
+  
+    noStroke();
+    fill(#141414);
+    drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
+    fill(#070707);
+    stroke(#222222);
+    beginShape();
+    vertex(0, 0, 0);
+    vertex(TRAILER_WIDTH, 0, 0);
+    vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
+    vertex(0, 0, TRAILER_DEPTH);
+    endShape();
+  
+    // Draw the logo on the front of platform  
+    pushMatrix();
+    translate(0, 0, -1);
+    float s = .07;
+    scale(s, -s, s);
+    image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
+    popMatrix();
     
     
-  // Send output colors
-  color[] sendColors = glucose.getColors();
-  if (debugMode) {
-    debugUI.maskColors(colors);
-  }
+    noStroke();
+    for (Cube c : model.cubes) {
+      drawCube(c);
+    }
   
   
-  // Gamma correction here. Apply a cubic to the brightness
-  // for better representation of dynamic range
-  for (int i = 0; i < colors.length; ++i) {
-    float b = brightness(colors[i]) / 100.f;
-    colors[i] = color(
-      hue(colors[i]),
-      saturation(colors[i]),
-      (b*b*b) * 100.
-    );
+    noFill();
+    strokeWeight(2);
+    beginShape(POINTS);
+    for (LXPoint p : model.points) {
+      stroke(simulationColors[p.index]);
+      vertex(p.x, p.y, p.z);
+    }
+    endShape();
   }
   
   }
   
-  // TODO(mcslee): move into GLucose engine
-  for (PandaDriver p : pandaBoards) {
-    p.send(colors);
+  void drawCube(Cube c) {
+    float in = .15;
+    noStroke();
+    fill(#393939);  
+    drawBox(c.x+in, c.y+in, c.z+in, c.rx, c.ry, c.rz, Cube.EDGE_WIDTH-in*2, Cube.EDGE_HEIGHT-in*2, Cube.EDGE_WIDTH-in*2, Cube.CHANNEL_WIDTH-in);
   }
   }
-}
-
-void drawBassBox(BassBox b) {
-  float in = .15;
-
-  noStroke();
-  fill(#191919);
-  pushMatrix();
-  translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
-  box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
-  popMatrix();
-
-  noStroke();
-  fill(#393939);
-  drawBox(b.x+in, b.y+in, b.z+in, 0, 0, 0, BassBox.EDGE_WIDTH-in*2, BassBox.EDGE_HEIGHT-in*2, BassBox.EDGE_DEPTH-in*2, Cube.CHANNEL_WIDTH-in);
-
-  pushMatrix();
-  translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
-  float lastOffset = 0;
-  for (float offset : BoothFloor.STRIP_OFFSETS) {
-    translate(offset - lastOffset, 0, 0);
-    box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
-    lastOffset = offset;
-  }
-  popMatrix();
-
-  pushMatrix();
-  translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
-  for (int j = 0; j < 2; ++j) {
+  
+  void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
     pushMatrix();
     pushMatrix();
-    for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
-      translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
-      box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
+    translate(x, y, z);
+    rotate(rx / 180. * PI, -1, 0, 0);
+    rotate(ry / 180. * PI, 0, -1, 0);
+    rotate(rz / 180. * PI, 0, 0, -1);
+    for (int i = 0; i < 4; ++i) {
+      float wid = (i % 2 == 0) ? xd : zd;
+      
+      beginShape();
+      vertex(0, 0);
+      vertex(wid, 0);
+      vertex(wid, yd);
+      vertex(wid - sw, yd);
+      vertex(wid - sw, sw);
+      vertex(0, sw);
+      endShape();
+      beginShape();
+      vertex(0, sw);
+      vertex(0, yd);
+      vertex(wid - sw, yd);
+      vertex(wid - sw, yd - sw);
+      vertex(sw, yd - sw);
+      vertex(sw, sw);
+      endShape();
+  
+      translate(wid, 0, 0);
+      rotate(HALF_PI, 0, -1, 0);
     }
     popMatrix();
     }
     popMatrix();
-    translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
   }
   }
-  popMatrix();
-  
-  pushMatrix();
-  translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
-  box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
-  translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
-  box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
-  popMatrix();
-  
 }
 
 }
 
-void drawCube(Cube c) {
-  float in = .15;
-  noStroke();
-  fill(#393939);  
-  drawBox(c.x+in, c.y+in, c.z+in, c.rx, c.ry, c.rz, Cube.EDGE_WIDTH-in*2, Cube.EDGE_HEIGHT-in*2, Cube.EDGE_WIDTH-in*2, Cube.CHANNEL_WIDTH-in);
-}
-
-void drawSpeaker(Speaker s) {
-  float in = .15;
-  
+void drawDiagnostics(long drawNanos, long simulationNanos, long uiNanos, long gammaNanos) {
+  float ws = 4 / 1000000.;
+  int thirtyfps = 1000000000 / 30;
+  int sixtyfps = 1000000000 / 60;
+  int x = width - 138;
+  int y = height - 14;
+  int h = 10;
+  noFill();
+  stroke(#999999);
+  rect(x, y, thirtyfps * ws, h);
   noStroke();
   noStroke();
-  fill(#191919);
-  pushMatrix();
-  translate(s.x, s.y, s.z);
-  rotate(s.ry / 180. * PI, 0, -1, 0);
-  translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
-  box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
-  translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
-
-  fill(#222222);
-  box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
-  popMatrix();
+  int xp = x;
+  float hv = 0;
+  for (long val : new long[] {lx.timer.drawNanos, simulationNanos, uiNanos, gammaNanos, lx.timer.outputNanos }) {
+    fill(lx.hsb(hv % 360, 100, 80));
+    rect(xp, y, val * ws, h-1);
+    hv += 140;
+    xp += val * ws;
+  }
+  noFill();
+  stroke(#333333);
+  line(x+sixtyfps*ws, y+1, x+sixtyfps*ws, y+h-1);
   
   
+  y = y - 14;
+  xp = x;
+  float tw = thirtyfps * ws;
+  noFill();
+  stroke(#999999);
+  rect(x, y, tw, h);
+  h = 5;
   noStroke();
   noStroke();
-  fill(#393939);  
-  drawBox(s.x+in, s.y+in, s.z+in, 0, s.ry, 0, Speaker.EDGE_WIDTH-in*2, Speaker.EDGE_HEIGHT-in*2, Speaker.EDGE_DEPTH-in*2, Cube.CHANNEL_WIDTH-in);
-}
-
-void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
-  pushMatrix();
-  translate(x, y, z);
-  rotate(rx / 180. * PI, -1, 0, 0);
-  rotate(ry / 180. * PI, 0, -1, 0);
-  rotate(rz / 180. * PI, 0, 0, -1);
-  for (int i = 0; i < 4; ++i) {
-    float wid = (i % 2 == 0) ? xd : zd;
-    
-    beginShape();
-    vertex(0, 0);
-    vertex(wid, 0);
-    vertex(wid, yd);
-    vertex(wid - sw, yd);
-    vertex(wid - sw, sw);
-    vertex(0, sw);
-    endShape();
-    beginShape();
-    vertex(0, sw);
-    vertex(0, yd);
-    vertex(wid - sw, yd);
-    vertex(wid - sw, yd - sw);
-    vertex(sw, yd - sw);
-    vertex(sw, sw);
-    endShape();
-
-    translate(wid, 0, 0);
-    rotate(HALF_PI, 0, -1, 0);
+  for (long val : new long[] {
+    lx.engine.timer.deckNanos,
+    lx.engine.timer.copyNanos,
+    lx.engine.timer.fxNanos}) {
+    float amt = val / (float) lx.timer.drawNanos;
+    fill(lx.hsb(hv % 360, 100, 80));
+    rect(xp, y, amt * tw, h-1);
+    hv += 140;
+    xp += amt * tw;
+  }
+  
+  xp = x;
+  y += h;
+  hv = 120;
+  for (long val : new long[] {
+    lx.engine.getDeck(0).timer.runNanos,
+    lx.engine.getDeck(1).timer.runNanos,
+    lx.engine.getDeck(1).getFaderTransition().timer.blendNanos}) {
+    float amt = val / (float) lx.timer.drawNanos;
+    fill(lx.hsb(hv % 360, 100, 80));
+    rect(xp, y, amt * tw, h-1);
+    hv += 140;
+    xp += amt * tw;
   }
   }
-  popMatrix();
 }
 
 }
 
-void drawUI() {
-  camera();
-  javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
-  gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
-  ((PGraphicsOpenGL)g).endGL();
-  strokeWeight(1);
-
-  if (uiOn) {
-    for (UIContext context : overlays) {
-      context.draw();
-    }
-  }
-  
+void drawFPS() {  
   // Always draw FPS meter
   fill(#555555);
   textSize(9);
   textAlign(LEFT, BASELINE);
   text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
   // Always draw FPS meter
   fill(#555555);
   textSize(9);
   textAlign(LEFT, BASELINE);
   text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
-
-  if (debugMode) {
-    debugUI.draw();
-  }
 }
 
 
 }
 
 
@@ -602,6 +504,44 @@ void keyPressed() {
     mappingTool.keyPressed(uiMapping);
   }
   switch (key) {
     mappingTool.keyPressed(uiMapping);
   }
   switch (key) {
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+      if (!midiEngine.isQwertyEnabled()) {
+        presetManager.select(midiEngine.getFocusedDeck(), key - '1');
+      }
+      break;
+    
+    case '!':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 0);
+      break;
+    case '@':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 1);
+      break;
+    case '#':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 2);
+      break;
+    case '$':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 3);
+      break;
+    case '%':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 4);
+      break;
+    case '^':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 5);
+      break;
+    case '&':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 6);
+      break;
+    case '*':
+      if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 7);
+      break;
+      
     case '-':
     case '_':
       frameRate(--targetFramerate);
     case '-':
     case '_':
       frameRate(--targetFramerate);
@@ -609,15 +549,18 @@ void keyPressed() {
     case '=':
     case '+':
       frameRate(++targetFramerate);
     case '=':
     case '+':
       frameRate(++targetFramerate);
-      break;      
+      break; 
+    case 'b':
+      effects.boom.trigger();
+      break;    
     case 'd':
     case 'd':
-      if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
+      if (!midiEngine.isQwertyEnabled()) {
         debugMode = !debugMode;
         println("Debug output: " + (debugMode ? "ON" : "OFF"));
       }
       break;
     case 'm':
         debugMode = !debugMode;
         println("Debug output: " + (debugMode ? "ON" : "OFF"));
       }
       break;
     case 'm':
-      if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
+      if (!midiEngine.isQwertyEnabled()) {
         mappingMode = !mappingMode;
         uiPatternA.setVisible(!mappingMode);
         uiMapping.setVisible(mappingMode);
         mappingMode = !mappingMode;
         uiPatternA.setVisible(!mappingMode);
         uiMapping.setVisible(mappingMode);
@@ -633,69 +576,27 @@ void keyPressed() {
         }
       }
       break;
         }
       }
       break;
+    case 't':
+      if (!midiEngine.isQwertyEnabled()) {
+        lx.engine.setThreaded(!lx.engine.isThreaded());
+      }
+      break;
+    case 'o':
     case 'p':
     case 'p':
-      for (PandaDriver p : pandaBoards) {
-        p.toggle();
+      for (LXOutput output : grizzlies) {
+        output.enabled.toggle();
       }
       break;
       }
       break;
-    case 'u':
-      if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
-        uiOn = !uiOn;
+    case 'q':
+      if (!midiEngine.isQwertyEnabled()) {
+        diagnosticsOn = !diagnosticsOn;
+      }
+      break;
+    case 's':
+      if (!midiEngine.isQwertyEnabled()) {
+        simulationOn = !simulationOn;
       }
       break;
   }
 }
 
       }
       break;
   }
 }
 
-/**
- * Top-level mouse event handling
- */
-int mx, my;
-void mousePressed() {
-  boolean debugged = false;
-  if (debugMode) {
-    debugged = debugUI.mousePressed();
-  }
-  if (!debugged) {
-    for (UIContext context : overlays) {
-      context.mousePressed(mouseX, mouseY);
-    }
-  }
-  mx = mouseX;
-  my = mouseY;
-}
-
-void mouseDragged() {
-  boolean dragged = false;
-  for (UIContext context : overlays) {
-    dragged |= context.mouseDragged(mouseX, mouseY);
-  }
-  if (!dragged) {
-    int dx = mouseX - mx;
-    int dy = mouseY - my;
-    mx = mouseX;
-    my = mouseY;
-    eyeA += dx*.003;
-    eyeX = midX + eyeR*sin(eyeA);
-    eyeZ = midZ + eyeR*cos(eyeA);
-    eyeY += dy;
-  }
-}
-
-void mouseReleased() {
-  for (UIContext context : overlays) {
-    context.mouseReleased(mouseX, mouseY);
-  }
-}
-void mouseWheel(int delta) {
-  boolean wheeled = false;
-  for (UIContext context : overlays) {
-    wheeled |= context.mouseWheel(mouseX, mouseY, delta);
-  }
-  
-  if (!wheeled) {
-    eyeR = constrain(eyeR - delta, -500, -80);
-    eyeX = midX + eyeR*sin(eyeA);
-    eyeZ = midZ + eyeR*cos(eyeA);
-  }
-}