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

SugarCubes.pde
TestPatterns.pde
_Internals.pde
_Mappings.pde
_Overlay.pde
_PandaDriver.pde [new file with mode: 0644]
code/GLucose.jar
code/HeronLX.jar
code/oscP5.jar [new file with mode: 0644]

index 5504001bf88d3834f2d82b3b40f12340c35b00cf..3f6cc8f4e47494ad81954d9065c4fcc5c84985f8 100644 (file)
@@ -1,7 +1,7 @@
 /**
  *           +-+-+-+-+-+               +-+-+-+-+-+
  *          /         /|               |\         \
- *         /         / +               + \         \
+ *         /         / +               <+ \         \
  *        +-+-+-+-+-+  |   +-+-+-+-+   |  +-+-+-+-+-+
  *        |         |  +  /         \  +  |         |
  *        +   THE   + /  /           \  \ +  CUBES  +
index 2b44a93f632c4b5189e90cc7a6567a3481ebe27d..9b465311b62be1a0cfa1b65ef035541971358f36 100644 (file)
@@ -133,3 +133,184 @@ class TestProjectionPattern extends SCPattern {
     }
   } 
 }
+
+class MappingTool extends SCPattern {
+    
+  private int cubeIndex = 0;
+  private int stripIndex = 0;
+  private int channelIndex = 0;
+
+  public final int MAPPING_MODE_ALL = 0;
+  public final int MAPPING_MODE_CHANNEL = 1;
+  public final int MAPPING_MODE_SINGLE_CUBE = 2;
+  public int mappingMode = MAPPING_MODE_ALL;
+
+  public final int CUBE_MODE_ALL = 0;
+  public final int CUBE_MODE_SINGLE_STRIP = 1;
+  public final int CUBE_MODE_STRIP_PATTERN = 2;
+  public int cubeMode = CUBE_MODE_ALL;
+
+  public boolean channelModeRed = true;
+  public boolean channelModeGreen = false;
+  public boolean channelModeBlue = false;
+  
+  private final static int NUM_CHANNELS = 16;
+  
+  private final int[][] frontChannels;
+  private final int[][] rearChannels;
+  private int[] activeChannels;
+  
+  MappingTool(GLucose glucose, int[][]frontChannels, int[][]rearChannels) {
+    super(glucose);
+    this.frontChannels = frontChannels;
+    this.rearChannels = rearChannels;
+    setChannel();
+  }
+  
+  private void setChannel() {
+    if (channelIndex < frontChannels.length) {
+      activeChannels = frontChannels[channelIndex];
+    } else {
+      activeChannels = rearChannels[channelIndex - frontChannels.length];
+    }
+  }
+  
+  private int cubeInChannel(Cube c) {
+    int i = 1;
+    for (int index : activeChannels) {
+      if (c == model.getCubeByRawIndex(index)) {
+        return i;
+      }
+      ++i;
+    }
+    return 0;
+  }
+  
+  private void printInfo() {
+    println("Cube:" + cubeIndex + " Strip:" + (stripIndex+1));
+  }
+  
+  public void cube(int delta) {
+    int len = model.cubes.size();
+    cubeIndex = (len + cubeIndex + delta) % len;
+    printInfo();
+  }
+  
+  public void strip(int delta) {
+    int len = Cube.CLIPS_PER_CUBE * Clip.STRIPS_PER_CLIP;
+    stripIndex = (len + stripIndex + delta) % len;
+    printInfo();
+  }
+  
+  public void run(int deltaMs) {
+    color off = color(0, 0, 0);
+    color c = off;
+    color r = #FF0000;
+    color g = #00FF00;
+    color b = #0000FF;
+    if (channelModeRed) c |= r;
+    if (channelModeGreen) c |= g;
+    if (channelModeBlue) c |= b;
+    
+    int ci = 0;
+    for (Cube cube : model.cubes) {
+      boolean cubeOn = false;
+      int channelIndex = cubeInChannel(cube);
+      switch (mappingMode) {
+        case MAPPING_MODE_ALL: cubeOn = true; break;
+        case MAPPING_MODE_SINGLE_CUBE: cubeOn = (cubeIndex == ci); break;
+        case MAPPING_MODE_CHANNEL: cubeOn = (channelIndex > 0); break;
+      }
+      if (cubeOn) {
+        if (mappingMode == MAPPING_MODE_CHANNEL) {
+          color cc = off;
+          switch (channelIndex) {
+            case 1: cc = r; break;
+            case 2: cc = r|g; break;
+            case 3: cc = g; break;
+            case 4: cc = b; break;
+            case 5: cc = r|b; break;
+          }
+          setColor(cube, cc);
+        } else if (cubeMode == CUBE_MODE_STRIP_PATTERN) {
+          int si = 0;
+          color sc = off;
+          for (Strip strip : cube.strips) {
+            int clipI = si / Clip.STRIPS_PER_CLIP;
+            switch (clipI) {
+              case 0: sc = r; break;
+              case 1: sc = g; break;
+              case 2: sc = b; break;
+              case 3: sc = r|g|b; break;
+            }
+            if (si % Clip.STRIPS_PER_CLIP == 2) {
+              sc = r|g;
+            }
+            setColor(strip, sc);
+            ++si;
+          }
+        } else if (cubeMode == CUBE_MODE_SINGLE_STRIP) {
+          setColor(cube, off);
+          setColor(cube.strips.get(stripIndex), c);
+        } else {
+          setColor(cube, c);
+        }
+      } else {
+        setColor(cube, off);
+      }
+      ++ci;
+    }
+    
+  }
+  
+  public void incCube() {
+    cubeIndex = (cubeIndex + 1) % model.cubes.size();
+  }
+  
+  public void decCube() {
+    --cubeIndex;
+    if (cubeIndex < 0) {
+      cubeIndex += model.cubes.size();
+    }
+  }
+
+  public void incChannel() {
+    channelIndex = (channelIndex + 1) % NUM_CHANNELS;
+    setChannel();
+  }
+  
+  public void decChannel() {
+    --channelIndex;
+    if (channelIndex < 0) {
+      channelIndex += NUM_CHANNELS;
+    }
+    setChannel();    
+  }
+  
+  public void incStrip() {
+    int stripsPerCube = Cube.CLIPS_PER_CUBE * Clip.STRIPS_PER_CLIP;
+    stripIndex = (stripIndex + 1) % stripsPerCube;
+  }
+  
+  public void decStrip() {
+    int stripsPerCube = Cube.CLIPS_PER_CUBE * Clip.STRIPS_PER_CLIP;
+    --stripIndex;
+    if (stripIndex < 0) {
+      stripIndex += stripsPerCube;
+    }
+  }
+  
+  public void keyPressed() {
+    switch (keyCode) {
+      case UP: if (mappingMode == MAPPING_MODE_CHANNEL) incChannel(); else incCube(); break;
+      case DOWN: if (mappingMode == MAPPING_MODE_CHANNEL) decChannel(); else decCube(); break;
+      case LEFT: decStrip(); break;
+      case RIGHT: incStrip(); break;
+    }
+    switch (key) {
+      case 'r': channelModeRed = !channelModeRed; break;
+      case 'g': channelModeGreen = !channelModeGreen; break;
+      case 'b': channelModeBlue = !channelModeBlue; break;
+    }
+  }
+}
index 46c39390dcf93ac1dfe29deaee243eba0c36efa5..0249603b3f209e0fffc1013290aeca8a4368bcab 100644 (file)
@@ -29,7 +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,10 +38,18 @@ 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;
 
@@ -69,9 +76,19 @@ void setup() {
   logTime("Built effects");
   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
@@ -82,6 +99,7 @@ void setup() {
   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) {
@@ -111,6 +129,13 @@ void logTime(String evt) {
 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() {
@@ -123,12 +148,37 @@ 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;
index 813705046635f543bc23002302e3d3ebc752593d..10c1083fe0b810c468bde8471676195df1037b5d 100644 (file)
@@ -39,7 +39,7 @@ class SCMapping implements GLucose.Mapping {
     cubes[19] = new Cube(24, 2, 20, 0, 0, 25, true, 0, 3);
     cubes[20] = new Cube(26, 26, 20, 0, 0, 70, true, 2, 3);
     cubes[21] = new Cube(3.5, 10.5, 20, 0, 0, 35, true, 1, 0);
-    cubes[22] =  new Cube(63, 133, 20, 0, 0, 80, false, 0, 2);
+    cubes[22] = new Cube(63, 133, 20, 0, 0, 80, false, 0, 2);
     cubes[23] = new Cube(56, 159, 20, 0, 0, 65);
     cubes[24] = new Cube(68, 194, 20, 0, -45, 0);
     cubes[25] = new Cube(34, 194, 20, 20, 0, 35 );
@@ -76,7 +76,7 @@ class SCMapping implements GLucose.Mapping {
     cubes[56] = new Cube(1, 53, 0, 40, 70, 70);
     cubes[57] = new Cube(-15, 24, 0, 15, 0, 0);
     //cubes[58] what the heck happened here? never noticed before 4/8/2013
-    //cubes[59] what the heck happened here? never noticed before 4/8/2013
+    cubes[59] = new Cube(40, 46, 100, 0, 0, 355, false, 2, 3); // copies from 75
     cubes[60] = new Cube(40, 164, 120, 0, 0, 12.5, false, 4, 3);
     cubes[61] = new Cube(32, 148, 100, 0, 0, 3, false, 4, 2);
     cubes[62] = new Cube(30, 132, 90, 10, 350, 5);
@@ -87,7 +87,7 @@ class SCMapping implements GLucose.Mapping {
     cubes[68] = new Cube(29, 94, 105, 15, 20, 10, false, 4, 0);
     cubes[69] = new Cube(30, 77, 100, 15, 345, 20, false, 2, 1);
     cubes[70] = new Cube(38, 96, 95, 30, 0, 355);
-    //cubes[71]= new Cube(38,96,95,30,0,355);
+    //cubes[71] = new Cube(38,96,95,30,0,355); //old power cube
     cubes[72] = new Cube(44, 20, 100, 0, 0, 345);
     cubes[73] = new Cube(28, 24, 100, 0, 0, 13, true, 5, 1);
     cubes[74] = new Cube(8, 38, 100, 10, 0, 0, true, 5, 1);
@@ -97,5 +97,178 @@ class SCMapping implements GLucose.Mapping {
     cubes[78] = new Cube(20, 140, 80, 0, 0, 0, false, 0, 3);
     return cubes;
   }
+
+  public int[][] buildFrontChannelList() {
+    return new int[][] {
+      {
+        1, 57, 56, 55, 0  // Pandaboard A, structural channel 1
+      }
+      , 
+      {
+        31, 32, 17, 3, 0  // Pandaboard B, structural channel 2,  normally 30, 31, 32, 17, 3 (disconnected 30)
+      }
+      , 
+      {
+        20, 21, 15, 19, 0  // Pandaboard C, structural channel 3
+      }
+      , 
+      {
+        69, 75, 74, 76, 73  // Pandaboard D, structural channel 4, normally 64 first
+      }
+      , 
+      {
+        16, 2, 5, 0, 0  // Pandaboard E, structural channel 5
+      }
+      , 
+      {
+        48, 47, 37, 29, 0  // Pandaboard F, structural channel 6 (is there a 5th?)
+      }
+      , 
+      {
+        68, 63, 62, 78, 45  // Pandaboard G, structural channel 7, left top front side
+      }
+      , 
+      {
+        18, 6, 7, 0, 0  // Pandaboard H, structural channel 8
+      }
+    };
+  }
+
+  public int[][] buildRearChannelList() {
+    return new int[][] {
+      {
+        22, 8, 14, 28, 0  // Pandaboard A, structural channel 9
+      }
+      , 
+      {
+        36, 34, 40, 52, 66  // Pandaboard B, structural channel 10
+      }
+      , 
+      {
+        65, 61, 60, 54, 51  // Pandaboard C, structural channel 11
+      }
+      , 
+      {
+        35, 25, 11, 10, 24  // Pandaboard D, structural channel 12
+      }
+      , 
+      {
+        23, 9, 13, 27, 12  // Pandaboard E, structural channel 13, missing taillight?
+      }
+      , 
+      {
+        64, 59, 72, 49, 50  // Pandaboard F, structural channel 14, right top backside (second cube is missing from sim)
+      }
+      , 
+      {
+        77, 39, 46, 33, 26  // Pandaboard G, structural channel 15
+      }
+      , 
+      {
+        44, 53, 42, 43, 41  // Pandaboard H, structural channel 16, last cube busted?
+      }
+    };
+  }
+
+  public int[][] buildFlippedRGBList() {
+    // syntax is {cube #, strip #, strip #, . . . }
+    return new int[][] { 
+      {
+        22, 4, 7
+      }
+      , 
+      {
+        50, 1, 3
+      }
+      , 
+      {
+        7, 1, 2, 11
+      }
+      , 
+      {
+        49, 1
+      }
+      , 
+      {
+        39, 1
+      }
+      , 
+      {
+        41, 1
+      }
+      , 
+      {
+        26, 3, 5
+      }
+      , 
+      {
+        64, 1
+      }
+      , 
+      {
+        32, 2
+      }
+      , 
+      {
+        20, 6, 7
+      }
+      , 
+      {
+        19, 1, 2
+      }
+      , 
+      {
+        15, 6, 8, 9
+      }
+      , 
+      {
+        29, 3, 10
+      }
+      , 
+      {
+        68, 4, 9
+      }
+      , 
+      {
+        18, 12
+      }
+      , 
+      {
+        6, 2, 4
+      }
+      , 
+      {
+        78, 11
+      }
+      , 
+      {
+        56, 2
+      }
+      , 
+      {
+        57, 3
+      }
+      , 
+      {
+        74, 6, 7
+      }
+      , 
+      {
+        21, 10
+      }
+      , 
+      {
+        37, 11
+      }
+      , 
+      {
+        61, 5
+      }
+      , 
+      {
+        33, 12
+      }
+    };
+  }
 }
 
index 793d313170c577eda7f577babff4cd84e57b434c..d33e4a14d33a9e4ac9318a3d6f1c48788d8f7d9b 100644 (file)
@@ -1,3 +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;
@@ -49,12 +174,8 @@ class OverlayUI {
   private Method patternStateMethod;
   private Method transitionStateMethod;
   private Method effectStateMethod;
-    
-  OverlayUI() {
-    leftPos = width - w;
-    leftTextPos = leftPos + 4;
-    logo = loadImage("logo-sm.png");
-    
+
+  ControlUI() {    
     patternNames = classNameArray(patterns, "Pattern");
     transitionNames = classNameArray(transitions, "Transition");
     effectNames = classNameArray(effects, "Effect");
@@ -67,22 +188,10 @@ class OverlayUI {
       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;
@@ -131,10 +240,7 @@ class OverlayUI {
     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) {
@@ -144,17 +250,6 @@ class OverlayUI {
     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;
@@ -181,56 +276,8 @@ class OverlayUI {
     }
     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) {
@@ -270,33 +317,6 @@ class OverlayUI {
     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;
   }
   
   private int patternKnobIndex = -1;
@@ -319,7 +339,7 @@ class OverlayUI {
     } 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 (effects[effectIndex] == glucose.getSelectedEffect()) {
           effects[effectIndex].enable();
@@ -330,7 +350,7 @@ class OverlayUI {
     } 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) {
         glucose.setSelectedTransition(transitionIndex);
       }
@@ -340,7 +360,7 @@ class OverlayUI {
         patternKnobIndex += glucose.NUM_PATTERN_KNOBS / 2;
       }      
     } else if (mouseY > firstPatternY) {
-      int patternIndex = (mouseY - firstPatternY) / lineHeight;
+      int patternIndex = objectClickIndex(firstPatternY);
       if (patternIndex < patterns.length) {
         lx.goIndex(patternIndex);
       }
@@ -372,6 +392,225 @@ class OverlayUI {
   
 }
 
+/**
+ * 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 --git a/_PandaDriver.pde b/_PandaDriver.pde
new file mode 100644 (file)
index 0000000..6a4a3da
--- /dev/null
@@ -0,0 +1,132 @@
+import netP5.*;
+import oscP5.*;
+
+/**
+ *     DOUBLE BLACK DIAMOND        DOUBLE BLACK DIAMOND
+ *
+ *         //\\   //\\                 //\\   //\\  
+ *        ///\\\ ///\\\               ///\\\ ///\\\
+ *        \\\/// \\\///               \\\/// \\\///
+ *         \\//   \\//                 \\//   \\//
+ *
+ *        EXPERTS ONLY!!              EXPERTS ONLY!!
+ *
+ * This class implements the output function to the Panda Boards. It
+ * will be moved into GLucose once stabilized.
+ */
+public class PandaDriver {
+
+  // Address to send to
+  private final NetAddress address;
+  
+  // OSC message
+  private final OscMessage message;
+
+  // List of point indices on the board
+  private final int[] points;
+
+  // Bit for flipped status of each point index
+  private final boolean[] flipped;
+
+  // Packet data
+  private final byte[] packet = new byte[4*352]; // TODO: de-magic-number
+
+  public PandaDriver(NetAddress address, Model model, int[][] channelList, int[][] flippedList) {
+    this.address = address;
+    message = new OscMessage("/shady/pointbuffer");
+    List<Integer> pointList = buildMappedList(model, channelList);
+    points = new int[pointList.size()];
+    int i = 0;
+    for (int value : pointList) {
+      points[i++] = value;
+    }
+    flipped = buildFlippedList(model, flippedList);
+  }
+
+  private ArrayList<Integer> buildMappedList(Model model, int[][] channelList) {
+    ArrayList<Integer> points = new ArrayList<Integer>();
+    for (int[] channel : channelList) {
+      for (int cubeNumber : channel) {
+        if (cubeNumber == 0) {
+          for (int i = 0; i < (Cube.CLIPS_PER_CUBE*Clip.STRIPS_PER_CLIP*Strip.POINTS_PER_STRIP); ++i) {
+            points.add(0);
+          }
+        } else {
+          Cube cube = model.getCubeByRawIndex(cubeNumber);
+          if (cube == null) {
+            throw new RuntimeException("Non-zero, non-existing cube specified in channel mapping (" + cubeNumber + ")");
+          }
+          for (Point p : cube.points) {
+            points.add(p.index);
+          }
+        }
+      }
+    }
+    return points;
+  }
+
+  private boolean[] buildFlippedList(Model model, int[][] flippedRGBList) {
+    boolean[] flipped = new boolean[model.points.size()];
+    for (int i = 0; i < flipped.length; ++i) {
+      flipped[i] = false;
+    }
+    for (int[] cubeInfo : flippedRGBList) {
+      int cubeNumber = cubeInfo[0];
+      Cube cube = model.getCubeByRawIndex(cubeNumber);
+      if (cube == null) {
+        throw new RuntimeException("Non-existing cube specified in flipped RGB mapping (" + cubeNumber + ")");
+      }
+      for (int i = 1; i < cubeInfo.length; ++i) {
+        int stripIndex = cubeInfo[i];
+        for (Point p : cube.strips.get(stripIndex-1).points) {
+          flipped[p.index] = true;
+        }
+      }
+    }
+    return flipped;
+  } 
+
+  public final void send(int[] colors) {
+    int len = 0;
+    int packetNum = 0;
+    for (int index : points) {
+      int c = colors[index];
+      byte r = (byte) ((c >> 16) & 0xFF);
+      byte g = (byte) ((c >> 8) & 0xFF);
+      byte b = (byte) ((c) & 0xFF);
+      if (flipped[index]) {
+        byte tmp = r;
+        r = g;
+        g = tmp;
+      }
+      packet[len++] = 0;
+      packet[len++] = r;
+      packet[len++] = g;
+      packet[len++] = b;
+
+      // Flush once packet is full buffer size
+      if (len >= packet.length) {
+        sendPacket(packetNum++, len);
+        len = 0;
+      }
+    }
+
+    // Flush any remaining data
+    if (len > 0) {
+      sendPacket(packetNum++, len);
+    }
+  }
+  
+  private void sendPacket(int packetNum, int len) {
+    message.clearArguments();
+    message.add(packetNum);
+    message.add(len);
+    message.add(packet);
+    try {
+      OscP5.flush(message, address);     
+    } catch (Exception x) {
+      x.printStackTrace();
+    }
+  }
+}
+
index 6f5463dfb6a0c6a0624cb6baad40cdf6c1be60db..fe2ba46acbc8c9023cc3456713fe752a021ebc5a 100644 (file)
Binary files a/code/GLucose.jar and b/code/GLucose.jar differ
index dbe579cd5ab18eb756933a657b9581c92d00d345..608c30c076596b559a19d96b3bde7d980fab3d6c 100644 (file)
Binary files a/code/HeronLX.jar and b/code/HeronLX.jar differ
diff --git a/code/oscP5.jar b/code/oscP5.jar
new file mode 100644 (file)
index 0000000..24c6756
Binary files /dev/null and b/code/oscP5.jar differ