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