From: Jack Stahl Date: Wed, 21 Aug 2013 06:27:32 +0000 (-0700) Subject: Merge pull request #1 from sugarcubes/master X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=ccce6b8702508e1a40a8599b2a7e46370aa247d7;hp=b6a79a671fa97d90d89d32f586fc9ee9766a15d2;p=SugarCubes.git Merge pull request #1 from sugarcubes/master merge --- diff --git a/Audio.pde b/Audio.pde new file mode 100644 index 0000000..06390bc --- /dev/null +++ b/Audio.pde @@ -0,0 +1,85 @@ +/** + * 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(); + } + } + } +} + + diff --git a/MarkSlee.pde b/MarkSlee.pde index f955f97..68d8de1 100644 --- a/MarkSlee.pde +++ b/MarkSlee.pde @@ -1,6 +1,6 @@ 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; @@ -8,8 +8,10 @@ class SpaceTime extends SCPattern { 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(); @@ -39,10 +41,10 @@ class SpaceTime extends SCPattern { 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; @@ -52,7 +54,7 @@ class SpaceTime extends SCPattern { 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); @@ -61,6 +63,7 @@ class Swarm extends SCPattern { public Swarm(GLucose glucose) { super(glucose); + addModulator(offset).trigger(); addModulator(rate).trigger(); addModulator(falloff).trigger(); @@ -83,14 +86,14 @@ class Swarm extends SCPattern { 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; } @@ -125,80 +128,105 @@ class SwipeTransition extends SCTransition { } } +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 + ); } } } @@ -368,8 +396,7 @@ class CrossSections extends SCPattern { addParams(); } - public void addParams() - { + protected void addParams() { addParameter(xr); addParameter(yr); addParameter(zr); @@ -380,8 +407,8 @@ class CrossSections extends SCPattern { 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) { @@ -390,20 +417,18 @@ class CrossSections extends SCPattern { 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(); @@ -463,7 +488,7 @@ class Blinders extends SCPattern { 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; } @@ -606,3 +631,77 @@ class ShiftingPlane extends SCPattern { } } +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); + } + } +} diff --git a/SugarCubes.pde b/SugarCubes.pde index 401a2fb..4681f2d 100644 --- a/SugarCubes.pde +++ b/SugarCubes.pde @@ -25,23 +25,34 @@ 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 @@ -52,27 +63,26 @@ LXPattern[] patterns(GLucose glucose) { 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), }; } diff --git a/TestPatterns.pde b/TestPatterns.pde index 14572a2..bdaa78a 100644 --- a/TestPatterns.pde +++ b/TestPatterns.pde @@ -5,6 +5,28 @@ abstract class TestPattern extends SCPattern { } } +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. @@ -158,11 +180,11 @@ class TestProjectionPattern extends TestPattern { 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) ); } } @@ -215,8 +237,8 @@ class MappingTool extends TestPattern { 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); @@ -226,17 +248,19 @@ class MappingTool extends TestPattern { } 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; } @@ -270,7 +294,7 @@ class MappingTool extends TestPattern { 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; @@ -279,7 +303,7 @@ class MappingTool extends TestPattern { 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; @@ -343,15 +367,13 @@ class MappingTool extends TestPattern { } 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; } } diff --git a/TobySegaran.pde b/TobySegaran.pde index c5e19d2..443be5b 100644 --- a/TobySegaran.pde +++ b/TobySegaran.pde @@ -213,16 +213,16 @@ class FaceSync extends SCPattern { 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())); diff --git a/_Internals.pde b/_Internals.pde index 1b32abd..580e173 100644 --- a/_Internals.pde +++ b/_Internals.pde @@ -34,16 +34,11 @@ import rwmidi.*; 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; @@ -125,7 +120,6 @@ void setup() { mouseWheel(mwe.getWheelRotation()); }}); - println("Total setup: " + (millis() - startMillis) + "ms"); println("Hit the 'p' key to toggle Panda Board output"); } @@ -181,8 +175,10 @@ void draw() { 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); } @@ -193,7 +189,6 @@ void draw() { 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(); @@ -215,11 +210,81 @@ void draw() { } } +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); diff --git a/_Mappings.pde b/_Mappings.pde index 967048f..1c62ed1 100644 --- a/_Mappings.pde +++ b/_Mappings.pde @@ -41,22 +41,24 @@ public Model buildModel() { 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[][] { @@ -100,68 +102,68 @@ public Model buildModel() { }), // 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}, @@ -171,13 +173,13 @@ public Model buildModel() { {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}, @@ -215,65 +217,143 @@ public Model buildModel() { } towerList.add(new Tower(tower)); } - - return new Model(towerList, cubes); + + BassBox bassBox = new BassBox(56, 0, 2); + + List speakers = new ArrayList(); + 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; + } + } +} diff --git a/_Overlay.pde b/_Overlay.pde index a02aa12..2ee00c8 100644 --- a/_Overlay.pde +++ b/_Overlay.pde @@ -748,7 +748,7 @@ class MappingUI extends OverlayUI { class DebugUI { - final int[][] channelList; + final ChannelMapping[] channelList; final int debugX = 10; final int debugY = 42; final int debugXSpacing = 28; @@ -761,10 +761,10 @@ class DebugUI { 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; } } @@ -784,26 +784,44 @@ class DebugUI { 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; @@ -851,20 +869,61 @@ class DebugUI { 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; } diff --git a/_PandaDriver.pde b/_PandaDriver.pde index ef86dbe..62d4c94 100644 --- a/_PandaDriver.pde +++ b/_PandaDriver.pde @@ -28,77 +28,114 @@ public class PandaDriver { // 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; @@ -106,11 +143,11 @@ public class PandaDriver { 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; diff --git a/code/GLucose.jar b/code/GLucose.jar index 54b4b70..d8f3c05 100644 Binary files a/code/GLucose.jar and b/code/GLucose.jar differ