2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
9 * EXPERTS ONLY!! EXPERTS ONLY!!
11 * If you are an artist, you may ignore this file! It just sets
12 * up the framework to run the patterns. Should not need modification
13 * for general animation work.
17 import glucose.control.*;
18 import glucose.effect.*;
19 import glucose.model.*;
20 import glucose.pattern.*;
21 import glucose.transform.*;
22 import glucose.transition.*;
23 import heronarts.lx.*;
24 import heronarts.lx.control.*;
25 import heronarts.lx.effect.*;
26 import heronarts.lx.modulator.*;
27 import heronarts.lx.pattern.*;
28 import heronarts.lx.transition.*;
30 import ddf.minim.analysis.*;
31 import processing.opengl.*;
34 final int VIEWPORT_WIDTH = 900;
35 final int VIEWPORT_HEIGHT = 700;
37 // The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
38 final float TRAILER_WIDTH = 240;
39 final float TRAILER_DEPTH = 97;
40 final float TRAILER_HEIGHT = 33;
42 final int MaxCubeHeight = 5;
43 final int NumBackTowers = 11;
45 int targetFramerate = 60;
46 int startMillis, lastMillis;
48 // Core engine variables
52 MappingTool mappingTool;
53 PandaDriver[] pandaBoards;
54 PresetManager presetManager;
55 MidiEngine midiEngine;
57 // Display configuration mode
58 boolean mappingMode = false;
59 boolean debugMode = false;
62 LXPattern restoreToPattern = null;
64 float[] hsb = new float[3];
66 // Handles to UI objects
68 UIPatternDeck uiPatternA;
69 UICrossfader uiCrossfader;
72 UIDebugText uiDebugText;
75 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
78 * Engine construction and initialization.
81 LXTransition _transition(GLucose glucose) {
82 return new DissolveTransition(glucose.lx).setDuration(1000);
85 LXPattern[] _leftPatterns(GLucose glucose) {
86 LXPattern[] patterns = patterns(glucose);
87 for (LXPattern p : patterns) {
88 p.setTransition(_transition(glucose));
93 LXPattern[] _rightPatterns(GLucose glucose) {
94 LXPattern[] patterns = _leftPatterns(glucose);
95 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
97 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
98 for (LXPattern p : patterns) {
99 rightPatterns[i++] = p;
101 return rightPatterns;
105 void logTime(String evt) {
107 println(evt + ": " + (now - lastMillis) + "ms");
112 startMillis = lastMillis = millis();
114 // Initialize the Processing graphics environment
115 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
116 frameRate(targetFramerate);
118 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
119 logTime("Created viewport");
121 // Create the GLucose engine to run the cubes
122 glucose = new GLucose(this, buildModel());
124 lx.enableKeyboardTempo();
125 logTime("Built GLucose engine");
128 LXEngine engine = lx.engine;
129 engine.setPatterns(patterns = _leftPatterns(glucose));
130 engine.addDeck(_rightPatterns(glucose));
131 logTime("Built patterns");
132 glucose.setTransitions(transitions(glucose));
133 logTime("Built transitions");
134 glucose.lx.addEffects(effects(glucose));
135 logTime("Built effects");
138 presetManager = new PresetManager();
139 logTime("Loaded presets");
142 midiEngine = new MidiEngine();
143 logTime("Setup MIDI devices");
145 // Build output driver
146 PandaMapping[] pandaMappings = buildPandaList();
147 pandaBoards = new PandaDriver[pandaMappings.length];
149 for (PandaMapping pm : pandaMappings) {
150 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
152 mappingTool = new MappingTool(glucose, pandaMappings);
153 logTime("Built PandaDriver");
156 debugUI = new DebugUI(pandaMappings);
157 overlays = new UIContext[] {
158 uiPatternA = new UIPatternDeck(lx.engine.getDeck(GLucose.LEFT_DECK), "PATTERN A", 4, 4, 140, 324),
159 new UIBlendMode(4, 332, 140, 86),
160 new UIEffects(4, 422, 140, 144),
161 new UITempo(4, 570, 140, 50),
162 new UISpeed(4, 624, 140, 50),
164 new UIPatternDeck(lx.engine.getDeck(GLucose.RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324),
165 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
166 new UIOutput(width-144, 494, 140, 106),
168 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
170 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
171 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
173 uiMapping.setVisible(false);
174 logTime("Built overlay UI");
177 logo = loadImage("data/logo.png");
180 midX = TRAILER_WIDTH/2.;
181 midY = glucose.model.yMax/2;
182 midZ = TRAILER_DEPTH/2.;
186 eyeX = midX + eyeR*sin(eyeA);
187 eyeZ = midZ + eyeR*cos(eyeA);
189 // Add mouse scrolling event support
190 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
191 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
192 mouseWheel(mwe.getWheelRotation());
195 println("Total setup: " + (millis() - startMillis) + "ms");
196 println("Hit the 'p' key to toggle Panda Board output");
200 * Core render loop and drawing functionality.
203 // Draws the simulation and the 2D UI overlay
206 color[] simulationColors;
208 simulationColors = sendColors = glucose.getColors();
209 String displayMode = uiCrossfader.getDisplayMode();
210 if (displayMode == "A") {
211 simulationColors = lx.engine.getDeck(0).getColors();
212 } else if (displayMode == "B") {
213 simulationColors = lx.engine.getDeck(1).getColors();
216 debugUI.maskColors(simulationColors);
217 debugUI.maskColors(sendColors);
230 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
235 vertex(TRAILER_WIDTH, 0, 0);
236 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
237 vertex(0, 0, TRAILER_DEPTH);
240 // Draw the logo on the front of platform
245 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
249 if (glucose.model.bassBox.exists) {
250 drawBassBox(glucose.model.bassBox, false);
252 for (Speaker speaker : glucose.model.speakers) {
253 drawSpeaker(speaker);
255 for (Cube c : glucose.model.cubes) {
262 for (Point p : glucose.model.points) {
263 stroke(simulationColors[p.index]);
264 vertex(p.x, p.y, p.z);
271 // Gamma correction here. Apply a cubic to the brightness
272 // for better representation of dynamic range
273 for (int i = 0; i < sendColors.length; ++i) {
274 lx.RGBtoHSB(sendColors[i], hsb);
276 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
279 // TODO(mcslee): move into GLucose engine
280 for (PandaDriver p : pandaBoards) {
285 void drawBassBox(BassBox b, boolean hasSub) {
293 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
294 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
300 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);
303 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
304 float lastOffset = 0;
305 for (float offset : BoothFloor.STRIP_OFFSETS) {
306 translate(offset - lastOffset, 0, 0);
307 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
313 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
314 for (int j = 0; j < 2; ++j) {
316 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
317 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
318 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
321 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
326 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
327 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
328 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
329 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
334 void drawCube(Cube c) {
338 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);
341 void drawSpeaker(Speaker s) {
347 translate(s.x, s.y, s.z);
348 rotate(s.ry / 180. * PI, 0, -1, 0);
349 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
350 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
351 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
354 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
359 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);
362 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
365 rotate(rx / 180. * PI, -1, 0, 0);
366 rotate(ry / 180. * PI, 0, -1, 0);
367 rotate(rz / 180. * PI, 0, 0, -1);
368 for (int i = 0; i < 4; ++i) {
369 float wid = (i % 2 == 0) ? xd : zd;
375 vertex(wid - sw, yd);
376 vertex(wid - sw, sw);
382 vertex(wid - sw, yd);
383 vertex(wid - sw, yd - sw);
388 translate(wid, 0, 0);
389 rotate(HALF_PI, 0, -1, 0);
396 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
397 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
398 ((PGraphicsOpenGL)g).endGL();
402 for (UIContext context : overlays) {
407 // Always draw FPS meter
410 textAlign(LEFT, BASELINE);
411 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
420 * Top-level keyboard event handling
424 mappingTool.keyPressed(uiMapping);
435 if (!midiEngine.isQwertyEnabled()) {
436 presetManager.select(midiEngine.getFocusedDeck(), key - '1');
441 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 0);
444 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 1);
447 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 2);
450 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 3);
453 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 4);
456 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 5);
459 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 6);
462 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 7);
467 frameRate(--targetFramerate);
471 frameRate(++targetFramerate);
477 if (!midiEngine.isQwertyEnabled()) {
478 debugMode = !debugMode;
479 println("Debug output: " + (debugMode ? "ON" : "OFF"));
483 if (!midiEngine.isQwertyEnabled()) {
484 mappingMode = !mappingMode;
485 uiPatternA.setVisible(!mappingMode);
486 uiMapping.setVisible(mappingMode);
488 restoreToPattern = lx.getPattern();
489 lx.setPatterns(new LXPattern[] { mappingTool });
491 lx.setPatterns(patterns);
492 LXTransition pop = restoreToPattern.getTransition();
493 restoreToPattern.setTransition(null);
494 lx.goPattern(restoreToPattern);
495 restoreToPattern.setTransition(pop);
500 if (!midiEngine.isQwertyEnabled()) {
501 lx.engine.setThreaded(!lx.engine.isThreaded());
505 for (PandaDriver p : pandaBoards) {
510 if (!midiEngine.isQwertyEnabled()) {
518 * Top-level mouse event handling
521 void mousePressed() {
522 boolean debugged = false;
524 debugged = debugUI.mousePressed();
527 for (UIContext context : overlays) {
528 context.mousePressed(mouseX, mouseY);
535 void mouseDragged() {
536 boolean dragged = false;
537 for (UIContext context : overlays) {
538 dragged |= context.mouseDragged(mouseX, mouseY);
541 int dx = mouseX - mx;
542 int dy = mouseY - my;
546 eyeX = midX + eyeR*sin(eyeA);
547 eyeZ = midZ + eyeR*cos(eyeA);
552 void mouseReleased() {
553 for (UIContext context : overlays) {
554 context.mouseReleased(mouseX, mouseY);
558 void mouseWheel(int delta) {
559 boolean wheeled = false;
560 for (UIContext context : overlays) {
561 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
565 eyeR = constrain(eyeR - delta, -500, -80);
566 eyeX = midX + eyeR*sin(eyeA);
567 eyeZ = midZ + eyeR*cos(eyeA);