updated midi stuff
[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 = 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 float[] hsb = new float[3];
64
65 // Handles to UI objects
66 UIContext[] overlays;
67 UIPatternDeck uiPatternA;
68 UICrossfader uiCrossfader;
69 UIMidi uiMidi;
70 UIMapping uiMapping;
71 UIDebugText uiDebugText;
72
73 // Camera variables
74 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
75
76 /**
77 * Engine construction and initialization.
78 */
79
80 LXTransition _transition(GLucose glucose) {
81 return new DissolveTransition(glucose.lx).setDuration(1000);
82 }
83
84 LXPattern[] _leftPatterns(GLucose glucose) {
85 LXPattern[] patterns = patterns(glucose);
86 for (LXPattern p : patterns) {
87 p.setTransition(_transition(glucose));
88 }
89 return patterns;
90 }
91
92 LXPattern[] _rightPatterns(GLucose glucose) {
93 LXPattern[] patterns = _leftPatterns(glucose);
94 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
95 int i = 0;
96 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
97 for (LXPattern p : patterns) {
98 rightPatterns[i++] = p;
99 }
100 return rightPatterns;
101 }
102
103
104 void logTime(String evt) {
105 int now = millis();
106 println(evt + ": " + (now - lastMillis) + "ms");
107 lastMillis = now;
108 }
109
110 void setup() {
111 startMillis = lastMillis = millis();
112
113 // Initialize the Processing graphics environment
114 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
115 frameRate(targetFramerate);
116 noSmooth();
117 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
118 logTime("Created viewport");
119
120 // Create the GLucose engine to run the cubes
121 glucose = new GLucose(this, buildModel());
122 lx = glucose.lx;
123 lx.enableKeyboardTempo();
124 logTime("Built GLucose engine");
125
126 // Set the patterns
127 Engine engine = lx.engine;
128 engine.setPatterns(patterns = _leftPatterns(glucose));
129 engine.addDeck(_rightPatterns(glucose));
130 logTime("Built patterns");
131 glucose.setTransitions(transitions(glucose));
132 logTime("Built transitions");
133 glucose.lx.addEffects(effects(glucose));
134 logTime("Built effects");
135
136 // MIDI devices
137 midiEngine = new MidiEngine();
138 logTime("Setup MIDI devices");
139
140 // Build output driver
141 PandaMapping[] pandaMappings = buildPandaList();
142 pandaBoards = new PandaDriver[pandaMappings.length];
143 int pbi = 0;
144 for (PandaMapping pm : pandaMappings) {
145 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
146 }
147 mappingTool = new MappingTool(glucose, pandaMappings);
148 logTime("Built PandaDriver");
149
150 // Build overlay UI
151 debugUI = new DebugUI(pandaMappings);
152 overlays = new UIContext[] {
153 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
154 new UIBlendMode(4, 332, 140, 86),
155 new UIEffects(4, 422, 140, 144),
156 new UITempo(4, 570, 140, 50),
157 new UISpeed(4, 624, 140, 50),
158
159 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
160 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
161 new UIOutput(width-144, 494, 140, 106),
162
163 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
164
165 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
166 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
167 };
168 uiMapping.setVisible(false);
169 logTime("Built overlay UI");
170
171 // Load logo image
172 logo = loadImage("data/logo.png");
173
174 // Setup camera
175 midX = TRAILER_WIDTH/2.;
176 midY = glucose.model.yMax/2;
177 midZ = TRAILER_DEPTH/2.;
178 eyeR = -290;
179 eyeA = .15;
180 eyeY = midY + 70;
181 eyeX = midX + eyeR*sin(eyeA);
182 eyeZ = midZ + eyeR*cos(eyeA);
183
184 // Add mouse scrolling event support
185 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
186 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
187 mouseWheel(mwe.getWheelRotation());
188 }});
189
190 println("Total setup: " + (millis() - startMillis) + "ms");
191 println("Hit the 'p' key to toggle Panda Board output");
192 }
193
194 /**
195 * Core render loop and drawing functionality.
196 */
197 void draw() {
198 // Draws the simulation and the 2D UI overlay
199 background(40);
200
201 color[] simulationColors;
202 color[] sendColors;
203 simulationColors = sendColors = glucose.getColors();
204 String displayMode = uiCrossfader.getDisplayMode();
205 if (displayMode == "A") {
206 simulationColors = lx.engine.getDeck(0).getColors();
207 } else if (displayMode == "B") {
208 simulationColors = lx.engine.getDeck(1).getColors();
209 }
210 if (debugMode) {
211 debugUI.maskColors(simulationColors);
212 debugUI.maskColors(sendColors);
213 }
214
215 camera(
216 eyeX, eyeY, eyeZ,
217 midX, midY, midZ,
218 0, -1, 0
219 );
220
221 translate(0, 40, 0);
222
223 noStroke();
224 fill(#141414);
225 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
226 fill(#070707);
227 stroke(#222222);
228 beginShape();
229 vertex(0, 0, 0);
230 vertex(TRAILER_WIDTH, 0, 0);
231 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
232 vertex(0, 0, TRAILER_DEPTH);
233 endShape();
234
235 // Draw the logo on the front of platform
236 pushMatrix();
237 translate(0, 0, -1);
238 float s = .07;
239 scale(s, -s, s);
240 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
241 popMatrix();
242
243 noStroke();
244 if (glucose.model.bassBox.exists) {
245 drawBassBox(glucose.model.bassBox, false);
246 }
247 for (Speaker speaker : glucose.model.speakers) {
248 drawSpeaker(speaker);
249 }
250 for (Cube c : glucose.model.cubes) {
251 drawCube(c);
252 }
253
254 noFill();
255 strokeWeight(2);
256 beginShape(POINTS);
257 for (Point p : glucose.model.points) {
258 stroke(simulationColors[p.index]);
259 vertex(p.x, p.y, p.z);
260 }
261 endShape();
262
263 // 2D Overlay UI
264 drawUI();
265
266 // Gamma correction here. Apply a cubic to the brightness
267 // for better representation of dynamic range
268 for (int i = 0; i < sendColors.length; ++i) {
269 lx.RGBtoHSB(sendColors[i], hsb);
270 float b = hsb[2];
271 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
272 }
273
274 // TODO(mcslee): move into GLucose engine
275 for (PandaDriver p : pandaBoards) {
276 p.send(sendColors);
277 }
278 }
279
280 void drawBassBox(BassBox b, boolean hasSub) {
281
282 float in = .15;
283
284 if (hasSub) {
285 noStroke();
286 fill(#191919);
287 pushMatrix();
288 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
289 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
290 popMatrix();
291 }
292
293 noStroke();
294 fill(#393939);
295 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);
296
297 pushMatrix();
298 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
299 float lastOffset = 0;
300 for (float offset : BoothFloor.STRIP_OFFSETS) {
301 translate(offset - lastOffset, 0, 0);
302 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
303 lastOffset = offset;
304 }
305 popMatrix();
306
307 pushMatrix();
308 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
309 for (int j = 0; j < 2; ++j) {
310 pushMatrix();
311 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
312 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
313 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
314 }
315 popMatrix();
316 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
317 }
318 popMatrix();
319
320 pushMatrix();
321 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
322 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
323 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
324 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
325 popMatrix();
326
327 }
328
329 void drawCube(Cube c) {
330 float in = .15;
331 noStroke();
332 fill(#393939);
333 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);
334 }
335
336 void drawSpeaker(Speaker s) {
337 float in = .15;
338
339 noStroke();
340 fill(#191919);
341 pushMatrix();
342 translate(s.x, s.y, s.z);
343 rotate(s.ry / 180. * PI, 0, -1, 0);
344 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
345 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
346 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
347
348 fill(#222222);
349 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
350 popMatrix();
351
352 noStroke();
353 fill(#393939);
354 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);
355 }
356
357 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
358 pushMatrix();
359 translate(x, y, z);
360 rotate(rx / 180. * PI, -1, 0, 0);
361 rotate(ry / 180. * PI, 0, -1, 0);
362 rotate(rz / 180. * PI, 0, 0, -1);
363 for (int i = 0; i < 4; ++i) {
364 float wid = (i % 2 == 0) ? xd : zd;
365
366 beginShape();
367 vertex(0, 0);
368 vertex(wid, 0);
369 vertex(wid, yd);
370 vertex(wid - sw, yd);
371 vertex(wid - sw, sw);
372 vertex(0, sw);
373 endShape();
374 beginShape();
375 vertex(0, sw);
376 vertex(0, yd);
377 vertex(wid - sw, yd);
378 vertex(wid - sw, yd - sw);
379 vertex(sw, yd - sw);
380 vertex(sw, sw);
381 endShape();
382
383 translate(wid, 0, 0);
384 rotate(HALF_PI, 0, -1, 0);
385 }
386 popMatrix();
387 }
388
389 void drawUI() {
390 camera();
391 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
392 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
393 ((PGraphicsOpenGL)g).endGL();
394 strokeWeight(1);
395
396 if (uiOn) {
397 for (UIContext context : overlays) {
398 context.draw();
399 }
400 }
401
402 // Always draw FPS meter
403 fill(#555555);
404 textSize(9);
405 textAlign(LEFT, BASELINE);
406 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
407
408 if (debugMode) {
409 debugUI.draw();
410 }
411 }
412
413
414 /**
415 * Top-level keyboard event handling
416 */
417 void keyPressed() {
418 if (mappingMode) {
419 mappingTool.keyPressed(uiMapping);
420 }
421 switch (key) {
422 case '-':
423 case '_':
424 frameRate(--targetFramerate);
425 break;
426 case '=':
427 case '+':
428 frameRate(++targetFramerate);
429 break;
430 case 'b':
431 EFF_boom.trigger();
432 break;
433 case 'd':
434 if (!midiEngine.isQwertyEnabled()) {
435 debugMode = !debugMode;
436 println("Debug output: " + (debugMode ? "ON" : "OFF"));
437 }
438 break;
439 case 'm':
440 if (!midiEngine.isQwertyEnabled()) {
441 mappingMode = !mappingMode;
442 uiPatternA.setVisible(!mappingMode);
443 uiMapping.setVisible(mappingMode);
444 if (mappingMode) {
445 restoreToPattern = lx.getPattern();
446 lx.setPatterns(new LXPattern[] { mappingTool });
447 } else {
448 lx.setPatterns(patterns);
449 LXTransition pop = restoreToPattern.getTransition();
450 restoreToPattern.setTransition(null);
451 lx.goPattern(restoreToPattern);
452 restoreToPattern.setTransition(pop);
453 }
454 }
455 break;
456 case 't':
457 if (!midiEngine.isQwertyEnabled()) {
458 lx.engine.setThreaded(!lx.engine.isThreaded());
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 }