From: Mark Slee Date: Sat, 15 Jun 2013 02:09:05 +0000 (-0700) Subject: Merge branch 'panda-refactor' of https://github.com/sugarcubes/SugarCubes X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=b11ff42b4235ebf514245ac177cb4809067c988b;hp=cc9fcf4be00a99376a2083ddfbcf589f94ee6785;p=SugarCubes.git Merge branch 'panda-refactor' of https://github.com/sugarcubes/SugarCubes Conflicts: _Internals.pde _Overlay.pde code/GLucose.jar --- diff --git a/SugarCubes.pde b/SugarCubes.pde index 5504001..3f6cc8f 100644 --- a/SugarCubes.pde +++ b/SugarCubes.pde @@ -1,7 +1,7 @@ /** * +-+-+-+-+-+ +-+-+-+-+-+ * / /| |\ \ - * / / + + \ \ + * / / + <+ \ \ * +-+-+-+-+-+ | +-+-+-+-+ | +-+-+-+-+-+ * | | + / \ + | | * + THE + / / \ \ + CUBES + diff --git a/TestPatterns.pde b/TestPatterns.pde index 2b44a93..9b46531 100644 --- a/TestPatterns.pde +++ b/TestPatterns.pde @@ -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; + } + } +} diff --git a/_Internals.pde b/_Internals.pde index 46c3939..0249603 100644 --- a/_Internals.pde +++ b/_Internals.pde @@ -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; diff --git a/_Mappings.pde b/_Mappings.pde index 8137050..10c1083 100644 --- a/_Mappings.pde +++ b/_Mappings.pde @@ -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 + } + }; + } } diff --git a/_Overlay.pde b/_Overlay.pde index 793d313..d33e4a1 100644 --- a/_Overlay.pde +++ b/_Overlay.pde @@ -1,3 +1,5 @@ +import java.lang.reflect.*; + /** * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND * @@ -12,30 +14,153 @@ * 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 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 index 0000000..6a4a3da --- /dev/null +++ b/_PandaDriver.pde @@ -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 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 buildMappedList(Model model, int[][] channelList) { + ArrayList points = new ArrayList(); + 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(); + } + } +} + diff --git a/code/GLucose.jar b/code/GLucose.jar index 6f5463d..fe2ba46 100644 Binary files a/code/GLucose.jar and b/code/GLucose.jar differ diff --git a/code/HeronLX.jar b/code/HeronLX.jar index dbe579c..608c30c 100644 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 index 0000000..24c6756 Binary files /dev/null and b/code/oscP5.jar differ