redo with new anti-GLucose
[SugarCubes.git] / MarkSlee.pde
index 1ecb640dbd10c7bd59581ea3f34f3a6660171719..fea4a43d18a668070981be8d4c57fa745bbd75c4 100644 (file)
@@ -1,6 +1,598 @@
+import java.util.Stack;
+class Cathedrals extends SCPattern {
+  
+  private final BasicParameter xpos = new BasicParameter("XPOS", 0.5);
+  private final BasicParameter wid = new BasicParameter("WID", 0.5);
+  private final BasicParameter arms = new BasicParameter("ARMS", 0.5);
+  private final BasicParameter sat = new BasicParameter("SAT", 0.5);
+  private GraphicEQ eq;
+  
+  Cathedrals(LX lx) {
+    super(lx);
+    addParameter(xpos);
+    addParameter(wid);
+    addParameter(arms);
+    addParameter(sat);
+  }
+  void onActive() {
+    if (eq == null) {
+      eq = new GraphicEQ(lx, 16);
+      eq.slope.setValue(0.7);
+      eq.range.setValue(0.4);
+      eq.attack.setValue(0.4);
+      eq.release.setValue(0.4);
+      addParameter(eq.level);
+      addParameter(eq.range);
+      addParameter(eq.attack);
+      addParameter(eq.release);
+      addParameter(eq.slope);
+    }
+  }
+
+  public void run(double deltaMs) {
+    eq.run(deltaMs);
+    float bassLevel = eq.getAverageLevel(0, 4);
+    float trebleLevel = eq.getAverageLevel(8, 6);
+    
+    float falloff = 100 / (2 + 14*wid.getValuef());
+    float cx = model.xMin + (model.xMax-model.xMin) * xpos.getValuef();
+    float barm = 12 + 60*arms.getValuef()*max(0, 2*(bassLevel-0.1));
+    float tarm = 12 + 60*arms.getValuef()*max(0, 2*(trebleLevel-0.1));
+    
+    float arm = 0;
+    float middle = 0;
+    
+    float sf = 100. / (70 - 69.9*sat.getValuef());
+
+    for (LXPoint p : model.points) {
+      float d = MAX_FLOAT;
+      if (p.y > model.cy) {
+        arm = tarm;
+        middle = model.yMax * 3/5.;
+      } else {
+        arm = barm;
+        middle = model.yMax * 1/5.;
+      }
+      if (abs(p.x - cx) < arm) {
+        d = min(abs(p.x - cx), abs(p.y - middle));
+      }
+      colors[p.index] = lx.hsb(
+        (lx.getBaseHuef() + .2*abs(p.y - model.cy)) % 360,
+        min(100, sf*dist(abs(p.x - cx), p.y, arm, middle)),
+        constrain(120 - d*falloff, 0, 100));
+    }
+  } 
+}
+  
+class MidiMusic extends SCPattern {
+  
+  private final Stack<LXLayer> newLayers = new Stack<LXLayer>();
+  
+  private final Map<Integer, LightUp> lightMap = new HashMap<Integer, LightUp>();
+  private final List<LightUp> lights = new ArrayList<LightUp>();
+  private final BasicParameter lightSize = new BasicParameter("SIZE", 0.5);
+
+  private final List<Sweep> sweeps = new ArrayList<Sweep>();
+
+  private final LinearEnvelope sparkle = new LinearEnvelope(0, 1, 500);
+  private boolean sparkleDirection = true;
+  private float sparkleBright = 100;
+  
+  private final BasicParameter wave = new BasicParameter("WAVE", 0);
+  
+  MidiMusic(LX lx) {
+    super(lx);
+    addParameter(lightSize);
+    addParameter(wave);
+    addModulator(sparkle).setValue(1);
+  }
+  
+  void onReset() {
+    for (LightUp light : lights) {
+      light.noteOff(null);
+    }
+  }
+  
+  class Sweep extends LXLayer {
+    
+    final LinearEnvelope position = new LinearEnvelope(0, 1, 1000);
+    float bright = 100;
+    float falloff = 10;
+    
+    Sweep() {
+      addModulator(position);
+    }
+    
+    public void run(double deltaMs, color[] colors) {
+      if (!position.isRunning()) {
+        return;
+      }
+      float posf = position.getValuef();
+      for (LXPoint p : model.points) {
+        colors[p.index] = blendColor(colors[p.index], lx.hsb(
+          (lx.getBaseHuef() + .2*abs(p.x - model.cx) + .2*abs(p.y - model.cy)) % 360,
+          100,
+          max(0, bright - posf*100 - falloff*abs(p.y - posf*model.yMax))
+        ), ADD);
+      }
+    }
+  }
+  
+  class LightUp extends LXLayer {
+    
+    private final LinearEnvelope brt = new LinearEnvelope(0, 0, 0);
+    private final Accelerator yPos = new Accelerator(0, 0, 0);
+    private float xPos;
+    
+    LightUp() {
+      addModulator(brt);
+      addModulator(yPos);
+    }
+    
+    boolean isAvailable() {
+      return brt.getValuef() <= 0;
+    }
+    
+    void noteOn(Note note) {
+      xPos = lerp(0, model.xMax, constrain(0.5 + (note.getPitch() - 60) / 28., 0, 1));
+      yPos.setValue(lerp(20, model.yMax*.72, note.getVelocity() / 127.)).stop();
+      brt.setRangeFromHereTo(lerp(40, 100, note.getVelocity() / 127.), 20).start();     
+    }
+
+    void noteOff(Note note) {
+      yPos.setVelocity(0).setAcceleration(-380).start();
+      brt.setRangeFromHereTo(0, 1000).start();
+    }
+    
+    public void run(double deltaMs, color[] colors) {
+      float bVal = brt.getValuef();
+      if (bVal <= 0) {
+        return;
+      }
+      float yVal = yPos.getValuef();
+      for (LXPoint p : model.points) {
+        float falloff = 6 - 5*lightSize.getValuef();
+        float b = max(0, bVal - falloff*dist(p.x, p.y, xPos, yVal));
+        if (b > 0) {
+          colors[p.index] = blendColor(colors[p.index], lx.hsb(
+            (lx.getBaseHuef() + .2*abs(p.x - model.cx) + .2*abs(p.y - model.cy)) % 360,
+            100,
+            b
+          ), ADD);
+        }
+      }
+    }
+  }
+  
+  private LightUp getLight() {
+    for (LightUp light : lights) {
+      if (light.isAvailable()) {
+        return light;
+      }
+    }
+    LightUp newLight = new LightUp();
+    lights.add(newLight);
+    synchronized(newLayers) {
+      newLayers.push(newLight);
+    }
+    return newLight;
+  }
+  
+  private Sweep getSweep() {
+    for (Sweep s : sweeps) {
+      if (!s.position.isRunning()) {
+        return s;
+      }
+    }
+    Sweep newSweep = new Sweep();
+    sweeps.add(newSweep);
+    synchronized(newLayers) {
+      newLayers.push(newSweep);
+    }
+    return newSweep;
+  }
+  
+  public synchronized boolean noteOn(Note note) {
+    if (note.getChannel() == 0) {
+      LightUp light = getLight();
+      lightMap.put(note.getPitch(), light);
+      light.noteOn(note);
+    } else if (note.getChannel() == 1) {
+    } else if (note.getChannel() == 9) {
+      if (note.getVelocity() > 0) {
+        switch (note.getPitch()) {
+          case 36:
+            Sweep s = getSweep();
+            s.bright = 50 + note.getVelocity() / 127. * 50;
+            s.falloff = 20 - note.getVelocity() / 127. * 17;
+            s.position.trigger();
+            break;
+          case 37:
+            sparkleBright = note.getVelocity() / 127. * 100;
+            sparkleDirection = true;
+            sparkle.trigger();
+            break;
+          case 38:
+            sparkleBright = note.getVelocity() / 127. * 100;
+            sparkleDirection = false;
+            sparkle.trigger();       
+            break;
+          case 39:
+            effects.boom.trigger();
+            break;
+          case 40:
+            //effects.flash.trigger();
+            break;
+        }
+      }
+    }
+    return true;
+  }
+  
+  public synchronized boolean noteOff(Note note) {
+    if (note.getChannel() == 0) {
+      LightUp light = lightMap.get(note.getPitch());
+      if (light != null) {
+        light.noteOff(note);
+      }
+    }
+    return true;
+  }
+  
+  final float[] wval = new float[16];
+  float wavoff = 0;
+  
+  public synchronized void run(double deltaMs) {
+    wavoff += deltaMs * .001;
+    for (int i = 0; i < wval.length; ++i) {
+      wval[i] = model.cy + 0.2 * model.yMax/2. * sin(wavoff + i / 1.9);
+    }
+    float sparklePos = (sparkleDirection ? sparkle.getValuef() : (1 - sparkle.getValuef())) * (Cube.POINTS_PER_STRIP)/2.;
+    float maxBright = sparkleBright * (1 - sparkle.getValuef());
+    for (Strip s : model.strips) {
+      int i = 0;
+      for (LXPoint p : s.points) {
+        int wavi = (int) constrain(p.x / model.xMax * wval.length, 0, wval.length-1);
+        float wavb = max(0, wave.getValuef()*100. - 8.*abs(p.y - wval[wavi]));
+        colors[p.index] = lx.hsb(
+          (lx.getBaseHuef() + .2*abs(p.x - model.cx) + .2*abs(p.y - model.cy)) % 360,
+          100,
+          constrain(wavb + max(0, maxBright - 40.*abs(sparklePos - abs(i - (Cube.POINTS_PER_STRIP-1)/2.))), 0, 100)
+        );
+        ++i;
+      }
+    }
+        
+    if (!newLayers.isEmpty()) {
+      synchronized(newLayers) {
+        while (!newLayers.isEmpty()) {
+          addLayer(newLayers.pop());
+        }
+      }
+    }
+  }
+}
+
+class Pulley extends SCPattern {
+  
+  final int NUM_DIVISIONS = 16;
+  private final Accelerator[] gravity = new Accelerator[NUM_DIVISIONS];
+  private final Click[] delays = new Click[NUM_DIVISIONS];
+  
+  private final Click reset = new Click(9000);
+  private boolean isRising = false;
+  
+  private BasicParameter sz = new BasicParameter("SIZE", 0.5);
+  private BasicParameter beatAmount = new BasicParameter("BEAT", 0);
+  
+  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));
+    }
+    addModulator(reset).start();
+    addParameter(sz);
+    addParameter(beatAmount);
+    trigger();
+
+  }
+  
+  private void trigger() {
+    isRising = !isRising;
+    int i = 0;
+    for (Accelerator g : gravity) {
+      if (isRising) {
+        g.setSpeed(random(20, 33), 0).start();
+      } else {
+        g.setVelocity(0).setAcceleration(-420);
+        delays[i].setDuration(random(0, 500)).trigger();
+      }
+      ++i;
+    }
+  }
+  
+  public void run(double deltaMs) {
+    if (reset.click()) {
+      trigger();
+    }
+        
+    if (isRising) {
+      // Fucking A, had to comment this all out because of that bizarre
+      // Processing bug where some simple loop takes an absurd amount of
+      // time, must be some pre-processor bug
+//      for (Accelerator g : gravity) {
+//        if (g.getValuef() > model.yMax) {
+//          g.stop();
+//        } else if (g.getValuef() > model.yMax*.55) {
+//          if (g.getVelocityf() > 10) {
+//            g.setAcceleration(-16);
+//          } else {
+//            g.setAcceleration(0);
+//          }
+//        }
+//      }
+    } else {
+      int j = 0;
+      for (Click d : delays) {
+        if (d.click()) {
+          gravity[j].start();
+          d.stop();
+        }
+        ++j;
+      }
+      for (Accelerator g : gravity) {
+        if (g.getValuef() < 0) {
+          g.setValue(-g.getValuef());
+          g.setVelocity(-g.getVelocityf() * random(0.74, 0.84));
+        }
+      }
+    }
+
+    // A little silliness to test the grid API    
+    if (midiEngine != null && midiEngine.getFocusedPattern() == this) {
+           for (int i = 0; i < 5; ++i) {
+        for (int j = 0; j < 8; ++j) {
+          int gi = (int) constrain(j * NUM_DIVISIONS / 8, 0, NUM_DIVISIONS-1);
+          float b = 1 - 4.*abs((6-i)/6. - gravity[gi].getValuef() / model.yMax);
+          midiEngine.grid.setState(i, j, (b < 0) ? 0 : 3);
+        }
+      }
+    }
+    
+    float fPos = 1 - lx.tempo.rampf();
+    if (fPos < .2) {
+      fPos = .2 + 4 * (.2 - fPos);
+    }
+    float falloff = 100. / (3 + sz.getValuef() * 36 + fPos * beatAmount.getValuef()*48);
+    for (LXPoint p : model.points) {
+      int gi = (int) constrain((p.x - model.xMin) * NUM_DIVISIONS / (model.xMax - model.xMin), 0, NUM_DIVISIONS-1);
+      colors[p.index] = lx.hsb(
+        (lx.getBaseHuef() + abs(p.x - model.cx)*.8 + p.y*.4) % 360,
+        constrain(130 - p.y*.8, 0, 100),
+        max(0, 100 - abs(p.y - gravity[gi].getValuef())*falloff)
+      );
+    }
+  }
+}
+
+class ViolinWave extends SCPattern {
+  
+  BasicParameter level = new BasicParameter("LVL", 0.45);
+  BasicParameter range = new BasicParameter("RNG", 0.5);
+  BasicParameter edge = new BasicParameter("EDG", 0.5);
+  BasicParameter release = new BasicParameter("RLS", 0.5);
+  BasicParameter speed = new BasicParameter("SPD", 0.5);
+  BasicParameter amp = new BasicParameter("AMP", 0.25);
+  BasicParameter period = new BasicParameter("WAVE", 0.5);
+  BasicParameter pSize = new BasicParameter("PSIZE", 0.5);
+  BasicParameter pSpeed = new BasicParameter("PSPD", 0.5);
+  BasicParameter pDensity = new BasicParameter("PDENS", 0.25);
+  
+  LinearEnvelope dbValue = new LinearEnvelope(0, 0, 10);
+
+  ViolinWave(LX lx) {
+    super(lx);
+    addParameter(level);
+    addParameter(edge);
+    addParameter(range);
+    addParameter(release);
+    addParameter(speed);
+    addParameter(amp);
+    addParameter(period);
+    addParameter(pSize);
+    addParameter(pSpeed);
+    addParameter(pDensity);
+
+    addModulator(dbValue);
+  }
+  
+  final List<Particle> particles = new ArrayList<Particle>();
+  
+  class Particle {
+    
+    LinearEnvelope x = new LinearEnvelope(0, 0, 0);
+    LinearEnvelope y = new LinearEnvelope(0, 0, 0);
+    
+    Particle() {
+      addModulator(x);
+      addModulator(y);
+    }
+    
+    Particle trigger(boolean direction) {
+      float xInit = random(model.xMin, model.xMax);
+      float time = 3000 - 2500*pSpeed.getValuef();
+      x.setRange(xInit, xInit + random(-40, 40), time).trigger();
+      y.setRange(model.cy + 10, direction ? model.yMax + 50 : model.yMin - 50, time).trigger();
+      return this;
+    }
+    
+    boolean isActive() {
+      return x.isRunning() || y.isRunning();
+    }
+    
+    public void run(double deltaMs) {
+      if (!isActive()) {
+        return;
+      }
+      
+      float pFalloff = (30 - 27*pSize.getValuef());
+      for (LXPoint p : model.points) {
+        float b = 100 - pFalloff * (abs(p.x - x.getValuef()) + abs(p.y - y.getValuef()));
+        if (b > 0) {
+          colors[p.index] = blendColor(colors[p.index], lx.hsb(
+            lx.getBaseHuef(), 20, b
+          ), ADD);
+        }
+      }
+    }
+  }
+  
+  float[] centers = new float[30];
+  double accum = 0;
+  boolean rising = true;
+  
+  void fireParticle(boolean direction) {
+    boolean gotOne = false;
+    for (Particle p : particles) {
+      if (!p.isActive()) {
+       p.trigger(direction);
+       return;
+      }
+    }
+    particles.add(new Particle().trigger(direction));
+  }
+  
+  public void run(double deltaMs) {
+    accum += deltaMs / (1000. - 900.*speed.getValuef());
+    for (int i = 0; i < centers.length; ++i) {
+      centers[i] = model.cy + 30*amp.getValuef()*sin((float) (accum + (i-centers.length/2.)/(1. + 9.*period.getValuef())));
+    }
+    
+    float zeroDBReference = pow(10, (50 - 190*level.getValuef())/20.);
+    float dB = 20*GraphicEQ.log10(lx.audioInput().mix.level() / zeroDBReference);
+    if (dB > dbValue.getValuef()) {
+      rising = true;
+      dbValue.setRangeFromHereTo(dB, 10).trigger();
+    } else {
+      if (rising) {
+        for (int j = 0; j < pDensity.getValuef()*3; ++j) {
+          fireParticle(true);
+          fireParticle(false);
+        }
+      }
+      rising = false;
+      dbValue.setRangeFromHereTo(max(dB, -96), 50 + 1000*release.getValuef()).trigger();
+    }
+    float edg = 1 + edge.getValuef() * 40;
+    float rng = (78 - 64 * range.getValuef()) / (model.yMax - model.cy);
+    float val = max(2, dbValue.getValuef());
+    
+    for (LXPoint p : model.points) {
+      int ci = (int) lerp(0, centers.length-1, (p.x - model.xMin) / (model.xMax - model.xMin));
+      float rFactor = 1.0 -  0.9 * abs(p.x - model.cx) / (model.xMax - model.cx);
+      colors[p.index] = lx.hsb(
+        (lx.getBaseHuef() + abs(p.x - model.cx)) % 360,
+        min(100, 20 + 8*abs(p.y - centers[ci])),
+        constrain(edg*(val*rFactor - rng * abs(p.y-centers[ci])), 0, 100)
+      );
+    }
+    
+    for (Particle p : particles) {
+      p.run(deltaMs);
+    }
+  }
+}
+
+class BouncyBalls extends SCPattern {
+  
+  static final int NUM_BALLS = 6;
+  
+  class BouncyBall {
+       
+    Accelerator yPos;
+    TriangleLFO xPos = new TriangleLFO(0, model.xMax, random(8000, 19000));
+    float zPos;
+    
+    BouncyBall(int i) {
+      addModulator(xPos.setBasis(random(0, TWO_PI)).start());
+      addModulator(yPos = new Accelerator(0, 0, 0));
+      zPos = lerp(model.zMin, model.zMax, (i+2.) / (NUM_BALLS + 4.));
+    }
+    
+    void bounce(float midiVel) {
+      float v = 100 + 8*midiVel;
+      yPos.setSpeed(v, getAccel(v, 60 / lx.tempo.bpmf())).start();
+    }
+    
+    float getAccel(float v, float oneBeat) {
+      return -2*v / oneBeat;
+    }
+    
+    void run(double deltaMs) {
+      float flrLevel = flr.getValuef() * model.xMax/2.;
+      if (yPos.getValuef() < flrLevel) {
+        if (yPos.getVelocity() < -50) {
+          yPos.setValue(2*flrLevel-yPos.getValuef());
+          float v = -yPos.getVelocityf() * bounce.getValuef();
+          yPos.setSpeed(v, getAccel(v, 60 / lx.tempo.bpmf()));
+        } else {
+          yPos.setValue(flrLevel).stop();
+        }
+      }
+      float falloff = 130.f / (12 + blobSize.getValuef() * 36);
+      float xv = xPos.getValuef();
+      float yv = yPos.getValuef();
+      
+      for (LXPoint p : model.points) {
+        float d = sqrt((p.x-xv)*(p.x-xv) + (p.y-yv)*(p.y-yv) + .1*(p.z-zPos)*(p.z-zPos));
+        float b = constrain(130 - falloff*d, 0, 100);
+        if (b > 0) {
+          colors[p.index] = blendColor(colors[p.index], lx.hsb(
+            (lx.getBaseHuef() + p.y*.5 + abs(model.cx - p.x) * .5) % 360,
+            max(0, 100 - .45*(p.y - flrLevel)),
+            b
+          ), ADD);
+        }
+      }
+    }
+  }
+  
+  final BouncyBall[] balls = new BouncyBall[NUM_BALLS];
+  
+  final BasicParameter bounce = new BasicParameter("BNC", .8);
+  final BasicParameter flr = new BasicParameter("FLR", 0);
+  final BasicParameter blobSize = new BasicParameter("SIZE", 0.5);
+  
+  BouncyBalls(LX lx) {
+    super(lx);
+    for (int i = 0; i < balls.length; ++i) {
+      balls[i] = new BouncyBall(i);
+    }
+    addParameter(bounce);
+    addParameter(flr);
+    addParameter(blobSize);
+  }
+  
+  public void run(double deltaMs) {
+    setColors(#000000);
+    for (BouncyBall b : balls) {
+      b.run(deltaMs);
+    }
+  }
+  
+  public boolean noteOn(Note note) {
+    int pitch = (note.getPitch() + note.getChannel()) % NUM_BALLS;
+    balls[pitch].bounce(note.getVelocity());
+    return true;
+  }
+}
+
 class SpaceTime extends SCPattern {
 
-  SinLFO pos = new SinLFO(0, 15, 3000);
+  SinLFO pos = new SinLFO(0, 1, 3000);
   SinLFO rate = new SinLFO(1000, 9000, 13000);
   SinLFO falloff = new SinLFO(10, 70, 5000);
   float angle = 0;
@@ -8,8 +600,10 @@ class SpaceTime extends SCPattern {
   BasicParameter rateParameter = new BasicParameter("RATE", 0.5);
   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();
     addModulator(falloff).trigger();    
@@ -26,7 +620,7 @@ class SpaceTime extends SCPattern {
     }
   }
 
-  void run(int deltaMs) {    
+  void run(double deltaMs) {    
     angle += deltaMs * 0.0007;
     float sVal1 = model.strips.size() * (0.5 + 0.5*sin(angle));
     float sVal2 = model.strips.size() * (0.5 + 0.5*cos(angle));
@@ -37,12 +631,12 @@ class SpaceTime extends SCPattern {
     int s = 0;
     for (Strip strip : model.strips) {
       int i = 0;
-      for (Point p : strip.points) {
-        colors[p.index] = color(
-        (lx.getBaseHuef() + 360 - p.fx*.2 + p.fy * .3) % 360, 
-        constrain(.4 * min(abs(s - sVal1), abs(s - sVal2)), 20, 100),
-        max(0, 100 - fVal*abs(i - pVal))
-          );
+      for (LXPoint p : strip.points) {
+        colors[p.index] = lx.hsb(
+          (lx.getBaseHuef() + 360 - p.x*.2 + p.y * .3) % 360, 
+          constrain(.4 * min(abs(s - sVal1), abs(s - sVal2)), 20, 100),
+          max(0, 100 - fVal*abs(i - pVal*(strip.metrics.numPoints - 1)))
+        );
         ++i;
       }
       ++s;
@@ -51,16 +645,17 @@ class SpaceTime extends SCPattern {
 }
 
 class Swarm extends SCPattern {
-
-  SawLFO offset = new SawLFO(0, 16, 1000);
+  
+  SawLFO offset = new SawLFO(0, 1, 1000);
   SinLFO rate = new SinLFO(350, 1200, 63000);
   SinLFO falloff = new SinLFO(15, 50, 17000);
-  SinLFO fX = new SinLFO(0, model.xMax, 19000);
-  SinLFO fY = new SinLFO(0, model.yMax, 11000);
-  SinLFO hOffX = new SinLFO(0, model.xMax, 13000);
+  SinLFO fX = new SinLFO(model.xMin, model.xMax, 19000);
+  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();
     addModulator(falloff).trigger();
@@ -81,30 +676,31 @@ class Swarm extends SCPattern {
     }
   }
 
-  void run(int deltaMs) {
+  void run(double deltaMs) {
     float s = 0;
     for (Strip strip : model.strips) {
       int i = 0;
-      for (Point p : strip.points) {
-        float fV = max(-1, 1 - dist(p.fx/2., p.fy, fX.getValuef()/2., fY.getValuef()) / 64.);
-        colors[p.index] = color(
-        (lx.getBaseHuef() + 0.3 * abs(p.fx - hOffX.getValuef())) % 360, 
+      for (LXPoint p : strip.points) {
+        float fV = max(-1, 1 - dist(p.x/2., p.y, fX.getValuef()/2., fY.getValuef()) / 64.);
+        colors[p.index] = lx.hsb(
+        (lx.getBaseHuef() + 0.3 * abs(p.x - hOffX.getValuef())) % 360, 
         constrain(80 + 40 * fV, 0, 100), 
-        constrain(100 - (30 - fV * falloff.getValuef()) * modDist(i + (s*63)%61, offset.getValuef(), 16), 0, 100)
+        constrain(100 - 
+          (30 - fV * falloff.getValuef()) * modDist(i + (s*63)%61, offset.getValuef() * strip.metrics.numPoints, strip.metrics.numPoints), 0, 100)
           );
         ++i;
-      }
+      } 
       ++s;
     }
   }
 }
 
-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);
   }
@@ -112,8 +708,8 @@ class SwipeTransition extends SCTransition {
   void computeBlend(int[] c1, int[] c2, double progress) {
     float bleedf = 10 + bleed.getValuef() * 200.;
     float xPos = (float) (-bleedf + progress * (model.xMax + bleedf));
-    for (Point p : model.points) {
-      float d = (p.fx - xPos) / bleedf;
+    for (LXPoint p : model.points) {
+      float d = (p.x - xPos) / bleedf;
       if (d < 0) {
         colors[p.index] = c2[p.index];
       } else if (d > 1) {
@@ -125,85 +721,117 @@ class SwipeTransition extends SCTransition {
   }
 }
 
-class CubeEQ extends SCPattern {
-
-  private FFT fft = null; 
-  private LinearEnvelope[] bandVals = null;
-  private int avgSize;
+class BassPod extends SCPattern {
 
-  private final BasicParameter thrsh = new BasicParameter("LVL", 0.35);
-  private final BasicParameter range = new BasicParameter("RANG", 0.45);
-  private final BasicParameter edge = new BasicParameter("EDGE", 0.5);
-  private final BasicParameter speed = new BasicParameter("SPD", 0.5);
-  private final BasicParameter tone = new BasicParameter("TONE", 0.5);
+  private GraphicEQ eq = null;
+  
   private final BasicParameter clr = new BasicParameter("CLR", 0.5);
-
-  public CubeEQ(GLucose glucose) {
-    super(glucose);
-    addParameter(thrsh);
-    addParameter(range);
-    addParameter(edge);
-    addParameter(speed);
-    addParameter(tone);
+  
+  public BassPod(LX lx) {
+    super(lx);
     addParameter(clr);
   }
+  
+  void onActive() {
+    if (eq == null) {
+      eq = new GraphicEQ(lx, 16);
+      eq.range.setValue(0.4);
+      eq.level.setValue(0.4);
+      eq.slope.setValue(0.6);
+      addParameter(eq.level);
+      addParameter(eq.range);
+      addParameter(eq.attack);
+      addParameter(eq.release);
+      addParameter(eq.slope);
+    }
+  }
 
-  protected void onActive() {
-    if (this.fft == null) {
-      this.fft = new FFT(lx.audioInput().bufferSize(), lx.audioInput().sampleRate());
-      this.fft.window(FFT.HAMMING);
-      this.fft.logAverages(40, 1);
-      this.avgSize = this.fft.avgSize();
-      this.bandVals = new LinearEnvelope[this.avgSize];
-      for (int i = 0; i < this.bandVals.length; ++i) {
-        this.addModulator(this.bandVals[i] = (new LinearEnvelope(0, 0, 700+i*4))).trigger();
+  public void run(double deltaMs) {
+    eq.run(deltaMs);
+    
+    float bassLevel = eq.getAverageLevel(0, 5);
+    
+    float satBase = bassLevel*480*clr.getValuef();
+    
+    for (LXPoint p : model.points) {
+      int avgIndex = (int) constrain(1 + abs(p.x-model.cx)/(model.cx)*(eq.numBands-5), 0, eq.numBands-5);
+      float value = 0;
+      for (int i = avgIndex; i < avgIndex + 5; ++i) {
+        value += eq.getLevel(i);
       }
+      value /= 5.;
+
+      float b = constrain(8 * (value*model.yMax - abs(p.y-model.yMax/2.)), 0, 100);
+      colors[p.index] = lx.hsb(
+        (lx.getBaseHuef() + abs(p.y - model.cy) + abs(p.x - model.cx)) % 360,
+        constrain(satBase - .6*dist(p.x, p.y, model.cx, model.cy), 0, 100),
+        b
+      );
     }
   }
+}
 
-  public void run(int deltaMs) {
-    this.fft.forward(this.lx.audioInput().mix);
-    float toneConst = .35 + .4 * (tone.getValuef() - 0.5);
-    float edgeConst = 2 + 30*(edge.getValuef()*edge.getValuef()*edge.getValuef());
 
-    for (int i = 0; i < avgSize; ++i) {
-      float value = this.fft.getAvg(i);
-      value = 20*log(1 + sqrt(value));
-      float sqdist = avgSize - i;
-      value -= toneConst*sqdist*sqdist + .5*sqdist;
-      value *= 6;
-      if (value > this.bandVals[i].getValue()) {
-        this.bandVals[i].setEndVal(value, 40).trigger();
-      } 
-      else {
-        this.bandVals[i].setEndVal(value, 1000 - 900*speed.getValuef()).trigger();
-      }
+class CubeEQ extends SCPattern {
+
+  private GraphicEQ eq = null;
+
+  private final BasicParameter edge = new BasicParameter("EDGE", 0.5);
+  private final BasicParameter clr = new BasicParameter("CLR", 0.5);
+  private final BasicParameter blockiness = new BasicParameter("BLK", 0.5);
+
+  public CubeEQ(LX lx) {
+    super(lx);
+  }
+
+  void onActive() {
+    if (eq == null) {
+      eq = new GraphicEQ(lx, 16);
+      addParameter(eq.level);
+      addParameter(eq.range);
+      addParameter(eq.attack);
+      addParameter(eq.release);
+      addParameter(eq.slope);
+      addParameter(edge);
+      addParameter(clr);
+      addParameter(blockiness);
     }
+  }
 
-    float jBase = 120 - 360*thrsh.getValuef();
-    float jConst = 300.*(1-range.getValuef());
+  public void run(double deltaMs) {
+    eq.run(deltaMs);
+
+    float edgeConst = 2 + 30*edge.getValuef();
     float clrConst = 1.1 + clr.getValuef();
 
-    for (Point p : model.points) {
-      float avgIndex = constrain((p.fx / model.xMax * avgSize), 0, avgSize-2);
+    for (LXPoint p : model.points) {
+      float avgIndex = constrain(2 + p.x / model.xMax * (eq.numBands-4), 0, eq.numBands-4);
       int avgFloor = (int) avgIndex;
-      float j = jBase + jConst * (p.fy / model.yMax);
-      float value = lerp(
-      this.bandVals[avgFloor].getValuef(), 
-      this.bandVals[avgFloor+1].getValuef(), 
-      avgIndex-avgFloor
-        );
 
-      float b = constrain(edgeConst * (value - j), 0, 100);
-      colors[p.index] = color(
-      (480 + lx.getBaseHuef() - min(clrConst*p.fy, 120)) % 360, 
-      100, 
-      b);
+      float leftVal = eq.getLevel(avgFloor);
+      float rightVal = eq.getLevel(avgFloor+1);
+      float smoothValue = lerp(leftVal, rightVal, avgIndex-avgFloor);
+      
+      float chunkyValue = (
+        eq.getLevel(avgFloor/4*4) +
+        eq.getLevel(avgFloor/4*4 + 1) +
+        eq.getLevel(avgFloor/4*4 + 2) +
+        eq.getLevel(avgFloor/4*4 + 3)
+      ) / 4.; 
+      
+      float value = lerp(smoothValue, chunkyValue, blockiness.getValuef());
+
+      float b = constrain(edgeConst * (value*model.yMax - p.y), 0, 100);
+      colors[p.index] = lx.hsb(
+        (480 + lx.getBaseHuef() - min(clrConst*p.y, 120)) % 360, 
+        100, 
+        b
+      );
     }
   }
 }
 
-class BoomEffect extends SCEffect {
+class BoomEffect extends LXEffect {
 
   final BasicParameter falloff = new BasicParameter("WIDTH", 0.5);
   final BasicParameter speed = new BasicParameter("SPD", 0.5);
@@ -226,22 +854,22 @@ class BoomEffect extends SCEffect {
       boom.trigger();
     }
 
-    void doApply(int[] colors) {
+    void apply(int[] colors) {
       float brightv = 100 * bright.getValuef();
       float falloffv = falloffv();
       float satv = sat.getValuef() * 100;
       float huev = lx.getBaseHuef();
-      for (Point p : model.points) {
+      for (LXPoint p : model.points) {
         colors[p.index] = blendColor(
         colors[p.index], 
-        color(huev, satv, constrain(brightv - falloffv*abs(boom.getValuef() - dist(p.fx, 2*p.fy, 3*p.fz, model.xMax/2, model.yMax, model.zMax*1.5)), 0, 100)), 
+        lx.hsb(huev, satv, constrain(brightv - falloffv*abs(boom.getValuef() - dist(p.x, 2*p.y, 3*p.z, model.xMax/2, model.yMax, model.zMax*1.5)), 0, 100)), 
         ADD);
       }
     }
   }
 
-  BoomEffect(GLucose glucose) {
-    super(glucose, true);
+  BoomEffect(LX lx) {
+    super(lx, true);
     addParameter(falloff);
     addParameter(speed);
     addParameter(bright);
@@ -266,10 +894,10 @@ class BoomEffect extends SCEffect {
     onEnable();
   }
 
-  public void doApply(int[] colors) {
+  public void apply(int[] colors) {
     for (Layer l : layers) {
       if (l.boom.isRunning()) {
-        l.doApply(colors);
+        l.apply(colors);
       }
     }
   }
@@ -283,13 +911,9 @@ public class PianoKeyPattern extends SCPattern {
   final BasicParameter release = new BasicParameter("REL", 0.5);
   final BasicParameter level = new BasicParameter("AMB", 0.6);
   
-  PianoKeyPattern(GLucose glucose) {
-    super(glucose);
-    
-    for (MidiInputDevice input : RWMidi.getInputDevices()) {
-      input.createInput(this);
-    }
-    
+  PianoKeyPattern(LX lx) {
+    super(lx);
+        
     addParameter(attack);
     addParameter(release);
     addParameter(level);
@@ -319,22 +943,24 @@ public class PianoKeyPattern extends SCPattern {
     return base[index % base.length];
   }
     
-  public void noteOnReceived(Note note) {
+  public boolean noteOn(Note note) {
     LinearEnvelope env = getEnvelope(note.getPitch());
     env.setEndVal(min(1, env.getValuef() + (note.getVelocity() / 127.)), getAttackTime()).start();
+    return true;
   }
   
-  public void noteOffReceived(Note note) {
+  public boolean noteOff(Note note) {
     getEnvelope(note.getPitch()).setEndVal(0, getReleaseTime()).start();
+    return true;
   }
   
-  public void run(int deltaMs) {
+  public void run(double deltaMs) {
     int i = 0;
     float huef = lx.getBaseHuef();
     float levelf = level.getValuef();
     for (Cube c : model.cubes) {
       float v = max(getBase(i).getValuef() * levelf/4., getEnvelope(i++).getValuef());
-      setColor(c, color(
+      setColor(c, lx.hsb(
         (huef + 20*v + abs(c.cx-model.xMax/2.)*.3 + c.cy) % 360,
         min(100, 120*v),
         100*v
@@ -360,11 +986,15 @@ 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();
+    addParams();
+  }
+  
+  protected void addParams() {
     addParameter(xr);
     addParameter(yr);
     addParameter(zr);    
@@ -385,11 +1015,18 @@ class CrossSections extends SCPattern {
       z.setDuration(10000 - 9000*p.getValuef());
     }
   }
+  
+  float xv, yv, zv;
+  
+  protected void updateXYZVals() {
+    xv = x.getValuef();
+    yv = y.getValuef();
+    zv = z.getValuef();    
+  }
 
-  public void run(int deltaMs) {
-    float xv = x.getValuef();
-    float yv = y.getValuef();
-    float zv = z.getValuef();    
+  public void run(double deltaMs) {
+    updateXYZVals();
+    
     float xlv = 100*xl.getValuef();
     float ylv = 100*yl.getValuef();
     float zlv = 100*zl.getValuef();
@@ -398,22 +1035,22 @@ class CrossSections extends SCPattern {
     float ywv = 100. / (10 + 40*yw.getValuef());
     float zwv = 100. / (10 + 40*zw.getValuef());
     
-    for (Point p : model.points) {
+    for (LXPoint p : model.points) {
       color c = 0;
-      c = blendColor(c, color(
-      (lx.getBaseHuef() + p.fx/10 + p.fy/3) % 360, 
-      constrain(140 - 1.1*abs(p.fx - model.xMax/2.), 0, 100), 
-      max(0, xlv - xwv*abs(p.fx - xv))
+      c = blendColor(c, lx.hsb(
+      (lx.getBaseHuef() + p.x/10 + p.y/3) % 360, 
+      constrain(140 - 1.1*abs(p.x - model.xMax/2.), 0, 100), 
+      max(0, xlv - xwv*abs(p.x - xv))
         ), ADD);
-      c = blendColor(c, color(
-      (lx.getBaseHuef() + 80 + p.fy/10) % 360, 
-      constrain(140 - 2.2*abs(p.fy - model.yMax/2.), 0, 100), 
-      max(0, ylv - ywv*abs(p.fy - yv))
+      c = blendColor(c, lx.hsb(
+      (lx.getBaseHuef() + 80 + p.y/10) % 360, 
+      constrain(140 - 2.2*abs(p.y - model.yMax/2.), 0, 100), 
+      max(0, ylv - ywv*abs(p.y - yv))
         ), ADD); 
-      c = blendColor(c, color(
-      (lx.getBaseHuef() + 160 + p.fz / 10 + p.fy/2) % 360, 
-      constrain(140 - 2.2*abs(p.fz - model.zMax/2.), 0, 100), 
-      max(0, zlv - zwv*abs(p.fz - zv))
+      c = blendColor(c, lx.hsb(
+      (lx.getBaseHuef() + 160 + p.z / 10 + p.y/2) % 360, 
+      constrain(140 - 2.2*abs(p.z - model.zMax/2.), 0, 100), 
+      max(0, zlv - zwv*abs(p.z - zv))
         ), ADD); 
       colors[p.index] = c;
     }
@@ -427,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();
@@ -439,17 +1076,17 @@ class Blinders extends SCPattern {
     s.modulateDurationBy(r);
   }
 
-  public void run(int deltaMs) {
+  public void run(double deltaMs) {
     float hv = lx.getBaseHuef();
     int si = 0;
     for (Strip strip : model.strips) {
       int i = 0;
       float mv = m[si % m.length].getValuef();
-      for (Point p : strip.points) {
-        colors[p.index] = color(
-          (hv + p.fz + p.fy*hs.getValuef()) % 360, 
-          min(100, abs(p.fx - s.getValuef())/2.), 
-          max(0, 100 - mv/2. - mv * abs(i - 7.5))
+      for (LXPoint p : strip.points) {
+        colors[p.index] = lx.hsb(
+          (hv + p.z + p.y*hs.getValuef()) % 360, 
+          min(100, abs(p.x - s.getValuef())/2.), 
+          max(0, 100 - mv/2. - mv * abs(i - (strip.metrics.length-1)/2.))
         );
         ++i;
       }
@@ -466,25 +1103,25 @@ 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();
     addModulator(c).trigger();
   }
 
-  void run(int deltaMs) {
+  void run(double deltaMs) {
     float huev = h.getValuef();
     float cv = c.getValuef();
     float sv = s.getValuef();
     float mv = m.getValuef();
     int i = 0;
     for (Strip strip : model.strips) {
-      for (Point p : strip.points) {
-        colors[p.index] = color(
-          (huev + i*constrain(cv, 0, 2) + p.fz/2. + p.fx/4.) % 360, 
-          min(100, abs(p.fy-sv)), 
+      for (LXPoint p : strip.points) {
+        colors[p.index] = lx.hsb(
+          (huev + i*constrain(cv, 0, 2) + p.z/2. + p.x/4.) % 360, 
+          min(100, abs(p.y-sv)), 
           max(0, 100 - 50*abs((i%NUM) - mv))
         );
       }
@@ -499,10 +1136,10 @@ class AskewPlanes extends SCPattern {
     private final SinLFO a;
     private final SinLFO b;
     private final SinLFO c;
-    float av;
-    float bv;
-    float cv;
-    float denom;
+    float av = 1;
+    float bv = 1;
+    float cv = 1;
+    float denom = 0.1;
     
     Plane(int i) {
       addModulator(a = new SinLFO(-1, 1, 4000 + 1029*i)).trigger();
@@ -510,7 +1147,7 @@ class AskewPlanes extends SCPattern {
       addModulator(c = new SinLFO(-50, 50, 4000 + 1000*i * ((i % 2 == 0) ? 1 : -1))).trigger();      
     }
     
-    void run(int deltaMs) {
+    void run(double deltaMs) {
       av = a.getValuef();
       bv = b.getValuef();
       cv = c.getValuef();
@@ -521,32 +1158,40 @@ 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);
     }
   }
-
-  private final float denoms[] = new float[NUM_PLANES];
   
-  public void run(int deltaMs) {
+  public void run(double deltaMs) {
     float huev = lx.getBaseHuef();
-    int i = 0;
-    for (Plane p : planes) {
-      p.run(deltaMs);
-    }
-    for (Point p : model.points) {
-       float d = MAX_FLOAT;
-       for (Plane plane : planes) {
-         d = min(d, abs(plane.av*(p.fx-model.xMax/2.) + plane.bv*(p.fy-model.yMax/2.) + plane.cv) / plane.denom);
-       }
-       colors[p.index] = color(
-         (lx.getBaseHuef() + abs(p.fx-model.xMax/2.)*.3 + p.fy*.8) % 360,
-         max(0, 100 - .8*abs(p.fx - model.xMax/2.)),
-         constrain(140 - 10.*d, 0, 100)
-       );
+    
+    // This is super fucking bizarre. But if this is a for loop, the framerate
+    // tanks to like 30FPS, instead of 60. Call them manually and it works fine.
+    // Doesn't make ANY sense... there must be some weird side effect going on
+    // with the Processing internals perhaps?
+//    for (Plane plane : planes) {
+//      plane.run(deltaMs);
+//    }
+    planes[0].run(deltaMs);
+    planes[1].run(deltaMs);
+    planes[2].run(deltaMs);    
+    
+    for (LXPoint p : model.points) {
+      float d = MAX_FLOAT;
+      for (Plane plane : planes) {
+        if (plane.denom != 0) {
+          d = min(d, abs(plane.av*(p.x-model.cx) + plane.bv*(p.y-model.cy) + plane.cv) / plane.denom);
+        }
+      }
+      colors[p.index] = lx.hsb(
+        (huev + abs(p.x-model.cx)*.3 + p.y*.8) % 360,
+        max(0, 100 - .8*abs(p.x - model.cx)),
+        constrain(140 - 10.*d, 0, 100)
+      );
     }
   }
 }
@@ -558,25 +1203,25 @@ 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();
     addModulator(d).trigger();    
   }
   
-  public void run(int deltaMs) {
+  public void run(double deltaMs) {
     float hv = lx.getBaseHuef();
     float av = a.getValuef();
     float bv = b.getValuef();
     float cv = c.getValuef();
     float dv = d.getValuef();    
     float denom = sqrt(av*av + bv*bv + cv*cv);
-    for (Point p : model.points) {
-      float d = abs(av*(p.fx-model.xMax/2.) + bv*(p.fy-model.yMax/2.) + cv*(p.fz-model.zMax/2.) + dv) / denom;
-      colors[p.index] = color(
-        (hv + abs(p.fx-model.xMax/2.)*.6 + abs(p.fy-model.yMax/2)*.9 + abs(p.fz - model.zMax/2.)) % 360,
+    for (LXPoint p : model.points) {
+      float d = abs(av*(p.x-model.cx) + bv*(p.y-model.cy) + cv*(p.z-model.cz) + dv) / denom;
+      colors[p.index] = lx.hsb(
+        (hv + abs(p.x-model.cx)*.6 + abs(p.y-model.cy)*.9 + abs(p.z - model.cz)) % 360,
         constrain(110 - d*6, 0, 100),
         constrain(130 - 7*d, 0, 100)
       );
@@ -584,3 +1229,219 @@ class ShiftingPlane extends SCPattern {
   }
 }
 
+class Traktor extends SCPattern {
+
+  final int FRAME_WIDTH = 60;
+  
+  final BasicParameter speed = new BasicParameter("SPD", 0.5);
+  
+  private float[] bass = new float[FRAME_WIDTH];
+  private float[] treble = new float[FRAME_WIDTH];
+    
+  private int index = 0;
+  private GraphicEQ eq = null;
+
+  public Traktor(LX lx) {
+    super(lx);
+    for (int i = 0; i < FRAME_WIDTH; ++i) {
+      bass[i] = 0;
+      treble[i] = 0;
+    }
+    addParameter(speed);
+  }
+
+  public void onActive() {
+    if (eq == null) {
+      eq = new GraphicEQ(lx, 16);
+      eq.slope.setValue(0.6);
+      eq.level.setValue(0.65);
+      eq.range.setValue(0.35);
+      eq.release.setValue(0.4);
+      addParameter(eq.level);
+      addParameter(eq.range);
+      addParameter(eq.attack);
+      addParameter(eq.release);
+      addParameter(eq.slope);
+    }
+  }
+
+  int counter = 0;
+  
+  public void run(double deltaMs) {
+    eq.run(deltaMs);
+    
+    int stepThresh = (int) (40 - 39*speed.getValuef());
+    counter += deltaMs;
+    if (counter < stepThresh) {
+      return;
+    }
+    counter = counter % stepThresh;
+
+    index = (index + 1) % FRAME_WIDTH;
+    
+    float rawBass = eq.getAverageLevel(0, 4);
+    float rawTreble = eq.getAverageLevel(eq.numBands-7, 7);
+    
+    bass[index] = rawBass * rawBass * rawBass * rawBass;
+    treble[index] = rawTreble * rawTreble;
+
+    for (LXPoint p : model.points) {
+      int i = (int) constrain((model.xMax - p.x) / model.xMax * FRAME_WIDTH, 0, FRAME_WIDTH-1);
+      int pos = (index + FRAME_WIDTH - i) % FRAME_WIDTH;
+      
+      colors[p.index] = lx.hsb(
+        (360 + lx.getBaseHuef() + .8*abs(p.x-model.cx)) % 360,
+        100,
+        constrain(9 * (bass[pos]*model.cy - abs(p.y - model.cy + 5)), 0, 100)
+      );
+      colors[p.index] = blendColor(colors[p.index], lx.hsb(
+        (400 + lx.getBaseHuef() + .5*abs(p.x-model.cx)) % 360,
+        60,
+        constrain(5 * (treble[pos]*.6*model.cy - abs(p.y - model.cy)), 0, 100)
+
+      ), ADD);
+    }
+  }
+}
+
+class ColorFuckerEffect extends LXEffect {
+  
+  final BasicParameter level = new BasicParameter("BRT", 1);
+  final BasicParameter desat = new BasicParameter("DSAT", 0);
+  final BasicParameter hueShift = new BasicParameter("HSHFT", 0);
+  final BasicParameter sharp = new BasicParameter("SHARP", 0);
+  final BasicParameter soft = new BasicParameter("SOFT", 0);
+  final BasicParameter mono = new BasicParameter("MONO", 0);
+  final BasicParameter invert = new BasicParameter("INVERT", 0);
+
+  
+  float[] hsb = new float[3];
+  
+  ColorFuckerEffect(LX lx) {
+    super(lx);
+    addParameter(level);
+    addParameter(desat);
+    addParameter(sharp);
+    addParameter(hueShift);
+    addParameter(soft);
+    addParameter(mono);
+    addParameter(invert);
+  }
+  
+  public void apply(int[] colors) {
+    if (!isEnabled()) {
+      return;
+    }
+    float bMod = level.getValuef();
+    float sMod = 1 - desat.getValuef();
+    float hMod = hueShift.getValuef();
+    float fSharp = sharp.getValuef();
+    float fSoft = soft.getValuef();
+    boolean mon = mono.getValuef() > 0.5;
+    boolean ivt = invert.getValuef() > 0.5;
+    if (bMod < 1 || sMod < 1 || hMod > 0 || fSharp > 0 || ivt || mon || fSoft > 0) {
+      for (int i = 0; i < colors.length; ++i) {
+        lx.RGBtoHSB(colors[i], hsb);
+        if (mon) {
+          hsb[0] = lx.getBaseHuef() / 360.;
+        }
+        if (ivt) {
+          hsb[2] = 1 - hsb[2];
+        }
+        if (fSharp > 0) {
+          fSharp = 1/(1-fSharp);
+          if (hsb[2] < .5) {
+            hsb[2] = pow(hsb[2],fSharp);
+          } else {
+            hsb[2] = 1-pow(1-hsb[2],fSharp);
+          }
+        }
+        if (fSoft > 0) {
+          if (hsb[2] > 0.5) {
+            hsb[2] = lerp(hsb[2], 0.5 + 2 * (hsb[2]-0.5)*(hsb[2]-0.5), fSoft);
+          } else {
+            hsb[2] = lerp(hsb[2], 0.5 * sqrt(2*hsb[2]), fSoft);
+          }
+        }
+        colors[i] = lx.hsb(
+          (360. * hsb[0] + hMod*360.) % 360,
+          100. * hsb[1] * sMod,
+          100. * hsb[2] * bMod
+        );
+      }
+    }
+  }
+}
+
+class QuantizeEffect extends LXEffect {
+  
+  color[] quantizedFrame;
+  float lastQuant;
+  final BasicParameter amount = new BasicParameter("AMT", 0);
+  
+  QuantizeEffect(LX lx) {
+    super(lx);
+    quantizedFrame = new color[lx.total];
+    lastQuant = 0;
+  } 
+  
+  public void apply(int[] colors) {
+    float fQuant = amount.getValuef();
+    if (fQuant > 0) {
+      float tRamp = (lx.tempo.rampf() % (1./pow(2,floor((1-fQuant) * 4))));
+      float f = lastQuant;
+      lastQuant = tRamp;
+      if (tRamp > f) {
+        for (int i = 0; i < colors.length; ++i) {
+          colors[i] = quantizedFrame[i];
+        }
+        return;
+      }
+    }
+    for (int i = 0; i < colors.length; ++i) {
+      quantizedFrame[i] = colors[i];
+    }
+  }
+}
+
+class BlurEffect extends LXEffect {
+  
+  final BasicParameter amount = new BasicParameter("AMT", 0);
+  final int[] frame;
+  final LinearEnvelope env = new LinearEnvelope(0, 1, 100);
+  
+  BlurEffect(LX lx) {
+    super(lx);
+    addParameter(amount);
+    addModulator(env);
+    frame = new int[lx.total];
+    for (int i = 0; i < frame.length; ++i) {
+      frame[i] = #000000;
+    }
+  }
+  
+  public void onEnable() {
+    env.setRangeFromHereTo(1, 400).start();
+    for (int i = 0; i < frame.length; ++i) {
+      frame[i] = #000000;
+    }
+  }
+  
+  public void onDisable() {
+    env.setRangeFromHereTo(0, 1000).start();
+  }
+  
+  public void apply(int[] colors) {
+    float amt = env.getValuef() * amount.getValuef();
+    if (amt > 0) {    
+      amt = (1 - amt);
+      amt = 1 - (amt*amt*amt);
+      for (int i = 0; i < colors.length; ++i) {
+        // frame[i] = colors[i] = blendColor(colors[i], lerpColor(#000000, frame[i], amt, RGB), SCREEN);
+        frame[i] = colors[i] = lerpColor(colors[i], blendColor(colors[i], frame[i], SCREEN), amt, RGB);
+      }
+    }
+      
+  }  
+}
+