--- /dev/null
+/**
+ * This is a reusable equalizer class that lets you get averaged
+ * bands with dB scaling and smoothing.
+ */
+public static class GraphicEQ {
+
+ private final HeronLX lx;
+
+ public final BasicParameter level = new BasicParameter("LVL", 0.5);
+ public final BasicParameter range = new BasicParameter("RNGE", 0.5);
+ public final BasicParameter slope = new BasicParameter("SLOP", 0.5);
+ public final BasicParameter attack = new BasicParameter("ATK", 0.5);
+ public final BasicParameter release = new BasicParameter("REL", 0.5);
+
+ private final FFT fft;
+ private final int numBands;
+
+ private final LinearEnvelope[] bandVals;
+
+ public final static int DEFAULT_NUM_BANDS = 16;
+
+ public GraphicEQ(HeronLX lx) {
+ this(lx, DEFAULT_NUM_BANDS);
+ }
+
+ /**
+ * Note that the number of bands is a suggestion. Due to the FFT implementation
+ * the actual number may be slightly different.
+ */
+ public GraphicEQ(HeronLX lx, int num) {
+ this.lx = lx;
+ fft = new FFT(lx.audioInput().bufferSize(), lx.audioInput().sampleRate());
+ fft.window(FFT.HAMMING);
+ fft.logAverages(50, num/8);
+ numBands = this.fft.avgSize();
+ bandVals = new LinearEnvelope[numBands];
+ for (int i = 0; i < bandVals.length; ++i) {
+ bandVals[i] = new LinearEnvelope(0, 0, 500).trigger();
+ }
+ }
+
+ final float logTen = log(10);
+ public float log10(float val) {
+ return log(val) / logTen;
+ }
+
+ public float getLevel(int band) {
+ return bandVals[band].getValuef();
+ }
+
+ public float getAverageLevel(int minBand, int numBands) {
+ float avg = 0;
+ for (int i = minBand; i < minBand + numBands; ++i) {
+ avg += bandVals[i].getValuef();
+ }
+ avg /= numBands;
+ return avg;
+ }
+
+ public void run(int deltaMs) {
+ fft.forward(lx.audioInput().mix);
+ float zeroDBReference = pow(10, 100*(1-level.getValuef())/20.);
+ float decibelRange = 12 + range.getValuef() * 60;
+ float decibelSlope = slope.getValuef() * 60.f / numBands;
+ for (int i = 0; i < numBands; ++i) {
+ float raw = fft.getAvg(i);
+ float decibels = 20*log10(raw / zeroDBReference);
+ float positiveDecibels = decibels + decibelRange;
+ positiveDecibels += i*decibelSlope;
+ float value = constrain(positiveDecibels / decibelRange, 0, 1);
+
+ if (value > bandVals[i].getValuef()) {
+ bandVals[i].setEndVal(value, attack.getValuef() * 20).trigger();
+ }
+ }
+ for (LinearEnvelope band : bandVals) {
+ band.run(deltaMs);
+ if (!band.isRunning() && band.getValuef() > 0) {
+ band.setEndVal(0, release.getValuef() * 1600).trigger();
+ }
+ }
+ }
+}
+
+
class SpaceTime extends SCPattern {
- SinLFO pos = new SinLFO(0, 15, 3000);
+ SinLFO pos = new SinLFO(0, 1, 3000);
SinLFO rate = new SinLFO(1000, 9000, 13000);
SinLFO falloff = new SinLFO(10, 70, 5000);
float angle = 0;
BasicParameter rateParameter = new BasicParameter("RATE", 0.5);
BasicParameter sizeParameter = new BasicParameter("SIZE", 0.5);
+
public SpaceTime(GLucose glucose) {
super(glucose);
+
addModulator(pos).trigger();
addModulator(rate).trigger();
addModulator(falloff).trigger();
int i = 0;
for (Point p : strip.points) {
colors[p.index] = color(
- (lx.getBaseHuef() + 360 - p.fx*.2 + p.fy * .3) % 360,
- constrain(.4 * min(abs(s - sVal1), abs(s - sVal2)), 20, 100),
- max(0, 100 - fVal*abs(i - pVal))
- );
+ (lx.getBaseHuef() + 360 - p.fx*.2 + p.fy * .3) % 360,
+ constrain(.4 * min(abs(s - sVal1), abs(s - sVal2)), 20, 100),
+ max(0, 100 - fVal*abs(i - pVal*(strip.metrics.numPoints - 1)))
+ );
++i;
}
++s;
class Swarm extends SCPattern {
- SawLFO offset = new SawLFO(0, 16, 1000);
+ SawLFO offset = new SawLFO(0, 1, 1000);
SinLFO rate = new SinLFO(350, 1200, 63000);
SinLFO falloff = new SinLFO(15, 50, 17000);
SinLFO fX = new SinLFO(0, model.xMax, 19000);
public Swarm(GLucose glucose) {
super(glucose);
+
addModulator(offset).trigger();
addModulator(rate).trigger();
addModulator(falloff).trigger();
void run(int deltaMs) {
float s = 0;
- for (Strip strip : model.strips) {
+ for (Strip strip : model.strips ) {
int i = 0;
for (Point p : strip.points) {
float fV = max(-1, 1 - dist(p.fx/2., p.fy, fX.getValuef()/2., fY.getValuef()) / 64.);
colors[p.index] = color(
(lx.getBaseHuef() + 0.3 * abs(p.fx - hOffX.getValuef())) % 360,
constrain(80 + 40 * fV, 0, 100),
- constrain(100 - (30 - fV * falloff.getValuef()) * modDist(i + (s*63)%61, offset.getValuef(), 16), 0, 100)
+ constrain(100 - (30 - fV * falloff.getValuef()) * modDist(i + (s*63)%61, (int) (offset.getValuef() * strip.metrics.numPoints), strip.metrics.numPoints), 0, 100)
);
++i;
}
}
}
+class BassPod extends SCPattern {
+
+ private GraphicEQ eq = null;
+
+ public BassPod(GLucose glucose) {
+ super(glucose);
+ }
+
+ protected void onActive() {
+ if (eq == null) {
+ eq = new GraphicEQ(lx, 16);
+ eq.slope.setValue(0.6);
+ addParameter(eq.level);
+ addParameter(eq.range);
+ addParameter(eq.attack);
+ addParameter(eq.release);
+ addParameter(eq.slope);
+ }
+ }
+
+ public void run(int deltaMs) {
+ eq.run(deltaMs);
+
+ float bassLevel = eq.getAverageLevel(0, 5);
+
+ for (Point p : model.points) {
+ int avgIndex = (int) constrain(1 + abs(p.fx-model.xMax/2.)/(model.xMax/2.)*(eq.numBands-5), 0, eq.numBands-5);
+ float value = 0;
+ for (int i = avgIndex; i < avgIndex + 5; ++i) {
+ value += eq.getLevel(i);
+ }
+ value /= 5.;
+
+ float b = constrain(8 * (value*model.yMax - abs(p.fy-model.yMax/2.)), 0, 100);
+ colors[p.index] = color(
+ (lx.getBaseHuef() + abs(p.fy - model.cy) + abs(p.fx - model.cx)) % 360,
+ constrain(bassLevel*240 - .6*dist(p.fx, p.fy, model.cx, model.cy), 0, 100),
+ b
+ );
+ }
+ }
+}
+
+
class CubeEQ extends SCPattern {
- private FFT fft = null;
- private LinearEnvelope[] bandVals = null;
- private int avgSize;
+ private GraphicEQ eq = null;
- private final BasicParameter thrsh = new BasicParameter("LVL", 0.35);
- private final BasicParameter range = new BasicParameter("RANG", 0.45);
private final BasicParameter edge = new BasicParameter("EDGE", 0.5);
- private final BasicParameter speed = new BasicParameter("SPD", 0.5);
- private final BasicParameter tone = new BasicParameter("TONE", 0.5);
private final BasicParameter clr = new BasicParameter("CLR", 0.5);
+ private final BasicParameter blockiness = new BasicParameter("BLK", 0.5);
public CubeEQ(GLucose glucose) {
super(glucose);
- addParameter(thrsh);
- addParameter(range);
- addParameter(edge);
- addParameter(speed);
- addParameter(tone);
- addParameter(clr);
}
protected void onActive() {
- if (this.fft == null) {
- this.fft = new FFT(lx.audioInput().bufferSize(), lx.audioInput().sampleRate());
- this.fft.window(FFT.HAMMING);
- this.fft.logAverages(40, 1);
- this.avgSize = this.fft.avgSize();
- this.bandVals = new LinearEnvelope[this.avgSize];
- for (int i = 0; i < this.bandVals.length; ++i) {
- this.addModulator(this.bandVals[i] = (new LinearEnvelope(0, 0, 700+i*4))).trigger();
- }
+ if (eq == null) {
+ eq = new GraphicEQ(lx, 16);
+ addParameter(eq.level);
+ addParameter(eq.range);
+ addParameter(eq.attack);
+ addParameter(eq.release);
+ addParameter(eq.slope);
+ addParameter(edge);
+ addParameter(clr);
+ addParameter(blockiness);
}
}
public void run(int deltaMs) {
- this.fft.forward(this.lx.audioInput().mix);
- float toneConst = .35 + .4 * (tone.getValuef() - 0.5);
- float edgeConst = 2 + 30*(edge.getValuef()*edge.getValuef()*edge.getValuef());
-
- for (int i = 0; i < avgSize; ++i) {
- float value = this.fft.getAvg(i);
- value = 20*log(1 + sqrt(value));
- float sqdist = avgSize - i;
- value -= toneConst*sqdist*sqdist + .5*sqdist;
- value *= 6;
- if (value > this.bandVals[i].getValue()) {
- this.bandVals[i].setEndVal(value, 40).trigger();
- }
- else {
- this.bandVals[i].setEndVal(value, 1000 - 900*speed.getValuef()).trigger();
- }
- }
+ eq.run(deltaMs);
- float jBase = 120 - 360*thrsh.getValuef();
- float jConst = 300.*(1-range.getValuef());
+ float edgeConst = 2 + 30*edge.getValuef();
float clrConst = 1.1 + clr.getValuef();
for (Point p : model.points) {
- float avgIndex = constrain((p.fx / model.xMax * avgSize), 0, avgSize-2);
+ float avgIndex = constrain(2 + p.fx / model.xMax * (eq.numBands-4), 0, eq.numBands-4);
int avgFloor = (int) avgIndex;
- float j = jBase + jConst * (p.fy / model.yMax);
- float value = lerp(
- this.bandVals[avgFloor].getValuef(),
- this.bandVals[avgFloor+1].getValuef(),
- avgIndex-avgFloor
- );
- float b = constrain(edgeConst * (value - j), 0, 100);
+ float leftVal = eq.getLevel(avgFloor);
+ float rightVal = eq.getLevel(avgFloor+1);
+ float smoothValue = lerp(leftVal, rightVal, avgIndex-avgFloor);
+
+ float chunkyValue = (
+ eq.getLevel(avgFloor/4*4) +
+ eq.getLevel(avgFloor/4*4 + 1) +
+ eq.getLevel(avgFloor/4*4 + 2) +
+ eq.getLevel(avgFloor/4*4 + 3)
+ ) / 4.;
+
+ float value = lerp(smoothValue, chunkyValue, blockiness.getValuef());
+
+ float b = constrain(edgeConst * (value*model.yMax - p.fy), 0, 100);
colors[p.index] = color(
- (480 + lx.getBaseHuef() - min(clrConst*p.fy, 120)) % 360,
- 100,
- b);
+ (480 + lx.getBaseHuef() - min(clrConst*p.fy, 120)) % 360,
+ 100,
+ b
+ );
}
}
}
addParams();
}
- public void addParams()
- {
+ protected void addParams() {
addParameter(xr);
addParameter(yr);
addParameter(zr);
addParameter(yw);
addParameter(zw);
}
-
- public void onParameterChanged(LXParameter p) {
+
+ void onParameterChanged(LXParameter p) {
if (p == xr) {
x.setDuration(10000 - 8800*p.getValuef());
} else if (p == yr) {
z.setDuration(10000 - 9000*p.getValuef());
}
}
-
- float xv;
- float yv;
- float zv;
-
- public void updateXYZVals()
- {
+
+ float xv, yv, zv;
+
+ protected void updateXYZVals() {
xv = x.getValuef();
yv = y.getValuef();
- zv = z.getValuef();
+ zv = z.getValuef();
}
public void run(int deltaMs) {
- updateXYZVals();
+ updateXYZVals();
+
float xlv = 100*xl.getValuef();
float ylv = 100*yl.getValuef();
float zlv = 100*zl.getValuef();
colors[p.index] = color(
(hv + p.fz + p.fy*hs.getValuef()) % 360,
min(100, abs(p.fx - s.getValuef())/2.),
- max(0, 100 - mv/2. - mv * abs(i - 7.5))
+ max(0, 100 - mv/2. - mv * abs(i - (strip.metrics.length-1)/2.))
);
++i;
}
}
}
+class Traktor extends SCPattern {
+
+ final int FRAME_WIDTH = 60;
+
+ final BasicParameter speed = new BasicParameter("SPD", 0.5);
+
+ private float[] bass = new float[FRAME_WIDTH];
+ private float[] treble = new float[FRAME_WIDTH];
+
+ private int index = 0;
+ private GraphicEQ eq = null;
+
+ public Traktor(GLucose glucose) {
+ super(glucose);
+ for (int i = 0; i < FRAME_WIDTH; ++i) {
+ bass[i] = 0;
+ treble[i] = 0;
+ }
+ addParameter(speed);
+ }
+
+ public void onActive() {
+ if (eq == null) {
+ eq = new GraphicEQ(lx, 16);
+ eq.slope.setValue(0.6);
+ eq.level.setValue(0.65);
+ eq.range.setValue(0.35);
+ eq.release.setValue(0.4);
+ addParameter(eq.level);
+ addParameter(eq.range);
+ addParameter(eq.attack);
+ addParameter(eq.release);
+ addParameter(eq.slope);
+ }
+ }
+
+ int counter = 0;
+
+ public void run(int deltaMs) {
+ eq.run(deltaMs);
+
+ int stepThresh = (int) (40 - 39*speed.getValuef());
+ counter += deltaMs;
+ if (counter < stepThresh) {
+ return;
+ }
+ counter = counter % stepThresh;
+
+ index = (index + 1) % FRAME_WIDTH;
+
+ float rawBass = eq.getAverageLevel(0, 4);
+ float rawTreble = eq.getAverageLevel(eq.numBands-7, 7);
+
+ bass[index] = rawBass * rawBass * rawBass * rawBass;
+ treble[index] = rawTreble * rawTreble;
+
+ for (Point p : model.points) {
+ int i = (int) constrain((model.xMax - p.x) / model.xMax * FRAME_WIDTH, 0, FRAME_WIDTH-1);
+ int pos = (index + FRAME_WIDTH - i) % FRAME_WIDTH;
+
+ colors[p.index] = color(
+ (360 + lx.getBaseHuef() + .8*abs(p.x-model.cx)) % 360,
+ 100,
+ constrain(9 * (bass[pos]*model.cy - abs(p.fy - model.cy)), 0, 100)
+ );
+ colors[p.index] = blendColor(colors[p.index], color(
+ (400 + lx.getBaseHuef() + .5*abs(p.x-model.cx)) % 360,
+ 60,
+ constrain(5 * (treble[pos]*.6*model.cy - abs(p.fy - model.cy)), 0, 100)
+
+ ), ADD);
+ }
+ }
+}
LXPattern[] patterns(GLucose glucose) {
return new LXPattern[] {
-
+
+ // Slee
new ShiftingPlane(glucose),
new AskewPlanes(glucose),
new Swarm(glucose),
new SpaceTime(glucose),
- new Pong(glucose),
- new Noise(glucose),
new Blinders(glucose),
new CrossSections(glucose),
new Psychedelia(glucose),
- new CubeEQ(glucose),
- new PianoKeyPattern(glucose),
+
+ new Traktor(glucose).setEligible(false),
+ new BassPod(glucose).setEligible(false),
+ new CubeEQ(glucose).setEligible(false),
+ new PianoKeyPattern(glucose).setEligible(false),
+
+ // Dan
+ new Pong(glucose),
+ new Noise(glucose),
+
+ // Shaheen
+ new HelixPattern(glucose).setEligible(false),
+
+ // Toby
new GlitchPlasma(glucose),
- new FireEffect(glucose),
+ new FireEffect(glucose).setEligible(false),
new StripBounce(glucose),
- new SoundRain(glucose),
- new SoundSpikes(glucose),
+ new SoundRain(glucose).setEligible(false),
+ new SoundSpikes(glucose).setEligible(false),
new FaceSync(glucose),
// Jack
new TimPinwheels(glucose),
new TimRaindrops(glucose),
new TimCubes(glucose),
- //new TimTrace(glucose),
+ // new TimTrace(glucose),
new TimSpheres(glucose),
- //Ben
+ // Ben
new DriveableCrossSections(glucose),
new GranimTestPattern2(glucose),
-
- //Sam
+
+ // Sam
new JazzRainbow(glucose),
-
+
// Basic test patterns for reference, not art
new TestCubePattern(glucose),
new TestTowerPattern(glucose),
new TestProjectionPattern(glucose),
+ new TestStripPattern(glucose),
// new TestHuePattern(glucose),
// new TestXPattern(glucose),
// new TestYPattern(glucose),
// new TestZPattern(glucose),
- //slow for now, relegated to the bottom until faster!
- new HelixPattern(glucose),
};
}
}
}
+class TestStripPattern extends TestPattern {
+
+ SinLFO d = new SinLFO(4, 40, 4000);
+
+ public TestStripPattern(GLucose glucose) {
+ super(glucose);
+ addModulator(d).trigger();
+ }
+
+ public void run(int deltaMs) {
+ for (Strip s : model.strips) {
+ for (Point p : s.points) {
+ colors[p.index] = color(
+ lx.getBaseHuef(),
+ 100,
+ max(0, 100 - d.getValuef()*dist(p.x, p.y, s.cx, s.cy))
+ );
+ }
+ }
+ }
+}
+
/**
* Simplest demonstration of using the rotating master hue.
* All pixels are full-on the same color.
for (Coord c : projection) {
float d = sqrt(c.x*c.x + c.y*c.y + c.z*c.z); // distance from origin
// d = abs(d-60) + max(0, abs(c.z) - 20); // life saver / ring thing
- d = max(0, abs(c.y) - 10 + .3*abs(c.z) + .08*abs(c.x)); // plane / spear thing
+ d = max(0, abs(c.y) - 10 + .1*abs(c.z) + .02*abs(c.x)); // plane / spear thing
colors[c.index] = color(
(hv + .6*abs(c.x) + abs(c.z)) % 360,
100,
- constrain(140 - 10*d, 0, 100)
+ constrain(140 - 40*d, 0, 100)
);
}
}
private final int numChannels;
private final PandaMapping[] pandaMappings;
- private PandaMapping activeMapping;
- private int mappingChannelIndex;
+ private PandaMapping activePanda;
+ private ChannelMapping activeChannel;
MappingTool(GLucose glucose, PandaMapping[] pandaMappings) {
super(glucose);
}
private void setChannel() {
- mappingChannelIndex = channelIndex % PandaMapping.CHANNELS_PER_BOARD;
- activeMapping = pandaMappings[channelIndex / PandaMapping.CHANNELS_PER_BOARD];
+ activePanda = pandaMappings[channelIndex / PandaMapping.CHANNELS_PER_BOARD];
+ activeChannel = activePanda.channelList[channelIndex % PandaMapping.CHANNELS_PER_BOARD];
}
- private int cubeInChannel(Cube c) {
- int i = 1;
- for (int index : activeMapping.channelList[mappingChannelIndex]) {
- if (c == model.getCubeByRawIndex(index)) {
- return i;
+ private int indexOfCubeInChannel(Cube c) {
+ if (activeChannel.mode == ChannelMapping.MODE_CUBES) {
+ int i = 1;
+ for (int index : activeChannel.objectIndices) {
+ if (c == model.getCubeByRawIndex(index)) {
+ return i;
+ }
+ ++i;
}
- ++i;
}
return 0;
}
int ci = 0;
for (Cube cube : model.cubes) {
boolean cubeOn = false;
- int channelIndex = cubeInChannel(cube);
+ int indexOfCubeInChannel = indexOfCubeInChannel(cube);
switch (mappingMode) {
case MAPPING_MODE_ALL: cubeOn = true; break;
case MAPPING_MODE_SINGLE_CUBE: cubeOn = (cubeIndex == ci); break;
if (cubeOn) {
if (mappingMode == MAPPING_MODE_CHANNEL) {
color cc = off;
- switch (channelIndex) {
+ switch (indexOfCubeInChannel) {
case 1: cc = r; break;
case 2: cc = r|g; break;
case 3: cc = g; break;
}
public void incStrip() {
- int stripsPerCube = Cube.FACES_PER_CUBE * Face.STRIPS_PER_FACE;
- stripIndex = (stripIndex + 1) % stripsPerCube;
+ stripIndex = (stripIndex + 1) % Cube.STRIPS_PER_CUBE;
}
public void decStrip() {
- int stripsPerCube = Cube.FACES_PER_CUBE * Face.STRIPS_PER_FACE;
--stripIndex;
if (stripIndex < 0) {
- stripIndex += stripsPerCube;
+ stripIndex += Cube.STRIPS_PER_CUBE;
}
}
public void run(int deltaMs) {
int i=0;
- for (Cube c : model.cubes) {
+ for (Strip s : model.strips) {
i++;
- for (Point p : c.points) {
+ for (Point p : s.points) {
float dx, dz;
- if (i%2==0) {
- dx = p.fx - (c.cx+xosc.getValuef());
- dz = p.fz - (c.cz+zosc.getValuef());
+ if (i%32 < 16) {
+ dx = p.fx - (s.cx+xosc.getValuef());
+ dz = p.fz - (s.cz+zosc.getValuef());
} else {
- dx = p.fx - (c.cx+zosc.getValuef());
- dz = p.fz - (c.cz+xosc.getValuef());
+ dx = p.fx - (s.cx+zosc.getValuef());
+ dz = p.fz - (s.cz+xosc.getValuef());
}
//println(dx);
float a1=max(0,100-abs(p.fx-col1.getValuef()));
final int VIEWPORT_WIDTH = 900;
final int VIEWPORT_HEIGHT = 700;
+// The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
final float TRAILER_WIDTH = 240;
final float TRAILER_DEPTH = 97;
final float TRAILER_HEIGHT = 33;
-final float BASS_WIDTH = 124;
-final float BASS_HEIGHT = 31.5;
-final float BASS_DEPTH = 66;
-final float BASS_X = (TRAILER_WIDTH - BASS_WIDTH) / 2.;
-final float BASS_Z = (TRAILER_DEPTH - BASS_DEPTH) / 2.;
-
int targetFramerate = 60;
int startMillis, lastMillis;
mouseWheel(mwe.getWheelRotation());
}});
-
println("Total setup: " + (millis() - startMillis) + "ms");
println("Hit the 'p' key to toggle Panda Board output");
}
endShape();
noStroke();
- fill(#393939);
- drawBox(BASS_X, 0, BASS_Z, 0, 0, 0, BASS_WIDTH, BASS_HEIGHT, BASS_DEPTH, Cube.CHANNEL_WIDTH);
+ drawBassBox(glucose.model.bassBox);
+ for (Speaker s : glucose.model.speakers) {
+ drawSpeaker(s);
+ }
for (Cube c : glucose.model.cubes) {
drawCube(c);
}
for (Point p : glucose.model.points) {
stroke(colors[p.index]);
vertex(p.fx, p.fy, p.fz);
- // println(p.fx + ":" + p.fy + ":" + p.fz);
}
endShape();
}
}
+void drawBassBox(BassBox b) {
+ float in = .15;
+
+ noStroke();
+ fill(#191919);
+ pushMatrix();
+ translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
+ box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
+ popMatrix();
+
+ noStroke();
+ fill(#393939);
+ drawBox(b.x+in, b.y+in, b.z+in, 0, 0, 0, BassBox.EDGE_WIDTH-in*2, BassBox.EDGE_HEIGHT-in*2, BassBox.EDGE_DEPTH-in*2, Cube.CHANNEL_WIDTH-in);
+
+ pushMatrix();
+ translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
+ float lastOffset = 0;
+ for (float offset : BoothFloor.STRIP_OFFSETS) {
+ translate(offset - lastOffset, 0, 0);
+ box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
+ lastOffset = offset;
+ }
+ popMatrix();
+
+ pushMatrix();
+ translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
+ for (int j = 0; j < 2; ++j) {
+ pushMatrix();
+ for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
+ translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
+ box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
+ }
+ popMatrix();
+ translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
+ }
+ popMatrix();
+
+ pushMatrix();
+ translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
+ box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
+ translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
+ box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
+ popMatrix();
+
+}
+
void drawCube(Cube c) {
float in = .15;
+ noStroke();
+ fill(#393939);
drawBox(c.x+in, c.y+in, c.z+in, c.rx, c.ry, c.rz, Cube.EDGE_WIDTH-in*2, Cube.EDGE_HEIGHT-in*2, Cube.EDGE_WIDTH-in*2, Cube.CHANNEL_WIDTH-in);
}
+void drawSpeaker(Speaker s) {
+ float in = .15;
+
+ noStroke();
+ fill(#191919);
+ pushMatrix();
+ translate(s.x, s.y, s.z);
+ rotate(s.ry / 180. * PI, 0, -1, 0);
+ translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
+ box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
+ translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
+
+ fill(#222222);
+ box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
+ popMatrix();
+
+ noStroke();
+ fill(#393939);
+ drawBox(s.x+in, s.y+in, s.z+in, 0, s.ry, 0, Speaker.EDGE_WIDTH-in*2, Speaker.EDGE_HEIGHT-in*2, Speaker.EDGE_DEPTH-in*2, Cube.CHANNEL_WIDTH-in);
+}
+
+
void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
pushMatrix();
translate(x, y, z);
final float STACKED_RELATIVE = 1;
final float STACKED_REL_SPIN = 2;
+ final float BASS_DEPTH = BassBox.EDGE_DEPTH + 4;
TowerMapping[] mapping = new TowerMapping[] {
-
- new TowerMapping(0, 0, 0, new float[][] {
- {STACKED_RELATIVE, 0, 0},
- {STACKED_RELATIVE, 5, -10, 20},
- {STACKED_RELATIVE, 0, -6},
- {STACKED_RELATIVE, -5, -2, -20},
- }),
- new TowerMapping(Cube.EDGE_WIDTH + 2, 0, 0, new float[][] {
- {STACKED_RELATIVE, 0, 0},
- {STACKED_RELATIVE, 0, 5, 10},
- {STACKED_RELATIVE, 0, 2, 20},
- {STACKED_RELATIVE, 0, 0, 30},
- }),
+ // Front left cubes
+// new TowerMapping(0, 0, 0, new float[][] {
+// {STACKED_RELATIVE, 0, 0},
+// {STACKED_RELATIVE, 5, -10, 20},
+// {STACKED_RELATIVE, 0, -6},
+// {STACKED_RELATIVE, -5, -2, -20},
+// }),
+//
+// new TowerMapping(Cube.EDGE_WIDTH + 2, 0, 0, new float[][] {
+// {STACKED_RELATIVE, 0, 0},
+// {STACKED_RELATIVE, 0, 5, 10},
+// {STACKED_RELATIVE, 0, 2, 20},
+// {STACKED_RELATIVE, 0, 0, 30},
+// }),
// Back Cubes behind DJ platform (in order of increasing x)
new TowerMapping(50, 5, BASS_DEPTH, new float[][] {
}),
// front DJ cubes
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2, BASS_HEIGHT, 10, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, 10, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, 0, -10, 20},
}),
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2 + Cube.EDGE_HEIGHT, BASS_HEIGHT, 10, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2 + Cube.EDGE_HEIGHT, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, 10, new float[][] {
{STACKED_RELATIVE, 3, 0},
{STACKED_RELATIVE, 2, -10, 20},
}),
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2 + 2*Cube.EDGE_HEIGHT + 5, BASS_HEIGHT, 10, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2 + 2*Cube.EDGE_HEIGHT + 5, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, 10, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, 1, 0, 10},
}),
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2 + 3*Cube.EDGE_HEIGHT + 9, BASS_HEIGHT, 10, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2 + 3*Cube.EDGE_HEIGHT + 9, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, 10, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, -1, 0},
}),
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2 + 4*Cube.EDGE_HEIGHT + 15, BASS_HEIGHT, 10, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2 + 4*Cube.EDGE_HEIGHT + 15, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, 10, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, -1, 0},
}),
// left dj cubes
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2, BASS_HEIGHT, Cube.EDGE_HEIGHT + 2, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, Cube.EDGE_HEIGHT + 2, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, 0, 2, 20},
}),
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2, BASS_HEIGHT, 2*Cube.EDGE_HEIGHT + 4, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, 2*Cube.EDGE_HEIGHT + 4, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, 0, 2, 20},
}),
// right dj cubes
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2 + 4*Cube.EDGE_HEIGHT + 15, BASS_HEIGHT, Cube.EDGE_HEIGHT + 2, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2 + 4*Cube.EDGE_HEIGHT + 15, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, Cube.EDGE_HEIGHT + 2, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, 0, 2, 20},
}),
- new TowerMapping((TRAILER_WIDTH - BASS_WIDTH)/2 + 4*Cube.EDGE_HEIGHT + 15, BASS_HEIGHT, 2*Cube.EDGE_HEIGHT + 4, new float[][] {
+ new TowerMapping((TRAILER_WIDTH - BassBox.EDGE_WIDTH)/2 + 4*Cube.EDGE_HEIGHT + 15, BassBox.EDGE_HEIGHT + BoothFloor.PLEXI_WIDTH, 2*Cube.EDGE_HEIGHT + 4, new float[][] {
{STACKED_RELATIVE, 0, 0},
{STACKED_RELATIVE, 0, 2, 20},
}),
- new TowerMapping(200, 0, 0, new float[][] {
- {STACKED_RELATIVE, 0, 10},
- {STACKED_RELATIVE, 5, 0, 20},
- {STACKED_RELATIVE, 0, 4},
- {STACKED_RELATIVE, -5, 8, -20},
- {STACKED_RELATIVE, 0, 3},
- }),
+// new TowerMapping(200, 0, 0, new float[][] {
+// {STACKED_RELATIVE, 0, 10},
+// {STACKED_RELATIVE, 5, 0, 20},
+// {STACKED_RELATIVE, 0, 4},
+// {STACKED_RELATIVE, -5, 8, -20},
+// {STACKED_RELATIVE, 0, 3},
+// }),
- new TowerMapping(0, 0, Cube.EDGE_HEIGHT + 10, new float[][] {
- {STACKED_RELATIVE, 10, 0, 40},
- {STACKED_RELATIVE, 3, -2, 20},
- {STACKED_RELATIVE, 0, 0, 40},
- {STACKED_RELATIVE, 0, 0, 60},
- {STACKED_RELATIVE, 0, 0, 40},
- }),
+// new TowerMapping(0, 0, Cube.EDGE_HEIGHT + 10, new float[][] {
+// {STACKED_RELATIVE, 10, 0, 40},
+// {STACKED_RELATIVE, 3, -2, 20},
+// {STACKED_RELATIVE, 0, 0, 40},
+// {STACKED_RELATIVE, 0, 0, 60},
+// {STACKED_RELATIVE, 0, 0, 40},
+// }),
new TowerMapping(20, 0, 2*Cube.EDGE_HEIGHT + 18, new float[][] {
{STACKED_RELATIVE, 0, 0, 40},
{STACKED_RELATIVE, 12, 0, 40},
}),
- new TowerMapping(210, 0, Cube.EDGE_HEIGHT + 15, new float[][] {
- {STACKED_RELATIVE, 0, 0, 40},
- {STACKED_RELATIVE, 5, 0, 20},
- {STACKED_RELATIVE, 8, 0, 40},
- {STACKED_RELATIVE, 3, 0, 60},
- {STACKED_RELATIVE, 0, 0, 40},
- }),
+// new TowerMapping(210, 0, Cube.EDGE_HEIGHT + 15, new float[][] {
+// {STACKED_RELATIVE, 0, 0, 40},
+// {STACKED_RELATIVE, 5, 0, 20},
+// {STACKED_RELATIVE, 8, 0, 40},
+// {STACKED_RELATIVE, 3, 0, 60},
+// {STACKED_RELATIVE, 0, 0, 40},
+// }),
new TowerMapping(210, 0, 2*Cube.EDGE_HEIGHT + 25, new float[][] {
{STACKED_RELATIVE, 0, 0, 40},
}
towerList.add(new Tower(tower));
}
-
- return new Model(towerList, cubes);
+
+ BassBox bassBox = new BassBox(56, 0, 2);
+
+ List<Speaker> speakers = new ArrayList<Speaker>();
+ speakers.add(new Speaker(-12, 6, 0, 15));
+ speakers.add(new Speaker(TRAILER_WIDTH - Speaker.EDGE_WIDTH, 6, 6, -15));
+
+ return new Model(towerList, cubes, bassBox, speakers);
}
public PandaMapping[] buildPandaList() {
return new PandaMapping[] {
new PandaMapping(
- "10.200.1.28", new int[][] {
- { 1, 2, 3, 4 }, // ch1
- { 5, 6, 7, 8 }, // ch2
- { 9, 10, 11, 12 }, // ch3
- { 13, 14, 15, 16 }, // ch4
- { 17, 18, 19, 20 }, // ch5
- { 21, 22, 23, 24 }, // ch6
- { 25, 26, 27, 28 }, // ch7
- { 29, 30, 31, 32 }, // ch8
+ "10.200.1.28", new ChannelMapping[] {
+ new ChannelMapping(ChannelMapping.MODE_BASS),
+ new ChannelMapping(ChannelMapping.MODE_FLOOR),
+ new ChannelMapping(ChannelMapping.MODE_SPEAKER, 0),
+ new ChannelMapping(ChannelMapping.MODE_SPEAKER, 1),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 1, 2, 3, 4 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 5, 6, 7, 8 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 9, 10, 11, 12 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 13, 14, 15, 16 }),
}),
new PandaMapping(
- "10.200.1.29", new int[][] {
- { 33, 34, 35, 36 }, // ch9
- { 37, 38, 39, 40 }, // ch10
- { 41, 42, 43, 44 }, // ch11
- { 45, 46, 47, 48 }, // ch12
- { 49, 50, 51, 52 }, // ch13
- { 53, 54, 55, 56 }, // ch14
- { 57, 58, 59, 60 }, // ch15
- { 61, 62, 63, 64 }, // ch16
+ "10.200.1.29", new ChannelMapping[] {
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 17, 18, 19, 20 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 21, 22, 23, 24 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 25, 26, 27, 28 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 29, 30, 31, 32 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 33, 34, 35, 36 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 37, 38, 39, 40 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 41, 42, 43, 44 }),
+ new ChannelMapping(ChannelMapping.MODE_CUBES, new int[] { 45, 46, 47, 48 }),
}),
-
};
}
+/**
+ * Each panda board has an IP address and a fixed number of channels. The channels
+ * each have a fixed number of pixels on them. Whether or not that many physical
+ * pixels are connected to the channel, we still send it that much data.
+ */
class PandaMapping {
// How many channels are on the panda board
public final static int CHANNELS_PER_BOARD = 8;
- // How many cubes per channel xc_PB is configured for
- public final static int CUBES_PER_CHANNEL = 4;
-
- // How many total pixels on each channel
- public final static int PIXELS_PER_CHANNEL = Cube.POINTS_PER_CUBE * CUBES_PER_CHANNEL;
-
// How many total pixels on the whole board
- public final static int PIXELS_PER_BOARD = PIXELS_PER_CHANNEL * CHANNELS_PER_BOARD;
+ public final static int PIXELS_PER_BOARD = ChannelMapping.PIXELS_PER_CHANNEL * CHANNELS_PER_BOARD;
final String ip;
- final int[][] channelList = new int[CHANNELS_PER_BOARD][CUBES_PER_CHANNEL];
+ final ChannelMapping[] channelList = new ChannelMapping[CHANNELS_PER_BOARD];
- PandaMapping(String ip, int[][] rawChannelList) {
+ PandaMapping(String ip, ChannelMapping[] rawChannelList) {
this.ip = ip;
- for (int chi = 0; chi < CHANNELS_PER_BOARD; ++chi) {
- int[] cubes = (chi < rawChannelList.length) ? rawChannelList[chi] : new int[]{};
- for (int cui = 0; cui < CUBES_PER_CHANNEL; ++cui) {
- channelList[chi][cui] = (cui < cubes.length) ? cubes[cui] : 0;
+
+ // Ensure our array is the right length and has all valid items in it
+ for (int i = 0; i < channelList.length; ++i) {
+ channelList[i] = (i < rawChannelList.length) ? rawChannelList[i] : new ChannelMapping();
+ if (channelList[i] == null) {
+ channelList[i] = new ChannelMapping();
}
}
}
}
+/**
+ * Each channel on a pandaboard can be mapped in a number of modes. The typial is
+ * to a series of connected cubes, but we also have special mappings for the bass box,
+ * the speaker enclosures, and the DJ booth floor.
+ *
+ * This class is just the mapping meta-data. It sanitizes the input to make sure
+ * that the cubes and objects being referenced actually exist in the model.
+ *
+ * The logic for how to encode the pixels is contained in the PandaDriver.
+ */
+class ChannelMapping {
+
+ // How many cubes per channel xc_PB is configured for
+ public final static int CUBES_PER_CHANNEL = 4;
+
+ // How many total pixels on each channel
+ public final static int PIXELS_PER_CHANNEL = Cube.POINTS_PER_CUBE * CUBES_PER_CHANNEL;
+
+ public static final int MODE_NULL = 0;
+ public static final int MODE_CUBES = 1;
+ public static final int MODE_BASS = 2;
+ public static final int MODE_SPEAKER = 3;
+ public static final int MODE_FLOOR = 4;
+ public static final int MODE_INVALID = 5;
+
+ public static final int NO_OBJECT = -1;
+
+ final int mode;
+ final int[] objectIndices = new int[CUBES_PER_CHANNEL];
+
+ ChannelMapping() {
+ this(MODE_NULL);
+ }
+
+ ChannelMapping(int mode) {
+ this(mode, new int[]{});
+ }
+
+ ChannelMapping(int mode, int rawObjectIndex) {
+ this(mode, new int[]{ rawObjectIndex });
+ }
+
+ ChannelMapping(int mode, int[] rawObjectIndices) {
+ if (mode < 0 || mode >= MODE_INVALID) {
+ throw new RuntimeException("Invalid channel mapping mode: " + mode);
+ }
+ if (mode == MODE_SPEAKER) {
+ if (rawObjectIndices.length != 1) {
+ throw new RuntimeException("Speaker channel mapping mode must specify one speaker index");
+ }
+ int speakerIndex = rawObjectIndices[0];
+ if (speakerIndex < 0 || speakerIndex >= glucose.model.speakers.size()) {
+ throw new RuntimeException("Invalid speaker channel mapping: " + speakerIndex);
+ }
+ } else if ((mode == MODE_FLOOR) || (mode == MODE_BASS) || (mode == MODE_NULL)) {
+ if (rawObjectIndices.length > 0) {
+ throw new RuntimeException("Bass/floor/null mappings cannot specify object indices");
+ }
+ } else if (mode == MODE_CUBES) {
+ for (int rawCubeIndex : rawObjectIndices) {
+ if (glucose.model.getCubeByRawIndex(rawCubeIndex) == null) {
+ throw new RuntimeException("Non-existing cube specified in cube mapping: " + rawCubeIndex);
+ }
+ }
+ }
+
+ this.mode = mode;
+ for (int i = 0; i < objectIndices.length; ++i) {
+ objectIndices[i] = (i < rawObjectIndices.length) ? rawObjectIndices[i] : NO_OBJECT;
+ }
+ }
+}
class DebugUI {
- final int[][] channelList;
+ final ChannelMapping[] channelList;
final int debugX = 10;
final int debugY = 42;
final int debugXSpacing = 28;
DebugUI(PandaMapping[] pandaMappings) {
int totalChannels = pandaMappings.length * PandaMapping.CHANNELS_PER_BOARD;
- channelList = new int[totalChannels][];
+ channelList = new ChannelMapping[totalChannels];
int channelIndex = 0;
for (PandaMapping pm : pandaMappings) {
- for (int[] channel : pm.channelList) {
+ for (ChannelMapping channel : pm.channelList) {
channelList[channelIndex++] = channel;
}
}
rect(4, 32, 172, 388);
int channelNum = 0;
- for (int[] channel : channelList) {
+ for (ChannelMapping channel : channelList) {
int xPos = xBase;
drawNumBox(xPos, yPos, channelNum+1, debugState[channelNum][0]);
+ xPos += debugXSpacing;
- boolean first = true;
- int cubeNum = 0;
- for (int cube : channel) {
- if (cube <= 0) {
+ switch (channel.mode) {
+ case ChannelMapping.MODE_CUBES:
+ int stateIndex = 0;
+ boolean first = true;
+ for (int rawCubeIndex : channel.objectIndices) {
+ if (rawCubeIndex < 0) {
+ break;
+ }
+ if (first) {
+ first = false;
+ } else {
+ stroke(#999999);
+ line(xPos - 12, yPos + 8, xPos, yPos + 8);
+ }
+ drawNumBox(xPos, yPos, rawCubeIndex, debugState[channelNum][stateIndex+1]);
+ ++stateIndex;
+ xPos += debugXSpacing;
+ }
break;
- }
- xPos += debugXSpacing;
- if (first) {
- first = false;
- } else {
- stroke(#999999);
- line(xPos - 12, yPos + 8, xPos, yPos + 8);
- }
- drawNumBox(xPos, yPos, cube, debugState[channelNum][cubeNum+1]);
- ++cubeNum;
- }
+ case ChannelMapping.MODE_BASS:
+ drawNumBox(xPos, yPos, "B", debugState[channelNum][1]);
+ break;
+ case ChannelMapping.MODE_SPEAKER:
+ drawNumBox(xPos, yPos, "S" + channel.objectIndices[0], debugState[channelNum][1]);
+ break;
+ case ChannelMapping.MODE_FLOOR:
+ drawNumBox(xPos, yPos, "F", debugState[channelNum][1]);
+ break;
+ case ChannelMapping.MODE_NULL:
+ break;
+ default:
+ throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode);
+ }
yPos += debugYSpacing;
++channelNum;
color white = #FFFFFF;
color off = #000000;
int channelIndex = 0;
- for (int[] channel : channelList) {
- int cubeIndex = 1;
- for (int rawCubeIndex : channel) {
- if (rawCubeIndex > 0) {
- int state = debugState[channelIndex][cubeIndex];
- if (state != DEBUG_STATE_ANIM) {
- color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
- Cube cube = glucose.model.getCubeByRawIndex(rawCubeIndex);
- for (Point p : cube.points) {
- colors[p.index] = debugColor;
+ int state;
+ for (ChannelMapping channel : channelList) {
+ switch (channel.mode) {
+ case ChannelMapping.MODE_CUBES:
+ int cubeIndex = 1;
+ for (int rawCubeIndex : channel.objectIndices) {
+ if (rawCubeIndex >= 0) {
+ state = debugState[channelIndex][cubeIndex];
+ if (state != DEBUG_STATE_ANIM) {
+ color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
+ Cube cube = glucose.model.getCubeByRawIndex(rawCubeIndex);
+ for (Point p : cube.points) {
+ colors[p.index] = debugColor;
+ }
+ }
}
+ ++cubeIndex;
}
- }
- ++cubeIndex;
+ break;
+
+ case ChannelMapping.MODE_BASS:
+ state = debugState[channelIndex][1];
+ if (state != DEBUG_STATE_ANIM) {
+ color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
+ for (Point p : glucose.model.bassBox.points) {
+ colors[p.index] = debugColor;
+ }
+ }
+ break;
+
+ case ChannelMapping.MODE_FLOOR:
+ state = debugState[channelIndex][1];
+ if (state != DEBUG_STATE_ANIM) {
+ color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
+ for (Point p : glucose.model.boothFloor.points) {
+ colors[p.index] = debugColor;
+ }
+ }
+ break;
+
+ case ChannelMapping.MODE_SPEAKER:
+ state = debugState[channelIndex][1];
+ if (state != DEBUG_STATE_ANIM) {
+ color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
+ for (Point p : glucose.model.speakers.get(channel.objectIndices[0]).points) {
+ colors[p.index] = debugColor;
+ }
+ }
+ break;
+
+ case ChannelMapping.MODE_NULL:
+ break;
+
+ default:
+ throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode);
}
++channelIndex;
}
// OSC message
private final OscMessage message;
- // List of point indices on the board
+ // List of point indices that get sent to this board
private final int[] points;
// Packet data
- private final byte[] packet = new byte[4*352]; // TODO: de-magic-number, UDP related?
+ private final byte[] packet = new byte[4*352]; // magic number, our UDP packet size
+
+ private static final int NO_POINT = -1;
public PandaDriver(String ip) {
this.ip = ip;
- this.address = new NetAddress(ip, 9001);
+
+ // Initialize our OSC output stuff
+ address = new NetAddress(ip, 9001);
message = new OscMessage("/shady/pointbuffer");
+
+ // Build the array of points, initialize all to nothing
points = new int[PandaMapping.PIXELS_PER_BOARD];
for (int i = 0; i < points.length; ++i) {
- points[i] = 0;
+ points[i] = NO_POINT;
}
}
- public PandaDriver(String ip, int[] pointList) {
- this(ip);
- for (int i = 0; i < pointList.length && i < points.length; ++i) {
- this.points[i] = pointList[i];
- }
- }
+ /**
+ * These constant arrays indicate the order in which the strips of a cube
+ * are wired. There are four different options, depending on which bottom
+ * corner of the cube the data wire comes in.
+ */
+ private final int[][] CUBE_STRIP_ORDERINGS = new int[][] {
+ { 2, 1, 0, 3, 13, 12, 15, 14, 4, 7, 6, 5, 11, 10, 9, 8 }, // FRONT_LEFT
+ { 6, 5, 4, 7, 1, 0, 3, 2, 8, 11, 10, 9, 15, 14, 13, 12 }, // FRONT_RIGHT
+ { 14, 13, 12, 15, 9, 8, 11, 10, 0, 3, 2, 1, 7, 6, 5, 4 }, // REAR_LEFT
+ { 10, 9, 8, 11, 5, 4, 7, 6, 12, 15, 14, 13, 3, 2, 1, 0 }, // REAR_RIGHT
+ };
public PandaDriver(String ip, Model model, PandaMapping pm) {
this(ip);
- buildPointList(model, pm);
- }
-
- public void toggle() {
- enabled = !enabled;
- println("PandaBoard/" + ip + ": " + (enabled ? "ON" : "OFF"));
- }
-
- private void buildPointList(Model model, PandaMapping pm) {
- final int[][] stripOrderings = new int[][] {
- { 2, 1, 0, 3, 13, 12, 15, 14, 4, 7, 6, 5, 11, 10, 9, 8 }, // FRONT_LEFT
- { 6, 5, 4, 7, 1, 0, 3, 2, 8, 11, 10, 9, 15, 14, 13, 12 }, // FRONT_RIGHT
- { 14, 13, 12, 15, 9, 8, 11, 10, 0, 3, 2, 1, 7, 6, 5, 4 }, // REAR_LEFT
- { 10, 9, 8, 11, 5, 4, 7, 6, 12, 15, 14, 13, 3, 2, 1, 0 }, // REAR_RIGHT
- };
-
- int pi = 0;
- for (int[] channel : pm.channelList) {
- for (int cubeNumber : channel) {
- if (cubeNumber <= 0) {
- for (int i = 0; i < Cube.POINTS_PER_CUBE; ++i) {
- points[pi++] = 0;
- }
- } else {
- Cube cube = model.getCubeByRawIndex(cubeNumber);
- if (cube == null) {
- throw new RuntimeException("Non-zero, non-existing cube specified in channel mapping (" + cubeNumber + ")");
- }
- int stripOrderIndex = 0;
- switch (cube.wiring) {
- case FRONT_LEFT: stripOrderIndex = 0; break;
- case FRONT_RIGHT: stripOrderIndex = 1; break;
- case REAR_LEFT: stripOrderIndex = 2; break;
- case REAR_RIGHT: stripOrderIndex = 3; break;
- }
- for (int stripIndex : stripOrderings[stripOrderIndex]) {
- Strip s = cube.strips.get(stripIndex);
- for (int j = s.points.size() - 1; j >= 0; --j) {
- points[pi++] = s.points.get(j).index;
+
+ // Ok, we are initialized, time to build the array if points in order to
+ // send out. We start at the head of our point buffer, and work our way
+ // down. This is the order in which points will be sent down the wire.
+ int ci = -1;
+
+ // Iterate through all our channels
+ for (ChannelMapping channel : pm.channelList) {
+ ++ci;
+ int pi = ci * ChannelMapping.PIXELS_PER_CHANNEL;
+
+ switch (channel.mode) {
+
+ case ChannelMapping.MODE_CUBES:
+ // We have a list of cubes per channel
+ for (int rawCubeIndex : channel.objectIndices) {
+ if (rawCubeIndex < 0) {
+ // No cube here, skip ahead in the buffer
+ pi += Cube.POINTS_PER_CUBE;
+ } else {
+ // The cube exists, check which way it is wired to
+ // figure out the order of strips.
+ Cube cube = model.getCubeByRawIndex(rawCubeIndex);
+ int stripOrderIndex = 0;
+ switch (cube.wiring) {
+ case FRONT_LEFT: stripOrderIndex = 0; break;
+ case FRONT_RIGHT: stripOrderIndex = 1; break;
+ case REAR_LEFT: stripOrderIndex = 2; break;
+ case REAR_RIGHT: stripOrderIndex = 3; break;
+ }
+
+ // Iterate through all the strips on the cube and add the points
+ for (int stripIndex : CUBE_STRIP_ORDERINGS[stripOrderIndex]) {
+ // We go backwards here... in the model strips go clockwise, but
+ // the physical wires are run counter-clockwise
+ Strip s = cube.strips.get(stripIndex);
+ for (int j = s.points.size() - 1; j >= 0; --j) {
+ points[pi++] = s.points.get(j).index;
+ }
+ }
}
}
- }
+ break;
+
+ case ChannelMapping.MODE_BASS:
+ // TODO(mapping): figure out how we end up connecting the bass cabinet
+ break;
+
+ case ChannelMapping.MODE_FLOOR:
+ // TODO(mapping): figure out how these strips are wired
+ break;
+
+ case ChannelMapping.MODE_SPEAKER:
+ // TODO(mapping): figure out how these strips are wired
+ break;
+
+ case ChannelMapping.MODE_NULL:
+ // No problem, nothing on this channel!
+ break;
+
+ default:
+ throw new RuntimeException("Invalid/unhandled channel mapping mode: " + channel.mode);
}
+
}
}
+ public void toggle() {
+ enabled = !enabled;
+ println("PandaBoard/" + ip + ": " + (enabled ? "ON" : "OFF"));
+ }
+
public final void send(int[] colors) {
if (!enabled) {
return;
int len = 0;
int packetNum = 0;
for (int index : points) {
- int c = colors[index];
+ int c = (index < 0) ? 0 : colors[index];
byte r = (byte) ((c >> 16) & 0xFF);
byte g = (byte) ((c >> 8) & 0xFF);
byte b = (byte) ((c) & 0xFF);
- packet[len++] = 0;
+ packet[len++] = 0; // alpha channel, unused but makes for 4-byte alignment
packet[len++] = r;
packet[len++] = g;
packet[len++] = b;