Merge branch 'panda-refactor' of https://github.com/sugarcubes/SugarCubes
authorMark Slee <mcslee@Mark-Slees-MacBook-Pro.local>
Sat, 15 Jun 2013 02:09:05 +0000 (19:09 -0700)
committerMark Slee <mcslee@Mark-Slees-MacBook-Pro.local>
Sat, 15 Jun 2013 02:09:05 +0000 (19:09 -0700)
Conflicts:
_Internals.pde
_Overlay.pde
code/GLucose.jar

1  2 
SugarCubes.pde
_Internals.pde
_Overlay.pde
code/GLucose.jar
code/HeronLX.jar

diff --combined SugarCubes.pde
index 5504001bf88d3834f2d82b3b40f12340c35b00cf,5504001bf88d3834f2d82b3b40f12340c35b00cf..3f6cc8f4e47494ad81954d9065c4fcc5c84985f8
@@@ -1,7 -1,7 +1,7 @@@
  /**
   *           +-+-+-+-+-+               +-+-+-+-+-+
   *          /         /|               |\         \
-- *         /         / +               + \         \
++ *         /         / +               <+ \         \
   *        +-+-+-+-+-+  |   +-+-+-+-+   |  +-+-+-+-+-+
   *        |         |  +  /         \  +  |         |
   *        +   THE   + /  /           \  \ +  CUBES  +
diff --combined _Internals.pde
index 46c39390dcf93ac1dfe29deaee243eba0c36efa5,4ab6f76d7d45be3bbc57266ad073f521466a610f..0249603b3f209e0fffc1013290aeca8a4368bcab
@@@ -29,7 -29,6 +29,6 @@@ import heronarts.lx.transition.*
  import ddf.minim.*;
  import ddf.minim.analysis.*;
  import processing.opengl.*;
- import java.lang.reflect.*;
  import rwmidi.*;
  
  final int VIEWPORT_WIDTH = 900;
@@@ -39,13 -38,19 +38,21 @@@ final int TARGET_FRAMERATE = 45
  int startMillis, lastMillis;
  GLucose glucose;
  HeronLX lx;
+ MappingTool mappingTool;
  LXPattern[] patterns;
  LXTransition[] transitions;
  LXEffect[] effects;
  OverlayUI ui;
+ ControlUI controlUI;
+ MappingUI mappingUI;
+ PandaDriver pandaFront;
+ PandaDriver pandaRear;
+ boolean mappingMode = false;
+ boolean pandaBoardsEnabled = false;
  
 +boolean debugMode = false;
 +
  void setup() {
    startMillis = lastMillis = millis();
  
@@@ -59,7 -64,6 +66,7 @@@
    // Create the GLucose engine to run the cubes
    glucose = new GLucose(this, new SCMapping());
    lx = glucose.lx;
 +  lx.enableKeyboardTempo();
    logTime("Built GLucose engine");
    
    // Set the patterns
    logTime("Built patterns");
    glucose.lx.addEffects(effects = effects(glucose));
    logTime("Built effects");
 -  transitions = transitions(glucose);
 +  glucose.setTransitions(transitions = transitions(glucose));
    logTime("Built transitions");
+     
+   // Build output driver
+   int[][] frontChannels = glucose.mapping.buildFrontChannelList();
+   int[][] rearChannels = glucose.mapping.buildRearChannelList();
+   int[][] flippedRGB = glucose.mapping.buildFlippedRGBList();
+   mappingTool = new MappingTool(glucose, frontChannels, rearChannels);
+   pandaFront = new PandaDriver(new NetAddress("192.168.1.28", 9001), glucose.model, frontChannels, flippedRGB);
+   pandaRear = new PandaDriver(new NetAddress("192.168.1.29", 9001), glucose.model, rearChannels, flippedRGB);
+   logTime("Build PandaDriver");
    
    // Build overlay UI
-   ui = new OverlayUI();
+   ui = controlUI = new ControlUI();
+   mappingUI = new MappingUI(mappingTool);
    logTime("Built overlay UI");
      
    // MIDI devices
 -  SCMidiDevices.initializeStandardDevices(glucose, controlUI.patternKnobs, controlUI.transitionKnobs, controlUI.effectKnobs);
 +  for (MidiInputDevice d : RWMidi.getInputDevices()) {
 +    d.createInput(this);
 +  }
 +  SCMidiDevices.initializeStandardDevices(glucose);
    logTime("Setup MIDI devices");
    
    println("Total setup: " + (millis() - startMillis) + "ms");
+   println("Hit the 'p' key to toggle Panda Board output");
  }
  
 +void controllerChangeReceived(rwmidi.Controller cc) {
 +  if (debugMode) {
 +    println("CC: " + cc.toString());
 +  }
 +}
 +
 +void noteOnReceived(Note note) {
 +  if (debugMode) {
 +    println("Note On: " + note.toString());
 +  }
 +}
 +
 +void noteOffReceived(Note note) {
 +  if (debugMode) {
 +    println("Note Off: " + note.toString());
 +  }
 +}
 +
  void logTime(String evt) {
    int now = millis();
    println(evt + ": " + (now - lastMillis) + "ms");
  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.
+   
+   // TODO(mcslee): move into GLucose engine
+   if (pandaBoardsEnabled) {
+     color[] colors = glucose.getColors();
+     pandaFront.send(colors);
+     pandaRear.send(colors);
+   }
  }
  
  void drawUI() {
  }
  
  boolean uiOn = true;
- boolean knobsOn = true;
+ int restoreToIndex = -1;
  void keyPressed() {
+   if (mappingMode) {
+     mappingTool.keyPressed();
+   }
    switch (key) {
 +    case 'd':
 +      debugMode = !debugMode;
 +      println("Debug output: " + (debugMode ? "ON" : "OFF"));
+     case 'm':
+       mappingMode = !mappingMode;
+       if (mappingMode) {
+         LXPattern pattern = lx.getPattern();
+         for (int i = 0; i < patterns.length; ++i) {
+           if (pattern == patterns[i]) {
+             restoreToIndex = i;
+             break;
+           }
+         }
+         ui = mappingUI;
+         lx.setPatterns(new LXPattern[] { mappingTool });
+       } else {
+         ui = controlUI;
+         lx.setPatterns(patterns);
+         lx.goIndex(restoreToIndex);
+       }
+       break;
+     case 'p':
+       pandaBoardsEnabled = !pandaBoardsEnabled;
+       println("PandaBoard Output: " + (pandaBoardsEnabled ? "ON" : "OFF"));
        break;
      case 'u':
        uiOn = !uiOn;
diff --combined _Overlay.pde
index 793d313170c577eda7f577babff4cd84e57b434c,06d106a255a4df53caaf2d484d0bb22196204c58..d33e4a14d33a9e4ac9318a3d6f1c48788d8f7d9b
@@@ -1,3 -1,5 +1,5 @@@
+ import java.lang.reflect.*;
  /**
   *     DOUBLE BLACK DIAMOND        DOUBLE BLACK DIAMOND
   *
   * 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("Lucida Grande", 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 controlSpacing = 18;
-   private final int tempoHeight = 20;
-   private final int knobSize = 28;
-   private final float knobIndent = .4;  
-   private final int knobSpacing = 6;
-   private final int knobLabelHeight = 14;
-   private final color lightBlue = #666699;
-   private final color lightGreen = #669966;
+ abstract class OverlayUI {
+   protected final PFont titleFont = createFont("Myriad Pro", 10);
+   protected final color titleColor = #AAAAAA;
+   protected final PFont itemFont = createFont("Lucida Grande", 11);
+   protected final PFont knobFont = titleFont;
+   protected final int w = 140;
+   protected final int leftPos;
+   protected final int leftTextPos;
+   protected final int lineHeight = 20;
+   protected final int sectionSpacing = 12;
+   protected final int controlSpacing = 18;
+   protected final int tempoHeight = 20;
+   protected final int knobSize = 28;
+   protected final float knobIndent = .4;  
+   protected final int knobSpacing = 6;
+   protected final int knobLabelHeight = 14;
+   protected final color lightBlue = #666699;
+   protected final color lightGreen = #669966;
+   
+   private PImage logo;
+   protected final int STATE_DEFAULT = 0;
+   protected final int STATE_ACTIVE = 1;
+   protected final int STATE_PENDING = 2;
+   
+   protected OverlayUI() {
+     leftPos = width - w;
+     leftTextPos = leftPos + 4;
+     logo = loadImage("logo-sm.png");
+   }
+   
+   protected void drawLogoAndBackground() {
+     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);
+   }
+   
+   protected void drawToggleTip(String s) {
+     fill(#999999);
+     textFont(itemFont);
+     textAlign(LEFT);
+     text(s, leftTextPos, height-6);
+   }
+   
+   protected void drawHelpTip() {
+     textFont(itemFont);
+     textAlign(RIGHT);
+     text("Tap 'u' to restore UI", width-4, height-6);
+   }
+   public void drawFPS() {
+     textFont(titleFont);
+     textAlign(LEFT);
+     fill(#666666);
+     text("FPS: " + (((int)(frameRate * 10)) / 10.), 4, height-6);     
+   }
+   protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) {
+     return drawObjectList(yPos, title, items, classNameArray(items, null), stateMethod);
+   }
+   
+   protected int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) {
+     noStroke();
+     fill(titleColor);
+     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;
+   }
+   
+   protected String[] classNameArray(Object[] objects, String suffix) {
+     if (objects == null) {
+       return null;
+     }
+     String[] names = new String[objects.length];
+     for (int i = 0; i < objects.length; ++i) {
+       names[i] = className(objects[i], suffix);
+     }
+     return names;
+   }
+   
+   protected String className(Object p, String suffix) {
+     String s = p.getClass().getName();
+     int li;
+     if ((li = s.lastIndexOf(".")) > 0) {
+       s = s.substring(li + 1);
+     }
+     if (s.indexOf("SugarCubes$") == 0) {
+       s = s.substring("SugarCubes$".length());
+     }
+     if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) {
+       s = s.substring(0, li);
+     }
+     return s;
+   }  
+   
+   protected int objectClickIndex(int firstItemY) {
+     return (mouseY - firstItemY) / lineHeight;
+   }
    
+   abstract public void draw();
+   abstract public void mousePressed();
+   abstract public void mouseDragged();
+   abstract public void mouseReleased();
+ }
+ /**
+  * UI for control of patterns, transitions, effects.
+  */
+ class ControlUI extends OverlayUI {  
    private final String[] patternNames;
    private final String[] transitionNames;
    private final String[] effectNames;
-   private PImage logo;
    
    private int firstPatternY;
    private int firstPatternKnobY;
    private Method patternStateMethod;
    private Method transitionStateMethod;
    private Method effectStateMethod;
-     
-   OverlayUI() {
-     leftPos = width - w;
-     leftTextPos = leftPos + 4;
-     logo = loadImage("logo-sm.png");
-     
 -  
 -  private final int NUM_PATTERN_KNOBS = 8;
 -  private final int NUM_TRANSITION_KNOBS = 4;
 -  private final int NUM_EFFECT_KNOBS = 4;
 -  
 -  private int activeTransitionIndex = 0;
 -  private int activeEffectIndex = 0;
 -  
 -  public final VirtualPatternKnob[] patternKnobs;
 -  public final VirtualTransitionKnob[] transitionKnobs;
 -  public final VirtualEffectKnob[] effectKnobs;
 -      
++
+   ControlUI() {    
      patternNames = classNameArray(patterns, "Pattern");
      transitionNames = classNameArray(transitions, "Transition");
      effectNames = classNameArray(effects, "Effect");
  
 -    patternKnobs = new VirtualPatternKnob[NUM_PATTERN_KNOBS];
 -    for (int i = 0; i < patternKnobs.length; ++i) {
 -      patternKnobs[i] = new VirtualPatternKnob(i);
 -    }
 -
 -    transitionKnobs = new VirtualTransitionKnob[NUM_TRANSITION_KNOBS];
 -    for (int i = 0; i < transitionKnobs.length; ++i) {
 -      transitionKnobs[i] = new VirtualTransitionKnob(i);
 -    }
 -
 -    effectKnobs = new VirtualEffectKnob[NUM_EFFECT_KNOBS];
 -    for (int i = 0; i < effectKnobs.length; ++i) {
 -      effectKnobs[i] = new VirtualEffectKnob(i);
 -    }
 -
      try {
        patternStateMethod = getClass().getMethod("getState", LXPattern.class);
        effectStateMethod = getClass().getMethod("getState", LXEffect.class);
        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;    
+       
+   public void draw() {    
+     drawLogoAndBackground();
+     int yPos = 0;
      firstPatternY = yPos + lineHeight + 6;
      yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod);
      yPos += controlSpacing;
      firstPatternKnobY = yPos;
      int xPos = leftTextPos;
 -    for (int i = 0; i < NUM_PATTERN_KNOBS/2; ++i) {
 -      drawKnob(xPos, yPos, knobSize, patternKnobs[i]);
 -      drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, patternKnobs[NUM_PATTERN_KNOBS/2 + i]);
 +    for (int i = 0; i < glucose.NUM_PATTERN_KNOBS/2; ++i) {
 +      drawKnob(xPos, yPos, knobSize, glucose.patternKnobs.get(i));
 +      drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, glucose.patternKnobs.get(glucose.NUM_PATTERN_KNOBS/2 + i));
        xPos += knobSize + knobSpacing;
      }
      yPos += 2*(knobSize + knobLabelHeight) + knobSpacing;
      yPos += controlSpacing;
      firstTransitionKnobY = yPos;
      xPos = leftTextPos;
 -    for (int i = 0; i < transitionKnobs.length; ++i) {
 -      drawKnob(xPos, yPos, knobSize, transitionKnobs[i]);
 +    for (VirtualTransitionKnob knob : glucose.transitionKnobs) {
 +      drawKnob(xPos, yPos, knobSize, knob);
        xPos += knobSize + knobSpacing;
      }
      yPos += knobSize + knobLabelHeight;
      yPos += controlSpacing;
      firstEffectKnobY = yPos;    
      xPos = leftTextPos;
 -    for (int i = 0; i < effectKnobs.length; ++i) {
 -      drawKnob(xPos, yPos, knobSize, effectKnobs[i]);
 +    for (VirtualEffectKnob knob : glucose.effectKnobs) {    
 +      drawKnob(xPos, yPos, knobSize, knob);
        xPos += knobSize + knobSpacing;
      }
      yPos += knobSize + knobLabelHeight;
      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", leftTextPos, height-6);
+     drawToggleTip("Tap 'u' to hide");
    }
    
    public LXParameter getOrNull(List<LXParameter> items, int 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;
    public int getState(LXEffect e) {
      if (e.isEnabled()) {
        return STATE_PENDING;
 -    } else if (effects[activeEffectIndex] == e) {
 +    } else if (e == glucose.getSelectedEffect()) {
        return STATE_ACTIVE;
      }
      return STATE_DEFAULT;
    public int getState(LXTransition t) {
      if (t == lx.getTransition()) {
        return STATE_PENDING;
 -    } else if (t == transitions[activeTransitionIndex]) {
 +    } else if (t == glucose.getSelectedTransition()) {
        return STATE_ACTIVE;
      }
      return STATE_DEFAULT;
    }
-   protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) {
-     return drawObjectList(yPos, title, items, classNameArray(items, null), 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, LXParameter knob) {
-     if (!knobsOn) {
-       return;
-     }
      final float knobValue = knob.getValuef();
      String knobLabel = knob.getLabel();
      if (knobLabel == null) {
      textAlign(CENTER);
      textFont(knobFont);
      text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2);
-   }
-   
-   private String[] classNameArray(Object[] objects, String suffix) {
-     if (objects == null) {
-       return null;
-     }
-     String[] names = new String[objects.length];
-     for (int i = 0; i < objects.length; ++i) {
-       names[i] = className(objects[i], suffix);
-     }
-     return names;
-   }
-   
-   private String className(Object p, String suffix) {
-     String s = p.getClass().getName();
-     int li;
-     if ((li = s.lastIndexOf(".")) > 0) {
-       s = s.substring(li + 1);
-     }
-     if (s.indexOf("SugarCubes$") == 0) {
-       s = s.substring("SugarCubes$".length());
-     }
-     if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) {
-       s = s.substring(0, li);
-     }
-     return s;
    }
 -
 -  class VirtualPatternKnob extends LXVirtualParameter {
 -    private final int index;
 -    
 -    VirtualPatternKnob(int index) {
 -      this.index = index;
 -    }
 -    
 -    public LXParameter getRealParameter() {
 -      List<LXParameter> parameters = glucose.getPattern().getParameters();
 -      if (index < parameters.size()) {
 -        return parameters.get(index);
 -      }
 -      return null;
 -    }
 -  }
 -
 -  class VirtualTransitionKnob extends LXVirtualParameter {
 -    private final int index;
 -    
 -    VirtualTransitionKnob(int index) {
 -      this.index = index;
 -    }
 -    
 -    public LXParameter getRealParameter() {
 -      List<LXParameter> parameters = transitions[activeTransitionIndex].getParameters();
 -      if (index < parameters.size()) {
 -        return parameters.get(index);
 -      }
 -      return null;
 -    }
 -  }
 -
 -  class VirtualEffectKnob extends LXVirtualParameter {
 -    private final int index;
 -    
 -    VirtualEffectKnob(int index) {
 -      this.index = index;
 -    }
 -    
 -    public LXParameter getRealParameter() {
 -      List<LXParameter> parameters = effects[activeEffectIndex].getParameters();
 -      if (index < parameters.size()) {
 -        return parameters.get(index);
 -      }
 -      return null;
 -    }
 -  }
    
    private int patternKnobIndex = -1;
    private int transitionKnobIndex = -1;
      } else if ((mouseY >= firstEffectKnobY) && (mouseY < firstEffectKnobY + knobSize + knobLabelHeight)) {
        effectKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
      } else if (mouseY > firstEffectY) {
-       int effectIndex = (mouseY - firstEffectY) / lineHeight;
+       int effectIndex = objectClickIndex(firstEffectY);
        if (effectIndex < effects.length) {
 -        if (activeEffectIndex == effectIndex) {
 +        if (effects[effectIndex] == glucose.getSelectedEffect()) {
            effects[effectIndex].enable();
            releaseEffect = effectIndex;
          }
 -        activeEffectIndex = effectIndex;        
 +        glucose.setSelectedEffect(effectIndex);
        }
      } else if ((mouseY >= firstTransitionKnobY) && (mouseY < firstTransitionKnobY + knobSize + knobLabelHeight)) {
        transitionKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
      } else if (mouseY > firstTransitionY) {
-       int transitionIndex = (mouseY - firstTransitionY) / lineHeight;
+       int transitionIndex = objectClickIndex(firstTransitionY);
        if (transitionIndex < transitions.length) {
 -        activeTransitionIndex = transitionIndex;
 +        glucose.setSelectedTransition(transitionIndex);
        }
      } else if ((mouseY >= firstPatternKnobY) && (mouseY < firstPatternKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) {
        patternKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
        if (mouseY >= firstPatternKnobY + knobSize + knobLabelHeight + knobSpacing) {
 -        patternKnobIndex += NUM_PATTERN_KNOBS / 2;
 +        patternKnobIndex += glucose.NUM_PATTERN_KNOBS / 2;
        }      
      } else if (mouseY > firstPatternY) {
-       int patternIndex = (mouseY - firstPatternY) / lineHeight;
+       int patternIndex = objectClickIndex(firstPatternY);
        if (patternIndex < patterns.length) {
 -        patterns[patternIndex].setTransition(transitions[activeTransitionIndex]);
          lx.goIndex(patternIndex);
        }
      }
    public void mouseDragged() {
      int dy = lastY - mouseY;
      lastY = mouseY;
 -    if (patternKnobIndex >= 0 && patternKnobIndex < NUM_PATTERN_KNOBS) {
 -      LXParameter p = patternKnobs[patternKnobIndex];
 +    if (patternKnobIndex >= 0 && patternKnobIndex < glucose.NUM_PATTERN_KNOBS) {
 +      LXParameter p = glucose.patternKnobs.get(patternKnobIndex);
        p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
 -    } else if (effectKnobIndex >= 0 && effectKnobIndex < NUM_EFFECT_KNOBS) {
 -      LXParameter p = effectKnobs[effectKnobIndex];
 +    } else if (effectKnobIndex >= 0 && effectKnobIndex < glucose.NUM_EFFECT_KNOBS) {
 +      LXParameter p = glucose.effectKnobs.get(effectKnobIndex);
        p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
 -    } else if (transitionKnobIndex >= 0 && transitionKnobIndex < NUM_TRANSITION_KNOBS) {
 -      LXParameter p = transitionKnobs[transitionKnobIndex];
 +    } else if (transitionKnobIndex >= 0 && transitionKnobIndex < glucose.NUM_TRANSITION_KNOBS) {
 +      LXParameter p = glucose.transitionKnobs.get(transitionKnobIndex);
        p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
      }
    }
    
  }
  
+ /**
+  * UI for control of mapping.
+  */
+ class MappingUI extends OverlayUI {
+   
+   private MappingTool mappingTool;
+   
+   private final String MAPPING_MODE_ALL = "All On";
+   private final String MAPPING_MODE_CHANNEL = "Channel";
+   private final String MAPPING_MODE_SINGLE_CUBE = "Single Cube";
+   
+   private final String[] mappingModes = {
+     MAPPING_MODE_ALL,
+     MAPPING_MODE_CHANNEL,
+     MAPPING_MODE_SINGLE_CUBE
+   };
+   private final Method mappingModeStateMethod;
+   
+   private final String CUBE_MODE_ALL = "All Strips";
+   private final String CUBE_MODE_SINGLE_STRIP = "Single Strip";
+   private final String CUBE_MODE_STRIP_PATTERN = "Strip Pattern";
+   private final String[] cubeModes = {
+     CUBE_MODE_ALL,
+     CUBE_MODE_SINGLE_STRIP,
+     CUBE_MODE_STRIP_PATTERN
+   };
+   private final Method cubeModeStateMethod;  
+   private final String CHANNEL_MODE_RED = "Red";
+   private final String CHANNEL_MODE_GREEN = "Green";
+   private final String CHANNEL_MODE_BLUE = "Blue";
+   private final String[] channelModes = {
+     CHANNEL_MODE_RED,
+     CHANNEL_MODE_GREEN,
+     CHANNEL_MODE_BLUE,    
+   };
+   private final Method channelModeStateMethod;
+   
+   private int firstMappingY;
+   private int firstCubeY;
+   private int firstChannelY;
+   private int channelFieldY;
+   private int cubeFieldY;
+   private int stripFieldY;
+   
+   private boolean dragCube;
+   private boolean dragStrip;
+   private boolean dragChannel;
+   MappingUI(MappingTool mappingTool) {
+     this.mappingTool = mappingTool;
+     try {
+       mappingModeStateMethod = getClass().getMethod("getMappingState", Object.class);
+       channelModeStateMethod = getClass().getMethod("getChannelState", Object.class);
+       cubeModeStateMethod = getClass().getMethod("getCubeState", Object.class);
+     } catch (Exception x) {
+       throw new RuntimeException(x);
+     }    
+   }
+   
+   public int getMappingState(Object mappingMode) {
+     boolean active = false;
+     if (mappingMode == MAPPING_MODE_ALL) {
+       active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_ALL;
+     } else if (mappingMode == MAPPING_MODE_CHANNEL) {
+       active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_CHANNEL;
+     } else if (mappingMode == MAPPING_MODE_SINGLE_CUBE) {
+       active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_SINGLE_CUBE;
+     }
+     return active ? STATE_ACTIVE : STATE_DEFAULT;
+   }
+   
+   public int getChannelState(Object channelMode) {
+     boolean active = false;
+     if (channelMode == CHANNEL_MODE_RED) {
+       active = mappingTool.channelModeRed;
+     } else if (channelMode == CHANNEL_MODE_GREEN) {
+       active = mappingTool.channelModeGreen;
+     } else if (channelMode == CHANNEL_MODE_BLUE) {
+       active = mappingTool.channelModeBlue;
+     }
+     return active ? STATE_ACTIVE : STATE_DEFAULT;
+   }
+   
+   public int getCubeState(Object cubeMode) {
+     boolean active = false;
+     if (cubeMode == CUBE_MODE_ALL) {
+       active = mappingTool.cubeMode == mappingTool.CUBE_MODE_ALL;
+     } else if (cubeMode == CUBE_MODE_SINGLE_STRIP) {
+       active = mappingTool.cubeMode == mappingTool.CUBE_MODE_SINGLE_STRIP;
+     } else if (cubeMode == CUBE_MODE_STRIP_PATTERN) {
+       active = mappingTool.cubeMode == mappingTool.CUBE_MODE_STRIP_PATTERN;
+     }
+     return active ? STATE_ACTIVE : STATE_DEFAULT;
+   }
+   
+   public void draw() {
+     drawLogoAndBackground();
+     int yPos = 0;
+     firstMappingY = yPos + lineHeight + 6;    
+     yPos = drawObjectList(yPos, "MAPPING MODE", mappingModes, mappingModes, mappingModeStateMethod);
+     yPos += sectionSpacing;
+     firstCubeY = yPos + lineHeight + 6;    
+     yPos = drawObjectList(yPos, "CUBE MODE", cubeModes, cubeModes, cubeModeStateMethod);
+     yPos += sectionSpacing;
+     firstChannelY = yPos + lineHeight + 6;    
+     yPos = drawObjectList(yPos, "CHANNELS", channelModes, channelModes, channelModeStateMethod);    
+     yPos += sectionSpacing;
+     
+     channelFieldY = yPos + lineHeight + 6;
+     yPos = drawValueField(yPos, "CHANNEL ID", mappingTool.channelIndex + 1);
+     yPos += sectionSpacing;
+     cubeFieldY = yPos + lineHeight + 6;
+     yPos = drawValueField(yPos, "CUBE ID", glucose.model.getRawIndexForCube(mappingTool.cubeIndex));
+     yPos += sectionSpacing;
+     stripFieldY = yPos + lineHeight + 6;
+     yPos = drawValueField(yPos, "STRIP ID", mappingTool.stripIndex + 1);
+     
+     drawToggleTip("Tap 'm' to return");    
+   }
+   
+   private int drawValueField(int yPos, String label, int value) {
+     yPos += lineHeight;
+     textAlign(LEFT);
+     textFont(titleFont);
+     fill(titleColor);
+     text(label, leftTextPos, yPos);
+     fill(0);
+     yPos += 6;
+     rect(leftTextPos, yPos, w-8, lineHeight);
+     yPos += lineHeight;
+     fill(#999999);
+     textAlign(CENTER);
+     textFont(itemFont);
+     text("" + value, leftTextPos + (w-8)/2, yPos - 5);
+     
+     return yPos;    
+   }
+   private int lastY;
+   
+   public void mousePressed() {
+     dragCube = dragStrip = dragChannel = false;
+     lastY = mouseY;
+     if (mouseY >= stripFieldY) {
+       if (mouseY < stripFieldY + lineHeight) {
+         dragStrip = true;
+       }
+     } else if (mouseY >= cubeFieldY) {
+       if (mouseY < cubeFieldY + lineHeight) {
+         dragCube = true;
+       }
+     } else if (mouseY >= channelFieldY) {
+       if (mouseY < channelFieldY + lineHeight) {
+         dragChannel = true;
+       }
+     } else if (mouseY >= firstChannelY) {
+       int index = objectClickIndex(firstChannelY);
+       switch (index) {
+         case 0: mappingTool.channelModeRed = !mappingTool.channelModeRed; break;
+         case 1: mappingTool.channelModeGreen = !mappingTool.channelModeGreen; break;
+         case 2: mappingTool.channelModeBlue = !mappingTool.channelModeBlue; break;
+       }
+     } else if (mouseY >= firstCubeY) {
+       int index = objectClickIndex(firstCubeY);
+       switch (index) {
+         case 0: mappingTool.cubeMode = mappingTool.CUBE_MODE_ALL; break;
+         case 1: mappingTool.cubeMode = mappingTool.CUBE_MODE_SINGLE_STRIP; break;
+         case 2: mappingTool.cubeMode = mappingTool.CUBE_MODE_STRIP_PATTERN; break;
+       }
+     } else if (mouseY >= firstMappingY) {
+       int index = objectClickIndex(firstMappingY);
+       switch (index) {
+         case 0: mappingTool.mappingMode = mappingTool.MAPPING_MODE_ALL; break;
+         case 1: mappingTool.mappingMode = mappingTool.MAPPING_MODE_CHANNEL; break;
+         case 2: mappingTool.mappingMode = mappingTool.MAPPING_MODE_SINGLE_CUBE; break;
+       }
+     }
+   }
+   public void mouseReleased() {
+   }
+   public void mouseDragged() {
+     final int DRAG_THRESHOLD = 5;
+     int dy = lastY - mouseY;
+     if (abs(dy) >= DRAG_THRESHOLD) {
+       lastY = mouseY;
+       if (dragCube) {
+         if (dy < 0) {
+           mappingTool.decCube();
+         } else {
+           mappingTool.incCube();
+         }
+       } else if (dragStrip) {
+         if (dy < 0) {
+           mappingTool.decStrip();
+         } else {
+           mappingTool.incStrip();
+         }
+       } else if (dragChannel) {
+         if (dy < 0) {
+           mappingTool.decChannel();
+         } else {
+           mappingTool.incChannel();
+         }
+       }
+     }
+     
+   }
+ }
  void mousePressed() {
    if (mouseX > ui.leftPos) {
      ui.mousePressed();
diff --combined code/GLucose.jar
index 6f5463dfb6a0c6a0624cb6baad40cdf6c1be60db,fdf9ec33188b2f8a7920ddd72181c3641c65f9ef..fe2ba46acbc8c9023cc3456713fe752a021ebc5a
Binary files differ
diff --combined code/HeronLX.jar
index dbe579cd5ab18eb756933a657b9581c92d00d345,e6ac53e47ae04653ee7759bff74f262a8011fd44..608c30c076596b559a19d96b3bde7d980fab3d6c
Binary files differ