MidiMusic pattern in progress and some framework changes
[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 = 7;
43 final int NumBackTowers = 9;
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 MidiEngine midiEngine;
55
56 // Display configuration mode
57 boolean mappingMode = false;
58 boolean debugMode = false;
59 DebugUI debugUI;
60 boolean uiOn = true;
61 LXPattern restoreToPattern = null;
62 PImage logo;
63
64 // Handles to UI objects
65 UIContext[] overlays;
66 UIPatternDeck uiPatternA;
67 UICrossfader uiCrossfader;
68 UIMidi uiMidi;
69 UIMapping uiMapping;
70 UIDebugText uiDebugText;
71
72 // Camera variables
73 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
74
75 /**
76 * Engine construction and initialization.
77 */
78
79 LXTransition _transition(GLucose glucose) {
80 return new DissolveTransition(glucose.lx).setDuration(1000);
81 }
82
83 LXPattern[] _leftPatterns(GLucose glucose) {
84 LXPattern[] patterns = patterns(glucose);
85 for (LXPattern p : patterns) {
86 p.setTransition(_transition(glucose));
87 }
88 return patterns;
89 }
90
91 LXPattern[] _rightPatterns(GLucose glucose) {
92 LXPattern[] patterns = _leftPatterns(glucose);
93 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
94 int i = 0;
95 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
96 for (LXPattern p : patterns) {
97 rightPatterns[i++] = p;
98 }
99 return rightPatterns;
100 }
101
102
103 void logTime(String evt) {
104 int now = millis();
105 println(evt + ": " + (now - lastMillis) + "ms");
106 lastMillis = now;
107 }
108
109 void setup() {
110 startMillis = lastMillis = millis();
111
112 // Initialize the Processing graphics environment
113 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
114 frameRate(targetFramerate);
115 noSmooth();
116 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
117 logTime("Created viewport");
118
119 // Create the GLucose engine to run the cubes
120 glucose = new GLucose(this, buildModel());
121 lx = glucose.lx;
122 lx.enableKeyboardTempo();
123 logTime("Built GLucose engine");
124
125 // Set the patterns
126 Engine engine = lx.engine;
127 engine.setPatterns(patterns = _leftPatterns(glucose));
128 engine.addDeck(_rightPatterns(glucose));
129 logTime("Built patterns");
130 glucose.setTransitions(transitions(glucose));
131 logTime("Built transitions");
132 glucose.lx.addEffects(effects(glucose));
133 logTime("Built effects");
134
135 // MIDI devices
136 midiEngine = new MidiEngine();
137 logTime("Setup MIDI devices");
138
139 // Build output driver
140 PandaMapping[] pandaMappings = buildPandaList();
141 pandaBoards = new PandaDriver[pandaMappings.length];
142 int pbi = 0;
143 for (PandaMapping pm : pandaMappings) {
144 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
145 }
146 mappingTool = new MappingTool(glucose, pandaMappings);
147 logTime("Built PandaDriver");
148
149 // Build overlay UI
150 debugUI = new DebugUI(pandaMappings);
151 overlays = new UIContext[] {
152 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
153 new UIBlendMode(4, 332, 140, 86),
154 new UIEffects(4, 422, 140, 144),
155 new UITempo(4, 570, 140, 50),
156 new UISpeed(4, 624, 140, 50),
157
158 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
159 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
160 new UIOutput(width-144, 494, 140, 106),
161
162 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
163
164 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
165 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
166 };
167 uiMapping.setVisible(false);
168 logTime("Built overlay UI");
169
170 // Load logo image
171 logo = loadImage("data/logo.png");
172
173 // Setup camera
174 midX = TRAILER_WIDTH/2.;
175 midY = glucose.model.yMax/2;
176 midZ = TRAILER_DEPTH/2.;
177 eyeR = -290;
178 eyeA = .15;
179 eyeY = midY + 70;
180 eyeX = midX + eyeR*sin(eyeA);
181 eyeZ = midZ + eyeR*cos(eyeA);
182
183 // Add mouse scrolling event support
184 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
185 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
186 mouseWheel(mwe.getWheelRotation());
187 }});
188
189 println("Total setup: " + (millis() - startMillis) + "ms");
190 println("Hit the 'p' key to toggle Panda Board output");
191 }
192
193 /**
194 * Core render loop and drawing functionality.
195 */
196 void draw() {
197 // Draws the simulation and the 2D UI overlay
198 background(40);
199 color[] colors = glucose.getColors();
200
201 String displayMode = uiCrossfader.getDisplayMode();
202 if (displayMode == "A") {
203 colors = lx.engine.getDeck(0).getColors();
204 } else if (displayMode == "B") {
205 colors = lx.engine.getDeck(1).getColors();
206 }
207 if (debugMode) {
208 debugUI.maskColors(colors);
209 }
210
211 camera(
212 eyeX, eyeY, eyeZ,
213 midX, midY, midZ,
214 0, -1, 0
215 );
216
217 translate(0, 40, 0);
218
219 noStroke();
220 fill(#141414);
221 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
222 fill(#070707);
223 stroke(#222222);
224 beginShape();
225 vertex(0, 0, 0);
226 vertex(TRAILER_WIDTH, 0, 0);
227 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
228 vertex(0, 0, TRAILER_DEPTH);
229 endShape();
230
231 // Draw the logo on the front of platform
232 pushMatrix();
233 translate(0, 0, -1);
234 float s = .07;
235 scale(s, -s, s);
236 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
237 popMatrix();
238
239 noStroke();
240 if (glucose.model.bassBox.exists) {
241 drawBassBox(glucose.model.bassBox, false);
242 }
243 for (Speaker speaker : glucose.model.speakers) {
244 drawSpeaker(speaker);
245 }
246 for (Cube c : glucose.model.cubes) {
247 drawCube(c);
248 }
249
250 noFill();
251 strokeWeight(2);
252 beginShape(POINTS);
253 for (Point p : glucose.model.points) {
254 stroke(colors[p.index]);
255 vertex(p.fx, p.fy, p.fz);
256 }
257 endShape();
258
259 // 2D Overlay UI
260 drawUI();
261
262 // Send output colors
263 color[] sendColors = glucose.getColors();
264 if (debugMode) {
265 debugUI.maskColors(sendColors);
266 }
267
268 // Gamma correction here. Apply a cubic to the brightness
269 // for better representation of dynamic range
270 for (int i = 0; i < sendColors.length; ++i) {
271 float b = brightness(sendColors[i]) / 100.f;
272 sendColors[i] = color(
273 hue(sendColors[i]),
274 saturation(sendColors[i]),
275 (b*b*b) * 100.
276 );
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 '-':
428 case '_':
429 frameRate(--targetFramerate);
430 break;
431 case '=':
432 case '+':
433 frameRate(++targetFramerate);
434 break;
435 case 'b':
436 EFF_boom.trigger();
437 break;
438 case 'd':
439 if (!midiEngine.isQwertyEnabled()) {
440 debugMode = !debugMode;
441 println("Debug output: " + (debugMode ? "ON" : "OFF"));
442 }
443 break;
444 case 'm':
445 if (!midiEngine.isQwertyEnabled()) {
446 mappingMode = !mappingMode;
447 uiPatternA.setVisible(!mappingMode);
448 uiMapping.setVisible(mappingMode);
449 if (mappingMode) {
450 restoreToPattern = lx.getPattern();
451 lx.setPatterns(new LXPattern[] { mappingTool });
452 } else {
453 lx.setPatterns(patterns);
454 LXTransition pop = restoreToPattern.getTransition();
455 restoreToPattern.setTransition(null);
456 lx.goPattern(restoreToPattern);
457 restoreToPattern.setTransition(pop);
458 }
459 }
460 break;
461 case 'p':
462 for (PandaDriver p : pandaBoards) {
463 p.toggle();
464 }
465 break;
466 case 'u':
467 if (!midiEngine.isQwertyEnabled()) {
468 uiOn = !uiOn;
469 }
470 break;
471 }
472 }
473
474 /**
475 * Top-level mouse event handling
476 */
477 int mx, my;
478 void mousePressed() {
479 boolean debugged = false;
480 if (debugMode) {
481 debugged = debugUI.mousePressed();
482 }
483 if (!debugged) {
484 for (UIContext context : overlays) {
485 context.mousePressed(mouseX, mouseY);
486 }
487 }
488 mx = mouseX;
489 my = mouseY;
490 }
491
492 void mouseDragged() {
493 boolean dragged = false;
494 for (UIContext context : overlays) {
495 dragged |= context.mouseDragged(mouseX, mouseY);
496 }
497 if (!dragged) {
498 int dx = mouseX - mx;
499 int dy = mouseY - my;
500 mx = mouseX;
501 my = mouseY;
502 eyeA += dx*.003;
503 eyeX = midX + eyeR*sin(eyeA);
504 eyeZ = midZ + eyeR*cos(eyeA);
505 eyeY += dy;
506 }
507 }
508
509 void mouseReleased() {
510 for (UIContext context : overlays) {
511 context.mouseReleased(mouseX, mouseY);
512 }
513 }
514
515 void mouseWheel(int delta) {
516 boolean wheeled = false;
517 for (UIContext context : overlays) {
518 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
519 }
520
521 if (!wheeled) {
522 eyeR = constrain(eyeR - delta, -500, -80);
523 eyeX = midX + eyeR*sin(eyeA);
524 eyeZ = midZ + eyeR*cos(eyeA);
525 }
526 }