New HeronLX with fix for patterns not having Deck value set
[SugarCubes.git] / _Internals.pde
1 /**
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
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.
14 */
15
16 import glucose.*;
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.*;
29 import ddf.minim.*;
30 import ddf.minim.analysis.*;
31 import processing.opengl.*;
32 import rwmidi.*;
33 import java.lang.reflect.*;
34
35 final int VIEWPORT_WIDTH = 900;
36 final int VIEWPORT_HEIGHT = 700;
37
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;
42
43 final int MaxCubeHeight = 5;
44 final int NumBackTowers = 11;
45
46 int targetFramerate = 60;
47 int startMillis, lastMillis;
48
49 // Core engine variables
50 GLucose glucose;
51 HeronLX lx;
52 LXPattern[] patterns;
53 Effects effects;
54 MappingTool mappingTool;
55 PandaDriver[] pandaBoards;
56 PresetManager presetManager;
57 MidiEngine midiEngine;
58
59 // Display configuration mode
60 boolean mappingMode = false;
61 boolean debugMode = false;
62 DebugUI debugUI;
63 boolean uiOn = true;
64 LXPattern restoreToPattern = null;
65 PImage logo;
66 float[] hsb = new float[3];
67
68 // Handles to UI objects
69 UIContext[] overlays;
70 UIPatternDeck uiPatternA;
71 UICrossfader uiCrossfader;
72 UIMidi uiMidi;
73 UIMapping uiMapping;
74 UIDebugText uiDebugText;
75
76 // Camera variables
77 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
78
79 /**
80 * Engine construction and initialization.
81 */
82
83 LXTransition _transition(GLucose glucose) {
84 return new DissolveTransition(glucose.lx).setDuration(1000);
85 }
86
87 LXPattern[] _leftPatterns(GLucose glucose) {
88 LXPattern[] patterns = patterns(glucose);
89 for (LXPattern p : patterns) {
90 p.setTransition(_transition(glucose));
91 }
92 return patterns;
93 }
94
95 LXPattern[] _rightPatterns(GLucose glucose) {
96 LXPattern[] patterns = _leftPatterns(glucose);
97 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
98 int i = 0;
99 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
100 for (LXPattern p : patterns) {
101 rightPatterns[i++] = p;
102 }
103 return rightPatterns;
104 }
105
106 LXEffect[] _effectsArray(Effects effects) {
107 List<LXEffect> effectList = new ArrayList<LXEffect>();
108 for (Field f : effects.getClass().getDeclaredFields()) {
109 try {
110 Object val = f.get(effects);
111 if (val instanceof LXEffect) {
112 effectList.add((LXEffect)val);
113 }
114 } catch (IllegalAccessException iax) {}
115 }
116 return effectList.toArray(new LXEffect[]{});
117 }
118
119 void logTime(String evt) {
120 int now = millis();
121 println(evt + ": " + (now - lastMillis) + "ms");
122 lastMillis = now;
123 }
124
125 void setup() {
126 startMillis = lastMillis = millis();
127
128 // Initialize the Processing graphics environment
129 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
130 frameRate(targetFramerate);
131 noSmooth();
132 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
133 logTime("Created viewport");
134
135 // Create the GLucose engine to run the cubes
136 glucose = new GLucose(this, buildModel());
137 lx = glucose.lx;
138 lx.enableKeyboardTempo();
139 logTime("Built GLucose engine");
140
141 // Set the patterns
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");
150
151 // Preset manager
152 presetManager = new PresetManager();
153 logTime("Loaded presets");
154
155 // MIDI devices
156 midiEngine = new MidiEngine();
157 logTime("Setup MIDI devices");
158
159 // Build output driver
160 PandaMapping[] pandaMappings = buildPandaList();
161 pandaBoards = new PandaDriver[pandaMappings.length];
162 int pbi = 0;
163 for (PandaMapping pm : pandaMappings) {
164 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
165 }
166 mappingTool = new MappingTool(glucose, pandaMappings);
167 logTime("Built PandaDriver");
168
169 // Build overlay UI
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),
177
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),
181
182 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
183
184 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
185 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
186 };
187 uiMapping.setVisible(false);
188 logTime("Built overlay UI");
189
190 // Load logo image
191 logo = loadImage("data/logo.png");
192
193 // Setup camera
194 midX = TRAILER_WIDTH/2.;
195 midY = glucose.model.yMax/2;
196 midZ = TRAILER_DEPTH/2.;
197 eyeR = -290;
198 eyeA = .15;
199 eyeY = midY + 70;
200 eyeX = midX + eyeR*sin(eyeA);
201 eyeZ = midZ + eyeR*cos(eyeA);
202
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());
207 }});
208
209 println("Total setup: " + (millis() - startMillis) + "ms");
210 println("Hit the 'p' key to toggle Panda Board output");
211 }
212
213 /**
214 * Core render loop and drawing functionality.
215 */
216 void draw() {
217 // Draws the simulation and the 2D UI overlay
218 background(40);
219
220 color[] simulationColors;
221 color[] sendColors;
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();
228 }
229 if (debugMode) {
230 debugUI.maskColors(simulationColors);
231 debugUI.maskColors(sendColors);
232 }
233
234 camera(
235 eyeX, eyeY, eyeZ,
236 midX, midY, midZ,
237 0, -1, 0
238 );
239
240 translate(0, 40, 0);
241
242 noStroke();
243 fill(#141414);
244 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
245 fill(#070707);
246 stroke(#222222);
247 beginShape();
248 vertex(0, 0, 0);
249 vertex(TRAILER_WIDTH, 0, 0);
250 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
251 vertex(0, 0, TRAILER_DEPTH);
252 endShape();
253
254 // Draw the logo on the front of platform
255 pushMatrix();
256 translate(0, 0, -1);
257 float s = .07;
258 scale(s, -s, s);
259 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
260 popMatrix();
261
262 noStroke();
263 if (glucose.model.bassBox.exists) {
264 drawBassBox(glucose.model.bassBox, false);
265 }
266 for (Speaker speaker : glucose.model.speakers) {
267 drawSpeaker(speaker);
268 }
269 for (Cube c : glucose.model.cubes) {
270 drawCube(c);
271 }
272
273 noFill();
274 strokeWeight(2);
275 beginShape(POINTS);
276 for (Point p : glucose.model.points) {
277 stroke(simulationColors[p.index]);
278 vertex(p.x, p.y, p.z);
279 }
280 endShape();
281
282 // 2D Overlay UI
283 drawUI();
284
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);
289 float b = hsb[2];
290 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
291 }
292
293 // TODO(mcslee): move into GLucose engine
294 for (PandaDriver p : pandaBoards) {
295 p.send(sendColors);
296 }
297 }
298
299 void drawBassBox(BassBox b, boolean hasSub) {
300
301 float in = .15;
302
303 if (hasSub) {
304 noStroke();
305 fill(#191919);
306 pushMatrix();
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);
309 popMatrix();
310 }
311
312 noStroke();
313 fill(#393939);
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);
315
316 pushMatrix();
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);
322 lastOffset = offset;
323 }
324 popMatrix();
325
326 pushMatrix();
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) {
329 pushMatrix();
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);
333 }
334 popMatrix();
335 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
336 }
337 popMatrix();
338
339 pushMatrix();
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);
344 popMatrix();
345
346 }
347
348 void drawCube(Cube c) {
349 float in = .15;
350 noStroke();
351 fill(#393939);
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);
353 }
354
355 void drawSpeaker(Speaker s) {
356 float in = .15;
357
358 noStroke();
359 fill(#191919);
360 pushMatrix();
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);
366
367 fill(#222222);
368 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
369 popMatrix();
370
371 noStroke();
372 fill(#393939);
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);
374 }
375
376 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
377 pushMatrix();
378 translate(x, y, z);
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;
384
385 beginShape();
386 vertex(0, 0);
387 vertex(wid, 0);
388 vertex(wid, yd);
389 vertex(wid - sw, yd);
390 vertex(wid - sw, sw);
391 vertex(0, sw);
392 endShape();
393 beginShape();
394 vertex(0, sw);
395 vertex(0, yd);
396 vertex(wid - sw, yd);
397 vertex(wid - sw, yd - sw);
398 vertex(sw, yd - sw);
399 vertex(sw, sw);
400 endShape();
401
402 translate(wid, 0, 0);
403 rotate(HALF_PI, 0, -1, 0);
404 }
405 popMatrix();
406 }
407
408 void drawUI() {
409 camera();
410 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
411 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
412 ((PGraphicsOpenGL)g).endGL();
413 strokeWeight(1);
414
415 if (uiOn) {
416 for (UIContext context : overlays) {
417 context.draw();
418 }
419 }
420
421 // Always draw FPS meter
422 fill(#555555);
423 textSize(9);
424 textAlign(LEFT, BASELINE);
425 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
426
427 if (debugMode) {
428 debugUI.draw();
429 }
430 }
431
432
433 /**
434 * Top-level keyboard event handling
435 */
436 void keyPressed() {
437 if (mappingMode) {
438 mappingTool.keyPressed(uiMapping);
439 }
440 switch (key) {
441 case '1':
442 case '2':
443 case '3':
444 case '4':
445 case '5':
446 case '6':
447 case '7':
448 case '8':
449 if (!midiEngine.isQwertyEnabled()) {
450 presetManager.select(midiEngine.getFocusedDeck(), key - '1');
451 }
452 break;
453
454 case '!':
455 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 0);
456 break;
457 case '@':
458 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 1);
459 break;
460 case '#':
461 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 2);
462 break;
463 case '$':
464 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 3);
465 break;
466 case '%':
467 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 4);
468 break;
469 case '^':
470 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 5);
471 break;
472 case '&':
473 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 6);
474 break;
475 case '*':
476 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 7);
477 break;
478
479 case '-':
480 case '_':
481 frameRate(--targetFramerate);
482 break;
483 case '=':
484 case '+':
485 frameRate(++targetFramerate);
486 break;
487 case 'b':
488 effects.boom.trigger();
489 break;
490 case 'd':
491 if (!midiEngine.isQwertyEnabled()) {
492 debugMode = !debugMode;
493 println("Debug output: " + (debugMode ? "ON" : "OFF"));
494 }
495 break;
496 case 'm':
497 if (!midiEngine.isQwertyEnabled()) {
498 mappingMode = !mappingMode;
499 uiPatternA.setVisible(!mappingMode);
500 uiMapping.setVisible(mappingMode);
501 if (mappingMode) {
502 restoreToPattern = lx.getPattern();
503 lx.setPatterns(new LXPattern[] { mappingTool });
504 } else {
505 lx.setPatterns(patterns);
506 LXTransition pop = restoreToPattern.getTransition();
507 restoreToPattern.setTransition(null);
508 lx.goPattern(restoreToPattern);
509 restoreToPattern.setTransition(pop);
510 }
511 }
512 break;
513 case 't':
514 if (!midiEngine.isQwertyEnabled()) {
515 lx.engine.setThreaded(!lx.engine.isThreaded());
516 }
517 break;
518 case 'p':
519 for (PandaDriver p : pandaBoards) {
520 p.toggle();
521 }
522 break;
523 case 'u':
524 if (!midiEngine.isQwertyEnabled()) {
525 uiOn = !uiOn;
526 }
527 break;
528 }
529 }
530
531 /**
532 * Top-level mouse event handling
533 */
534 int mx, my;
535 void mousePressed() {
536 boolean debugged = false;
537 if (debugMode) {
538 debugged = debugUI.mousePressed();
539 }
540 if (!debugged) {
541 for (UIContext context : overlays) {
542 context.mousePressed(mouseX, mouseY);
543 }
544 }
545 mx = mouseX;
546 my = mouseY;
547 }
548
549 void mouseDragged() {
550 boolean dragged = false;
551 for (UIContext context : overlays) {
552 dragged |= context.mouseDragged(mouseX, mouseY);
553 }
554 if (!dragged) {
555 int dx = mouseX - mx;
556 int dy = mouseY - my;
557 mx = mouseX;
558 my = mouseY;
559 eyeA += dx*.003;
560 eyeX = midX + eyeR*sin(eyeA);
561 eyeZ = midZ + eyeR*cos(eyeA);
562 eyeY += dy;
563 }
564 }
565
566 void mouseReleased() {
567 for (UIContext context : overlays) {
568 context.mouseReleased(mouseX, mouseY);
569 }
570 }
571
572 void mouseWheel(int delta) {
573 boolean wheeled = false;
574 for (UIContext context : overlays) {
575 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
576 }
577
578 if (!wheeled) {
579 eyeR = constrain(eyeR - delta, -500, -80);
580 eyeX = midX + eyeR*sin(eyeA);
581 eyeZ = midZ + eyeR*cos(eyeA);
582 }
583 }