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