SugarCubes sketch initial code dump
authorMark Slee <mcslee@Mark-Slees-MacBook-Pro.local>
Thu, 23 May 2013 01:37:07 +0000 (18:37 -0700)
committerMark Slee <mcslee@Mark-Slees-MacBook-Pro.local>
Thu, 23 May 2013 01:37:07 +0000 (18:37 -0700)
MarkSlee.pde [new file with mode: 0644]
SugarCubes.pde [new file with mode: 0644]
TestPatterns.pde [new file with mode: 0644]
_Internals.pde [new file with mode: 0644]
_Overlay.pde [new file with mode: 0644]
code/GLucose.jar [new file with mode: 0644]
code/HeronLX.jar [new file with mode: 0644]
code/rwmidi.jar [new file with mode: 0644]
logo-sm.png [new file with mode: 0644]

diff --git a/MarkSlee.pde b/MarkSlee.pde
new file mode 100644 (file)
index 0000000..2df823a
--- /dev/null
@@ -0,0 +1,199 @@
+class SpaceTime extends SCPattern {
+
+  SinLFO pos = new SinLFO(0, 15, 3000);
+  SinLFO rate = new SinLFO(1000, 9000, 13000);
+  SinLFO falloff = new SinLFO(10, 70, 5000);
+  float sat = 0;
+  
+  BasicKnob rateKnob = new BasicKnob("RATE", 0.5);
+  BasicKnob sizeKnob = new BasicKnob("SIZE", 0.5);
+
+  public SpaceTime(GLucose glucose) {
+    super(glucose);
+    addModulator(pos).trigger();
+    addModulator(rate).trigger();
+    addModulator(falloff).trigger();    
+    pos.modulateDurationBy(rate);
+    addKnob(rateKnob);
+    addKnob(sizeKnob);
+  }
+  
+  public void onKnobChange(Knob knob) {
+    if (knob == rateKnob) {
+        rate.stop().setValue(9000 - 8000*knob.getValuef());
+    } else if (knob == sizeKnob) {
+        falloff.stop().setValue(70 - 60*knob.getValuef());
+    }
+  }
+
+  void run(int deltaMs) {    
+    sat += deltaMs * 0.00004;
+    float sVal1 = Strip.list.size() * (0.5 + 0.5*sin(sat));
+    float sVal2 = Strip.list.size() * (0.5 + 0.5*cos(sat));
+
+    float pVal = pos.getValuef();
+    float fVal = falloff.getValuef();
+
+    int s = 0;
+    for (Strip strip : Strip.list) {
+      int i = 0;
+      for (Point p : strip.points) {
+        colors[p.index] = color(
+        (lx.getBaseHuef() + s*.2 + i*3) % 360, 
+        min(100, min(abs(s - sVal1), abs(s - sVal2))), 
+        max(0, 100 - fVal*abs(i - pVal))
+          );
+        ++i;
+      }
+    }
+  }
+}
+
+class Swarm extends SCPattern {
+
+  SawLFO offset = new SawLFO(0, 16, 1000);
+  SinLFO rate = new SinLFO(350, 1200, 63000);
+  SinLFO falloff = new SinLFO(15, 50, 17000);
+  SinLFO fY = new SinLFO(0, 250, 19000);
+  SinLFO fZ = new SinLFO(0, 127, 11000);
+  SinLFO hOffY = new SinLFO(0, 255, 13000);
+
+  public Swarm(GLucose glucose) {
+    super(glucose);
+    addModulator(offset).trigger();
+    addModulator(rate).trigger();
+    addModulator(falloff).trigger();
+    addModulator(fY).trigger();
+    addModulator(fZ).trigger();
+    addModulator(hOffY).trigger();
+    offset.modulateDurationBy(rate);
+  }
+
+  float modDist(float v1, float v2, float mod) {
+    v1 = v1 % mod;
+    v2 = v2 % mod;
+    if (v2 > v1) {
+      return min(v2-v1, v1+mod-v2);
+    } 
+    else {
+      return min(v1-v2, v2+mod-v1);
+    }
+  }
+
+  void run(int deltaMs) {
+    float s = 0;
+    for (Strip strip : Strip.list) {
+      int i = 0;
+      for (Point p : strip.points) {
+        float fV = max(-1, 1 - dist(p.fy/2., p.fz, fY.getValuef()/2., fZ.getValuef()) / 64.);
+        colors[p.index] = color(
+        (lx.getBaseHuef() + 0.3 * abs(p.fy - hOffY.getValuef())) % 360, 
+        constrain(80 + 40 * fV, 0, 100), 
+        constrain(100 - (30 - fV * falloff.getValuef()) * modDist(i + (s*63)%61, offset.getValuef(), 16), 0, 100)
+          );
+        ++i;
+      }
+      ++s;
+    }
+  }
+}
+
+class SwipeTransition extends SCTransition {
+  SwipeTransition(GLucose glucose) {
+    super(glucose);
+    setDuration(5000);
+  }
+
+  void computeBlend(int[] c1, int[] c2, double progress) {
+    float bleed = 50.;
+    float yPos = (float) (-bleed + progress * (255. + bleed));
+    for (Point p : Point.list) {
+      float d = (p.fy - yPos) / 50.;
+      if (d < 0) {
+        colors[p.index] = c2[p.index];
+      } else if (d > 1) {
+        colors[p.index] = c1[p.index];
+      } else {
+        colors[p.index] = lerpColor(c2[p.index], c1[p.index], d, RGB);
+      }
+    }
+  }
+}
+
+class CubeEQ extends SCPattern {
+
+  private FFT fft = null; 
+  private LinearEnvelope[] bandVals = null;
+  private int avgSize;
+
+  private final BasicKnob thrsh = new BasicKnob("LVL", 0.35);
+  private final BasicKnob range = new BasicKnob("RANG", 0.45);
+  private final BasicKnob edge = new BasicKnob("EDGE", 0.5);
+  private final BasicKnob speed = new BasicKnob("SPD", 0.5);
+  private final BasicKnob tone = new BasicKnob("TONE", 0.5);
+  private final BasicKnob clr = new BasicKnob("CLR", 0.5);
+
+  public CubeEQ(GLucose glucose) {
+    super(glucose);
+    addKnob(thrsh);
+    addKnob(range);
+    addKnob(edge);
+    addKnob(speed);
+    addKnob(tone);
+    addKnob(clr);
+  }
+  
+  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(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();
+      }
+    }
+    
+    float jBase = 120 - 360*thrsh.getValuef();
+    float jConst = 300.*(1-range.getValuef());
+    float clrConst = 1.1 + clr.getValuef();
+
+    for (Point p : Point.list) {
+      float avgIndex = constrain((p.fy / 256. * avgSize), 0, avgSize-2);
+      int avgFloor = (int) avgIndex;
+      float j = jBase + jConst * (p.fz / 128.);
+      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.fz, 120)) % 360, 
+        100, 
+        b);
+    }
+  }
+}
+
diff --git a/SugarCubes.pde b/SugarCubes.pde
new file mode 100644 (file)
index 0000000..8428367
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ *           +-+-+-+-+-+               +-+-+-+-+-+
+ *          /         /|               |\         \
+ *         /         / +               + \         \
+ *        +-+-+-+-+-+  |   +-+-+-+-+   |  +-+-+-+-+-+
+ *        |         |  +  /         \  +  |         |
+ *        +   THE   + /  /           \  \ +  CUBES  +
+ *        |         |/  +-+-+-+-+-+-+-+  \|         |
+ *        +-+-+-+-+-+   |             |   +-+-+-+-+-+
+ *                      +             +
+ *                      |    SUGAR    |
+ *                      +             +
+ *                      |             |
+ *                      +-+-+-+-+-+-+-+
+ *
+ * Welcome to the Sugar Cubes! This Processing sketch is a fun place to build
+ * animations, effects, and interactions for the platform. Most of the icky
+ * code guts are embedded in the GLucose library extension. If you're an
+ * artist, you shouldn't need to worry about any of that.
+ *
+ * Below, you will find definitions of the Patterns, Effects, and Interactions.
+ * If you're an artist, create a new tab in the Processing environment with
+ * your name. Implement your classes there, and add them to the list below.
+ */ 
+
+LXPattern[] patterns(GLucose glucose) {
+  return new LXPattern[] {
+    new SpaceTime(glucose),
+    new Swarm(glucose),
+    new CubeEQ(glucose),
+    
+    // Basic test patterns for reference, not art
+//    new TestHuePattern(glucose),
+//    new TestXPattern(glucose),
+//    new TestYPattern(glucose),
+//    new TestZPattern(glucose),
+  };
+}
+
+LXTransition[] transitions(GLucose glucose) {
+  return new LXTransition[] {
+    new DissolveTransition(lx).setDuration(3000),
+    new SwipeTransition(glucose),
+    new FadeTransition(lx).setDuration(2000),
+  };
+}
+
+LXEffect[] effects(GLucose glucose) {
+  return new LXEffect[] {
+    new FlashEffect(lx),
+    new DesaturationEffect(lx),
+  };
+}
+
diff --git a/TestPatterns.pde b/TestPatterns.pde
new file mode 100644 (file)
index 0000000..f54e888
--- /dev/null
@@ -0,0 +1,61 @@
+class TestHuePattern extends SCPattern {
+  public TestHuePattern(GLucose glucose) {
+    super(glucose);
+  }
+  public void run(int deltaMs) {
+    for (int i = 0; i < colors.length; ++i) {
+      colors[i] = color(lx.getBaseHuef(), 100, 100);
+    }
+  } 
+}
+
+class TestXPattern extends SCPattern {
+  private SinLFO xPos = new SinLFO(0, 127, 4000);
+  public TestXPattern(GLucose glucose) {
+    super(glucose);
+    addModulator(xPos).trigger();
+  }
+  public void run(int deltaMs) {
+    for (Point p : Point.list) {
+      colors[p.index] = color(
+        lx.getBaseHuef(),
+        100,
+        max(0, 100 - abs(p.fx - xPos.getValuef()))
+      );      
+    }
+  }
+}
+
+class TestYPattern extends SCPattern {
+  private SinLFO yPos = new SinLFO(0, 255, 4000);
+  public TestYPattern(GLucose glucose) {
+    super(glucose);
+    addModulator(yPos).trigger();
+  }
+  public void run(int deltaMs) {
+    for (Point p : Point.list) {
+      colors[p.index] = color(
+        lx.getBaseHuef(),
+        100,
+        max(0, 100 - abs(p.fy - yPos.getValuef()))
+      );      
+    }
+  }
+}
+
+class TestZPattern extends SCPattern {
+  private SinLFO zPos = new SinLFO(0, 127, 4000);
+  public TestZPattern(GLucose glucose) {
+    super(glucose);
+    addModulator(zPos).trigger();
+  }
+  public void run(int deltaMs) {
+    for (Point p : Point.list) {
+      colors[p.index] = color(
+        lx.getBaseHuef(),
+        100,
+        max(0, 100 - abs(p.fz - zPos.getValuef()))
+      );      
+    }
+  }
+}
diff --git a/_Internals.pde b/_Internals.pde
new file mode 100644 (file)
index 0000000..d040f11
--- /dev/null
@@ -0,0 +1,95 @@
+/**
+ * If you are an artist, you may ignore this file! It just sets
+ * up the framework to run the patterns. Should not need modification
+ * for general animation work.
+ */
+
+import glucose.*;
+import glucose.control.*;
+import glucose.pattern.*;
+import glucose.transition.*;
+import glucose.model.*;
+import heronarts.lx.*;
+import heronarts.lx.effect.*;
+import heronarts.lx.pattern.*;
+import heronarts.lx.modulator.*;
+import heronarts.lx.transition.*;
+import ddf.minim.*;
+import ddf.minim.analysis.*;
+import processing.opengl.*;
+import java.lang.reflect.*;
+
+final int VIEWPORT_WIDTH = 900;
+final int VIEWPORT_HEIGHT = 700;
+final int TARGET_FRAMERATE = 45;
+
+int startMillis, lastMillis;
+GLucose glucose;
+HeronLX lx;
+LXPattern[] patterns;
+LXTransition[] transitions;
+LXEffect[] effects;
+OverlayUI ui;
+int activeTransitionIndex = 0;
+
+void setup() {
+  startMillis = lastMillis = millis();
+
+  // Initialize the Processing graphics environment
+  size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
+  frameRate(TARGET_FRAMERATE);
+  // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
+  logTime("Created viewport");
+
+  // Create the GLucose engine to run the cubes
+  glucose = new GLucose(this);
+  lx = glucose.lx;
+  logTime("Built GLucose engine");
+  
+  // Set the patterns
+  glucose.lx.setPatterns(patterns = patterns(glucose));
+  logTime("Built patterns");
+  glucose.lx.addEffects(effects = effects(glucose));
+  logTime("Built effects");
+  transitions = transitions(glucose);
+  logTime("Built transitions");
+  
+  // Build overlay UI
+  ui = new OverlayUI();
+  logTime("Built overlay UI");
+  
+  // MIDI devices
+  MidiKnobController.initializeStandardDevices(glucose);
+  logTime("Setup MIDI controllers");
+  
+  println("Total setup: " + (millis() - startMillis) + "ms");
+}
+
+void logTime(String evt) {
+  int now = millis();
+  println(evt + ": " + (now - lastMillis) + "ms");
+  lastMillis = now;
+}
+
+void draw() {
+  // The glucose engine deals with the core simulation here, we don't need
+  // to do anything specific. This method just needs to exist.
+}
+
+void drawUI() {
+  if (uiOn) {
+    ui.draw();
+  } else {
+    ui.drawHelpTip();
+  }
+  ui.drawFPS();
+}
+
+boolean uiOn = true;
+void keyPressed() {
+  if (key == 'u') {
+    uiOn = !uiOn;
+  }
+}
+
+
diff --git a/_Overlay.pde b/_Overlay.pde
new file mode 100644 (file)
index 0000000..e212e1e
--- /dev/null
@@ -0,0 +1,328 @@
+/**
+ * Overlay UI that indicates pattern control, etc. This will be moved
+ * into the Processing library once it is stabilized and need not be
+ * regularly modified.
+ */
+class OverlayUI {
+  
+  private final PFont titleFont = createFont("Myriad Pro", 10);
+  private final PFont itemFont = createFont("Myriad Pro", 11);
+  private final PFont knobFont = titleFont;
+  private final int w = 140;
+  private final int leftPos;
+  private final int leftTextPos;
+  private final int lineHeight = 20;
+  private final int sectionSpacing = 12;
+  private final int tempoHeight = 20;
+  private final int knobSize = 28;
+  private final int knobSpacing = 6;
+  private final int knobLabelHeight = 14;
+  private final color lightBlue = #666699;
+  private final color lightGreen = #669966;
+  
+  private final String[] patternNames;
+  private final String[] transitionNames;
+  private final String[] effectNames;
+
+  private PImage logo;
+  
+  private int firstPatternY;
+  private int firstTransitionY;
+  private int firstEffectY;
+  private int firstKnobY;
+  private int tempoY;
+  
+  private Method patternStateMethod;
+  private Method transitionStateMethod;
+  private Method effectStateMethod;
+  
+  OverlayUI() {
+    leftPos = width - w;
+    leftTextPos = leftPos + 4;
+    logo = loadImage("logo-sm.png");
+    
+    patternNames = classNameArray(patterns);
+    transitionNames = classNameArray(transitions);
+    effectNames = classNameArray(effects);    
+
+    try {
+      patternStateMethod = getClass().getMethod("getState", LXPattern.class);
+      effectStateMethod = getClass().getMethod("getState", LXEffect.class);
+      transitionStateMethod = getClass().getMethod("getState", LXTransition.class);
+    } catch (Exception x) {
+      throw new RuntimeException(x);
+    }    
+  }
+  
+  void drawHelpTip() {
+    textFont(itemFont);
+    textAlign(RIGHT);
+    text("Tap 'u' to restore UI", width-4, height-6);
+  }
+  
+  void draw() {    
+    image(logo, 4, 4);
+    
+    stroke(color(0, 0, 100));
+    // fill(color(0, 0, 50, 50)); // alpha is bad for perf
+    fill(color(0, 0, 30));
+    rect(leftPos-1, -1, w+2, height+2);
+    
+    int yPos = 0;    
+    firstPatternY = yPos + lineHeight + 6;
+    yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod);
+
+    yPos += sectionSpacing;
+    yPos = drawObjectList(yPos, "CONTROL", null, null, null);
+    yPos += 6;
+    firstKnobY = yPos;
+    int xPos = leftTextPos;
+    for (int i = 0; i < glucose.NUM_PATTERN_KNOBS/2; ++i) {
+      drawKnob(xPos, yPos, knobSize, glucose.patternKnobs[i]);
+      drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, glucose.patternKnobs[glucose.NUM_PATTERN_KNOBS/2 + i]);
+      xPos += knobSize + knobSpacing;
+    }
+    yPos += 2*(knobSize + knobLabelHeight) + knobSpacing;
+
+    yPos += sectionSpacing;
+    firstTransitionY = yPos + lineHeight + 6;
+    yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod);
+    
+    yPos += sectionSpacing;
+    firstEffectY = yPos + lineHeight + 6;
+    yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod);
+    
+    yPos += sectionSpacing;
+    yPos = drawObjectList(yPos, "TEMPO", null, null, null);
+    yPos += 6;
+    tempoY = yPos;
+    stroke(#111111);
+    fill(tempoDown ? lightGreen : color(0, 0, 35 - 8*lx.tempo.rampf()));
+    rect(leftPos + 4, yPos, w - 8, tempoHeight);
+    fill(0);
+    textAlign(CENTER);
+    text("" + ((int)(lx.tempo.bpmf() * 100) / 100.), leftPos + w/2., yPos + tempoHeight - 6);
+    yPos += tempoHeight;
+    
+    fill(#999999);
+    textFont(itemFont);
+    textAlign(LEFT);
+    text("Tap 'u' to hide UI (~+3FPS)", leftTextPos, height-6);
+  }
+  
+  public Knob getOrNull(List<Knob> items, int index) {
+    if (index < items.size()) {
+      return items.get(index);
+    }
+    return null;
+  }
+  
+  public void drawFPS() {
+    textFont(titleFont);
+    textAlign(LEFT);
+    fill(#666666);
+    text("FPS: " + (((int)(frameRate * 10)) / 10.), 4, height-6);     
+  }
+
+  private final int STATE_DEFAULT = 0;
+  private final int STATE_ACTIVE = 1;
+  private final int STATE_PENDING = 2;
+
+  public int getState(LXPattern p) {
+    if (p == lx.getPattern()) {
+      return STATE_ACTIVE;
+    } else if (p == lx.getNextPattern()) {
+      return STATE_PENDING;
+    }
+    return STATE_DEFAULT;
+  }
+  
+  public int getState(LXEffect e) {
+    return e.isEnabled() ? STATE_ACTIVE : STATE_DEFAULT;
+  }
+  
+  public int getState(LXTransition t) {
+    if (t == lx.getTransition()) {
+      return STATE_PENDING;
+    } else if (t == transitions[activeTransitionIndex]) {
+      return STATE_ACTIVE;
+    }
+    return STATE_DEFAULT;
+  }
+
+  protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) {
+    return drawObjectList(yPos, title, items, classNameArray(items), stateMethod);
+  }
+  
+  private int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) {
+    noStroke();
+    fill(#aaaaaa);
+    textFont(titleFont);
+    textAlign(LEFT);
+    text(title, leftTextPos, yPos += lineHeight);    
+    if (items != null) {
+      textFont(itemFont);
+      color textColor;      
+      boolean even = true;
+      for (int i = 0; i < items.length; ++i) {
+        Object o = items[i];
+        int state = STATE_DEFAULT;
+        try {
+           state = ((Integer) stateMethod.invoke(this, o)).intValue();
+        } catch (Exception x) {
+          throw new RuntimeException(x);
+        }
+        switch (state) {
+          case STATE_ACTIVE:
+            fill(lightGreen);
+            textColor = #eeeeee;
+            break;
+          case STATE_PENDING:
+            fill(lightBlue);
+            textColor = color(0, 0, 75 + 15*sin(millis()/200.));;
+            break;
+          default:
+            textColor = 0;
+            fill(even ? #666666 : #777777);
+            break;
+        }
+        rect(leftPos, yPos+6, width, lineHeight);
+        fill(textColor);
+        text(names[i], leftTextPos, yPos += lineHeight);
+        even = !even;       
+      }
+    }
+    return yPos;
+  }
+  
+  private void drawKnob(int xPos, int yPos, int knobSize, Knob knob) {
+    final float knobIndent = .4;
+    final float knobValue = knob.getValuef();
+    String knobLabel = knob.getLabel();
+    if (knobLabel.length() > 4) {
+      knobLabel = knobLabel.substring(0, 4);
+    } else if (knobLabel.length() == 0) {
+      knobLabel = "-";
+    }
+    
+    ellipseMode(CENTER);
+    fill(#222222);
+    arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent));
+
+    fill(lightGreen);
+    arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue);
+
+    fill(#333333);
+    ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2);
+    
+    fill(0);
+    rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2);
+    fill(#999999);
+    textAlign(CENTER);
+    textFont(knobFont);
+    text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2);
+
+  }
+  
+  private String[] classNameArray(Object[] objects) {
+    if (objects == null) {
+      return null;
+    }
+    String[] names = new String[objects.length];
+    for (int i = 0; i < objects.length; ++i) {
+      names[i] = className(objects[i]);
+    }
+    return names;
+  }
+  
+  private String className(Object p) {
+    String s = p.getClass().getName();
+    int li;
+    if ((li = s.lastIndexOf(".")) > 0) {
+      s = s.substring(li + 1);
+    }
+    if (s.indexOf("SugarCubes$") == 0) {
+      return s.substring("SugarCubes$".length());
+    }
+    return s;
+  }
+  
+  private int knobIndex = -1;
+  private int lastY;
+  private int releaseEffect = -1;
+  private boolean tempoDown = false;
+
+  public void mousePressed() {
+    lastY = mouseY;
+    knobIndex = -1;
+    releaseEffect = -1;
+    if (mouseY > tempoY) {
+      if (mouseY - tempoY < tempoHeight) {
+        lx.tempo.tap();
+        tempoDown = true;
+      }
+    } else if (mouseY > firstEffectY) {
+      int effectIndex = (mouseY - firstEffectY) / lineHeight;
+      if (effectIndex < effects.length) {
+        if (effects[effectIndex].isMomentary()) {
+          effects[effectIndex].enable();
+          releaseEffect = effectIndex;
+        } else {
+          effects[effectIndex].toggle();
+        }
+      }
+    } else if (mouseY > firstTransitionY) {
+      int transitionIndex = (mouseY - firstTransitionY) / lineHeight;
+      if (transitionIndex < transitions.length) {
+        activeTransitionIndex = transitionIndex;
+      }
+    } else if ((mouseY >= firstKnobY) && (mouseY < firstKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) {
+      knobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
+      if (mouseY >= firstKnobY + knobSize + knobLabelHeight + knobSpacing) {
+        knobIndex += glucose.NUM_PATTERN_KNOBS / 2;
+      }      
+    } else if (mouseY > firstPatternY) {
+      int patternIndex = (mouseY - firstPatternY) / lineHeight;
+      if (patternIndex < patterns.length) {
+        patterns[patternIndex].setTransition(transitions[activeTransitionIndex]);
+        lx.goIndex(patternIndex);
+      }
+    }
+  }
+  
+  public void mouseDragged() {
+    int dy = lastY - mouseY;
+    lastY = mouseY;
+    if (knobIndex >= 0 && knobIndex < glucose.NUM_PATTERN_KNOBS) {
+      Knob k = glucose.patternKnobs[knobIndex];
+      k.setValue(k.getValuef() + dy*.01);
+    }
+  }
+    
+  public void mouseReleased() {
+    tempoDown = false;
+    if (releaseEffect >= 0) {
+      effects[releaseEffect].trigger();
+      releaseEffect = -1;      
+    }
+  }
+}
+
+void mousePressed() {
+  if (mouseX > ui.leftPos) {
+    ui.mousePressed();
+  }
+}
+
+void mouseReleased() {
+  if (mouseX > ui.leftPos) {
+    ui.mouseReleased();
+  }
+}
+
+void mouseDragged() {
+  if (mouseX > ui.leftPos) {
+    ui.mouseDragged();
+  }
+}
+
diff --git a/code/GLucose.jar b/code/GLucose.jar
new file mode 100644 (file)
index 0000000..6ae9494
Binary files /dev/null and b/code/GLucose.jar differ
diff --git a/code/HeronLX.jar b/code/HeronLX.jar
new file mode 100644 (file)
index 0000000..2724cc6
Binary files /dev/null and b/code/HeronLX.jar differ
diff --git a/code/rwmidi.jar b/code/rwmidi.jar
new file mode 100644 (file)
index 0000000..4788ff6
Binary files /dev/null and b/code/rwmidi.jar differ
diff --git a/logo-sm.png b/logo-sm.png
new file mode 100644 (file)
index 0000000..3c7aa83
Binary files /dev/null and b/logo-sm.png differ