Merge pull request #1 from sugarcubes/master
authorJack Stahl <jackstah@gmail.com>
Wed, 21 Aug 2013 06:27:32 +0000 (23:27 -0700)
committerJack Stahl <jackstah@gmail.com>
Wed, 21 Aug 2013 06:27:32 +0000 (23:27 -0700)
merge

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

diff --git a/Audio.pde b/Audio.pde
new file mode 100644 (file)
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();
+      }
+    }    
+  }
+}
+
+
index f955f97bd32474d7997ae50da122b729c73fa607..68d8de143f334e81c41b3317ff9f9286f586ef4e 100644 (file)
@@ -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);
+    }
+  }
+}
index 401a2fb15daeee89738cf68a720f9107ad50174c..4681f2d639caf36a051640447bfb8a32ff28d14e 100644 (file)
 
 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),
   };
 }
 
index 14572a261a00aed742cde23da645fe14d082f94d..bdaa78ac563b916aacd84974cb4a79c2c4d973c3 100644 (file)
@@ -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;
     }
   }
   
index c5e19d25efe1034450cad8a4a38d3fe93d8666d8..443be5b053b8bbdf3c333a3ec9b247774c064580 100644 (file)
@@ -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()));
index 1b32abd1b17c647202ddc631a4e7df04b3de9813..580e1730433f070baf019a40e421d19997ac34fc 100644 (file)
@@ -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);
index 967048f1ca0cc157f56bbfc45e11cec1d3a0e3ee..1c62ed129a7e4089df1ed2b180cc0c65e66c90df 100644 (file)
@@ -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<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;
+    }
+  }
+}
 
index a02aa12fc653111915f22c6d93673ec21d91fc41..2ee00c8c26b8247d8fac858ace30873851d2b67b 100644 (file)
@@ -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;
     }
index ef86dbe71714473e20375ca7f42df31c26a55451..62d4c94b46dc1ffe3438d96e7940132ceb819e6f 100644 (file)
@@ -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;
index 54b4b706c11da63d6ebdae3b92eab06d903c732e..d8f3c05c0993e311a70bb72bca484d598a6e5337 100644 (file)
Binary files a/code/GLucose.jar and b/code/GLucose.jar differ