redo with new anti-GLucose
authorBen Morrow <childoftv@gmail.com>
Mon, 3 Mar 2014 06:02:04 +0000 (22:02 -0800)
committerBen Morrow <childoftv@gmail.com>
Mon, 3 Mar 2014 06:02:04 +0000 (22:02 -0800)
18 files changed:
1  2 
AlexGreen.pde
AntonK.pde
Grizzly.pde
Internals.pde
JR.pde
JackieBavaro.pde
MIDI.pde
Mappings.pde
MarkSlee.pde
Model.pde
UIImplementation.pde
libraries/HeronLX.jar
libraries/JGraphT.jar
libraries/ScreenShot.dll
libraries/ScreenShot.jar
libraries/rwmidi.jar
libraries/toxiclibscore.jar
sketch.properties

diff --combined AlexGreen.pde
index 3e164d8bff34509891657cf09d585d69026c3f8a,54af5c04b14f40f24a35f963760afb4f793148f3..5a35b3e31bc24edb50b352d2d69d198569220436
@@@ -1,3 -1,3 +1,4 @@@
++
  class SineSphere extends APat {
    float modelrad = sqrt((model.xMax)*(model.xMax) + (model.yMax)*(model.yMax) + (model.zMax)*(model.zMax));
    private BasicParameter yrotspeed = new BasicParameter("yspeed", 3000, 1, 10000);
    public BasicParameter widthparameter= new BasicParameter("Width", 20, 1, 60);
    public BasicParameter vibration_magnitude = new BasicParameter("Vmag", 20, 2, modelrad/2);
    public BasicParameter scale = new BasicParameter("Scale", 1, .1, 5);
-   private int pitch = 0; 
-   private int channel = 0; 
-   private int velocity = 0; 
-   private int cur = 0; 
-   public final LXProjection sinespin; 
+   private int pitch = 0;
+   private int channel = 0;
+   private int velocity = 0;
+   private int cur = 0;
+   public final LXProjection sinespin;
    public final LXProjection sinespin2;
    public final LXProjection sinespin3;
   
-   Pick Galaxy, STime; 
+   Pick Galaxy, STime;
  
    public BasicParameter rotationx = new BasicParameter("rotx", 0, 0, 1 );
    public BasicParameter rotationy = new BasicParameter("roty", 1, 0, 1);
@@@ -29,8 -29,8 +30,8 @@@
  
    class Sphery {
    float f1xcenter, f1ycenter, f1zcenter, f2xcenter , f2ycenter, f2zcenter; //second three are for an ellipse with two foci
-   private  SinLFO vibration; 
-   private  SinLFO surfacewave;
+   private SinLFO vibration;
+   private SinLFO surfacewave;
  
    private SinLFO xbounce;
    public SinLFO ybounce;
    public BasicParameter vibrationrate;
    public final PVector circlecenter;
   
-   public Sphery(float f1xcenter, float f1ycenter, float f1zcenter, float radius, float vibration_magnitude , float vperiod) 
+   public Sphery(float f1xcenter, float f1ycenter, float f1zcenter, float radius, float vibration_magnitude , float vperiod)
    {
     this.f1xcenter = f1xcenter;
     this.f1ycenter = f1ycenter;
     this.f1zcenter = f1zcenter;
-    this.radius = radius; 
+    this.radius = radius;
     this.circlecenter= new PVector(f1xcenter,f1ycenter,f1zcenter);
  
     this.vibration_magnitude = vibration_magnitude;
     
     this.vperiod = vperiod;
     //addParameter(bounceamp = new BasicParameter("Amp", .5));
-    //addParameter(bouncerate = new BasicParameter("Rate", .5));  //ybounce.modulateDurationBy(bouncerate);
+    //addParameter(bouncerate = new BasicParameter("Rate", .5)); //ybounce.modulateDurationBy(bouncerate);
     //addParameter(vibrationrate = new BasicParameter("vibration", 1000, 10000));
      //addParameter(widthparameter = new BasicParameter("Width", .2));
-      //addModulator(xbounce = new SinLFO(model.xMax/3, 2*model.yMax/3, 2000)).trigger(); 
+      //addModulator(xbounce = new SinLFO(model.xMax/3, 2*model.yMax/3, 2000)).trigger();
     addModulator(ybounce= new SinLFO(model.yMax/3, 2*model.yMax/3, 240000)).trigger(); //bounce.modulateDurationBy
      
     //addModulator(bounceamp); //ybounce.setMagnitude(bouncerate);
        
    }
  
-   //  public Sphery(float f1xcenter, float f1ycenter, float f1zcenter, float vibration_magnitude, float vperiod) 
+   // public Sphery(float f1xcenter, float f1ycenter, float f1zcenter, float vibration_magnitude, float vperiod)
    // {
-   //  this.f1xcenter = f1xcenter;
-   //  this.f1ycenter = f1ycenter;
-   //  this.f1zcenter = f1zcenter;
-   //  this.vibration_magnitude = vibration_magnitude;
+   // this.f1xcenter = f1xcenter;
+   // this.f1ycenter = f1ycenter;
+   // this.f1zcenter = f1zcenter;
+   // this.vibration_magnitude = vibration_magnitude;
    // this.vperiod = vperiod;
-   //  addModulator(ybounce= new SinLFO(model.yMax/3, 2*model.yMax/3, 240000)).trigger(); //bounce.modulateDurationBy
-   //   addModulator( vibration = new SinLFO( modelrad/10 - vibration_magnitude , modelrad/10 + vibration_magnitude, vperiod)).trigger(); //vibration.setPeriod(240000/lx.tempo.bpm());
+   // addModulator(ybounce= new SinLFO(model.yMax/3, 2*model.yMax/3, 240000)).trigger(); //bounce.modulateDurationBy
+   // addModulator( vibration = new SinLFO( modelrad/10 - vibration_magnitude , modelrad/10 + vibration_magnitude, vperiod)).trigger(); //vibration.setPeriod(240000/lx.tempo.bpm());
        
    // }
  
    //for an ellipse
- //  public Sphery(float f1xcenter, float f1ycenter, float f1zcenter, float f2xcenter, float f2ycenter, float f2zcenter, 
- //   float vibration_min, float vibration_max, float vperiod)  
+ // public Sphery(float f1xcenter, float f1ycenter, float f1zcenter, float f2xcenter, float f2ycenter, float f2zcenter,
+ // float vibration_min, float vibration_max, float vperiod)
   
- //  {
- //     this.f1xcenter = f1xcenter;
- //    this.f1ycenter = f1ycenter;
- //    this.f1zcenter = f1zcenter;
- //    this.f2xcenter = f2xcenter;
- //    this.f2ycenter = f2ycenter;
- //    this.f2zcenter = f2zcenter;
- //    this.vibration_min = vibration_min;
- //    this.vibration_max = vibration_max;
- //    this.vperiod = vperiod;
- //    //addModulator(xbounce = new SinLFO(model.xMax/3, 2*model.yMax/3, 2000)).trigger(); 
- //    addModulator(ybounce).trigger(); 
- //    addModulator( vibration = new SinLFO(vibration_min , vibration_max, lx.tempo.rampf())).trigger(); //vibration.modulateDurationBy(vx);
- //    addParameter(widthparameter = new BasicParameter("Width", .1));
- //    //addParameter(huespread = new BasicParameter("bonk", .2));
+ // {
+ // this.f1xcenter = f1xcenter;
+ // this.f1ycenter = f1ycenter;
+ // this.f1zcenter = f1zcenter;
+ // this.f2xcenter = f2xcenter;
+ // this.f2ycenter = f2ycenter;
+ // this.f2zcenter = f2zcenter;
+ // this.vibration_min = vibration_min;
+ // this.vibration_max = vibration_max;
+ // this.vperiod = vperiod;
+ // //addModulator(xbounce = new SinLFO(model.xMax/3, 2*model.yMax/3, 2000)).trigger();
+ // addModulator(ybounce).trigger();
+ // addModulator( vibration = new SinLFO(vibration_min , vibration_max, lx.tempo.rampf())).trigger(); //vibration.modulateDurationBy(vx);
+ // addParameter(widthparameter = new BasicParameter("Width", .1));
+ // //addParameter(huespread = new BasicParameter("bonk", .2));
    
  // }
   
- public int     c1c    (float a)              { return round(100*constrain(a,0,1));               }
+ public int c1c (float a) { return round(100*constrain(a,0,1)); }
  
  void setVibrationPeriod(double period){
- // to-do:  make this conditional upon time signature
+ // to-do: make this conditional upon time signature
  
  this.vibration.setPeriod(period);
  }
@@@ -116,33 -116,33 +117,33 @@@ this.vibration.setRange(-mag,mag)
  }
  
  
- float distfromcirclecenter(float px, float py, float pz, float f1x, float f1y, float f1z) 
+ float distfromcirclecenter(float px, float py, float pz, float f1x, float f1y, float f1z)
  {
     return dist(px, py, pz, f1x, f1y, f1z);
      }
   //void updatespherey(deltaMs, )
  
   float quadrant(PVector q) {
-    float qtheta = atan2(  (q.x-f1xcenter) , (q.z - f1zcenter) ); 
-    float qphi = acos( (q.z-f1zcenter)/(PVector.dist(q,circlecenter)) );  
+    float qtheta = atan2( (q.x-f1xcenter) , (q.z - f1zcenter) );
+    float qphi = acos( (q.z-f1zcenter)/(PVector.dist(q,circlecenter)) );
  
  
      return map(qtheta, -PI/2, PI/2, 200-huespread.getValuef(), 240+huespread.getValuef());
    //if (q.x > f1xcenter ) {return 140 ;}
-     //else  {return 250;}  
+     //else {return 250;}
   }
  
   // float noisesat(PVector q) {
     
  
-  //  return noise()
+  // return noise()
  
   // }
   color spheryvalue (PVector p) {
-    circlecenter.set(this.f1xcenter, this.f1ycenter, this.f1zcenter); 
+    circlecenter.set(this.f1xcenter, this.f1ycenter, this.f1zcenter);
  
    
- //switch(sShpape.cur() ) {}  
+ //switch(sShpape.cur() ) {}
  
     float b = max(0, 100 - widthparameter.getValuef()*abs(p.dist(circlecenter)
        - vibration.getValuef()) );
       // constrain(100*noise(quadrant(p)), 0, 100),
       100,
        b
-    ); 
+    );
   }
   color ellipsevalue(float px, float py, float pz , float f1xc, float f1yc, float f1zc, float f2xc, float f2yc, float f2zc)
    {
- //switch(sShpape.cur() ) {}  
-    return lx.hsb(huespread.getValuef()*5*px, dist(model.xMax-px, model.yMax-py, model.zMax-pz, f1xc, f1yc, f1zc) , 
+ //switch(sShpape.cur() ) {}
+    return lx.hsb(huespread.getValuef()*5*px, dist(model.xMax-px, model.yMax-py, model.zMax-pz, f1xc, f1yc, f1zc) ,
      max(0, 100 - 100*widthparameter.getValuef() *
-       abs( (dist(px, py, pz, f1xc, ybounce.getValuef(), f1zc) + 
-         (dist(px, py , pz, f2xc, ybounce.getValuef(), f2zc) ) )/2  
-       - 1.2*vibration.getValuef() ) ) ) ; 
+       abs( (dist(px, py, pz, f1xc, ybounce.getValuef(), f1zc) +
+         (dist(px, py , pz, f2xc, ybounce.getValuef(), f2zc) ) )/2
+       - 1.2*vibration.getValuef() ) ) ) ;
    }
  
    
- }  
+ }
  
  boolean noteOn(Note note) {
      int row = note.getPitch(), col = note.getChannel();
-   //  if (row == 57) {KeyPressed = col; return true; }
+   // if (row == 57) {KeyPressed = col; return true; }
      return super.noteOn(note);
    }
  
  
- // public boolean noteOn(Note note)  {
- // pitch= note.getPitch();  
+ // public boolean noteOn(Note note) {
+ // pitch= note.getPitch();
  // velocity=note.getVelocity();
  // channel=note.getChannel();
  // return true;
  // }
  
  // public boolean gridPressed(int row, int col) {
- //  pitch = row; channel = col; 
- //  cur = NumApcCols*(pitch-53)+col; 
- // //setState(row, col, 0 ? 1 : 0); 
+ // pitch = row; channel = col;
+ // cur = NumApcCols*(pitch-53)+col;
+ // //setState(row, col, 0 ? 1 : 0);
  // return true;
  // }
  
  //public grid
  final Sphery[] spherys;
   
-   SineSphere(GLucose glucose) 
+   SineSphere(LX lx)
    {
-     super(glucose);
-     println("modelrad  " + modelrad);   
+     super(lx);
+     println("modelrad " + modelrad);
      sinespin = new LXProjection(model);
      sinespin2 = new LXProjection(model);
      sinespin3= new LXProjection(model);
      addParameter(vibration_magnitude);
      addParameter(scale);
      addModulator(yrot).trigger();
-     addModulator(yrot2).trigger(); 
+     addModulator(yrot2).trigger();
      addModulator(yrot3).trigger();
      //Galaxy = addPick("Galaxy", 1, 3, new String[] {"home", "vertical","single","aquarium"});
      STime =addPick("Time", 1, 4, new String[]{"half", "triplet", "beat", "2x", "3x" });
      spherys = new Sphery[] {
      new Sphery(model.xMax/4, model.yMax/2, model.zMax/2, modelrad/12, modelrad/25, 3000),
      new Sphery(.75*model.xMax, model.yMax/2, model.zMax/2, modelrad/14, modelrad/28, 2000),
-     new Sphery(model.cx, model.cy, model.cz,  modelrad/5, modelrad/15, 2300),
+     new Sphery(model.cx, model.cy, model.cz, modelrad/5, modelrad/15, 2300),
      new Sphery(.7*model.xMax, .65*model.yMax, .5*model.zMax, modelrad/11, modelrad/25, 3500),
      new Sphery(.75*model.xMax, .8*model.yMax, .7*model.zMax, modelrad/12, modelrad/30, 2000)
      
-             
-       // new Sphery(model.xMax/4, model.yMax/2, model.zMax/2, modelrad/16, modelrad/8, 3000),
-       // new Sphery(.75*model.xMax, model.yMax/2, model.zMax/2, modelrad/20, modelrad/10, 2000),
-       // new Sphery(model.xMax/2, model.yMax/2, model.zMax/2,  modelrad/4, modelrad/8, 2300),
-       // new Sphery(.7*model.xMax, .65*model.yMax, .5*model.zMax, modelrad/14, modelrad/7, 3500),
-       // new Sphery(.75*model.xMax, .8*model.yMax, .7*model.zMax, modelrad/20, modelrad/10, 2000),
-       // new Sphery(model.xMax/2, model.yMax/2, model.zMax/2,  modelrad/4, modelrad/8, 2300),
-       
-     };  
+           
+     };
    }
  
  // public void onParameterChanged(LXParameter parameter)
  // {
  
  
- //     for (Sphery s : spherys) {
- //       if (s == null) continue;
- //       double bampv = s.bounceamp.getValue();
- //       double brv = s.bouncerate.getValue();
- //       double tempobounce = lx.tempo.bpm();
- //       if (parameter == s.bounceamp) 
- //       {
- //         s.ybounce.setRange(bampv*model.yMax/3 , bampv*2*model.yMax/3, brv);
- //       }
- //       else if ( parameter == s.bouncerate )   
- //       {
- //         s.ybounce.setDuration(120000./tempobounce);
- //       }
- //     }
- //   }
+ // for (Sphery s : spherys) {
+ // if (s == null) continue;
+ // double bampv = s.bounceamp.getValue();
+ // double brv = s.bouncerate.getValue();
+ // double tempobounce = lx.tempo.bpm();
+ // if (parameter == s.bounceamp)
+ // {
+ // s.ybounce.setRange(bampv*model.yMax/3 , bampv*2*model.yMax/3, brv);
+ // }
+ // else if ( parameter == s.bouncerate )
+ // {
+ // s.ybounce.setDuration(120000./tempobounce);
+ // }
+ // }
+ // }
  
      public void run( double deltaMs) {
-      float  t = lx.tempo.rampf();
+      float t = lx.tempo.rampf();
       float bpm = lx.tempo.bpmf();
       float scalevalue = scale.getValuef();
       int spherytime= STime.Cur();
       
       switch (spherytime) {
  
-      case 0: t = map(.5*t ,0,.5, 0,1);   bpm = .5*bpm;  break;
+      case 0: t = map(.5*t ,0,.5, 0,1); bpm = .5*bpm; break;
  
-      case 1: t = t;   bpm = bpm;   break;
+      case 1: t = t; bpm = bpm; break;
  
-      case 2: t = map(2*t,0,2,0,1);  bpm = 2*bpm; break;
+      case 2: t = map(2*t,0,2,0,1); bpm = 2*bpm; break;
  
-      default: t= t;   bpm = bpm; 
+      default: t= t; bpm = bpm;
       }
  
       //switch(sphery.colorscheme)
          
        for ( Sphery s: spherys){
-       
-       //s.vibration.setBasis(t);
        s.setVibrationPeriod(vibrationrate.getValuef());
-     //  s.setVibrationMagnitude(vibration_magnitude.getValuef());
+     // s.setVibrationMagnitude(vibration_magnitude.getValuef());
       
         }
        
  
        sinespin.reset()
-       // Translate so the center of the car is the origin, offset 
+       // Translate so the center of the car is the origin, offset
        .center()
         .scale(scalevalue, scalevalue, scalevalue)
        // Rotate around the origin (now the center of the car) about an y-vector
     .translate(model.cx,model.cy,model.cz);
  
      for (LXVector p: sinespin2)
-     {   color c = 0;
+     { color c = 0;
        // PVector P = new PVector(p.x, p.y, p.z);
          P.set(p.x, p.y, p.z);
          c = blendIfColor(c, spherys[3].spheryvalue(P),ADD);
           
          colors[p.index] = blendIfColor(colors[p.index], c , ADD);
  
-     }  
+     }
      sinespin3.reset()
      .center()
      .scale(scalevalue,scalevalue,scalevalue)
      .rotate(yrot3.getValuef(),-1 + rotationx.getValuef(), rotationy.getValuef(), rotationz.getValuef())
      .translate(model.cx, model.cy, model.cz);
     for (LXVector p: sinespin3)
-     {   color c = 0;
+     { color c = 0;
        // PVector P = new PVector(p.x, p.y, p.z);
          P.set(p.x, p.y, p.z);
          c = blendIfColor(c, spherys[4].spheryvalue(P),ADD);
    }
    
  
-       //   color c = 0;
-       //   c = blendColor(c, spherys[3].ellipsevalue(Px.x, Px.y, Px.z, model.xMax/4, model.yMax/4, model.zMax/4, 3*model.xMax/4, 3*model.yMax/4, 3*model.zMax/4),ADD);
-       //   return c; 
+       // color c = 0;
+       // c = blendColor(c, spherys[3].ellipsevalue(Px.x, Px.y, Px.z, model.xMax/4, model.yMax/4, model.zMax/4, 3*model.xMax/4, 3*model.yMax/4, 3*model.zMax/4),ADD);
+       // return c;
        // }
        // return lx.hsb(0,0,0);
-       //  // else if(spheremode ==2)
+       // // else if(spheremode ==2)
         // { color c = 0;
-        //   return lx.hsb(CalcCone( (xyz by = new xyz(0,spherys[2].ybounce.getValuef(),0) ), Px, mid) );
+        // return lx.hsb(CalcCone( (xyz by = new xyz(0,spherys[2].ybounce.getValuef(),0) ), Px, mid) );
  
         // }
  
    
-        //   } 
+        // }
          
    }
  /*This just takes all of Dan Horwitz's code that I want to inherit and leaves the rest behind.
 A work in progress. */
+ A work in progress. */
  
  public class APat extends SCPattern
  
  
  {
-   ArrayList<Pick>   picks  = new ArrayList<Pick>  ();
-   ArrayList<DBool>  bools  = new ArrayList<DBool> ();
-   PVector   mMax, mCtr, mHalf;
-   MidiOutput  APCOut;
-   int     nMaxRow   = 53;
-   float   LastJog = -1;
-   float[]   xWaveNz, yWaveNz;
-   int     nPoint  , nPoints;
-   PVector   xyzJog = new PVector(), modmin;
-   float     NoiseMove = random(10000);
-   BasicParameter  pSpark, pWave, pRotX, pRotY, pRotZ, pSpin, pTransX, pTransY;
-   DBool     pXsym, pYsym, pRsym, pXdup, pXtrip, pJog, pGrey;
-   float   lxh   ()                  { return lx.getBaseHuef();                      }
-   int     c1c    (float a)              { return round(100*constrain(a,0,1));               }
-   float     interpWv(float i, float[] vals)       { return interp(i-floor(i), vals[floor(i)], vals[ceil(i)]);     }
-   void    setNorm (PVector vec)           { vec.set(vec.x/mMax.x, vec.y/mMax.y, vec.z/mMax.z);        }
-   void    setRand (PVector vec)           { vec.set(random(mMax.x), random(mMax.y), random(mMax.z));      }
-   void    setVec  (PVector vec, LXPoint p)        { vec.set(p.x, p.y, p.z);                       }
-   void    interpolate(float i, PVector a, PVector b)  { a.set(interp(i,a.x,b.x), interp(i,a.y,b.y), interp(i,a.z,b.z));   }
-   //void      StartRun(double deltaMs)          { }
-   float     val   (BasicParameter p)          { return p.getValuef();                       }
-   //color   CalcPoint(PVector p)            { return lx.hsb(0,0,0);                       }
-   color   blend3(color c1, color c2, color c3)    { return blendColor(c1,blendColor(c2,c3,ADD),ADD);          }
-   void  rotateZ (PVector p, PVector o, float nSin, float nCos) { p.set(    nCos*(p.x-o.x) - nSin*(p.y-o.y) + o.x    , nSin*(p.x-o.x) + nCos*(p.y-o.y) + o.y,p.z); }
-   void  rotateX (PVector p, PVector o, float nSin, float nCos) { p.set(p.x,nCos*(p.y-o.y) - nSin*(p.z-o.z) + o.y    , nSin*(p.y-o.y) + nCos*(p.z-o.z) + o.z    ); }
-   void  rotateY (PVector p, PVector o, float nSin, float nCos) { p.set(    nSin*(p.z-o.z) + nCos*(p.x-o.x) + o.x,p.y, nCos*(p.z-o.z) - nSin*(p.x-o.x) + o.z    ); }
-   BasicParameter  addParam(String label, double value)  { BasicParameter p = new BasicParameter(label, value); addParameter(p); return p; }
-   PVector   vT1 = new PVector(), vT2 = new PVector();
-   float     calcCone (PVector v1, PVector v2, PVector c)  { vT1.set(v1); vT2.set(v2); vT1.sub(c); vT2.sub(c);
+   ArrayList<Pick> picks = new ArrayList<Pick> ();
+   ArrayList<DBool> bools = new ArrayList<DBool> ();
+   PVector mMax, mCtr, mHalf;
+   MidiOutput APCOut;
+   int nMaxRow = 53;
+   float LastJog = -1;
+   float[] xWaveNz, yWaveNz;
+   int nPoint , nPoints;
+   PVector xyzJog = new PVector(), modmin;
+   float NoiseMove = random(10000);
+   BasicParameter pSpark, pWave, pRotX, pRotY, pRotZ, pSpin, pTransX, pTransY;
+   DBool pXsym, pYsym, pRsym, pXdup, pXtrip, pJog, pGrey;
+   float lxh () { return lx.getBaseHuef(); }
+   int c1c (float a) { return round(100*constrain(a,0,1)); }
+   float interpWv(float i, float[] vals) { return interp(i-floor(i), vals[floor(i)], vals[ceil(i)]); }
+   void setNorm (PVector vec) { vec.set(vec.x/mMax.x, vec.y/mMax.y, vec.z/mMax.z); }
+   void setRand (PVector vec) { vec.set(random(mMax.x), random(mMax.y), random(mMax.z)); }
+   void setVec (PVector vec, LXPoint p) { vec.set(p.x, p.y, p.z); }
+   void interpolate(float i, PVector a, PVector b) { a.set(interp(i,a.x,b.x), interp(i,a.y,b.y), interp(i,a.z,b.z)); }
+   void StartRun(double deltaMs) { }
+   float val (BasicParameter p) { return p.getValuef(); }
+   color CalcPoint(PVector p) { return lx.hsb(0,0,0); }
+   color blend3(color c1, color c2, color c3) { return blendColor(c1,blendColor(c2,c3,ADD),ADD); }
+   void rotateZ (PVector p, PVector o, float nSin, float nCos) { p.set( nCos*(p.x-o.x) - nSin*(p.y-o.y) + o.x , nSin*(p.x-o.x) + nCos*(p.y-o.y) + o.y,p.z); }
+   void rotateX (PVector p, PVector o, float nSin, float nCos) { p.set(p.x,nCos*(p.y-o.y) - nSin*(p.z-o.z) + o.y , nSin*(p.y-o.y) + nCos*(p.z-o.z) + o.z ); }
+   void rotateY (PVector p, PVector o, float nSin, float nCos) { p.set( nSin*(p.z-o.z) + nCos*(p.x-o.x) + o.x,p.y, nCos*(p.z-o.z) - nSin*(p.x-o.x) + o.z ); }
+   BasicParameter addParam(String label, double value) { BasicParameter p = new BasicParameter(label, value); addParameter(p); return p; }
+   PVector vT1 = new PVector(), vT2 = new PVector();
+   float calcCone (PVector v1, PVector v2, PVector c) { vT1.set(v1); vT2.set(v2); vT1.sub(c); vT2.sub(c);
                                    return degrees(PVector.angleBetween(vT1,vT2)); }
  
-   Pick    addPick(String name, int def, int _max, String[] desc) {
-     Pick P    = new Pick(name, def, _max+1, nMaxRow, desc); 
-     nMaxRow   = P.EndRow + 1;
+   Pick addPick(String name, int def, int _max, String[] desc) {
+     Pick P = new Pick(name, def, _max+1, nMaxRow, desc);
+     nMaxRow = P.EndRow + 1;
      picks.add(P);
      return P;
    }
  
-     boolean   noteOff(Note note) {
+     boolean noteOff(Note note) {
      int row = note.getPitch(), col = note.getChannel();
      for (int i=0; i<bools.size(); i++) if (bools.get(i).set(row, col, false)) { presetManager.dirty(this); return true; }
      updateLights(); return false;
    }
  
-     boolean   noteOn(Note note) {
+     boolean noteOn(Note note) {
      int row = note.getPitch(), col = note.getChannel();
-     for (int i=0; i<picks.size(); i++) if (picks.get(i).set(row, col))        { presetManager.dirty(this); return true; }
-     for (int i=0; i<bools.size(); i++) if (bools.get(i).set(row, col, true))  { presetManager.dirty(this); return true; }
-     println("row: " + row + "  col:   " + col); return false;
+     for (int i=0; i<picks.size(); i++) if (picks.get(i).set(row, col)) { presetManager.dirty(this); return true; }
+     for (int i=0; i<bools.size(); i++) if (bools.get(i).set(row, col, true)) { presetManager.dirty(this); return true; }
+     println("row: " + row + " col: " + col); return false;
    }
  
-   void    onInactive()      { uiDebugText.setText(""); }
-   void    onReset()         {
+   void onInactive() { uiDebugText.setText(""); }
+   void onReset() {
      for (int i=0; i<bools .size(); i++) bools.get(i).reset();
      for (int i=0; i<picks .size(); i++) picks.get(i).reset();
-     presetManager.dirty(this); 
-     updateLights(); 
+     presetManager.dirty(this);
+     updateLights();
    }
  
-   APat(GLucose glucose) {
-     super(glucose);
+   APat(LX lx) {
+     super(lx);
  
      
  
-     nPoints   = model.points.size();
-     pXsym     = new DBool("X-SYM", false, 48, 0); bools.add(pXsym );
-     pYsym     = new DBool("Y-SYM", false, 48, 1); bools.add(pYsym );
-     pRsym     = new DBool("R-SYM", false, 48, 2); bools.add(pRsym );
-     pXdup   = new DBool("X-DUP", false, 48, 3); bools.add(pXdup );
-     pJog    = new DBool("JOG"  , false, 48, 4); bools.add(pJog  );
-     pGrey   = new DBool("GREY" , false, 48, 5); bools.add(pGrey );
-     modmin    = new PVector(model.xMin, model.yMin, model.zMin);
-     mMax    =   new PVector(model.xMax, model.yMax, model.zMax); mMax.sub(modmin);
-     mCtr    =   new PVector(); mCtr.set(mMax); mCtr.mult(.5);
-     mHalf   =   new PVector(.5,.5,.5);
-     xWaveNz   = new float[ceil(mMax.y)+1];
-     yWaveNz   = new float[ceil(mMax.x)+1];
-     //println (model.xMin + " " + model.yMin + " " +  model.zMin);
-     //println (model.xMax + " " + model.yMax + " " +  model.zMax);
+     nPoints = model.points.size();
+     pXsym = new DBool("X-SYM", false, 48, 0); bools.add(pXsym );
+     pYsym = new DBool("Y-SYM", false, 48, 1); bools.add(pYsym );
+     pRsym = new DBool("R-SYM", false, 48, 2); bools.add(pRsym );
+     pXdup = new DBool("X-DUP", false, 48, 3); bools.add(pXdup );
+     pJog = new DBool("JOG" , false, 48, 4); bools.add(pJog );
+     pGrey = new DBool("GREY" , false, 48, 5); bools.add(pGrey );
+     modmin = new PVector(model.xMin, model.yMin, model.zMin);
+     mMax = new PVector(model.xMax, model.yMax, model.zMax); mMax.sub(modmin);
+     mCtr = new PVector(); mCtr.set(mMax); mCtr.mult(.5);
+     mHalf = new PVector(.5,.5,.5);
+     xWaveNz = new float[ceil(mMax.y)+1];
+     yWaveNz = new float[ceil(mMax.x)+1];
+     //println (model.xMin + " " + model.yMin + " " + model.zMin);
+     //println (model.xMax + " " + model.yMax + " " + model.zMax);
      //for (MidiOutputDevice o: RWMidi.getOutputDevices()) { if (o.toString().contains("APC")) { APCOut = o.createOutput(); break;}}
    }
  
    }
  
    void updateLights() { if (APCOut == null) return;
-       for (int i = 0; i < NumApcRows; ++i) 
-         for (int j = 0; j < 8; ++j)     APCOut.sendNoteOn(j, 53+i,  0);
-     for (int i=0; i<picks .size(); i++)   APCOut.sendNoteOn(picks.get(i).CurCol, picks.get(i).CurRow, 3);
-     for (int i=0; i<bools .size(); i++)   if (bools.get(i).b)   APCOut.sendNoteOn (bools.get(i).col, bools.get(i).row, 1);
-                         else          APCOut.sendNoteOff  (bools.get(i).col, bools.get(i).row, 0);
+       for (int i = 0; i < NumApcRows; ++i)
+         for (int j = 0; j < 8; ++j) APCOut.sendNoteOn(j, 53+i, 0);
+     for (int i=0; i<picks .size(); i++) APCOut.sendNoteOn(picks.get(i).CurCol, picks.get(i).CurRow, 3);
+     for (int i=0; i<bools .size(); i++) if (bools.get(i).b) APCOut.sendNoteOn (bools.get(i).col, bools.get(i).row, 1);
+                         else APCOut.sendNoteOff (bools.get(i).col, bools.get(i).row, 0);
    }
  
    void run(double deltaMs)
  
      if (this == midiEngine.getFocusedDeck().getActivePattern()) {
        String Text1="", Text2="";
-       for (int i=0; i<bools.size(); i++) if (bools.get(i).b) Text1 += " " + bools.get(i).tag       + "   ";
-       for (int i=0; i<picks.size(); i++) Text1 += picks.get(i).tag + ": " + picks.get(i).CurDesc() + "   ";
+       for (int i=0; i<bools.size(); i++) if (bools.get(i).b) Text1 += " " + bools.get(i).tag + " ";
+       for (int i=0; i<picks.size(); i++) Text1 += picks.get(i).tag + ": " + picks.get(i).CurDesc() + " ";
        uiDebugText.setText(Text1, Text2);
      }
  
-     
+    NoiseMove += deltaMs; NoiseMove = NoiseMove % 1e7;
+     StartRun (deltaMs);
+     PVector P = new PVector(), tP = new PVector(), pSave = new PVector();
+     PVector pTrans = new PVector(val(pTransX)*200-100, val(pTransY)*100-50,0);
+     nPoint = 0;
+     if (pJog.b) {
+       float tRamp = (lx.tempo.rampf() % .25);
+       if (tRamp < LastJog) xyzJog.set(randctr(mMax.x*.2), randctr(mMax.y*.2), randctr(mMax.z*.2));
+       LastJog = tRamp;
+     }
+     // precalculate this stuff
+     float wvAmp = val(pWave), sprk = val(pSpark);
+     if (wvAmp > 0) {
+       for (int i=0; i<ceil(mMax.x)+1; i++)
+         yWaveNz[i] = wvAmp * (noise(i/(mMax.x*.3)-(2e3+NoiseMove)/1500.) - .5) * (mMax.y/2.);
+       for (int i=0; i<ceil(mMax.y)+1; i++)
+         xWaveNz[i] = wvAmp * (noise(i/(mMax.y*.3)-(1e3+NoiseMove)/1500.) - .5) * (mMax.x/2.);
+     }
+     for (LXPoint p : model.points) { nPoint++;
+       setVec(P,p);
+       P.sub(modmin);
+       P.sub(pTrans);
+       if (sprk > 0) {P.y += sprk*randctr(50); P.x += sprk*randctr(50); P.z += sprk*randctr(50); }
+       if (wvAmp > 0) P.y += interpWv(p.x-modmin.x, yWaveNz);
+       if (wvAmp > 0) P.x += interpWv(p.y-modmin.y, xWaveNz);
+       if (pJog.b) P.add(xyzJog);
+       color cNew, cOld = colors[p.index];
+               { tP.set(P); cNew = CalcPoint(tP); }
+       if (pXsym.b) { tP.set(mMax.x-P.x,P.y,P.z); cNew = blendColor(cNew, CalcPoint(tP), ADD); }
+       if (pYsym.b) { tP.set(P.x,mMax.y-P.y,P.z); cNew = blendColor(cNew, CalcPoint(tP), ADD); }
+       if (pRsym.b) { tP.set(mMax.x-P.x,mMax.y-P.y,mMax.z-P.z); cNew = blendColor(cNew, CalcPoint(tP), ADD); }
+       if (pXdup.b) { tP.set((P.x+mMax.x*.5)%mMax.x,P.y,P.z); cNew = blendColor(cNew, CalcPoint(tP), ADD); }
+       if (pGrey.b) { cNew = lx.hsb(0, 0, lx.b(cNew)); }
+       colors[p.index] = cNew;
+     }
    }
  }
  
@@@ -517,12 -544,12 +545,12 @@@ class CubeCurl extends SCPattern
  float CH, CW, diag;
  ArrayList<PVector> cubeorigin = new ArrayList<PVector>();
  ArrayList<PVector> centerlist = new ArrayList<PVector>();
- private SinLFO curl = new SinLFO(0, Cube.EDGE_HEIGHT, 5000 ); 
+ private SinLFO curl = new SinLFO(0, Cube.EDGE_HEIGHT, 5000 );
  
  private SinLFO bg = new SinLFO(180, 220, 3000);
  
- CubeCurl(GLucose glucose){
- super(glucose);
+ CubeCurl(LX lx){
+ super(lx);
  addModulator(curl).trigger();
  addModulator(bg).trigger();
   this.CH = Cube.EDGE_HEIGHT;
@@@ -536,11 -563,11 +564,11 @@@ for (int i = 0; i < model.cubes.size()
    cubeorigin.add(new PVector(a.x, a.y, a.z));
    centerlist.add(new PVector(a.cx, a.cy, a.cz) );
    
- } 
+ }
  
  }
  //there is definitely a better way of doing this!
- PVector centerofcube(int i) { 
+ PVector centerofcube(int i) {
  Cube c = model.cubes.get(i);
  PVector cubecenter = new PVector(c.cx, c.cy, c.cz);
  
@@@ -549,36 -576,36 +577,36 @@@ return cubecenter
  
  
  void run(double deltaMs){
- for (int i =0; i < model.cubes.size(); i++)  {
+ for (int i =0; i < model.cubes.size(); i++) {
  Cube c = model.cubes.get(i);
  float cfloor = c.y;
+     
  // if (i%3 == 0){
  
  // for (LXPoint p : c.points ){
- //  // colors[p.index]=color(0,0,0);
- //   //float dif = (p.y - c.y);
- //   //colors[p.index] = color( bg.getValuef() , 80 , dif < curl.getValuef() ? 80 : 0, ADD);
- //    }
- //  }
+ // // colors[p.index]=color(0,0,0);
+ // //float dif = (p.y - c.y);
+ // //colors[p.index] = color( bg.getValuef() , 80 , dif < curl.getValuef() ? 80 : 0, ADD);
+ // }
+ // }
  
  // else if (i%3 == 1) {
    
- //  for (LXPoint p: c.points){
- //   colors[p.index]=color(0,0,0);
- //   float dif = (p.y - c.y);
- //   // colors[p.index] = 
- //   // color(bg.getValuef(),
- //   //   map(curl.getValuef(), 0, Cube.EDGE_HEIGHT, 20, 100), 
- //   //   100 - 10*abs(dif - curl.getValuef()), ADD );
- //      }
- //     }
+ // for (LXPoint p: c.points){
+ // colors[p.index]=color(0,0,0);
+ // float dif = (p.y - c.y);
+ // // colors[p.index] =
+ // // color(bg.getValuef(),
+ // // map(curl.getValuef(), 0, Cube.EDGE_HEIGHT, 20, 100),
+ // // 100 - 10*abs(dif - curl.getValuef()), ADD );
+ // }
+ // }
  // else if (i%3 == 2){
   // centerlist[i].sub(cubeorigin(i);
     for (LXPoint p: c.points) {
      PVector pv = new PVector(p.x, p.y, p.z);
-      colors[p.index] =color( constrain(4* pv.dist(centerlist.get(i)), 0, 360)  , 50, 100 );
-    // colors[p.index] =color(constrain(centerlist[i].x, 0, 360), constrain(centerlist[i].y, 0, 100),  );
+      colors[p.index] =color( constrain(4* pv.dist(centerlist.get(i)), 0, 360) , 50, 100 );
+    // colors[p.index] =color(constrain(centerlist[i].x, 0, 360), constrain(centerlist[i].y, 0, 100), );
  
  
      }
     }
    }
   }
+   JGraphAdapterDemo graph1; 
+   
+ // class SpinningCube extends SCPattern{
+ // LXProjection spin1, spin2, spin3; 
+ // SawLFO 
+ //}
+ class PixelGraph implements EdgeFactory<dPixel, dVertex> {
+ dPixel p0; dPixel p1; dVertex v0;
+ public dVertex createEdge(dPixel p0, dPixel p1) {
+   return v0;
+ }
+ }
+  class GraphTest extends SCPattern {
+   JGraphAdapterDemo graph1;
+   
+ GraphTest( LX lx) {super(lx); JGraphAdapterDemo graph1 = new JGraphAdapterDemo();}
+  
+   void run(double deltaMs){
+   }
+ }
+ class SpinningCube extends SCPattern{
+  
+  LXProjection spin1, spin2, spin3;
+  SawLFO spinx, spiny, spinz;
+  SinLFO spinx1, spiny1, spinz1, cubesize;
+  BasicParameter xoff = new BasicParameter("xoff", 10, 0, 100);
+  BasicParameter toff = new BasicParameter("toff", 10,0,1000);
+  BasicParameter huev = new BasicParameter("hue", 200, 0, 360);
+  BasicParameter density = new BasicParameter("density", 0, 0, 1);
+  BasicParameter Vsize = new BasicParameter("size", model.xMax/3,0, model.xMax);
+  VirtualCube V1, V2, V3;
+  PVector P = new PVector();
+  float noisetime=0.;
+   class VirtualCube {
+   float x,y,z,d;
+   PVector center;
+   VirtualCube(float x, float y, float z, float d) {
+     this.x=x;
+     this.y= y;
+     this.z=z;
+     this.d=d;
+     this.center=new PVector(x,y,z);
+       }
+     
+     color getcolor(LXVector q) {
+      if ( q.x > this.x + d/2 || q.x < this.x - d/2 || q.y > this.y + d/2 || q.y < this.y - d/2 || q.z > this.z + d/2 || q.z < this.z - d/2 )
+      {return 0;}
+       else {
+       return lx.hsb(huev.getValuef()*noise(xoff.getValuef()*.001*noisetime ) , constrain(100*noise(xoff.getValuef()*.001*q.x*noisetime), 0, 100), max(100*(noise(xoff.getValuef()*.001*q.x*noisetime)-density.getValuef()), 0) );
+       }
+     }
+     void setcenter(float x, float y, float z) {this.x=x; this.y = y; this.z=z; }
+     void setsize(float din){ this.d=din ; }
+     }
+ SpinningCube(LX lx) {
+   super(lx);
+   addParameter(xoff);
+   addParameter(toff);
+   addParameter(Vsize);
+   addParameter(huev);
+   addParameter(density);
+   //addModulator()
+   V1 = new VirtualCube(model.cx, model.cy, model.cz, model.xMax/2);
+   spinx= new SawLFO(0, TWO_PI, 8000);
+   spin1 = new LXProjection(model);
+    
+ }
+ void run(double deltaMs) {
+   
+   noisetime+= deltaMs*.0001*toff.getValuef();
+ spin1.reset()
+ .center()
+ //.scale ()
+ .rotate(spinx.getValuef(),0, 1, 0)
+ .translate(model.cx, model.cy, model.cz);
+ for (LXVector p: spin1) {
+   P.set(p.x, p.y, p.z);
+  colors[p.index] = V1.getcolor(p);
+ }
+ V1.setsize(Vsize.getValuef());
+ };
+ }
  
   class HueTestHSB extends SCPattern{
    BasicParameter HueT = new BasicParameter("Hue", .5);
    BasicParameter SatT = new BasicParameter("Sat", .5);
    BasicParameter BriT = new BasicParameter("Bright", .5);
  
- HueTestHSB(GLucose glucose) {
-   super(glucose);
+ HueTestHSB(LX lx) {
+   super(lx);
    addParameter(HueT);
    addParameter(SatT);
    addParameter(BriT);
     int now= millis();
     if (now % 1000 <= 20)
     {
-    println("Hue: " + 360*HueT.getValuef() + "Sat: " + 100*SatT.getValuef() + "Bright:  " + 100*BriT.getValuef());
+    println("Hue: " + 360*HueT.getValuef() + "Sat: " + 100*SatT.getValuef() + "Bright: " + 100*BriT.getValuef());
     }
    }
  
diff --combined AntonK.pde
index 33f4f5d0dd26f6df6e08834be011fa5ecb4dd543,5010a4ee16aeaee25e03cefb6ffca641d4b725ef..5b286f1c3c49c18d8eff9a36e9b7b291d69a7838
@@@ -1,8 -1,7 +1,8 @@@
  /**************************************************************
   * WORKING PATTERNS
   **************************************************************/
 -
 +import java.util.List;
 +import java.util.LinkedList;
  class AKPong extends SCPattern
  {
      private final BasicParameter speed = new BasicParameter("Speed", 0);
          return true;
      }
  
-     public AKPong(GLucose glucose)
+     public AKPong(LX lx)
      {
-         super(glucose);
+         super(lx);
          addParameter(speed);
          addParameter(leftKnob);
          addParameter(rightKnob);
  class AKInvader extends SCPattern
  {
      private final SawLFO h = new SawLFO(0, 1, 5000);
-     public AKInvader(GLucose glucose)
+     public AKInvader(LX lx)
      {
-         super(glucose);
+         super(lx);
          addModulator(h).trigger();
      }
      
@@@ -320,9 -319,9 +320,9 @@@ class AKTetris extends SCPatter
          }
      }
      
-     public AKTetris(GLucose glucose)
+     public AKTetris(LX lx)
      {
-         super(glucose);
+         super(lx);
      }
      
      public boolean noteOn(Note note)
@@@ -373,9 -372,9 +373,9 @@@ class AKMatrix extends SCPatter
          }
      }
  
-     public AKMatrix(GLucose glucose)
+     public AKMatrix(LX lx)
      {
-         super(glucose);
+         super(lx);
  //        for (Tower t : model.towers)
          {
              Tower t = model.towers.get(0);
@@@ -416,9 -415,9 +416,9 @@@ class AKEgg extends SCPatter
      private final float Y = model.yMax / 2;
      private final float Z = model.zMax / 2;
  
-     public AKEgg(GLucose glucose)
+     public AKEgg(LX lx)
      {
-         super(glucose);
+         super(lx);
          addModulator(xRadius).trigger();
          addModulator(yRadius).trigger();
          addModulator(zRadius).trigger();
@@@ -447,9 -446,9 +447,9 @@@ class AKCubes extends SCPatter
      private Cube cube;
      private int sec;
      
-     public AKCubes(GLucose glucose)
+     public AKCubes(LX lx)
      {
-         super(glucose);
+         super(lx);
          cube = model.cubes.get((int) random(model.cubes.size()));
          sec = 0;
      }
  class AKSpiral extends SCPattern
  {
      private int ms;
-     public AKSpiral(GLucose glucose)
+     public AKSpiral(LX lx)
      {
-         super(glucose);
+         super(lx);
          ms = 0;
      }
      
@@@ -538,9 -537,9 +538,9 @@@ class AKSpace extends SCPatter
          }
      }
      
-     public AKSpace(GLucose glucose)
+     public AKSpace(LX lx)
      {
-         super(glucose);
+         super(lx);
          stars = new LinkedList<Star>();
          for (int i = 0; i < 50; ++i)
              stars.add(new Star());
diff --combined Grizzly.pde
index 0000000000000000000000000000000000000000,25d390f07fba7d653039521539f7dabd395a2ca4..5f46b967d6cfae3628f7f9f4645dd60af3d1fdb1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,158 +1,158 @@@
 -
+ /**
+  *     DOUBLE BLACK DIAMOND        DOUBLE BLACK DIAMOND
+  *
+  *         //\\   //\\                 //\\   //\\  
+  *        ///\\\ ///\\\               ///\\\ ///\\\
+  *        \\\/// \\\///               \\\/// \\\///
+  *         \\//   \\//                 \\//   \\//H
+  *
+  *        EXPERTS ONLY!!              EXPERTS ONLY!!
+  *
+  * If you are an artist, you may ignore this file! It contains
+  * the code to drive grizzly board outputs.
+  */
++import java.net.*;
+ GrizzlyOutput[] buildGrizzlies() throws SocketException, UnknownHostException {
+   return new GrizzlyOutput[] {
+     new GrizzlyOutput(lx, "192.168.88.100", 6, 5, 6, 7, 7, 8, 1, 2, 4, 3, 11, 10, 9, 9, 12, 13),
+     new GrizzlyOutput(lx, "192.168.88.101", 25, 23, 24, 43, 45, 44, 1, 1, 1, 1, 1, 41, 42, 21, 20, 22),
+     new GrizzlyOutput(lx, "192.168.88.104", 26, 28, 27, 19, 18, 17, 1, 1, 18, 19, 15, 16, 14, 29, 30, 31),
+     new GrizzlyOutput(lx, "192.168.88.105", 1, 1, 1, 39, 38, 40, 34, 35, 33, 32, 37, 37, 1, 1, 1, 1),
+   };
+ }
+ /**
+  * Grizzly Output, sends packets to one grizzly board with a fixed IP and a number
+  * of channels.
+  */
+ class GrizzlyOutput extends LXDatagramOutput {
+   public final String ipAddress;
+   
+   private int frameNumber = 0;
+   
+   public GrizzlyOutput(LX lx, String ipAddress, int ... cubeIndices) throws UnknownHostException, SocketException {
+     super(lx);
+     this.ipAddress = ipAddress;
+     int channelNum = 0;
+     for (int rawCubeIndex : cubeIndices) {
+       if (rawCubeIndex > 0) {
+         Cube cube = model.getCubeByRawIndex(rawCubeIndex);
+         addDatagram(new GrizzlyDatagram(this, channelNum, cube).setAddress(ipAddress));
+       }
+       ++channelNum;
+     }
+     this.enabled.setValue(false);
+   }
+   
+   protected void beforeSend(int[] colors) {
+     ++frameNumber;
+   }
+   
+   public int getFrameNumber() {
+     return this.frameNumber;
+   }
+ }
+ /**
+  * Datagram to a Grizzlyboard. A simple fixed OSC packet.
+  */
+ static class GrizzlyDatagram extends LXDatagram {
+   
+   private static byte[] oscString(String s) {
+     int len = s.length();
+     int padding = (4 - ((len + 1) % 4)) % 4;
+     byte[] bytes = new byte[len + 1 + padding];
+     System.arraycopy(s.getBytes(), 0, bytes, 0, len);
+     for (int i = len; i < bytes.length; ++i) {
+       bytes[i] = 0;
+     }
+     return bytes;
+   }
+   
+   private static int oscIntCopy(int i, byte[] buffer, int pos) {
+     buffer[pos] = (byte) ((i >> 24) & 0xff);
+     buffer[pos + 1] = (byte) ((i >> 16) & 0xff);
+     buffer[pos + 2] = (byte) ((i >> 8) & 0xff);
+     buffer[pos + 3] = (byte) (i & 0xff);
+     return 4;
+   }
+   
+   private final static int[] STRIP_ORDERING = new int[] { 9, 8, 11, 5, 4, 7, 6, 10, 14, 2, 1, 0, 3, 13, 12, 15 };
+   
+   private static int[] cubePointIndices(Cube cube) {
+     int[] pointIndices = new int[Cube.POINTS_PER_CUBE - 2];
+     int pi = 0;
+     for (int stripIndex : STRIP_ORDERING) {
+       Strip strip = cube.strips.get(stripIndex);
+       int stripLen = ((stripIndex == 9) || (stripIndex == 15)) ? 15 : 16;
+       for (int i = stripLen-1; i >= 0; --i) {
+         pointIndices[pi++] = strip.points.get(i).index;
+       }
+     }
+     return pointIndices;
+   }
+   
+   private final static byte[] OSC_ADDRESS = oscString("/shady/pointbuffer");
+   private final static byte[] OSC_TYPETAG = oscString(",iiiiibi");  
+   private final static int HEADER_LENGTH = OSC_ADDRESS.length + OSC_TYPETAG.length + 24;
+   private final static int FOOTER_LENGTH = 4;
+   private final static int OSC_PORT = 779;
+   private GrizzlyOutput output;
+   
+   private int[] pointIndices;
+   private final int frameNumberPos;
+   
+   private final int dataPos;
+   
+   public GrizzlyDatagram(GrizzlyOutput output, int channelNum, Cube cube) {
+     this(output, channelNum, cubePointIndices(cube));
+   }
+   
+   public GrizzlyDatagram(GrizzlyOutput output, int channelNum, int[] pointIndices) {
+     super(HEADER_LENGTH + 4*pointIndices.length + FOOTER_LENGTH);
+     setPort(OSC_PORT);
+     
+     this.output = output;
+     this.pointIndices = pointIndices;
+     int dataLength = 4*pointIndices.length;
+     
+     int pos = 0;
+     // OSC address
+     System.arraycopy(OSC_ADDRESS, 0, this.buffer, pos, OSC_ADDRESS.length);
+     pos += OSC_ADDRESS.length;
+     // OSC typetag
+     System.arraycopy(OSC_TYPETAG, 0, this.buffer, pos, OSC_TYPETAG.length);
+     pos += OSC_TYPETAG.length;
+     this.frameNumberPos = pos;
+     pos += oscIntCopy(0, this.buffer, pos); // placeholder for frame number
+     pos += oscIntCopy(0xDEADBEEF, this.buffer, pos);
+     pos += oscIntCopy(channelNum, this.buffer, pos);
+     pos += oscIntCopy(0xFEEDBEEF, this.buffer, pos);
+     pos += oscIntCopy(dataLength, this.buffer, pos);
+     pos += oscIntCopy(dataLength, this.buffer, pos);
+     this.dataPos = pos;
+     
+     // end header
+     oscIntCopy(0xBEFFFFEB, this.buffer, this.buffer.length - 4);
+   }
+   
+   void onSend(int[] colors) {
+     oscIntCopy(this.output.getFrameNumber(), this.buffer, frameNumberPos);
+     int dataIndex = this.dataPos;
+     for (int index : this.pointIndices) {
+       color c = (index >= 0) ? colors[index] : 0;
+       this.buffer[dataIndex] = (byte) 0; // unused, alpha
+       this.buffer[dataIndex + 1] = (byte) ((c >> 16) & 0xff); // r
+       this.buffer[dataIndex + 2] = (byte) ((c >> 8) & 0xff); // g
+       this.buffer[dataIndex + 3] = (byte) (c & 0xff); // b
+       dataIndex += 4;
+     }
+   }
+ }
diff --combined Internals.pde
index a1b8baf03f6ea1ca1cfda7833ec6021098b6c22b,b593f3ef07b9c4aa7db088430348d3da9c653ae7..b593f3ef07b9c4aa7db088430348d3da9c653ae7
   * for general animation work.
   */
  
- import glucose.*;
- import glucose.model.*;
  import heronarts.lx.*;
  import heronarts.lx.effect.*;
+ import heronarts.lx.model.*;
  import heronarts.lx.modulator.*;
  import heronarts.lx.parameter.*;
  import heronarts.lx.pattern.*;
  import heronarts.lx.transform.*;
  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 java.lang.reflect.*;
- import javax.media.opengl.*;
+ import java.util.ArrayList;
+ import java.util.Collections;
+ import java.util.List;
  
- final int VIEWPORT_WIDTH = 900;
- final int VIEWPORT_HEIGHT = 700;
+ static final int VIEWPORT_WIDTH = 900;
+ static final int VIEWPORT_HEIGHT = 700;
+ static final int LEFT_DECK = 0;
+ static final int RIGHT_DECK = 1;
  
  // 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_HEIGHT = 33;
static final float TRAILER_WIDTH = 192;
static final float TRAILER_DEPTH = 192;
static final float TRAILER_HEIGHT = 33;
  
  int targetFramerate = 60;
  int startMillis, lastMillis;
  
  // Core engine variables
- GLucose glucose;
  LX lx;
+ Model model;
  LXPattern[] patterns;
+ LXTransition[] transitions;
  Effects effects;
+ LXEffect[] effectsArr;
+ DiscreteParameter selectedEffect;
  MappingTool mappingTool;
PandaDriver[] pandaBoards;
GrizzlyOutput[] grizzlies;
  PresetManager presetManager;
  MidiEngine midiEngine;
  
  // Display configuration mode
  boolean mappingMode = false;
  boolean debugMode = false;
- DebugUI debugUI;
- boolean uiOn = true;
  boolean simulationOn = true;
  boolean diagnosticsOn = false;
  LXPattern restoreToPattern = null;
@@@ -62,7 -70,6 +70,6 @@@ PImage logo
  float[] hsb = new float[3];
  
  // Handles to UI objects
- UIContext[] overlays;
  UIPatternDeck uiPatternA;
  UICrossfader uiCrossfader;
  UIMidi uiMidi;
@@@ -70,30 -77,27 +77,27 @@@ UIMapping uiMapping
  UIDebugText uiDebugText;
  UISpeed uiSpeed;
  
- // Camera variables
- float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
  /**
   * Engine construction and initialization.
   */
  
- LXTransition _transition(GLucose glucose) {
-   return new DissolveTransition(glucose.lx).setDuration(1000);
+ LXTransition _transition(LX lx) {
+   return new DissolveTransition(lx).setDuration(1000);
  }
  
- LXPattern[] _leftPatterns(GLucose glucose) {
-   LXPattern[] patterns = patterns(glucose);
+ LXPattern[] _leftPatterns(LX lx) {
+   LXPattern[] patterns = patterns(lx);
    for (LXPattern p : patterns) {
-     p.setTransition(_transition(glucose));
+     p.setTransition(_transition(lx));
    }
    return patterns;
  }
  
- LXPattern[] _rightPatterns(GLucose glucose) {
-   LXPattern[] patterns = _leftPatterns(glucose);
+ LXPattern[] _rightPatterns(LX lx) {
+   LXPattern[] patterns = _leftPatterns(lx);
    LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
    int i = 0;
-   rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
+   rightPatterns[i++] = new BlankPattern(lx).setTransition(_transition(lx));
    for (LXPattern p : patterns) {
      rightPatterns[i++] = p;
    }
@@@ -111,7 -115,11 +115,11 @@@ LXEffect[] _effectsArray(Effects effect
      } catch (IllegalAccessException iax) {}
    }
    return effectList.toArray(new LXEffect[]{});
- } 
+ }
+ LXEffect getSelectedEffect() {
+   return effectsArr[selectedEffect.getValuei()];
+ }
  
  void logTime(String evt) {
    int now = millis();
@@@ -129,20 -137,29 +137,29 @@@ void setup() 
    // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
    logTime("Created viewport");
  
-   // Create the GLucose engine to run the cubes
-   glucose = new GLucose(this, buildModel());
-   lx = glucose.lx;
+   // Create the model
+   model = buildModel();
+   logTime("Built Model");
+   
+   // LX engine
+   lx = new LX(this, model);
    lx.enableKeyboardTempo();
-   logTime("Built GLucose engine");
+   logTime("Built LX engine");
      
    // Set the patterns
    LXEngine engine = lx.engine;
-   engine.setPatterns(patterns = _leftPatterns(glucose));
-   engine.addDeck(_rightPatterns(glucose));
+   engine.setPatterns(patterns = _leftPatterns(lx));
+   engine.addDeck(_rightPatterns(lx));
    logTime("Built patterns");
-   glucose.setTransitions(transitions(glucose));
+   // Transitions
+   transitions = transitions(lx);
+   lx.engine.getDeck(RIGHT_DECK).setFaderTransition(transitions[0]);
    logTime("Built transitions");
-   glucose.lx.addEffects(_effectsArray(effects = new Effects()));
+   
+   // Effects
+   lx.addEffects(effectsArr = _effectsArray(effects = new Effects()));
+   selectedEffect = new DiscreteParameter("EFFECT", effectsArr.length);
    logTime("Built effects");
  
    // Preset manager
    logTime("Setup MIDI devices");
  
    // 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);
+   grizzlies = new GrizzlyOutput[]{};
+   try {
+     grizzlies = buildGrizzlies();
+     for (LXOutput output : grizzlies) {
+       lx.addOutput(output);
+     }
+   } catch (Exception x) {
+     x.printStackTrace();
    }
-   mappingTool = new MappingTool(glucose, pandaMappings);
-   logTime("Built PandaDriver");
+   logTime("Built Grizzly Outputs");
  
+   // Mapping tool
+   mappingTool = new MappingTool(lx);
+   logTime("Built Mapping Tool");
+   
    // Build overlay UI
-   debugUI = new DebugUI(pandaMappings);
-   overlays = new UIContext[] {
-     uiPatternA = new UIPatternDeck(lx.engine.getDeck(GLucose.LEFT_DECK), "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(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),
      uiSpeed = new UISpeed(4, 624, 140, 50),
          
-     new UIPatternDeck(lx.engine.getDeck(GLucose.RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324),
+     // Right controls
+     new UIPatternDeck(lx.ui, lx.engine.getDeck(RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324),
      uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
-     new UIOutput(width-144, 494, 140, 106),
+     new UIOutput(grizzlies, width-144, 494, 140, 106),
      
+     // Crossfader
      uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
      
+     // Overlays
      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");
-   
-   // 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);
-   
-   // Add mouse scrolling event support
-   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("Hit the 'p' key to toggle Panda Board output");
+   println("Hit the 'o' key to toggle live output");
  }
  
+ public SCPattern getPattern() {
+   return (SCPattern) lx.getPattern();
+ }     
+ /**
+  * Subclass of LXPattern specific to sugar cubes. These patterns
+  * get access to the 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();
+     }
+     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() {
    long drawStart = System.nanoTime();
    
-   // Draws the simulation and the 2D UI overlay
+   // Set background
    background(40);
-   color[] simulationColors;
-   color[] sendColors;
-   simulationColors = sendColors = 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();
-   }
-   if (debugMode) {
-     debugUI.maskColors(simulationColors);
-     debugUI.maskColors(sendColors);
-   }
-   long simulationStart = System.nanoTime();
-   if (simulationOn) {
-     drawSimulation(simulationColors);
-   }
-   long simulationNanos = System.nanoTime() - simulationStart;
-   
-   // 2D Overlay UI
-   long uiStart = System.nanoTime();
-   drawUI();
-   long uiNanos = System.nanoTime() - uiStart;
    
+   // Send colors
+   color[] sendColors = lx.getColors();  
    long gammaStart = System.nanoTime();
    // Gamma correction here. Apply a cubic to the brightness
    // for better representation of dynamic range
      sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
    }
    long gammaNanos = System.nanoTime() - gammaStart;
-   
-   long sendStart = System.nanoTime();
-   for (PandaDriver p : pandaBoards) {
-     p.send(sendColors);
-   }
-   long sendNanos = System.nanoTime() - sendStart;
-   
+   // Always draw FPS meter
+   drawFPS();
+   // TODO(mcslee): fix
    long drawNanos = System.nanoTime() - drawStart;
-   
+   long uiNanos = 0;
    if (diagnosticsOn) {
-     drawDiagnostics(drawNanos, simulationNanos, uiNanos, gammaNanos, sendNanos);
-   }
+     drawDiagnostics(drawNanos, simulationNanos, uiNanos, gammaNanos);
+   }  
  }
  
- void drawDiagnostics(long drawNanos, long simulationNanos, long uiNanos, long gammaNanos, long sendNanos) {
+ void drawDiagnostics(long drawNanos, long simulationNanos, long uiNanos, long gammaNanos) {
    float ws = 4 / 1000000.;
    int thirtyfps = 1000000000 / 30;
    int sixtyfps = 1000000000 / 60;
    noStroke();
    int xp = x;
    float hv = 0;
-   for (long val : new long[] {lx.timer.drawNanos, simulationNanos, uiNanos, gammaNanos, sendNanos }) {
+   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;
    }
  }
  
- void drawSimulation(color[] simulationColors) {
-   camera(
-     eyeX, eyeY, eyeZ,
-     midX, midY, midZ,
-     0, -1, 0
-   );
-   translate(0, 40, 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();
-   if (glucose.model.bassBox.exists) {
-     drawBassBox(glucose.model.bassBox, false);
-   }
-   for (Speaker speaker : glucose.model.speakers) {
-     drawSpeaker(speaker);
-   }
-   for (Cube c : glucose.model.cubes) {
-     drawCube(c);
-   }
-   noFill();
-   strokeWeight(2);
-   beginShape(POINTS);
-   for (LXPoint p : glucose.model.points) {
-     stroke(simulationColors[p.index]);
-     vertex(p.x, p.y, p.z);
-   }
-   endShape();
- }
- void drawBassBox(BassBox b, boolean hasSub) {
-   
-   float in = .15;
-   
-   if (hasSub) {
-     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) {
-     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);
-     }
-     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;
-   
-   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();
-   
-   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);
-   }
-   popMatrix();
- }
- void drawUI() {
-   camera();
-   PGraphicsOpenGL gl = (PGraphicsOpenGL) g;  
-   //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);
-   if (debugMode) {
-     debugUI.draw();
-   }
  }
  
  
@@@ -593,9 -498,10 +498,10 @@@ void keyPressed() 
          lx.engine.setThreaded(!lx.engine.isThreaded());
        }
        break;
+     case 'o':
      case 'p':
-       for (PandaDriver p : pandaBoards) {
-         p.toggle();
+       for (LXOutput output : grizzlies) {
+         output.enabled.toggle();
        }
        break;
      case 'q':
          simulationOn = !simulationOn;
        }
        break;
-     case 'u':
-       if (!midiEngine.isQwertyEnabled()) {
-         uiOn = !uiOn;
-       }
-       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);
-   }
- }
diff --combined JR.pde
index b1d8a437ce929293d43479ee1de8e615549462f6,4abcc62c1226d1b25dd1a8fbb0b162bae8364966..07ddf386c9d990f300bc702d7a20cae892c33a44
--- 1/JR.pde
--- 2/JR.pde
+++ b/JR.pde
@@@ -1,5 -1,3 +1,5 @@@
 +import java.util.Map;
 +import java.util.TreeMap;
  color BLACK = #000000;
  
  class Gimbal extends SCPattern {
@@@ -23,8 -21,8 +23,8 @@@
    private final BasicParameter bP = new BasicParameter("b", 0);
    private final BasicParameter gP = new BasicParameter("g", 0);
  
-   Gimbal(GLucose glucose) {
-     super(glucose);
+   Gimbal(LX lx) {
+     super(lx);
      projection = new LXProjection(model);
      addParameter(beatsPerRevolutionParam);
      addParameter(hueDeltaParam);
@@@ -207,8 -205,8 +207,8 @@@ class Zebra extends SCPattern 
    _P size;
    */
  
-   Zebra(GLucose glucose) {
-     super(glucose);
+   Zebra(LX lx) {
+     super(lx);
      projection = new LXProjection(model);
  
      addModulator(angleM).trigger();
diff --combined JackieBavaro.pde
index dc5922f6d4c51df660bfdbf38817eb686f9d65ab,806fec09f0f05957a31abc113867616fe3d153aa..fe18092e129a0bee2450c01cfb75ab3d68d40274
@@@ -1,4 -1,3 +1,4 @@@
 +import java.util.Iterator;
  class JackieSquares extends SCPattern {
    private BasicParameter rateParameter = new BasicParameter("RATE", 0.25);
    private BasicParameter attackParameter = new BasicParameter("ATTK", 0.3);
@@@ -43,8 -42,8 +43,8 @@@
    private List<FaceFlash> flashes;
    private int faceNum = 0;
    
-   public JackieSquares(GLucose glucose) {
-     super(glucose);
+   public JackieSquares(LX lx) {
+     super(lx);
      addParameter(rateParameter);
      addParameter(attackParameter);
      addParameter(decayParameter);
@@@ -132,8 -131,8 +132,8 @@@ class JackieLines extends SCPattern 
    private List<StripFlash> flashes;
    private int stripNum = 0;
    
-   public JackieLines(GLucose glucose) {
-     super(glucose);
+   public JackieLines(LX lx) {
+     super(lx);
      addParameter(rateParameter);
      addParameter(attackParameter);
      addParameter(decayParameter);
@@@ -222,8 -221,8 +222,8 @@@ class JackieDots extends SCPattern 
    private List<PointFlash> flashes;
    private int pointNum = 0;
    
-   public JackieDots(GLucose glucose) {
-     super(glucose);
+   public JackieDots(LX lx) {
+     super(lx);
      addParameter(rateParameter);
      addParameter(attackParameter);
      addParameter(decayParameter);
diff --combined MIDI.pde
index 2716494663ca33abb33be0908f86f618b22e06b1,562aa869a3d3193b1294219ffb84355ac3b47920..5a198f0e608df954c68058678e480fafcff32998
+++ b/MIDI.pde
@@@ -115,7 -115,7 +115,7 @@@ public interface SCMidiInputListener 
    public void onEnabled(SCMidiInput controller, boolean enabled);
  }
  
- public abstract class SCMidiInput extends AbstractScrollItem {
+ public abstract class SCMidiInput extends UIScrollList.AbstractItem {
  
    protected boolean enabled = false;
    private final String name;
@@@ -264,7 -264,7 +264,7 @@@ public class VirtualKeyMidiInput extend
      } else {
        mapKeys();
      }
 -    registerKeyEvent(this);    
 +    //registerKeyEvent(this);    
    }
  
    private void mapAPC() {
      if (!enabled) {
        return;
      }
 -    char c = Character.toLowerCase(e.getKeyChar());
 +   /* char c = 0;//0Character.toLowerCase(e.getKeyChar());
      NoteMeta nm = keyToNote.get(c);
      if (nm != null) {
        switch (e.getID()) {
          octaveShift = constrain(octaveShift+1, -4, 4);
          break;
        }
 -    }
 +    }*/
    }
  }
  
@@@ -445,7 -445,7 +445,7 @@@ public class APC40MidiInput extends Gen
  
      // Crossfader
      case 15:
-       lx.engine.getDeck(GLucose.RIGHT_DECK).getFader().setNormalized(value);
+       lx.engine.getDeck(RIGHT_DECK).getFader().setNormalized(value);
        return true;
        
      // Cue level
      
      if (number >= 20 && number <= 23) {
        int effectIndex = number - 20;
-       List<LXParameter> parameters = glucose.getSelectedEffect().getParameters();
+       // TODO(mclsee): fix selected effect
+       List<LXParameter> parameters = getSelectedEffect().getParameters();
        if (effectIndex < parameters.size()) {
          setNormalized(parameters.get(effectIndex), value);
          return true;
        return true;
        
      case 83: // scene 2
 -      effects.flash.trigger();
 +      //effects.flash.trigger();
        return true;
        
      case 84: // scene 3
  
      case 91: // play
        if (shiftOn) {
-         midiEngine.setFocusedDeck(GLucose.LEFT_DECK);
+         midiEngine.setFocusedDeck(LEFT_DECK);
        } else {
          uiCrossfader.setDisplayMode("A");
        }
        
      case 93: // rec
        if (shiftOn) {
-         midiEngine.setFocusedDeck(GLucose.RIGHT_DECK);
+         midiEngine.setFocusedDeck(RIGHT_DECK);
        } else {
          uiCrossfader.setDisplayMode("B");
        }
  
      case 94: // up bank
        if (shiftOn) {
-         glucose.incrementSelectedEffectBy(-1);
+         selectedEffect.setValue(selectedEffect.getValuei() - 1);
        } else {
          getTargetDeck().goPrev();
        }
        
      case 95: // down bank
        if (shiftOn) {
-         glucose.incrementSelectedEffectBy(1);
+         selectedEffect.setValue(selectedEffect.getValuei() + 1);
        } else {
          getTargetDeck().goNext();
        }
        return true;
  
      case 62: // Detail View / red 5
-       releaseEffect = glucose.getSelectedEffect(); 
+       releaseEffect = getSelectedEffect(); 
        if (releaseEffect.isMomentary()) {
          releaseEffect.enable();
        } else {
        return true;
  
      case 63: // rec quantize / red 6
-       glucose.getSelectedEffect().disable();
+       getSelectedEffect().disable();
        return true;
      }
  
@@@ -689,11 -690,11 +690,11 @@@ class KorgNanoKontrolMidiInput extends 
        switch (number) {
        
        case 58: // Left track
-         midiEngine.setFocusedDeck(GLucose.LEFT_DECK);
+         midiEngine.setFocusedDeck(LEFT_DECK);
          return true;
        
        case 59: // Right track
-         midiEngine.setFocusedDeck(GLucose.RIGHT_DECK);
+         midiEngine.setFocusedDeck(RIGHT_DECK);
          return true;
        
        case 43: // Left chevron
@@@ -737,8 -738,8 +738,8 @@@ class APC40MidiOutput implements LXPara
          }
        });
      }
-     glucose.addEffectListener(new GLucose.EffectListener() {
-       public void effectSelected(LXEffect effect) {
+     selectedEffect.addListener(new LXParameterListener() {
+       public void onParameterChanged(LXParameter parameter) {
          resetEffectParameters();
        }
      });
    }
    
    private void resetEffectParameters() {
-     LXEffect newEffect = glucose.getSelectedEffect();
+     LXEffect newEffect = getSelectedEffect();
      if (newEffect == focusedEffect) {
        return;
      }
diff --combined Mappings.pde
index d1c51b2e4d3b1509493cfbb35fbae7064c6dc381,8798fb59fea91797853017ed978d5ad191538e71..8798fb59fea91797853017ed978d5ad191538e71
   * when physical changes or tuning is being done to the structure.
   */
  
- import java.util.Arrays;
+ static final float SPACING = 27;
+ static final float RISER = 13.5;
+ static final float FLOOR = 0;
  
- final int MaxCubeHeight = 6;
- final int NumBackTowers = 16;
+ /**
+  * Definitions of tower positions. This is all that should need
+  * to be modified. Distances are measured from the left-most cube.
+  * The first value is the offset moving NE (towards back-right).
+  * The second value is the offset moving NW (negative comes forward-right).
+  */
+ static final float[][] TOWER_CONFIG = new float[][] {
+   new float[] { 0, 0, RISER, 4 },
+   new float[] { 25, -10, RISER, 4 },
+   new float[] { 50, -22.5, FLOOR, 5 },
+   new float[] { 17.25, -35.5, FLOOR, 6 },
+   new float[] { 43.25, -51.5, RISER, 6 },
+   new float[] { 69.25, -56, FLOOR, 6 },
+   new float[] { 12.75, -62.5, RISER, 4 },
+   new float[] { 38.75, -78.5, FLOOR, 5 },
+   new float[] { 65.75, -83, RISER, 5 },  
+ };
  
  public Model buildModel() {
  
-   // Shorthand helpers for specifying wiring more quickly
-   final Cube.Wiring WFL = Cube.Wiring.FRONT_LEFT;
-   final Cube.Wiring WFR = Cube.Wiring.FRONT_RIGHT;
-   final Cube.Wiring WRL = Cube.Wiring.REAR_LEFT;
-   final Cube.Wiring WRR = Cube.Wiring.REAR_RIGHT;
-   
-   // Utility value if you need the height of a cube shorthand
-   final float CH = Cube.EDGE_HEIGHT;
-   final float CW = Cube.EDGE_WIDTH ;
-   
-   // Positions for the bass box
-   final float BBY = BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH;
-   final float BBX = 56;
-   final float BBZ = 2;
-   // The model is represented as an array of towers. The cubes in the tower
-   // are represenented relatively. Each tower has an x, y, z reference position,
-   // which is typically the base cube's bottom left corner.
-   //
-   // Following that is an array of floats. A 2-d array contains an x-offset
-   // and a z-offset from the previous reference position. Typically the first cube
-   // will just be {0, 0}. Each successive cube uses the position of the previous
-   // cube as its reference.
-   //
-   // A 3-d array contains an x-offset, a z-offset, and a rotation about the
-   // y-axis.
-   //
-   // The cubes automatically increment their y-position by Cube.EDGE_HEIGHT.
-   // To-Do:  (Mark Slee, Alex Green, or Ben Morrow):   The Cube # is determined by the order in this list.
-   // "raw object index" is serialized by running through towermapping and then individual cube mapping below.
-   //  We can do better than this.  The raw object index should be obvious from the code-- looking through the
-   //  rendered simulation and counting through cubes in mapping mode is grossly inefficient. 
-   TowerMapping[] towerCubes = new TowerMapping[] {};
-   
-   // Single cubes can be constructed directly here if you need them
-   Cube[] singleCubes = new Cube[] {
-     // new Cube(15, int( Cube.EDGE_HEIGHT), 39, 0, 10, 0,  WRL),     // Back left channel behind speaker
-      //new Cube(x, y, z, rx, ry, rz, wiring),
-    //new Cube(0,0,0,0,225,0, WRR),
-   };
-   // The bass box!
-   // BassBox bassBox = BassBox.unlitBassBox(BBX, 0, BBZ); // frame exists, no lights
-      BassBox bassBox = BassBox.noBassBox(); // no bass box at all
-   // BassBox bassBox = new BassBox(BBX, 0, BBZ); // bass box with lights
-  
-   // The speakers!
-   List<Speaker> speakers = Arrays.asList(new Speaker[] {
-      // Each speaker parameter is x, y, z, rotation, the left speaker comes first
-      // new Speaker(TRAILER_WIDTH - Speaker.EDGE_WIDTH + 8, 6, 3, -15)
-   });
-   ////////////////////////////////////////////////////////////////////////
-   // dan's proposed lattice
-         ArrayList<StaggeredTower> scubes = new ArrayList<StaggeredTower>();
-         //if (NumBackTowers != 25) exit();
-         for (int i=0; i<NumBackTowers/2; i++) scubes.add(new StaggeredTower(
-                   (i+1)*CW,                                                                 // x
-                   (i % 2 == 0) ? 0 : CH * 2./3.                ,   // y
-                  - ((i % 2 == 0) ? 11 : 0) + 80          ,   // z
-                  -45, (i % 2 == 0) ? MaxCubeHeight : MaxCubeHeight) );         // num cubes
-         
- //        for (int i=0; i<NumBackTowers/2; i++) scubes.add(new StaggeredTower(
- //                  (i+1)*CW,                                                                 // x
- //                  (i % 2 == 0) ? 0 : CH * 2./3.                ,   // y
- //                 - ((i % 2 == 0) ? 0 : 11) + 80 - pow(CH*CH + CW*CW, .5),   // z
- //                 225, (i % 2 == 0) ? MaxCubeHeight : MaxCubeHeight-1) ); 
-       // for (int i=0; i<2 ; i++) scubes.add(new StaggeredTower(
-       //             (i+1)*CW,                                                                 // x
-       //                0             ,   // y
-       //            - 0 + 97 - 2*pow(CH*CH + CW*CW, .5),   // z
-       //            225,  MaxCubeHeight  ) ); 
-         
-         ArrayList<Cube> dcubes = new ArrayList<Cube>();
-         // for (int i=1; i<6; i++) {
-         //         if (i>1) dcubes.add(new Cube(-6+CW*4/3*i             , 0, 0, 0, 0, 0, WRR));        
-         //                          dcubes.add(new Cube(-6+CW*4/3*i+CW*2/3., CH*.5, 0, 0, 0, 0, WRR));        
-         // }
- float current_x_position = 0;
- // scubes.add(new StaggeredTower(//tower 1
- //       current_x_position,               // x
- //        15   ,   // y
- //        0  ,   // z
- //      45, 6, new Cube.Wiring[] { WFL, WRR, WFL, WRR, WFL, WRR}) );
- // current_x_position += 25.25;
- // scubes.add(new StaggeredTower(// tower 2
- //       current_x_position,               // x
- //        0  ,   // y
- //        -10.5   ,   // z
- //      45, 6, new Cube.Wiring[] { WFR, WFL, WRR, WRR, WFL, WRR}) );
- // current_x_position += 25.25;
- // scubes.add(new StaggeredTower(//tower 3
- //       current_x_position,               // x
- //        15   ,   // y
- //        0,   // z
- //      45, 6, new Cube.Wiring[] { WRR, WFL, WRR, WRR, WFL, WRR}) );
- // current_x_position += 25.25;
- // scubes.add(new StaggeredTower(//tower 4
- //     current_x_position,               // x
- //        0,   // y
- //        -10.5  ,   // z
- //      45, 6, new Cube.Wiring[] { WFL, WRR, WFL, WRR, WFL, WRR}) );
- // current_x_position += 28;
- // scubes.add(new StaggeredTower(//tower 5
- //       current_x_position,               // x
- //        15   ,   // y
- //        -4.5 ,   // z
- //      45, 6, new Cube.Wiring[] { WRR, WFL, WRR, WFL, WRR, WFL}) );
- // current_x_position += 28;
- // scubes.add(new StaggeredTower(//tower 6
- //       current_x_position,               // x
- //        0 ,   // y
- //        -10.5,   // z
- //      45, 6, new Cube.Wiring[] { WFL, WRR, WFL, WRR, WFL, WRR}) );
- // current_x_position += 25.25;
- // scubes.add(new StaggeredTower(// tower 7
- //       current_x_position,               // x
- //        15   ,   // y
- //       0,   // z
- //      45, 6, new Cube.Wiring[] { WRR, WFL, WRR, WFL, WRR, WFL}) );
- // current_x_position += 25.25;     
- // scubes.add(new StaggeredTower(//tower 8
- //       current_x_position,               // x
- //        0  ,   // y
- //        -10.5 ,   // z
- //      45, 6, new Cube.Wiring[] { WFL, WRR, WFL, WRR, WFL, WRR}) );
- // current_x_position += 25.25;
- // scubes.add(new StaggeredTower(//tower 9
- //       current_x_position,               // x
- //        15   ,   // y
- //        0,   // z
- //      45, 6, new Cube.Wiring[] { WFL, WRR, WFL, WRR, WFL, WRR}) );
- // current_x_position += 25.25;
- // //TOWERS ON DANCE FLOOR
- // scubes.add(new StaggeredTower(//tower 10
- //       83.75+39+43-124.5,   // x
- //       0,   // y
- //        -47.5-43,   // z
- //      45,  4, new Cube.Wiring[]{ WRR, WFL, WFL, WRR})  ); 
- // scubes.add(new StaggeredTower(//tower 11
- //       83.75,   // x
- //        0,   // y
- //        -47.5,   // z
- //      45,  4, new Cube.Wiring[]{ WFL, WRR, WRR, WFL})  );  
- // scubes.add(new StaggeredTower(//tower 12
- //       83.75+39,   // x
- //        0,   // y
- //        -47.5,   // z
- //      45,  4, new Cube.Wiring[]{ WRR, WFL, WFL, WRR})  ); 
- // scubes.add(new StaggeredTower(//tower 13
- //        83.75+39+43,   // x
- //        0,   // y
- //        -47.5-43,   // z
- //      45,  4, new Cube.Wiring[]{ WFL, WRR, WFL, WRR})  ); 
- // scubes.add(new StaggeredTower(// Single cube on top of tower 4
- //       42,               // x
- //        112   ,   // y
- //          72,   // z
- //      -10,  1, new Cube.Wiring[]{ WRL})  );  
-   //////////////////////////////////////////////////////////////////////
-   //      BENEATH HERE SHOULD NOT REQUIRE ANY MODIFICATION!!!!        //
-   //////////////////////////////////////////////////////////////////////
-   // These guts just convert the shorthand mappings into usable objects
-   ArrayList<Tower> towerList = new ArrayList<Tower>();
-   ArrayList<Cube> tower;
+   List<Tower> towers = new ArrayList<Tower>();
    Cube[] cubes = new Cube[200];
-   int cubeIndex = 1;  
-   float px, pz, ny;
-   for (TowerMapping tm : towerCubes) {
-     px = tm.x;
-     ny = tm.y;
-     pz = tm.z;
-     tower = new ArrayList<Cube>();
-     for (CubeMapping cm : tm.cubeMappings) {
-       tower.add(cubes[cubeIndex++] = new Cube(px = px + cm.dx, ny, pz = pz + cm.dz, 0, cm.ry, 0, cm.wiring));
-       ny += Cube.EDGE_HEIGHT;
+   int cubeIndex = 1;
+   float rt2 = sqrt(2);
+   float x, y, z, xd, zd, num;
+   for (float[] tc : TOWER_CONFIG) {
+     x = -tc[1];
+     z = tc[0]; 
+     y = tc[2];
+     num = tc[3];
+     if (z < x) {
+       zd = -(x-z)/rt2;
+       xd = z*rt2 - zd;
+     } else {
+       zd = (z-x)/rt2;
+       xd = z*rt2 - zd;
      }
-     towerList.add(new Tower(tower));
-   }
-   
-   for (Cube cube : singleCubes) {
-     cubes[cubeIndex++] = cube;
-   }
-   for (Cube cube : dcubes) {
-     cubes[cubeIndex++] = cube;
-   }
-   for (StaggeredTower st : scubes) {
-     tower = new ArrayList<Cube>();
-     for (int i=0; i < st.n; i++) {
-       Cube.Wiring w = (i < st.wiring.length) ? st.wiring[i] : WRR;
-       tower.add(cubes[cubeIndex++] = new Cube(st.x, st.y + CH* 4/3.*i, st.z, 0, st.r, 0, w));
-     }
-     towerList.add(new Tower(tower));
-   }   
-   return new Model(towerList, cubes, bassBox, speakers);
- }
- /**
-  * This function maps the panda boards. We have an array of them, each has
-  * an IP address and a list of channels.
-  */
- public PandaMapping[] buildPandaList() {
-   final int LEFT_SPEAKER = 0;
-   final int RIGHT_SPEAKER = 1;
-   
-   // 8 channels map to:  3, 4, 7, 8, 13, 14, 15, 16.
-   return new PandaMapping[] {
-     new PandaMapping(
-       "10.200.1.28", new ChannelMapping[] {
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 37, 38, 39 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] {  }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 43, 44, 45 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 46, 47, 48 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] {  }), // new front thing
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] {  }), // new back thing
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 13, 14, 15 }), // new back thing
-     }),
-     new PandaMapping(
-       "10.200.1.29", new ChannelMapping[] {
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 19, 20, 21 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] {  }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 1, 2, 3 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 4, 5, 6 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 7, 8, 9 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 10, 11, 12 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 16, 17, 18 }),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 34, 35, 36}),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 19, 20, 21}),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 22, 23, 24}), 
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 25, 26, 27}),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 28, 29, 30}),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 31, 32, 33}),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),
-     }),    
-     new PandaMapping(
-       "10.200.1.30", new ChannelMapping[] {
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 40, 41, 42 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 22, 23, 24 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 25, 26, 27 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 28, 29, 30 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 31, 32, 33 }),
-         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 34, 35, 36 }),
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 1,1,1}), // 30 J3 *
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 1,1,1}),  // 30 J4 //ORIG *
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 37, 38, 39}),                // 30 J7 *
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 40, 41, 42}),  // 30 J8 *
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 43, 44, 45}),                // 30 J13 (not working)
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 46, 47, 48}),                // 30 J14 (unplugged)
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 49, 50, 51}),                // 30 J15 (unplugged)
- //        new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 52, 53, 54}), // 30 J16
-    }),    
- //     new PandaMapping(
- //       "10.200.1.31", new ChannelMapping[] {
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 65, 66}),       // J3 
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 1,1}),       // J4
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 55, 56}), // 30 J7 
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 57, 58}), //  J8 
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 59, 60}),           // J13 
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 61, 62}),                // 30 J14 
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 63, 64}),                //  J15
- //         new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 1,1}),              //  J16
- //     }),
-      // new PandaMapping(
-      //   "10.200.1.32", new ChannelMapping[] {
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),       // J3 
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),       // J4
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 67, 68}), // 30 J7 
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 69, 70}), //  J8 
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),           // J13 
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),                // 30 J14 
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),                //  J15
-      //     new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { }),              //  J16
-      // }),
-   };
- }
- class TowerMapping {
-   public final float x, y, z;
-   public final CubeMapping[] cubeMappings;
-   
-   TowerMapping(float x, float y, float z, CubeMapping[] cubeMappings) {
-     this.x = x;
-     this.y = y;
-     this.z = z;
-     this.cubeMappings = cubeMappings;
-   }
- }
- class CubeMapping {
-   public final float dx, dz, ry;
-   public final Cube.Wiring wiring;
-   
-   CubeMapping(float dx, float dz, Cube.Wiring wiring) {
-     this(dx, dz, 0., wiring);
-   }
-   CubeMapping(float dx, float dz, float ry) {
-     this(dz, dz, ry, Cube.Wiring.FRONT_LEFT);
-   }
-   
-   CubeMapping(float dx, float dz, float ry, Cube.Wiring wiring) {
-     this.dx = dx;
-     this.dz = dz;
-     this.ry = ry;
-     this.wiring = wiring;
-   }
- }
- class StaggeredTower {
-   public final float x, y, z, r;
-   public final int n;
-   public final Cube.Wiring[] wiring;
-   StaggeredTower(float _x, float _y, float _z, float _r, int _n) { this(_x, _y, _z, _r, _n, new Cube.Wiring[]{}); }
-   StaggeredTower(float _x, float _y, float _z, float _r, int _n, Cube.Wiring[] _wiring) { x=_x; y=_y; z=_z; r=_r; n=_n; wiring=_wiring;}
- }
- /**
-  * Each panda board has an IP address and a fixed number of channels. The channels
-  * each have a fixed number of pixels on them. Whether or not that many physical
-  * pixels are connected to the channel, we still send it that much data.
-  */
- class PandaMapping {
-   
-   // How many channels are on the panda board
-   public final static int CHANNELS_PER_BOARD = 8;
-   
-   // How many total pixels on the whole board
-   public final static int PIXELS_PER_BOARD = ChannelMapping.PIXELS_PER_CHANNEL * CHANNELS_PER_BOARD;
-   
-   final String ip;
-   final ChannelMapping[] channelList = new ChannelMapping[CHANNELS_PER_BOARD];
-   
-   PandaMapping(String ip, ChannelMapping[] rawChannelList) {
-     this.ip = ip;
-     
-     // Ensure our array is the right length and has all valid items in it
-     for (int i = 0; i < channelList.length; ++i) {
-       channelList[i] = (i < rawChannelList.length) ? rawChannelList[i] : new ChannelMapping();
-       if (channelList[i] == null) {
-         channelList[i] = new ChannelMapping();
-       }
+     List<Cube> tower = new ArrayList<Cube>();
+     for (int n = 0; n < num; ++n) {
+       Cube cube = new Cube(xd + 24, y, zd + 84, 0, -45, 0);
+       tower.add(cube);
+       cubes[cubeIndex++] = cube;
+       y += SPACING;
      }
+     towers.add(new Tower(tower));
    }
- }
- /**
-  * Each channel on a pandaboard can be mapped in a number of modes. The typical is
-  * to a series of connected cubes, but we also have special mappings for the bass box,
-  * the speaker enclosures, and the DJ booth floor.
-  *
-  * This class is just the mapping meta-data. It sanitizes the input to make sure
-  * that the cubes and objects being referenced actually exist in the model.
-  *
-  * The logic for how to encode the pixels is contained in the PandaDriver.
-  */
- class ChannelMapping {
-   // How many cubes per channel xc_PB is configured for
-   public final static int CUBES_PER_CHANNEL = 4;  
  
-   // How many total pixels on each channel
-   public final static int PIXELS_PER_CHANNEL = Cube.POINTS_PER_CUBE * CUBES_PER_CHANNEL;
-   
-   public static final int MODE_NULL = 0;
-   public static final int MODE_CUBES = 1;
-   public static final int MODE_BASS = 2;
-   public static final int MODE_SPEAKER = 3;
-   public static final int MODE_STRUTS_AND_FLOOR = 4;
-   public static final int MODE_INVALID = 5;
-   
-   public static final int NO_OBJECT = -1;
-   
-   final int mode;
-   final int[] objectIndices = new int[CUBES_PER_CHANNEL];
-   
-   ChannelMapping() {
-     this(MODE_NULL);
-   }
-   
-   ChannelMapping(int mode) {
-     this(mode, new int[]{});
-   }
-   
-   ChannelMapping(int mode, int rawObjectIndex) {
-     this(mode, new int[]{ rawObjectIndex });
-   }
-   
-   ChannelMapping(int mode, int[] rawObjectIndices) {
-     if (mode < 0 || mode >= MODE_INVALID) {
-       throw new RuntimeException("Invalid channel mapping mode: " + mode);
-     }
-     if (mode == MODE_SPEAKER) {
-       if (rawObjectIndices.length != 1) {
-         throw new RuntimeException("Speaker channel mapping mode must specify one speaker index");
-       }
-       int speakerIndex = rawObjectIndices[0];
-       if (speakerIndex < 0 || speakerIndex >= glucose.model.speakers.size()) {
-         throw new RuntimeException("Invalid speaker channel mapping: " + speakerIndex);
-       }
-     } else if ((mode == MODE_STRUTS_AND_FLOOR) || (mode == MODE_BASS) || (mode == MODE_NULL)) {
-       if (rawObjectIndices.length > 0) {
-         throw new RuntimeException("Bass/floor/null mappings cannot specify object indices");
-       }
-     } else if (mode == MODE_CUBES) {
-       for (int rawCubeIndex : rawObjectIndices) {
-         if (glucose.model.getCubeByRawIndex(rawCubeIndex) == null) {
-           throw new RuntimeException("Non-existing cube specified in cube mapping: " + rawCubeIndex);
-         }
-       }
-     }
-     
-     this.mode = mode;
-     for (int i = 0; i < objectIndices.length; ++i) {
-       objectIndices[i] = (i < rawObjectIndices.length) ? rawObjectIndices[i] : NO_OBJECT;
-     }
-   }
+   return new Model(towers, cubes);
  }
diff --combined MarkSlee.pde
index 0976d57eb25a2a18d56362b43aa7e077bee306d5,a2d36fd24cdda3f460a26200abc2dd916374b30f..fea4a43d18a668070981be8d4c57fa745bbd75c4
@@@ -1,4 -1,3 +1,4 @@@
 +import java.util.Stack;
  class Cathedrals extends SCPattern {
    
    private final BasicParameter xpos = new BasicParameter("XPOS", 0.5);
@@@ -7,15 -6,15 +7,15 @@@
    private final BasicParameter sat = new BasicParameter("SAT", 0.5);
    private GraphicEQ eq;
    
-   Cathedrals(GLucose glucose) {
-     super(glucose);
+   Cathedrals(LX lx) {
+     super(lx);
      addParameter(xpos);
      addParameter(wid);
      addParameter(arms);
      addParameter(sat);
    }
   
-   protected void onActive() {
+   void onActive() {
      if (eq == null) {
        eq = new GraphicEQ(lx, 16);
        eq.slope.setValue(0.7);
@@@ -82,8 -81,8 +82,8 @@@ class MidiMusic extends SCPattern 
    
    private final BasicParameter wave = new BasicParameter("WAVE", 0);
    
-   MidiMusic(GLucose glucose) {
-     super(glucose);
+   MidiMusic(LX lx) {
+     super(lx);
      addParameter(lightSize);
      addParameter(wave);
      addModulator(sparkle).setValue(1);
              effects.boom.trigger();
              break;
            case 40:
 -            effects.flash.trigger();
 +            //effects.flash.trigger();
              break;
          }
        }
@@@ -287,8 -286,8 +287,8 @@@ class Pulley extends SCPattern 
    private BasicParameter sz = new BasicParameter("SIZE", 0.5);
    private BasicParameter beatAmount = new BasicParameter("BEAT", 0);
    
-   Pulley(GLucose glucose) {
-     super(glucose);
+   Pulley(LX lx) {
+     super(lx);
      for (int i = 0; i < NUM_DIVISIONS; ++i) {
        addModulator(gravity[i] = new Accelerator(0, 0, 0));
        addModulator(delays[i] = new Click(0));
@@@ -393,8 -392,8 +393,8 @@@ class ViolinWave extends SCPattern 
    
    LinearEnvelope dbValue = new LinearEnvelope(0, 0, 10);
  
-   ViolinWave(GLucose glucose) {
-     super(glucose);
+   ViolinWave(LX lx) {
+     super(lx);
      addParameter(level);
      addParameter(edge);
      addParameter(range);
@@@ -566,8 -565,8 +566,8 @@@ class BouncyBalls extends SCPattern 
    final BasicParameter flr = new BasicParameter("FLR", 0);
    final BasicParameter blobSize = new BasicParameter("SIZE", 0.5);
    
-   BouncyBalls(GLucose glucose) {
-     super(glucose);
+   BouncyBalls(LX lx) {
+     super(lx);
      for (int i = 0; i < balls.length; ++i) {
        balls[i] = new BouncyBall(i);
      }
@@@ -601,8 -600,8 +601,8 @@@ class SpaceTime extends SCPattern 
    BasicParameter sizeParameter = new BasicParameter("SIZE", 0.5);
  
  
-   public SpaceTime(GLucose glucose) {
-     super(glucose);
+   public SpaceTime(LX lx) {
+     super(lx);
      
      addModulator(pos).trigger();
      addModulator(rate).trigger();
@@@ -653,8 -652,8 +653,8 @@@ class Swarm extends SCPattern 
    SinLFO fY = new SinLFO(model.yMin, model.yMax, 11000);
    SinLFO hOffX = new SinLFO(model.xMin, model.xMax, 13000);
  
-   public Swarm(GLucose glucose) {
-     super(glucose);
+   public Swarm(LX lx) {
+     super(lx);
      
      addModulator(offset).trigger();
      addModulator(rate).trigger();
    }
  }
  
- class SwipeTransition extends SCTransition {
+ class SwipeTransition extends LXTransition {
    
    final BasicParameter bleed = new BasicParameter("WIDTH", 0.5);
    
-   SwipeTransition(GLucose glucose) {
-     super(glucose);
+   SwipeTransition(LX lx) {
+     super(lx);
      setDuration(5000);
      addParameter(bleed);
    }
    }
  }
  
- abstract class BlendTransition extends SCTransition {
-   
-   final int blendType;
-   
-   BlendTransition(GLucose glucose, int blendType) {
-     super(glucose);
-     this.blendType = blendType;
-   }
-   void computeBlend(int[] c1, int[] c2, double progress) {
-     if (progress < 0.5) {
-       for (int i = 0; i < c1.length; ++i) {
-         colors[i] = lerpColor(
-           c1[i],
-           blendColor(c1[i], c2[i], blendType),
-           (float) (2.*progress),
-           RGB);
-       }
-     } else {
-       for (int i = 0; i < c1.length; ++i) {
-         colors[i] = lerpColor(
-           c2[i],
-           blendColor(c1[i], c2[i], blendType),
-           (float) (2.*(1. - progress)),
-           RGB);
-       }
-     }
-   }
- }
- class MultiplyTransition extends BlendTransition {
-   MultiplyTransition(GLucose glucose) {
-     super(glucose, MULTIPLY);
-   }
- }
- class ScreenTransition extends BlendTransition {
-   ScreenTransition(GLucose glucose) {
-     super(glucose, SCREEN);
-   }
- }
- class BurnTransition extends BlendTransition {
-   BurnTransition(GLucose glucose) {
-     super(glucose, BURN);
-   }
- }
- class DodgeTransition extends BlendTransition {
-   DodgeTransition(GLucose glucose) {
-     super(glucose, DODGE);
-   }
- }
- class OverlayTransition extends BlendTransition {
-   OverlayTransition(GLucose glucose) {
-     super(glucose, OVERLAY);
-   }
- }
- class AddTransition extends BlendTransition {
-   AddTransition(GLucose glucose) {
-     super(glucose, ADD);
-   }
- }
- class SubtractTransition extends BlendTransition {
-   SubtractTransition(GLucose glucose) {
-     super(glucose, SUBTRACT);
-   }
- }
- class SoftLightTransition extends BlendTransition {
-   SoftLightTransition(GLucose glucose) {
-     super(glucose, SOFT_LIGHT);
-   }
- }
  class BassPod extends SCPattern {
  
    private GraphicEQ eq = null;
    
    private final BasicParameter clr = new BasicParameter("CLR", 0.5);
    
-   public BassPod(GLucose glucose) {
-     super(glucose);
+   public BassPod(LX lx) {
+     super(lx);
      addParameter(clr);
    }
    
-   protected void onActive() {
+   void onActive() {
      if (eq == null) {
        eq = new GraphicEQ(lx, 16);
        eq.range.setValue(0.4);
@@@ -858,11 -779,11 +780,11 @@@ class CubeEQ extends SCPattern 
    private final BasicParameter clr = new BasicParameter("CLR", 0.5);
    private final BasicParameter blockiness = new BasicParameter("BLK", 0.5);
  
-   public CubeEQ(GLucose glucose) {
-     super(glucose);
+   public CubeEQ(LX lx) {
+     super(lx);
    }
  
-   protected void onActive() {
+   void onActive() {
      if (eq == null) {
        eq = new GraphicEQ(lx, 16);
        addParameter(eq.level);
    }
  }
  
- class BoomEffect extends SCEffect {
+ class BoomEffect extends LXEffect {
  
    final BasicParameter falloff = new BasicParameter("WIDTH", 0.5);
    final BasicParameter speed = new BasicParameter("SPD", 0.5);
      }
    }
  
-   BoomEffect(GLucose glucose) {
-     super(glucose, true);
+   BoomEffect(LX lx) {
+     super(lx, true);
      addParameter(falloff);
      addParameter(speed);
      addParameter(bright);
@@@ -989,8 -910,8 +911,8 @@@ public class PianoKeyPattern extends SC
    final BasicParameter release = new BasicParameter("REL", 0.5);
    final BasicParameter level = new BasicParameter("AMB", 0.6);
    
-   PianoKeyPattern(GLucose glucose) {
-     super(glucose);
+   PianoKeyPattern(LX lx) {
+     super(lx);
          
      addParameter(attack);
      addParameter(release);
@@@ -1064,8 -985,8 +986,8 @@@ class CrossSections extends SCPattern 
    final BasicParameter zl = new BasicParameter("ZLEV", 0.5);
  
    
-   CrossSections(GLucose glucose) {
-     super(glucose);
+   CrossSections(LX lx) {
+     super(lx);
      addModulator(x).trigger();
      addModulator(y).trigger();
      addModulator(z).trigger();
@@@ -1142,8 -1063,8 +1064,8 @@@ class Blinders extends SCPattern 
    final SinLFO s;
    final TriangleLFO hs;
  
-   public Blinders(GLucose glucose) {
-     super(glucose);
+   public Blinders(LX lx) {
+     super(lx);
      m = new SinLFO[12];
      for (int i = 0; i < m.length; ++i) {  
        addModulator(m[i] = new SinLFO(0.5, 120, (120000. / (3+i)))).trigger();
@@@ -1181,8 -1102,8 +1103,8 @@@ class Psychedelia extends SCPattern 
    TriangleLFO h = new TriangleLFO(0, 240, 19000);
    SinLFO c = new SinLFO(-.2, .8, 31000);
  
-   Psychedelia(GLucose glucose) {
-     super(glucose);
+   Psychedelia(LX lx) {
+     super(lx);
      addModulator(m).trigger();
      addModulator(s).trigger();
      addModulator(h).trigger();
@@@ -1236,8 -1157,8 +1158,8 @@@ class AskewPlanes extends SCPattern 
    final Plane[] planes;
    final int NUM_PLANES = 3;
    
-   AskewPlanes(GLucose glucose) {
-     super(glucose);
+   AskewPlanes(LX lx) {
+     super(lx);
      planes = new Plane[NUM_PLANES];
      for (int i = 0; i < planes.length; ++i) {
        planes[i] = new Plane(i);
@@@ -1281,8 -1202,8 +1203,8 @@@ class ShiftingPlane extends SCPattern 
    final SinLFO c = new SinLFO(-1.4, 1.4, 5700);
    final SinLFO d = new SinLFO(-10, 10, 9500);
  
-   ShiftingPlane(GLucose glucose) {
-     super(glucose);
+   ShiftingPlane(LX lx) {
+     super(lx);
      addModulator(a).trigger();
      addModulator(b).trigger();
      addModulator(c).trigger();
@@@ -1319,8 -1240,8 +1241,8 @@@ class Traktor extends SCPattern 
    private int index = 0;
    private GraphicEQ eq = null;
  
-   public Traktor(GLucose glucose) {
-     super(glucose);
+   public Traktor(LX lx) {
+     super(lx);
      for (int i = 0; i < FRAME_WIDTH; ++i) {
        bass[i] = 0;
        treble[i] = 0;
    }
  }
  
- class ColorFuckerEffect extends SCEffect {
+ class ColorFuckerEffect extends LXEffect {
    
    final BasicParameter level = new BasicParameter("BRT", 1);
    final BasicParameter desat = new BasicParameter("DSAT", 0);
    
    float[] hsb = new float[3];
    
-   ColorFuckerEffect(GLucose glucose) {
-     super(glucose);
+   ColorFuckerEffect(LX lx) {
+     super(lx);
      addParameter(level);
      addParameter(desat);
      addParameter(sharp);
    }
    
    public void apply(int[] colors) {
-     if (!enabled) {
+     if (!isEnabled()) {
        return;
      }
      float bMod = level.getValuef();
    }
  }
  
- class QuantizeEffect extends SCEffect {
+ class QuantizeEffect extends LXEffect {
    
    color[] quantizedFrame;
    float lastQuant;
    final BasicParameter amount = new BasicParameter("AMT", 0);
    
-   QuantizeEffect(GLucose glucose) {
-     super(glucose);
-     quantizedFrame = new color[glucose.lx.total];
+   QuantizeEffect(LX lx) {
+     super(lx);
+     quantizedFrame = new color[lx.total];
      lastQuant = 0;
    } 
    
    }
  }
  
- class BlurEffect extends SCEffect {
+ class BlurEffect extends LXEffect {
    
    final BasicParameter amount = new BasicParameter("AMT", 0);
    final int[] frame;
    final LinearEnvelope env = new LinearEnvelope(0, 1, 100);
    
-   BlurEffect(GLucose glucose) {
-     super(glucose);
+   BlurEffect(LX lx) {
+     super(lx);
      addParameter(amount);
      addModulator(env);
      frame = new int[lx.total];
diff --combined Model.pde
index 0000000000000000000000000000000000000000,33762464c3c4ed4eaa50cfa225fea6befac50021..33762464c3c4ed4eaa50cfa225fea6befac50021
mode 000000,100644..100644
--- /dev/null
+++ b/Model.pde
@@@ -1,0 -1,365 +1,365 @@@
+ /**
+  *     DOUBLE BLACK DIAMOND        DOUBLE BLACK DIAMOND
+  *
+  *         //\\   //\\                 //\\   //\\  
+  *        ///\\\ ///\\\               ///\\\ ///\\\
+  *        \\\/// \\\///               \\\/// \\\///
+  *         \\//   \\//                 \\//   \\//
+  *
+  *        EXPERTS ONLY!!              EXPERTS ONLY!!
+  *
+  * Contains the model definitions for the cube structures.
+  */
+ /**
+  * Top-level model of the entire sculpture. This contains a list of
+  * every cube on the sculpture, which forms a hierarchy of faces, strips
+  * and points.
+  */
+ public static class Model extends LXModel {
+   public final List<Tower> towers;
+   public final List<Cube> cubes;
+   public final List<Face> faces;
+   public final List<Strip> strips;
+   private final Cube[] _cubes;
+   public Model(List<Tower> towerList, Cube[] cubeArr) {
+     super(new Fixture(cubeArr));
+     Fixture fixture = (Fixture) this.fixtures.get(0);
+     _cubes = cubeArr;
+     // Make unmodifiable accessors to the model data
+     List<Cube> cubeList = new ArrayList<Cube>();
+     List<Face> faceList = new ArrayList<Face>();
+     List<Strip> stripList = new ArrayList<Strip>();
+     for (Cube cube : _cubes) {
+       if (cube != null) {
+         cubeList.add(cube);
+         for (Face face : cube.faces) {
+           faceList.add(face);
+           for (Strip strip : face.strips) {
+             stripList.add(strip);
+           }
+         }
+       }
+     }
+     this.towers = Collections.unmodifiableList(towerList);
+     this.cubes = Collections.unmodifiableList(cubeList);
+     this.faces = Collections.unmodifiableList(faceList);
+     this.strips = Collections.unmodifiableList(stripList);
+   }
+   private static class Fixture extends LXAbstractFixture {
+     private Fixture(Cube[] cubeArr) {
+       for (Cube cube : cubeArr) {
+         if (cube != null) {
+           for (LXPoint point : cube.points) {
+             this.points.add(point);
+           }
+         }
+       }
+     }
+   }
+   /**
+    * TODO(mcslee): figure out better solution
+    * 
+    * @param index
+    * @return
+    */
+   public Cube getCubeByRawIndex(int index) {
+     return _cubes[index];
+   }
+ }
+ /**
+  * Model of a set of cubes stacked in a tower
+  */
+ public static class Tower extends LXModel {
+   /**
+    * Immutable list of cubes
+    */
+   public final List<Cube> cubes;
+   /**
+    * Immutable list of faces
+    */
+   public final List<Face> faces;
+   /**
+        * Immutable list of strips
+        */
+   public final List<Strip> strips;
+   /**
+    * Constructs a tower model from these cubes
+    * 
+    * @param cubes Array of cubes
+    */
+   public Tower(List<Cube> cubes) {
+     super(cubes.toArray(new Cube[] {}));
+     List<Cube> cubeList = new ArrayList<Cube>();
+     List<Face> faceList = new ArrayList<Face>();
+     List<Strip> stripList = new ArrayList<Strip>();
+     for (Cube cube : cubes) {
+       cubeList.add(cube);
+       for (Face face : cube.faces) {
+         faceList.add(face);
+         for (Strip strip : face.strips) {
+           stripList.add(strip);
+         }
+       }
+     }
+     this.cubes = Collections.unmodifiableList(cubeList);
+     this.faces = Collections.unmodifiableList(faceList);
+     this.strips = Collections.unmodifiableList(stripList);
+   }
+ }
+ /**
+  * Model of a single cube, which has an orientation and position on the
+  * car. The position is specified in x,y,z coordinates with rotation. The
+  * x axis is left->right, y is bottom->top, and z is front->back.
+  * 
+  * A cube's x,y,z position is specified as the left, bottom, front corner.
+  * 
+  * Dimensions are all specified in real-world inches.
+  */
+ public static class Cube extends LXModel {
+   public final static int FACES_PER_CUBE = 4; 
+   public static final int POINTS_PER_STRIP = 16;
+   public final static int STRIPS_PER_CUBE = FACES_PER_CUBE*Face.STRIPS_PER_FACE;
+   public final static int POINTS_PER_CUBE = STRIPS_PER_CUBE*POINTS_PER_STRIP;
+   public final static int POINTS_PER_FACE = Face.STRIPS_PER_FACE*POINTS_PER_STRIP;
+   public final static float EDGE_HEIGHT = 21.75f;
+   public final static float EDGE_WIDTH = 24.625f;
+   public final static float CHANNEL_WIDTH = 1.5f;
+   public final static Face.Metrics FACE_METRICS = new Face.Metrics(
+     new Strip.Metrics(EDGE_WIDTH, POINTS_PER_STRIP), 
+     new Strip.Metrics(EDGE_HEIGHT, POINTS_PER_STRIP)
+   );
+   /**
+    * Immutable list of all cube faces
+    */
+   public final List<Face> faces;
+   /**
+    * Immutable list of all strips
+    */
+   public final List<Strip> strips;
+   /**
+    * Front left corner x coordinate 
+    */
+   public final float x;
+   /**
+    * Front left corner y coordinate 
+    */
+   public final float y;
+   /**
+    * Front left corner z coordinate 
+    */
+   public final float z;
+   /**
+    * Rotation about the x-axis 
+    */
+   public final float rx;
+   /**
+    * Rotation about the y-axis 
+    */
+   public final float ry;
+   /**
+    * Rotation about the z-axis 
+    */
+   public final float rz;
+   public Cube(double x, double y, double z, double rx, double ry, double rz) {
+     this((float) x, (float) y, (float) z, (float) rx, (float) ry, (float) rz);
+   }
+   public Cube(float x, float y, float z, float rx, float ry, float rz) {
+     super(new Fixture(x, y, z, rx, ry, rz));
+     Fixture fixture = (Fixture) this.fixtures.get(0);
+     while (rx < 0) rx += 360;
+     while (ry < 0) ry += 360;
+     while (rz < 0) rz += 360;
+     rx = rx % 360;
+     ry = ry % 360;
+     rz = rz % 360;
+     this.x = x; 
+     this.y = y;
+     this.z = z;
+     this.rx = rx;
+     this.ry = ry;
+     this.rz = rz;
+     this.faces = Collections.unmodifiableList(fixture.faces);
+     this.strips = Collections.unmodifiableList(fixture.strips);
+   }
+   private static class Fixture extends LXAbstractFixture {
+     private final List<Face> faces = new ArrayList<Face>();
+     private final List<Strip> strips = new ArrayList<Strip>();
+     private Fixture(float x, float y, float z, float rx, float ry, float rz) {
+       LXTransform t = new LXTransform();
+       t.translate(x, y, z);
+       t.rotateX(rx * PI / 180.);
+       t.rotateY(ry * PI / 180.);
+       t.rotateZ(rz * PI / 180.);              
+       for (int i = 0; i < FACES_PER_CUBE; i++) {
+         Face face = new Face(FACE_METRICS, (ry + 90*i) % 360, t);
+         this.faces.add(face);
+         for (Strip s : face.strips) {
+           this.strips.add(s);
+         }
+         for (LXPoint p : face.points) {
+           this.points.add(p);
+         }
+         t.translate(EDGE_WIDTH, 0, 0);
+         t.rotateY(HALF_PI);
+       }
+     }
+   }
+ }
+ /**
+  * A face is a component of a cube. It is comprised of four strips forming
+  * the lights on this side of a cube. A whole cube is formed by four faces.
+  */
+ public static class Face extends LXModel {
+   public final static int STRIPS_PER_FACE = 4;
+   public static class Metrics {
+     final Strip.Metrics horizontal;
+     final Strip.Metrics vertical;
+     public Metrics(Strip.Metrics horizontal, Strip.Metrics vertical) {
+       this.horizontal = horizontal;
+       this.vertical = vertical;
+     }
+   }
+   /**
+    * Immutable list of strips
+    */
+   public final List<Strip> strips;
+   /**
+    * Rotation of the face about the y-axis
+    */
+   public final float ry;
+   Face(Metrics metrics, float ry, LXTransform transform) {
+     super(new Fixture(metrics, ry, transform));
+     Fixture fixture = (Fixture) this.fixtures.get(0);
+     this.ry = ry;
+     this.strips = Collections.unmodifiableList(fixture.strips);
+   }
+   private static class Fixture extends LXAbstractFixture {
+     private final List<Strip> strips = new ArrayList<Strip>();
+     private Fixture(Metrics metrics, float ry, LXTransform transform) {
+       transform.push();
+       transform.translate(0, metrics.vertical.length, 0);
+       for (int i = 0; i < STRIPS_PER_FACE; i++) {
+         boolean isHorizontal = (i % 2 == 0);
+         Strip.Metrics stripMetrics = isHorizontal ? metrics.horizontal : metrics.vertical;
+         Strip strip = new Strip(stripMetrics, ry, transform, isHorizontal);
+         this.strips.add(strip);
+         transform.translate(isHorizontal ? metrics.horizontal.length : metrics.vertical.length, 0, 0);
+         transform.rotateZ(HALF_PI);
+         for (LXPoint p : strip.points) {
+           this.points.add(p);
+         }
+       }
+       transform.pop();
+     }
+   }
+ }
+ /**
+  * A strip is a linear run of points along a single edge of one cube.
+  */
+ public static class Strip extends LXModel {
+   public static final float POINT_SPACING = 18.625f / 15.f;
+   public static class Metrics {
+     public final float length;
+     public final int numPoints;
+     public Metrics(float length, int numPoints) {
+       this.length = length;
+       this.numPoints = numPoints;
+     }
+   }
+   public final Metrics metrics;
+   /**
+    * Whether this is a horizontal strip
+    */
+   public final boolean isHorizontal;
+   /**
+    * Rotation about the y axis
+    */
+   public final float ry;
+   public Object obj1 = null, obj2 = null;
+   Strip(Metrics metrics, float ry, List<LXPoint> points, boolean isHorizontal) {
+     super(points);
+     this.isHorizontal = isHorizontal;
+     this.metrics = metrics;           
+     this.ry = ry;
+   }
+   Strip(Metrics metrics, float ry, LXTransform transform, boolean isHorizontal) {
+     super(new Fixture(metrics, ry, transform));
+     this.metrics = metrics;
+     this.isHorizontal = isHorizontal;
+     this.ry = ry;
+   }
+   private static class Fixture extends LXAbstractFixture {
+     private Fixture(Metrics metrics, float ry, LXTransform transform) {
+       float offset = (metrics.length - (metrics.numPoints - 1) * POINT_SPACING) / 2.f;
+       transform.push();
+       transform.translate(offset, -Cube.CHANNEL_WIDTH/2.f, 0);
+       for (int i = 0; i < metrics.numPoints; i++) {
+         LXPoint point = new LXPoint(transform.x(), transform.y(), transform.z());
+         this.points.add(point);
+         transform.translate(POINT_SPACING, 0, 0);
+       }
+       transform.pop();
+     }
+   }
+ }
diff --combined UIImplementation.pde
index 0ea8073ddb57d6701a6a0a1b55df6fc88c89bbe8,ba0f110bc826a00aec85ce91e1b7ad52e9520c37..7750ab930c365a3322d635b6f82728483f0bca7c
   *
   * Custom UI components using the framework.
   */
-  
- class UIPatternDeck extends UIWindow {
-     
-   LXDeck deck;
-   
-   public UIPatternDeck(LXDeck deck, String label, float x, float y, float w, float h) {
-     super(label, x, y, w, h);
-     this.deck = deck;
-     int yp = titleHeight;
-         
-     List<ScrollItem> items = new ArrayList<ScrollItem>();
-     for (LXPattern p : deck.getPatterns()) {
-       items.add(new PatternScrollItem(p));
-     }    
-     final UIScrollList patternList = new UIScrollList(1, yp, w-2, 140).setItems(items);
-     patternList.addToContainer(this);
-     yp += patternList.h + 10;
-     
-     final UIParameterKnob[] parameterKnobs = new UIParameterKnob[12];
-     for (int ki = 0; ki < parameterKnobs.length; ++ki) {
-       parameterKnobs[ki] = new UIParameterKnob(5 + 34*(ki % 4), yp + (ki/4) * 48);
-       parameterKnobs[ki].addToContainer(this);
 -
++import java.util.Arrays;
+ class UICubesLayer extends UICameraComponent {
+   void onDraw(UI ui) {
+     color[] simulationColors = lx.getColors();
+     String displayMode = uiCrossfader.getDisplayMode();
+     if (displayMode == "A") {
+       simulationColors = lx.engine.getDeck(LEFT_DECK).getColors();
+     } else if (displayMode == "B") {
+       simulationColors = lx.engine.getDeck(RIGHT_DECK).getColors();
      }
+     long simulationStart = System.nanoTime();
+     if (simulationOn) {
+       drawSimulation(simulationColors);
+     }
+     simulationNanos = System.nanoTime() - simulationStart;
      
-     LXDeck.Listener lxListener = new LXDeck.AbstractListener() {
-       public void patternWillChange(LXDeck deck, LXPattern pattern, LXPattern nextPattern) {
-         patternList.redraw();
-       }
-       public void patternDidChange(LXDeck deck, LXPattern pattern) {
-         patternList.redraw();
-         int pi = 0;
-         for (LXParameter parameter : pattern.getParameters()) {
-           if (pi >= parameterKnobs.length) {
-             break;
-           }
-           parameterKnobs[pi++].setParameter(parameter);
-         }
-         while (pi < parameterKnobs.length) {
-           parameterKnobs[pi++].setParameter(null);
-         }
-       }
-     };
-     
-     deck.addListener(lxListener);
-     lxListener.patternDidChange(deck, deck.getActivePattern());
-     
+     camera();
 -    javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
 -    gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
 -    ((PGraphicsOpenGL)g).endGL();
++    PGraphicsOpenGL gl = (PGraphicsOpenGL) g;  
+     strokeWeight(1);
    }
    
-   class PatternScrollItem extends AbstractScrollItem {
-     
-     private LXPattern pattern;
-     private String label;
-     
-     PatternScrollItem(LXPattern pattern) {
-       this.pattern = pattern;
-       label = className(pattern, "Pattern");
-     }
-     
-     public String getLabel() {
-       return label;
-     }
-     
-     public boolean isSelected() {
-       return deck.getActivePattern() == pattern;
-     }
+   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();
      
-     public boolean isPending() {
-       return deck.getNextPattern() == pattern;
+     noStroke();
+     for (Cube c : model.cubes) {
+       drawCube(c);
      }
-     
-     public void onMousePressed() {
-       deck.goPattern(pattern);
+   
+     noFill();
+     strokeWeight(2);
+     beginShape(POINTS);
+     for (LXPoint p : model.points) {
+       stroke(simulationColors[p.index]);
+       vertex(p.x, p.y, p.z);
+     }
+     endShape();
+   }
+   
+   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 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);
      }
+     popMatrix();
    }
  }
  
  class UIBlendMode extends UIWindow {
    public UIBlendMode(float x, float y, float w, float h) {
-     super("BLEND MODE", x, y, w, h);
-     List<ScrollItem> items = new ArrayList<ScrollItem>();
-     for (LXTransition t : glucose.getTransitions()) {
+     super(lx.ui, "BLEND MODE", x, y, w, h);
+     List<UIScrollList.Item> items = new ArrayList<UIScrollList.Item>();
+     for (LXTransition t : transitions) {
        items.add(new TransitionScrollItem(t));
      }
      final UIScrollList tList;
-     (tList = new UIScrollList(1, titleHeight, w-2, 60)).setItems(items).addToContainer(this);
+     (tList = new UIScrollList(1, UIWindow.TITLE_LABEL_HEIGHT, w-2, 60)).setItems(items).addToContainer(this);
  
-     lx.engine.getDeck(GLucose.RIGHT_DECK).addListener(new LXDeck.AbstractListener() {
+     lx.engine.getDeck(RIGHT_DECK).addListener(new LXDeck.AbstractListener() {
        public void faderTransitionDidChange(LXDeck deck, LXTransition transition) {
          tList.redraw();
        }
      });
    }
  
-   class TransitionScrollItem extends AbstractScrollItem {
+   class TransitionScrollItem extends UIScrollList.AbstractItem {
      private final LXTransition transition;
-     private String label;
+     private final String label;
      
      TransitionScrollItem(LXTransition transition) {
        this.transition = transition;
-       label = className(transition, "Transition");
+       this.label = className(transition, "Transition");
      }
      
      public String getLabel() {
      }
      
      public boolean isSelected() {
-       return transition == glucose.getSelectedTransition();
+       return this.transition == lx.engine.getDeck(RIGHT_DECK).getFaderTransition();
      }
      
      public boolean isPending() {
      }
      
      public void onMousePressed() {
-       glucose.setSelectedTransition(transition);
+       lx.engine.getDeck(RIGHT_DECK).setFaderTransition(this.transition);
      }
    }
  
@@@ -136,10 -162,10 +160,10 @@@ class UICrossfader extends UIWindow 
    private final UIToggleSet displayMode;
    
    public UICrossfader(float x, float y, float w, float h) {
-     super("CROSSFADER", x, y, w, h);
+     super(lx.ui, "CROSSFADER", x, y, w, h);
  
-     new UIParameterSlider(4, titleHeight, w-9, 32).setParameter(lx.engine.getDeck(GLucose.RIGHT_DECK).getFader()).addToContainer(this);
-     (displayMode = new UIToggleSet(4, titleHeight + 36, w-9, 20)).setOptions(new String[] { "A", "COMP", "B" }).setValue("COMP").addToContainer(this);
+     new UISlider(4, UIWindow.TITLE_LABEL_HEIGHT, w-9, 32).setParameter(lx.engine.getDeck(RIGHT_DECK).getFader()).addToContainer(this);
+     (displayMode = new UIToggleSet(4, UIWindow.TITLE_LABEL_HEIGHT + 36, w-9, 20)).setOptions(new String[] { "A", "COMP", "B" }).setValue("COMP").addToContainer(this);
    }
    
    public UICrossfader setDisplayMode(String value) {
  
  class UIEffects extends UIWindow {
    UIEffects(float x, float y, float w, float h) {
-     super("FX", x, y, w, h);
+     super(lx.ui, "FX", x, y, w, h);
  
-     int yp = titleHeight;
-     List<ScrollItem> items = new ArrayList<ScrollItem>();
-     for (LXEffect fx : glucose.lx.getEffects()) {
-       items.add(new FXScrollItem(fx));
+     int yp = UIWindow.TITLE_LABEL_HEIGHT;
+     List<UIScrollList.Item> items = new ArrayList<UIScrollList.Item>();
+     int i = 0;
+     for (LXEffect fx : effectsArr) {
+       items.add(new FXScrollItem(fx, i++));
      }    
      final UIScrollList effectsList = new UIScrollList(1, yp, w-2, 60).setItems(items);
      effectsList.addToContainer(this);
-     yp += effectsList.h + 10;
+     yp += effectsList.getHeight() + 10;
      
-     final UIParameterKnob[] parameterKnobs = new UIParameterKnob[4];
+     final UIKnob[] parameterKnobs = new UIKnob[4];
      for (int ki = 0; ki < parameterKnobs.length; ++ki) {
-       parameterKnobs[ki] = new UIParameterKnob(5 + 34*(ki % 4), yp + (ki/4) * 48);
+       parameterKnobs[ki] = new UIKnob(5 + 34*(ki % 4), yp + (ki/4) * 48);
        parameterKnobs[ki].addToContainer(this);
      }
      
-     GLucose.EffectListener fxListener = new GLucose.EffectListener() {
-       public void effectSelected(LXEffect effect) {
+     LXParameterListener fxListener = new LXParameterListener() {
+       public void onParameterChanged(LXParameter parameter) {
          int i = 0;
-         for (LXParameter p : effect.getParameters()) {
+         for (LXParameter p : getSelectedEffect().getParameters()) {
            if (i >= parameterKnobs.length) {
              break;
            }
-           parameterKnobs[i++].setParameter(p);
+           if (p instanceof BasicParameter) {
+             parameterKnobs[i++].setParameter((BasicParameter) p);
+           }
          }
          while (i < parameterKnobs.length) {
            parameterKnobs[i++].setParameter(null);
        }
      };
      
-     glucose.addEffectListener(fxListener);
-     fxListener.effectSelected(glucose.getSelectedEffect());
+     selectedEffect.addListener(fxListener);
+     fxListener.onParameterChanged(null);
  
    }
    
-   class FXScrollItem extends AbstractScrollItem {
+   class FXScrollItem extends UIScrollList.AbstractItem {
      
-     private LXEffect effect;
-     private String label;
+     private final LXEffect effect;
+     private final int index;
+     private final String label;
      
-     FXScrollItem(LXEffect effect) {
+     FXScrollItem(LXEffect effect, int index) {
        this.effect = effect;
-       label = className(effect, "Effect");
+       this.index = index;
+       this.label = className(effect, "Effect");
      }
      
      public String getLabel() {
      }
      
      public boolean isSelected() {
-       return !effect.isEnabled() && (glucose.getSelectedEffect() == effect);
+       return !effect.isEnabled() && (effect == getSelectedEffect());
      }
      
      public boolean isPending() {
      }
      
      public void onMousePressed() {
-       if (glucose.getSelectedEffect() == effect) {
+       if (effect == getSelectedEffect()) {
          if (effect.isMomentary()) {
            effect.enable();
          } else {
            effect.toggle();
          }
        } else {
-         glucose.setSelectedEffect(effect);
+         selectedEffect.setValue(index);
        }
      }
      
  }
  
  class UIOutput extends UIWindow {
-   public UIOutput(float x, float y, float w, float h) {
-     super("OUTPUT", x, y, w, h);
-     float yp = titleHeight;
+   public UIOutput(GrizzlyOutput[] grizzlies, float x, float y, float w, float h) {
+     super(lx.ui, "OUTPUT", x, y, w, h);
+     float yp = UIWindow.TITLE_LABEL_HEIGHT;
      
-     final UIScrollList outputs = new UIScrollList(1, titleHeight, w-2, 80);
+     final UIScrollList outputs = new UIScrollList(1, UIWindow.TITLE_LABEL_HEIGHT, w-2, 80);
      
-     List<ScrollItem> items = new ArrayList<ScrollItem>();
-     for (final PandaDriver panda : pandaBoards) {
-       items.add(new PandaScrollItem(panda));
-       panda.setListener(new PandaDriver.Listener() {
-         public void onToggle(boolean active) {
+     List<UIScrollList.Item> items = new ArrayList<UIScrollList.Item>();
+     for (GrizzlyOutput grizzly : grizzlies) {
+       items.add(new GrizzlyScrollItem(grizzly));
+       grizzly.enabled.addListener(new LXParameterListener() {
+         public void onParameterChanged(LXParameter parameter) {
             outputs.redraw();
          }
        });
      outputs.setItems(items).addToContainer(this);
    } 
   
-   class PandaScrollItem extends AbstractScrollItem {
-     final PandaDriver panda;
-     PandaScrollItem(PandaDriver panda) {
-       this.panda = panda;
+   class GrizzlyScrollItem extends UIScrollList.AbstractItem {
+     final GrizzlyOutput output;
+     GrizzlyScrollItem(GrizzlyOutput output) {
+       this.output = output;
      }
      
      public String getLabel() {
-       return panda.ip;
+       return output.ipAddress;
      }
      
      public boolean isSelected() {
-       return panda.isEnabled();
+       return output.enabled.isOn();
      }
      
      public void onMousePressed() {
-       panda.toggle();
+       output.enabled.setValue(!isSelected());
      }
    } 
  }
@@@ -279,8 -311,8 +309,8 @@@ class UITempo extends UIWindow 
    private final UIButton tempoButton;
    
    UITempo(float x, float y, float w, float h) {
-     super("TEMPO", x, y, w, h);
-     tempoButton = new UIButton(4, titleHeight, w-10, 20) {
+     super(lx.ui, "TEMPO", x, y, w, h);
+     tempoButton = new UIButton(4, UIWindow.TITLE_LABEL_HEIGHT, w-10, 20) {
        protected void onToggle(boolean active) {
          if (active) {
            lx.tempo.tap();
        }
      }.setMomentary(true);
      tempoButton.addToContainer(this);
+     new UITempoBlipper(8, UIWindow.TITLE_LABEL_HEIGHT + 5, 12, 12).addToContainer(this);
    }
    
-   public void draw() {
-     tempoButton.setLabel("" + ((int)(lx.tempo.bpm() * 10)) / 10.);
-     super.draw();
+   class UITempoBlipper extends UIObject {
+     UITempoBlipper(float x, float y, float w, float h) {
+       super(x, y, w, h);
+     }
      
-     // Overlay tempo thing with openGL, redraw faster than button UI
-     fill(color(0, 0, 24 - 8*lx.tempo.rampf()));
-     noStroke();
-     rect(x + 8, y + titleHeight + 5, 12, 12);
+     void onDraw(UI ui, PGraphics pg) {
+       tempoButton.setLabel("" + ((int)(lx.tempo.bpm() * 10)) / 10.);
+     
+       // Overlay tempo thing with openGL, redraw faster than button UI
+       pg.fill(color(0, 0, 24 - 8*lx.tempo.rampf()));
+       pg.noStroke();
+       pg.rect(0, 0, width, height);
+       
+       redraw();
+     }
    }
+   
  }
  
  class UIMapping extends UIWindow {
    private final UIIntegerBox stripBox;
    
    UIMapping(MappingTool tool, float x, float y, float w, float h) {
-     super("MAPPING", x, y, w, h);
+     super(lx.ui, "MAPPING", x, y, w, h);
      mappingTool = tool;
      
-     int yp = titleHeight;
+     int yp = UIWindow.TITLE_LABEL_HEIGHT;
      new UIToggleSet(4, yp, w-10, 20) {
        protected void onToggle(String value) {
          if (value == MAP_MODE_ALL) mappingTool.mappingMode = mappingTool.MAPPING_MODE_ALL;
        protected void onValueChange(int value) {
          mappingTool.setCube(value-1);
        }
-     }).setRange(1, glucose.model.cubes.size()).addToContainer(this);
+     }).setRange(1, model.cubes.size()).addToContainer(this);
      yp += 24;
      
      yp += 10;
          
-     new UIScrollList(1, yp, w-2, 60).setItems(Arrays.asList(new ScrollItem[] {
+     new UIScrollList(1, yp, w-2, 60).setItems(Arrays.asList(new UIScrollList.Item[] {
        new ColorScrollItem(ColorScrollItem.COLOR_RED),
        new ColorScrollItem(ColorScrollItem.COLOR_GREEN),
        new ColorScrollItem(ColorScrollItem.COLOR_BLUE),
      stripBox.setValue(value);
    }
    
-   class ColorScrollItem extends AbstractScrollItem {
+   class ColorScrollItem extends UIScrollList.AbstractItem {
      
      public static final int COLOR_RED = 1;
      public static final int COLOR_GREEN = 2;
@@@ -438,7 -479,7 +477,7 @@@ class UIDebugText extends UIContext 
    private String line2 = "";
    
    UIDebugText(float x, float y, float w, float h) {
-     super(x, y, w, h);
+     super(lx.ui, x, y, w, h);
    }
  
    public UIDebugText setText(String line1) {
      return this;
    }
    
-   protected void onDraw(PGraphics pg) {
-     super.onDraw(pg);
+   protected void onDraw(UI ui, PGraphics pg) {
+     super.onDraw(ui, pg);
      if (line1.length() + line2.length() > 0) {
        pg.noStroke();
        pg.fill(#444444);
-       pg.rect(0, 0, w, h);
-       pg.textFont(defaultItemFont);
+       pg.rect(0, 0, width, height);
+       pg.textFont(ui.getItemFont());
        pg.textSize(10);
        pg.textAlign(LEFT, TOP);
        pg.fill(#cccccc);
@@@ -476,14 -517,14 +515,14 @@@ class UISpeed extends UIWindow 
    final BasicParameter speed;
    
    UISpeed(float x, float y, float w, float h) {
-     super("SPEED", x, y, w, h);
+     super(lx.ui, "SPEED", x, y, w, h);
      speed = new BasicParameter("SPEED", 0.5);
-     new UIParameterSlider(4, titleHeight, w-10, 20)
-     .setParameter(speed.addListener(new LXParameterListener() {
+     speed.addListener(new LXParameterListener() {
        public void onParameterChanged(LXParameter parameter) {
          lx.setSpeed(parameter.getValuef() * 2);
        }
-     })).addToContainer(this);
+     });
+     new UISlider(4, UIWindow.TITLE_LABEL_HEIGHT, w-10, 20).setParameter(speed).addToContainer(this);
    }
  }
  
@@@ -493,15 -534,15 +532,15 @@@ class UIMidi extends UIWindow 
    private final UIButton logMode;
    
    UIMidi(final MidiEngine midiEngine, float x, float y, float w, float h) {
-     super("MIDI", x, y, w, h);
+     super(lx.ui, "MIDI", x, y, w, h);
  
      // Processing compiler doesn't seem to get that list of class objects also conform to interface
-     List<ScrollItem> scrollItems = new ArrayList<ScrollItem>();
+     List<UIScrollList.Item> scrollItems = new ArrayList<UIScrollList.Item>();
      for (SCMidiInput mc : midiEngine.getControllers()) {
        scrollItems.add(mc);
      }
      final UIScrollList scrollList;
-     (scrollList = new UIScrollList(1, titleHeight, w-2, 100)).setItems(scrollItems).addToContainer(this);
+     (scrollList = new UIScrollList(1, UIWindow.TITLE_LABEL_HEIGHT, w-2, 100)).setItems(scrollItems).addToContainer(this);
      (deckMode = new UIToggleSet(4, 130, 90, 20) {
        protected void onToggle(String value) {
          midiEngine.setFocusedDeck(value == "A" ? 0 : 1);
    }
    
    public LXDeck getFocusedDeck() {
-     return lx.engine.getDeck(deckMode.getValue() == "A" ? GLucose.LEFT_DECK : GLucose.RIGHT_DECK);
+     return lx.engine.getDeck(deckMode.getValue() == "A" ? LEFT_DECK : RIGHT_DECK);
    }
  }
  
diff --combined libraries/HeronLX.jar
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7c388a7187aa6cce3490c50164ba4cf90dbee62f
new file mode 100755 (executable)
Binary files differ
diff --combined libraries/JGraphT.jar
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1db92e60a5c2366f9eb3d5928e2405351398ae61
new file mode 100644 (file)
Binary files differ
diff --combined libraries/ScreenShot.dll
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..68a6c99f340b669cda7ccd01a7923da70035c033
new file mode 100755 (executable)
Binary files differ
diff --combined libraries/ScreenShot.jar
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c75b8de040d302fe178a9978eebf6b3f230fe5b8
new file mode 100755 (executable)
Binary files differ
diff --combined libraries/rwmidi.jar
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4788ff68beb23ae45330764ea00ed4c45f9d03aa
new file mode 100755 (executable)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..18fbbfe8671e1317e61ce41c8945e4eb78509f08
new file mode 100755 (executable)
Binary files differ
diff --combined sketch.properties
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8630fa24ac7a4cc1429c0aca64fb8630782f0cf6
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++mode.id=processing.mode.java.JavaMode
++mode=Java