Turn off track selection LEDs
[SugarCubes.git] / _Internals.pde
CommitLineData
49815cc0 1/**
1ecdb44a
MS
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
49815cc0
MS
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
16import glucose.*;
17import glucose.control.*;
3f8be614 18import glucose.effect.*;
f3f5a876 19import glucose.model.*;
49815cc0 20import glucose.pattern.*;
f3f5a876 21import glucose.transform.*;
49815cc0 22import glucose.transition.*;
49815cc0 23import heronarts.lx.*;
3f8be614 24import heronarts.lx.control.*;
49815cc0 25import heronarts.lx.effect.*;
49815cc0 26import heronarts.lx.modulator.*;
f3f5a876 27import heronarts.lx.pattern.*;
49815cc0
MS
28import heronarts.lx.transition.*;
29import ddf.minim.*;
30import ddf.minim.analysis.*;
31import processing.opengl.*;
5d70e4d7 32import rwmidi.*;
49815cc0
MS
33
34final int VIEWPORT_WIDTH = 900;
35final int VIEWPORT_HEIGHT = 700;
0e3c5542 36
92c06c97 37// The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
87998ff3
MS
38final float TRAILER_WIDTH = 240;
39final float TRAILER_DEPTH = 97;
40final float TRAILER_HEIGHT = 33;
41
41f26e8f 42final int MaxCubeHeight = 5;
41c436e4 43final int NumBackTowers = 11;
7e3329d2 44
51d0d59a 45int targetFramerate = 60;
49815cc0 46int startMillis, lastMillis;
a898d79b
MS
47
48// Core engine variables
49815cc0
MS
49GLucose glucose;
50HeronLX lx;
51LXPattern[] patterns;
a898d79b 52MappingTool mappingTool;
79ae8245 53PandaDriver[] pandaBoards;
d6ac1ee8 54MidiEngine midiEngine;
a898d79b
MS
55
56// Display configuration mode
bf551144 57boolean mappingMode = false;
cc9fcf4b 58boolean debugMode = false;
554e38ff 59DebugUI debugUI;
34327c96
MS
60boolean uiOn = true;
61LXPattern restoreToPattern = null;
4c640acc 62PImage logo;
a41f334c 63float[] hsb = new float[3];
d626bc9b 64
a898d79b 65// Handles to UI objects
d626bc9b
MS
66UIContext[] overlays;
67UIPatternDeck uiPatternA;
a898d79b 68UICrossfader uiCrossfader;
a8d55ade 69UIMidi uiMidi;
d626bc9b
MS
70UIMapping uiMapping;
71UIDebugText uiDebugText;
cc9fcf4b 72
0a9f99cc 73// Camera variables
bda5421d 74float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
0a9f99cc 75
34327c96
MS
76/**
77 * Engine construction and initialization.
78 */
d1dcc4b5
MS
79
80LXTransition _transition(GLucose glucose) {
81 return new DissolveTransition(glucose.lx).setDuration(1000);
82}
83
84LXPattern[] _leftPatterns(GLucose glucose) {
d626bc9b
MS
85 LXPattern[] patterns = patterns(glucose);
86 for (LXPattern p : patterns) {
d1dcc4b5 87 p.setTransition(_transition(glucose));
d626bc9b
MS
88 }
89 return patterns;
90}
91
d1dcc4b5
MS
92LXPattern[] _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}
e5308763 102
d1dcc4b5 103
34327c96
MS
104void logTime(String evt) {
105 int now = millis();
106 println(evt + ": " + (now - lastMillis) + "ms");
107 lastMillis = now;
108}
109
49815cc0
MS
110void setup() {
111 startMillis = lastMillis = millis();
112
113 // Initialize the Processing graphics environment
114 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
0e3c5542 115 frameRate(targetFramerate);
3f8be614 116 noSmooth();
49815cc0
MS
117 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
118 logTime("Created viewport");
119
120 // Create the GLucose engine to run the cubes
186bc4d3 121 glucose = new GLucose(this, buildModel());
49815cc0 122 lx = glucose.lx;
cc9fcf4b 123 lx.enableKeyboardTempo();
49815cc0
MS
124 logTime("Built GLucose engine");
125
126 // Set the patterns
d626bc9b 127 Engine engine = lx.engine;
d1dcc4b5
MS
128 engine.setPatterns(patterns = _leftPatterns(glucose));
129 engine.addDeck(_rightPatterns(glucose));
49815cc0 130 logTime("Built patterns");
a898d79b
MS
131 glucose.setTransitions(transitions(glucose));
132 logTime("Built transitions");
133 glucose.lx.addEffects(effects(glucose));
49815cc0 134 logTime("Built effects");
4214e9a2
MS
135
136 // MIDI devices
137 midiEngine = new MidiEngine();
138 logTime("Setup MIDI devices");
139
e73ef85d 140 // Build output driver
186bc4d3 141 PandaMapping[] pandaMappings = buildPandaList();
45f43cc2
MS
142 pandaBoards = new PandaDriver[pandaMappings.length];
143 int pbi = 0;
144 for (PandaMapping pm : pandaMappings) {
44b8de9c 145 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
45f43cc2
MS
146 }
147 mappingTool = new MappingTool(glucose, pandaMappings);
148 logTime("Built PandaDriver");
a8d55ade 149
49815cc0 150 // Build overlay UI
45f43cc2 151 debugUI = new DebugUI(pandaMappings);
d626bc9b 152 overlays = new UIContext[] {
a8d55ade
MS
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),
d6ac1ee8 160 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
4df91daf 161 new UIOutput(width-144, 494, 140, 106),
d626bc9b 162
a8d55ade 163 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
d626bc9b 164
a8d55ade
MS
165 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
166 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
d626bc9b
MS
167 };
168 uiMapping.setVisible(false);
49815cc0 169 logTime("Built overlay UI");
4c640acc
MS
170
171 // Load logo image
172 logo = loadImage("data/logo.png");
173
0a9f99cc 174 // Setup camera
d626bc9b 175 midX = TRAILER_WIDTH/2.;
0a9f99cc 176 midY = glucose.model.yMax/2;
e0cea600
MS
177 midZ = TRAILER_DEPTH/2.;
178 eyeR = -290;
0a9f99cc 179 eyeA = .15;
d626bc9b 180 eyeY = midY + 70;
0a9f99cc
MS
181 eyeX = midX + eyeR*sin(eyeA);
182 eyeZ = midZ + eyeR*cos(eyeA);
d6ac1ee8
MS
183
184 // Add mouse scrolling event support
bda5421d
MS
185 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
186 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
187 mouseWheel(mwe.getWheelRotation());
188 }});
189
49815cc0 190 println("Total setup: " + (millis() - startMillis) + "ms");
e73ef85d 191 println("Hit the 'p' key to toggle Panda Board output");
49815cc0
MS
192}
193
34327c96
MS
194/**
195 * Core render loop and drawing functionality.
196 */
49815cc0 197void draw() {
0a9f99cc
MS
198 // Draws the simulation and the 2D UI overlay
199 background(40);
a898d79b 200
19d16a16
MS
201 color[] simulationColors;
202 color[] sendColors;
203 simulationColors = sendColors = glucose.getColors();
a898d79b 204 String displayMode = uiCrossfader.getDisplayMode();
d626bc9b 205 if (displayMode == "A") {
19d16a16 206 simulationColors = lx.engine.getDeck(0).getColors();
d626bc9b 207 } else if (displayMode == "B") {
19d16a16 208 simulationColors = lx.engine.getDeck(1).getColors();
d626bc9b 209 }
554e38ff 210 if (debugMode) {
19d16a16
MS
211 debugUI.maskColors(simulationColors);
212 debugUI.maskColors(sendColors);
554e38ff 213 }
51d0d59a 214
0a9f99cc
MS
215 camera(
216 eyeX, eyeY, eyeZ,
217 midX, midY, midZ,
218 0, -1, 0
219 );
51d0d59a 220
a8d55ade 221 translate(0, 40, 0);
d626bc9b 222
51d0d59a
MS
223 noStroke();
224 fill(#141414);
87998ff3 225 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
51d0d59a
MS
226 fill(#070707);
227 stroke(#222222);
0a9f99cc 228 beginShape();
51d0d59a 229 vertex(0, 0, 0);
87998ff3
MS
230 vertex(TRAILER_WIDTH, 0, 0);
231 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
232 vertex(0, 0, TRAILER_DEPTH);
51d0d59a 233 endShape();
4c640acc
MS
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();
0a9f99cc 242
51d0d59a 243 noStroke();
e89eda9f
MS
244 if (glucose.model.bassBox.exists) {
245 drawBassBox(glucose.model.bassBox, false);
246 }
247 for (Speaker speaker : glucose.model.speakers) {
248 drawSpeaker(speaker);
249 }
51d0d59a
MS
250 for (Cube c : glucose.model.cubes) {
251 drawCube(c);
252 }
253
0a9f99cc
MS
254 noFill();
255 strokeWeight(2);
256 beginShape(POINTS);
e89eda9f 257 for (Point p : glucose.model.points) {
19d16a16 258 stroke(simulationColors[p.index]);
190d91c2 259 vertex(p.x, p.y, p.z);
0a9f99cc
MS
260 }
261 endShape();
262
d626bc9b 263 // 2D Overlay UI
0a9f99cc 264 drawUI();
d626bc9b 265
6702151a
MS
266 // Gamma correction here. Apply a cubic to the brightness
267 // for better representation of dynamic range
7f782b99 268 for (int i = 0; i < sendColors.length; ++i) {
a41f334c
MS
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));
6702151a 272 }
e7f14d4d 273
e73ef85d 274 // TODO(mcslee): move into GLucose engine
79ae8245 275 for (PandaDriver p : pandaBoards) {
7f782b99 276 p.send(sendColors);
e73ef85d 277 }
49815cc0
MS
278}
279
e89eda9f
MS
280void drawBassBox(BassBox b, boolean hasSub) {
281
92c06c97 282 float in = .15;
e89eda9f
MS
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 }
e76480d4
MS
292
293 noStroke();
294 fill(#393939);
92c06c97
MS
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
39011e7e
MS
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
92c06c97
MS
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);
ab77005f 325 popMatrix();
e76480d4 326
92c06c97
MS
327}
328
51d0d59a 329void drawCube(Cube c) {
254d34c0 330 float in = .15;
e76480d4
MS
331 noStroke();
332 fill(#393939);
254d34c0 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);
51d0d59a
MS
334}
335
e76480d4
MS
336void 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);
254fbb68
MS
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);
e76480d4
MS
350 popMatrix();
351
e76480d4
MS
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);
ab77005f 355}
e76480d4 356
51d0d59a
MS
357void 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);
e0cea600 360 rotate(rx / 180. * PI, -1, 0, 0);
51d0d59a 361 rotate(ry / 180. * PI, 0, -1, 0);
e0cea600 362 rotate(rz / 180. * PI, 0, 0, -1);
51d0d59a
MS
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
49815cc0 389void drawUI() {
d626bc9b
MS
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
49815cc0 396 if (uiOn) {
d626bc9b
MS
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();
49815cc0 410 }
49815cc0
MS
411}
412
4c640acc 413
34327c96
MS
414/**
415 * Top-level keyboard event handling
416 */
49815cc0 417void keyPressed() {
bf551144 418 if (mappingMode) {
d626bc9b 419 mappingTool.keyPressed(uiMapping);
bf551144 420 }
3f8be614 421 switch (key) {
0e3c5542
MS
422 case '-':
423 case '_':
424 frameRate(--targetFramerate);
425 break;
426 case '=':
427 case '+':
428 frameRate(++targetFramerate);
75e1ddde
MS
429 break;
430 case 'b':
3661fcee 431 EFF_boom.trigger();
75e1ddde 432 break;
cc9fcf4b 433 case 'd':
d6ac1ee8 434 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
435 debugMode = !debugMode;
436 println("Debug output: " + (debugMode ? "ON" : "OFF"));
437 }
554e38ff 438 break;
bf551144 439 case 'm':
d6ac1ee8 440 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
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 }
bf551144
MS
454 }
455 break;
19d16a16
MS
456 case 't':
457 if (!midiEngine.isQwertyEnabled()) {
458 lx.engine.setThreaded(!lx.engine.isThreaded());
459 }
460 break;
e73ef85d 461 case 'p':
79ae8245
MS
462 for (PandaDriver p : pandaBoards) {
463 p.toggle();
464 }
cc9fcf4b 465 break;
3f8be614 466 case 'u':
d6ac1ee8 467 if (!midiEngine.isQwertyEnabled()) {
4df91daf
MS
468 uiOn = !uiOn;
469 }
3f8be614 470 break;
49815cc0
MS
471 }
472}
473
34327c96
MS
474/**
475 * Top-level mouse event handling
476 */
0a9f99cc 477int mx, my;
0a9f99cc 478void mousePressed() {
d626bc9b
MS
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 }
0a9f99cc 487 }
d626bc9b
MS
488 mx = mouseX;
489 my = mouseY;
0a9f99cc
MS
490}
491
492void mouseDragged() {
d626bc9b
MS
493 boolean dragged = false;
494 for (UIContext context : overlays) {
495 dragged |= context.mouseDragged(mouseX, mouseY);
496 }
497 if (!dragged) {
0a9f99cc
MS
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
509void mouseReleased() {
d626bc9b
MS
510 for (UIContext context : overlays) {
511 context.mouseReleased(mouseX, mouseY);
512 }
0a9f99cc 513}
73687629 514
a0140e21 515void mouseWheel(int delta) {
d626bc9b
MS
516 boolean wheeled = false;
517 for (UIContext context : overlays) {
518 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
519 }
520
521 if (!wheeled) {
0ba6ac44
MS
522 eyeR = constrain(eyeR - delta, -500, -80);
523 eyeX = midX + eyeR*sin(eyeA);
524 eyeZ = midZ + eyeR*cos(eyeA);
525 }
bda5421d 526}