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.*;
33 import java.lang.reflect.*;
35 final int VIEWPORT_WIDTH = 900;
36 final int VIEWPORT_HEIGHT = 700;
38 // The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
39 final float TRAILER_WIDTH = 240;
40 final float TRAILER_DEPTH = 97;
41 final float TRAILER_HEIGHT = 33;
43 final int MaxCubeHeight = 5;
44 final int NumBackTowers = 11;
46 int targetFramerate = 60;
47 int startMillis, lastMillis;
49 // Core engine variables
54 MappingTool mappingTool;
55 PandaDriver[] pandaBoards;
56 PresetManager presetManager;
57 MidiEngine midiEngine;
59 // Display configuration mode
60 boolean mappingMode = false;
61 boolean debugMode = false;
64 LXPattern restoreToPattern = null;
66 float[] hsb = new float[3];
68 // Handles to UI objects
70 UIPatternDeck uiPatternA;
71 UICrossfader uiCrossfader;
74 UIDebugText uiDebugText;
77 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
80 * Engine construction and initialization.
83 LXTransition _transition(GLucose glucose) {
84 return new DissolveTransition(glucose.lx).setDuration(1000);
87 LXPattern[] _leftPatterns(GLucose glucose) {
88 LXPattern[] patterns = patterns(glucose);
89 for (LXPattern p : patterns) {
90 p.setTransition(_transition(glucose));
95 LXPattern[] _rightPatterns(GLucose glucose) {
96 LXPattern[] patterns = _leftPatterns(glucose);
97 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
99 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
100 for (LXPattern p : patterns) {
101 rightPatterns[i++] = p;
103 return rightPatterns;
106 LXEffect[] _effectsArray(Effects effects) {
107 List<LXEffect> effectList = new ArrayList<LXEffect>();
108 for (Field f : effects.getClass().getDeclaredFields()) {
110 Object val = f.get(effects);
111 if (val instanceof LXEffect) {
112 effectList.add((LXEffect)val);
114 } catch (IllegalAccessException iax) {}
116 return effectList.toArray(new LXEffect[]{});
119 void logTime(String evt) {
121 println(evt + ": " + (now - lastMillis) + "ms");
126 startMillis = lastMillis = millis();
128 // Initialize the Processing graphics environment
129 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
130 frameRate(targetFramerate);
132 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
133 logTime("Created viewport");
135 // Create the GLucose engine to run the cubes
136 glucose = new GLucose(this, buildModel());
138 lx.enableKeyboardTempo();
139 logTime("Built GLucose engine");
142 LXEngine engine = lx.engine;
143 engine.setPatterns(patterns = _leftPatterns(glucose));
144 engine.addDeck(_rightPatterns(glucose));
145 logTime("Built patterns");
146 glucose.setTransitions(transitions(glucose));
147 logTime("Built transitions");
148 glucose.lx.addEffects(_effectsArray(effects = new Effects()));
149 logTime("Built effects");
152 presetManager = new PresetManager();
153 logTime("Loaded presets");
156 midiEngine = new MidiEngine();
157 logTime("Setup MIDI devices");
159 // Build output driver
160 PandaMapping[] pandaMappings = buildPandaList();
161 pandaBoards = new PandaDriver[pandaMappings.length];
163 for (PandaMapping pm : pandaMappings) {
164 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
166 mappingTool = new MappingTool(glucose, pandaMappings);
167 logTime("Built PandaDriver");
170 debugUI = new DebugUI(pandaMappings);
171 overlays = new UIContext[] {
172 uiPatternA = new UIPatternDeck(lx.engine.getDeck(GLucose.LEFT_DECK), "PATTERN A", 4, 4, 140, 324),
173 new UIBlendMode(4, 332, 140, 86),
174 new UIEffects(4, 422, 140, 144),
175 new UITempo(4, 570, 140, 50),
176 new UISpeed(4, 624, 140, 50),
178 new UIPatternDeck(lx.engine.getDeck(GLucose.RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324),
179 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
180 new UIOutput(width-144, 494, 140, 106),
182 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
184 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
185 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
187 uiMapping.setVisible(false);
188 logTime("Built overlay UI");
191 logo = loadImage("data/logo.png");
194 midX = TRAILER_WIDTH/2.;
195 midY = glucose.model.yMax/2;
196 midZ = TRAILER_DEPTH/2.;
200 eyeX = midX + eyeR*sin(eyeA);
201 eyeZ = midZ + eyeR*cos(eyeA);
203 // Add mouse scrolling event support
204 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
205 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
206 mouseWheel(mwe.getWheelRotation());
209 println("Total setup: " + (millis() - startMillis) + "ms");
210 println("Hit the 'p' key to toggle Panda Board output");
214 * Core render loop and drawing functionality.
217 // Draws the simulation and the 2D UI overlay
220 color[] simulationColors;
222 simulationColors = sendColors = glucose.getColors();
223 String displayMode = uiCrossfader.getDisplayMode();
224 if (displayMode == "A") {
225 simulationColors = lx.engine.getDeck(GLucose.LEFT_DECK).getColors();
226 } else if (displayMode == "B") {
227 simulationColors = lx.engine.getDeck(GLucose.RIGHT_DECK).getColors();
230 debugUI.maskColors(simulationColors);
231 debugUI.maskColors(sendColors);
244 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
249 vertex(TRAILER_WIDTH, 0, 0);
250 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
251 vertex(0, 0, TRAILER_DEPTH);
254 // Draw the logo on the front of platform
259 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
263 if (glucose.model.bassBox.exists) {
264 drawBassBox(glucose.model.bassBox, false);
266 for (Speaker speaker : glucose.model.speakers) {
267 drawSpeaker(speaker);
269 for (Cube c : glucose.model.cubes) {
276 for (Point p : glucose.model.points) {
277 stroke(simulationColors[p.index]);
278 vertex(p.x, p.y, p.z);
285 // Gamma correction here. Apply a cubic to the brightness
286 // for better representation of dynamic range
287 for (int i = 0; i < sendColors.length; ++i) {
288 lx.RGBtoHSB(sendColors[i], hsb);
290 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
293 // TODO(mcslee): move into GLucose engine
294 for (PandaDriver p : pandaBoards) {
299 void drawBassBox(BassBox b, boolean hasSub) {
307 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
308 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
314 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);
317 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
318 float lastOffset = 0;
319 for (float offset : BoothFloor.STRIP_OFFSETS) {
320 translate(offset - lastOffset, 0, 0);
321 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
327 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
328 for (int j = 0; j < 2; ++j) {
330 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
331 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
332 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
335 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
340 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
341 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
342 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
343 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
348 void drawCube(Cube c) {
352 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);
355 void drawSpeaker(Speaker s) {
361 translate(s.x, s.y, s.z);
362 rotate(s.ry / 180. * PI, 0, -1, 0);
363 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
364 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
365 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
368 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
373 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);
376 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
379 rotate(rx / 180. * PI, -1, 0, 0);
380 rotate(ry / 180. * PI, 0, -1, 0);
381 rotate(rz / 180. * PI, 0, 0, -1);
382 for (int i = 0; i < 4; ++i) {
383 float wid = (i % 2 == 0) ? xd : zd;
389 vertex(wid - sw, yd);
390 vertex(wid - sw, sw);
396 vertex(wid - sw, yd);
397 vertex(wid - sw, yd - sw);
402 translate(wid, 0, 0);
403 rotate(HALF_PI, 0, -1, 0);
410 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
411 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
412 ((PGraphicsOpenGL)g).endGL();
416 for (UIContext context : overlays) {
421 // Always draw FPS meter
424 textAlign(LEFT, BASELINE);
425 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
434 * Top-level keyboard event handling
438 mappingTool.keyPressed(uiMapping);
449 if (!midiEngine.isQwertyEnabled()) {
450 presetManager.select(midiEngine.getFocusedDeck(), key - '1');
455 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 0);
458 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 1);
461 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 2);
464 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 3);
467 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 4);
470 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 5);
473 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 6);
476 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 7);
481 frameRate(--targetFramerate);
485 frameRate(++targetFramerate);
488 effects.boom.trigger();
491 if (!midiEngine.isQwertyEnabled()) {
492 debugMode = !debugMode;
493 println("Debug output: " + (debugMode ? "ON" : "OFF"));
497 if (!midiEngine.isQwertyEnabled()) {
498 mappingMode = !mappingMode;
499 uiPatternA.setVisible(!mappingMode);
500 uiMapping.setVisible(mappingMode);
502 restoreToPattern = lx.getPattern();
503 lx.setPatterns(new LXPattern[] { mappingTool });
505 lx.setPatterns(patterns);
506 LXTransition pop = restoreToPattern.getTransition();
507 restoreToPattern.setTransition(null);
508 lx.goPattern(restoreToPattern);
509 restoreToPattern.setTransition(pop);
514 if (!midiEngine.isQwertyEnabled()) {
515 lx.engine.setThreaded(!lx.engine.isThreaded());
519 for (PandaDriver p : pandaBoards) {
524 if (!midiEngine.isQwertyEnabled()) {
532 * Top-level mouse event handling
535 void mousePressed() {
536 boolean debugged = false;
538 debugged = debugUI.mousePressed();
541 for (UIContext context : overlays) {
542 context.mousePressed(mouseX, mouseY);
549 void mouseDragged() {
550 boolean dragged = false;
551 for (UIContext context : overlays) {
552 dragged |= context.mouseDragged(mouseX, mouseY);
555 int dx = mouseX - mx;
556 int dy = mouseY - my;
560 eyeX = midX + eyeR*sin(eyeA);
561 eyeZ = midZ + eyeR*cos(eyeA);
566 void mouseReleased() {
567 for (UIContext context : overlays) {
568 context.mouseReleased(mouseX, mouseY);
572 void mouseWheel(int delta) {
573 boolean wheeled = false;
574 for (UIContext context : overlays) {
575 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
579 eyeR = constrain(eyeR - delta, -500, -80);
580 eyeX = midX + eyeR*sin(eyeA);
581 eyeZ = midZ + eyeR*cos(eyeA);