--- /dev/null
+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);
+ }
+ }
+}
+
--- /dev/null
+/**
+ * +-+-+-+-+-+ +-+-+-+-+-+
+ * / /| |\ \
+ * / / + + \ \
+ * +-+-+-+-+-+ | +-+-+-+-+ | +-+-+-+-+-+
+ * | | + / \ + | |
+ * + 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),
+ };
+}
+
--- /dev/null
+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()))
+ );
+ }
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
+
+
--- /dev/null
+/**
+ * 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();
+ }
+}
+