Add two virtual keyboard modes
[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
51d0d59a 42int targetFramerate = 60;
49815cc0 43int startMillis, lastMillis;
a898d79b
MS
44
45// Core engine variables
49815cc0
MS
46GLucose glucose;
47HeronLX lx;
48LXPattern[] patterns;
a898d79b 49MappingTool mappingTool;
79ae8245 50PandaDriver[] pandaBoards;
4df91daf
MS
51MidiListener midiQwertyKeys;
52MidiListener midiQwertyAPC;
a898d79b
MS
53
54// Display configuration mode
bf551144 55boolean mappingMode = false;
cc9fcf4b 56boolean debugMode = false;
554e38ff 57DebugUI debugUI;
34327c96
MS
58boolean uiOn = true;
59LXPattern restoreToPattern = null;
d626bc9b 60
a898d79b 61// Handles to UI objects
d626bc9b
MS
62UIContext[] overlays;
63UIPatternDeck uiPatternA;
a898d79b 64UICrossfader uiCrossfader;
a8d55ade 65UIMidi uiMidi;
d626bc9b
MS
66UIMapping uiMapping;
67UIDebugText uiDebugText;
cc9fcf4b 68
0a9f99cc 69// Camera variables
bda5421d 70float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
0a9f99cc 71
34327c96
MS
72/**
73 * Engine construction and initialization.
74 */
d626bc9b
MS
75LXPattern[] _patterns(GLucose glucose) {
76 LXPattern[] patterns = patterns(glucose);
77 for (LXPattern p : patterns) {
78 p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
79 }
80 return patterns;
81}
82
34327c96
MS
83void logTime(String evt) {
84 int now = millis();
85 println(evt + ": " + (now - lastMillis) + "ms");
86 lastMillis = now;
87}
88
49815cc0
MS
89void setup() {
90 startMillis = lastMillis = millis();
91
92 // Initialize the Processing graphics environment
93 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
0e3c5542 94 frameRate(targetFramerate);
3f8be614 95 noSmooth();
49815cc0
MS
96 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
97 logTime("Created viewport");
98
99 // Create the GLucose engine to run the cubes
186bc4d3 100 glucose = new GLucose(this, buildModel());
49815cc0 101 lx = glucose.lx;
cc9fcf4b 102 lx.enableKeyboardTempo();
49815cc0
MS
103 logTime("Built GLucose engine");
104
105 // Set the patterns
d626bc9b 106 Engine engine = lx.engine;
d626bc9b
MS
107 engine.setPatterns(patterns = _patterns(glucose));
108 engine.addDeck(_patterns(glucose));
49815cc0 109 logTime("Built patterns");
a898d79b
MS
110 glucose.setTransitions(transitions(glucose));
111 logTime("Built transitions");
112 glucose.lx.addEffects(effects(glucose));
49815cc0 113 logTime("Built effects");
e73ef85d
MS
114
115 // Build output driver
186bc4d3 116 PandaMapping[] pandaMappings = buildPandaList();
45f43cc2
MS
117 pandaBoards = new PandaDriver[pandaMappings.length];
118 int pbi = 0;
119 for (PandaMapping pm : pandaMappings) {
44b8de9c 120 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
45f43cc2
MS
121 }
122 mappingTool = new MappingTool(glucose, pandaMappings);
123 logTime("Built PandaDriver");
a8d55ade
MS
124
125 // MIDI devices
126 List<MidiListener> midiListeners = new ArrayList<MidiListener>();
4df91daf
MS
127 midiListeners.add(midiQwertyKeys = new MidiListener(MidiListener.KEYS));
128 midiListeners.add(midiQwertyAPC = new MidiListener(MidiListener.APC));
a8d55ade
MS
129 for (MidiInputDevice device : RWMidi.getInputDevices()) {
130 boolean enableDevice = device.getName().contains("APC");
131 midiListeners.add(new MidiListener(device).setEnabled(enableDevice));
132 }
133 SCMidiDevices.initializeStandardDevices(glucose);
134 logTime("Setup MIDI devices");
135
49815cc0 136 // Build overlay UI
45f43cc2 137 debugUI = new DebugUI(pandaMappings);
d626bc9b 138 overlays = new UIContext[] {
a8d55ade
MS
139 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
140 new UIBlendMode(4, 332, 140, 86),
141 new UIEffects(4, 422, 140, 144),
142 new UITempo(4, 570, 140, 50),
143 new UISpeed(4, 624, 140, 50),
144
145 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
4df91daf
MS
146 uiMidi = new UIMidi(midiListeners, width-144, 332, 140, 158),
147 new UIOutput(width-144, 494, 140, 106),
d626bc9b 148
a8d55ade 149 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
d626bc9b 150
a8d55ade
MS
151 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
152 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
d626bc9b
MS
153 };
154 uiMapping.setVisible(false);
49815cc0 155 logTime("Built overlay UI");
1ecdb44a 156
0a9f99cc 157 // Setup camera
d626bc9b 158 midX = TRAILER_WIDTH/2.;
0a9f99cc 159 midY = glucose.model.yMax/2;
e0cea600
MS
160 midZ = TRAILER_DEPTH/2.;
161 eyeR = -290;
0a9f99cc 162 eyeA = .15;
d626bc9b 163 eyeY = midY + 70;
0a9f99cc
MS
164 eyeX = midX + eyeR*sin(eyeA);
165 eyeZ = midZ + eyeR*cos(eyeA);
bda5421d
MS
166 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
167 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
168 mouseWheel(mwe.getWheelRotation());
169 }});
170
49815cc0 171 println("Total setup: " + (millis() - startMillis) + "ms");
e73ef85d 172 println("Hit the 'p' key to toggle Panda Board output");
49815cc0
MS
173}
174
a8d55ade 175public class MidiListener extends AbstractScrollItem {
4df91daf
MS
176
177 public static final int MIDI = 0;
178 public static final int KEYS = 1;
179 public static final int APC = 2;
180
34327c96
MS
181 private boolean enabled = false;
182 private final String name;
183
184 MidiListener(MidiInputDevice d) {
4df91daf 185 mode = MIDI;
34327c96
MS
186 d.createInput(this);
187 name = d.getName();
188 }
189
a8d55ade
MS
190 class NoteMeta {
191 int channel;
192 int number;
193 NoteMeta(int channel, int number) {
194 this.channel = channel;
195 this.number = number;
196 }
197 }
198
199 final Map<Character, NoteMeta> keyToNote = new HashMap<Character, NoteMeta>();
200
4df91daf
MS
201 private final int mode;
202 private int octaveShift = 0;
203
204 MidiListener(int mode) {
205 this.mode = mode;
206 switch (mode) {
207 case APC:
208 name = "QWERTY (APC Mode)";
209 mapAPC();
210 break;
211 default:
212 case KEYS:
213 name = "QWERTY (Key Mode)";
214 mapKeys();
215 break;
216 }
217 }
218
219 private void mapAPC() {
a8d55ade
MS
220 mapNote('1', 0, 53);
221 mapNote('2', 1, 53);
222 mapNote('3', 2, 53);
223 mapNote('4', 3, 53);
224 mapNote('5', 4, 53);
225 mapNote('6', 5, 53);
226 mapNote('q', 0, 54);
227 mapNote('w', 1, 54);
228 mapNote('e', 2, 54);
229 mapNote('r', 3, 54);
230 mapNote('t', 4, 54);
231 mapNote('y', 5, 54);
232 mapNote('a', 0, 55);
233 mapNote('s', 1, 55);
234 mapNote('d', 2, 55);
235 mapNote('f', 3, 55);
236 mapNote('g', 4, 55);
237 mapNote('h', 5, 55);
238 mapNote('z', 0, 56);
239 mapNote('x', 1, 56);
240 mapNote('c', 2, 56);
241 mapNote('v', 3, 56);
242 mapNote('b', 4, 56);
243 mapNote('n', 5, 56);
34327c96 244 registerKeyEvent(this);
34327c96
MS
245 }
246
4df91daf
MS
247 private void mapKeys() {
248 int note = 48;
249 mapNote('a', 1, note++);
250 mapNote('w', 1, note++);
251 mapNote('s', 1, note++);
252 mapNote('e', 1, note++);
253 mapNote('d', 1, note++);
254 mapNote('f', 1, note++);
255 mapNote('t', 1, note++);
256 mapNote('g', 1, note++);
257 mapNote('y', 1, note++);
258 mapNote('h', 1, note++);
259 mapNote('u', 1, note++);
260 mapNote('j', 1, note++);
261 mapNote('k', 1, note++);
262 mapNote('o', 1, note++);
263 mapNote('l', 1, note++);
264 registerKeyEvent(this);
265 }
266
a8d55ade
MS
267 void mapNote(char ch, int channel, int number) {
268 keyToNote.put(ch, new NoteMeta(channel, number));
269 }
270
271 public String getLabel() {
34327c96
MS
272 return name;
273 }
274
275 public void keyEvent(KeyEvent e) {
4df91daf
MS
276 if (!enabled) {
277 return;
278 }
a8d55ade
MS
279 char c = Character.toLowerCase(e.getKeyChar());
280 NoteMeta nm = keyToNote.get(c);
281 if (nm != null) {
282 switch (e.getID()) {
283 case KeyEvent.KEY_PRESSED:
4df91daf 284 noteOnReceived(new Note(Note.NOTE_ON, nm.channel, nm.number + octaveShift*12, 127));
34327c96 285 break;
a8d55ade 286 case KeyEvent.KEY_RELEASED:
4df91daf
MS
287 noteOffReceived(new Note(Note.NOTE_OFF, nm.channel, nm.number + octaveShift*12, 0));
288 break;
289 }
290 }
291 if ((mode == KEYS) && (e.getID() == KeyEvent.KEY_PRESSED)) {
292 switch (c) {
293 case 'z':
294 octaveShift = constrain(octaveShift-1, -4, 4);
295 break;
296 case 'x':
297 octaveShift = constrain(octaveShift+1, -4, 4);
34327c96
MS
298 break;
299 }
300 }
301 }
302
a8d55ade
MS
303 public boolean isEnabled() {
304 return enabled;
305 }
306
307 public boolean isSelected() {
308 return enabled;
309 }
310
311 public void onMousePressed() {
312 setEnabled(!enabled);
313 }
314
34327c96
MS
315 public MidiListener setEnabled(boolean enabled) {
316 if (enabled != this.enabled) {
317 this.enabled = enabled;
a8d55ade 318 uiMidi.redraw();
34327c96
MS
319 }
320 return this;
321 }
322
a8d55ade
MS
323 private SCPattern getFocusedPattern() {
324 return (SCPattern) uiMidi.getFocusedDeck().getActivePattern();
325 }
326
327 void programChangeReceived(ProgramChange pc) {
328 if (!enabled) {
329 return;
330 }
9692dc7b
MS
331 if (uiMidi.logMidi()) {
332 println(getLabel() + " :: Program Change :: " + pc.getNumber());
333 }
a8d55ade
MS
334 }
335
34327c96
MS
336 void controllerChangeReceived(rwmidi.Controller cc) {
337 if (!enabled) {
338 return;
339 }
9692dc7b
MS
340 if (uiMidi.logMidi()) {
341 println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue());
342 }
a8d55ade 343 getFocusedPattern().controllerChangeReceived(cc);
cc9fcf4b 344 }
cc9fcf4b 345
34327c96
MS
346 void noteOnReceived(Note note) {
347 if (!enabled) {
348 return;
349 }
9692dc7b
MS
350 if (uiMidi.logMidi()) {
351 println(getLabel() + " :: Note On :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
352 }
a8d55ade 353 getFocusedPattern().noteOnReceived(note);
cc9fcf4b 354 }
cc9fcf4b 355
34327c96
MS
356 void noteOffReceived(Note note) {
357 if (!enabled) {
358 return;
359 }
9692dc7b
MS
360 if (uiMidi.logMidi()) {
361 println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
362 }
a8d55ade 363 getFocusedPattern().noteOffReceived(note);
cc9fcf4b 364 }
cc9fcf4b 365
49815cc0
MS
366}
367
34327c96
MS
368/**
369 * Core render loop and drawing functionality.
370 */
49815cc0 371void draw() {
0a9f99cc
MS
372 // Draws the simulation and the 2D UI overlay
373 background(40);
a898d79b
MS
374 color[] colors = glucose.getColors();
375
376 String displayMode = uiCrossfader.getDisplayMode();
d626bc9b
MS
377 if (displayMode == "A") {
378 colors = lx.engine.getDeck(0).getColors();
379 } else if (displayMode == "B") {
380 colors = lx.engine.getDeck(1).getColors();
381 }
554e38ff
MS
382 if (debugMode) {
383 debugUI.maskColors(colors);
384 }
51d0d59a 385
0a9f99cc
MS
386 camera(
387 eyeX, eyeY, eyeZ,
388 midX, midY, midZ,
389 0, -1, 0
390 );
51d0d59a 391
a8d55ade 392 translate(0, 40, 0);
d626bc9b 393
51d0d59a
MS
394 noStroke();
395 fill(#141414);
87998ff3 396 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
51d0d59a
MS
397 fill(#070707);
398 stroke(#222222);
0a9f99cc 399 beginShape();
51d0d59a 400 vertex(0, 0, 0);
87998ff3
MS
401 vertex(TRAILER_WIDTH, 0, 0);
402 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
403 vertex(0, 0, TRAILER_DEPTH);
51d0d59a 404 endShape();
0a9f99cc 405
51d0d59a 406 noStroke();
ab77005f
MS
407// drawBassBox(glucose.model.bassBox);
408// for (Speaker s : glucose.model.speakers) {
409// drawSpeaker(s);
410// }
51d0d59a
MS
411 for (Cube c : glucose.model.cubes) {
412 drawCube(c);
413 }
414
0a9f99cc
MS
415 noFill();
416 strokeWeight(2);
417 beginShape(POINTS);
922def98
MS
418 // TODO(mcslee): restore when bassBox/speakers are right again
419 // for (Point p : glucose.model.points) {
420 for (Cube cube : glucose.model.cubes) {
421 for (Point p : cube.points) {
422 stroke(colors[p.index]);
423 vertex(p.fx, p.fy, p.fz);
424 }
0a9f99cc
MS
425 }
426 endShape();
427
d626bc9b 428 // 2D Overlay UI
0a9f99cc 429 drawUI();
d626bc9b
MS
430
431 // Send output colors
432 color[] sendColors = glucose.getColors();
554e38ff 433 if (debugMode) {
d626bc9b 434 debugUI.maskColors(colors);
554e38ff 435 }
a797d019 436
6702151a
MS
437 // Gamma correction here. Apply a cubic to the brightness
438 // for better representation of dynamic range
439 for (int i = 0; i < colors.length; ++i) {
440 float b = brightness(colors[i]) / 100.f;
441 colors[i] = color(
442 hue(colors[i]),
443 saturation(colors[i]),
444 (b*b*b) * 100.
445 );
446 }
e7f14d4d 447
e73ef85d 448 // TODO(mcslee): move into GLucose engine
79ae8245 449 for (PandaDriver p : pandaBoards) {
b58e5a1d 450 p.send(colors);
e73ef85d 451 }
49815cc0
MS
452}
453
92c06c97
MS
454void drawBassBox(BassBox b) {
455 float in = .15;
e76480d4
MS
456
457 noStroke();
458 fill(#191919);
459 pushMatrix();
460 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
461 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
462 popMatrix();
463
464 noStroke();
465 fill(#393939);
92c06c97
MS
466 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);
467
39011e7e
MS
468 pushMatrix();
469 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
470 float lastOffset = 0;
471 for (float offset : BoothFloor.STRIP_OFFSETS) {
472 translate(offset - lastOffset, 0, 0);
473 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
474 lastOffset = offset;
475 }
476 popMatrix();
477
92c06c97
MS
478 pushMatrix();
479 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
480 for (int j = 0; j < 2; ++j) {
481 pushMatrix();
482 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
483 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
484 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
485 }
486 popMatrix();
487 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
488 }
489 popMatrix();
490
491 pushMatrix();
492 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
493 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
494 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
495 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
ab77005f 496 popMatrix();
e76480d4 497
92c06c97
MS
498}
499
51d0d59a 500void drawCube(Cube c) {
254d34c0 501 float in = .15;
e76480d4
MS
502 noStroke();
503 fill(#393939);
254d34c0 504 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
505}
506
e76480d4
MS
507void drawSpeaker(Speaker s) {
508 float in = .15;
509
510 noStroke();
511 fill(#191919);
512 pushMatrix();
513 translate(s.x, s.y, s.z);
514 rotate(s.ry / 180. * PI, 0, -1, 0);
515 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
516 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
254fbb68
MS
517 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
518
519 fill(#222222);
520 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
e76480d4
MS
521 popMatrix();
522
e76480d4
MS
523 noStroke();
524 fill(#393939);
525 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 526}
e76480d4 527
51d0d59a
MS
528void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
529 pushMatrix();
530 translate(x, y, z);
e0cea600 531 rotate(rx / 180. * PI, -1, 0, 0);
51d0d59a 532 rotate(ry / 180. * PI, 0, -1, 0);
e0cea600 533 rotate(rz / 180. * PI, 0, 0, -1);
51d0d59a
MS
534 for (int i = 0; i < 4; ++i) {
535 float wid = (i % 2 == 0) ? xd : zd;
536
537 beginShape();
538 vertex(0, 0);
539 vertex(wid, 0);
540 vertex(wid, yd);
541 vertex(wid - sw, yd);
542 vertex(wid - sw, sw);
543 vertex(0, sw);
544 endShape();
545 beginShape();
546 vertex(0, sw);
547 vertex(0, yd);
548 vertex(wid - sw, yd);
549 vertex(wid - sw, yd - sw);
550 vertex(sw, yd - sw);
551 vertex(sw, sw);
552 endShape();
553
554 translate(wid, 0, 0);
555 rotate(HALF_PI, 0, -1, 0);
556 }
557 popMatrix();
558}
559
49815cc0 560void drawUI() {
d626bc9b
MS
561 camera();
562 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
563 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
564 ((PGraphicsOpenGL)g).endGL();
565 strokeWeight(1);
566
49815cc0 567 if (uiOn) {
d626bc9b
MS
568 for (UIContext context : overlays) {
569 context.draw();
570 }
571 }
572
573 // Always draw FPS meter
574 fill(#555555);
575 textSize(9);
576 textAlign(LEFT, BASELINE);
577 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
578
579 if (debugMode) {
580 debugUI.draw();
49815cc0 581 }
49815cc0
MS
582}
583
34327c96
MS
584/**
585 * Top-level keyboard event handling
586 */
49815cc0 587void keyPressed() {
bf551144 588 if (mappingMode) {
d626bc9b 589 mappingTool.keyPressed(uiMapping);
bf551144 590 }
3f8be614 591 switch (key) {
0e3c5542
MS
592 case '-':
593 case '_':
594 frameRate(--targetFramerate);
595 break;
596 case '=':
597 case '+':
598 frameRate(++targetFramerate);
599 break;
cc9fcf4b 600 case 'd':
4df91daf 601 if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
a8d55ade
MS
602 debugMode = !debugMode;
603 println("Debug output: " + (debugMode ? "ON" : "OFF"));
604 }
554e38ff 605 break;
bf551144 606 case 'm':
4df91daf 607 if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
a8d55ade
MS
608 mappingMode = !mappingMode;
609 uiPatternA.setVisible(!mappingMode);
610 uiMapping.setVisible(mappingMode);
611 if (mappingMode) {
612 restoreToPattern = lx.getPattern();
613 lx.setPatterns(new LXPattern[] { mappingTool });
614 } else {
615 lx.setPatterns(patterns);
616 LXTransition pop = restoreToPattern.getTransition();
617 restoreToPattern.setTransition(null);
618 lx.goPattern(restoreToPattern);
619 restoreToPattern.setTransition(pop);
620 }
bf551144
MS
621 }
622 break;
e73ef85d 623 case 'p':
79ae8245
MS
624 for (PandaDriver p : pandaBoards) {
625 p.toggle();
626 }
cc9fcf4b 627 break;
3f8be614 628 case 'u':
4df91daf
MS
629 if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
630 uiOn = !uiOn;
631 }
3f8be614 632 break;
49815cc0
MS
633 }
634}
635
34327c96
MS
636/**
637 * Top-level mouse event handling
638 */
0a9f99cc 639int mx, my;
0a9f99cc 640void mousePressed() {
d626bc9b
MS
641 boolean debugged = false;
642 if (debugMode) {
643 debugged = debugUI.mousePressed();
644 }
645 if (!debugged) {
646 for (UIContext context : overlays) {
647 context.mousePressed(mouseX, mouseY);
648 }
0a9f99cc 649 }
d626bc9b
MS
650 mx = mouseX;
651 my = mouseY;
0a9f99cc
MS
652}
653
654void mouseDragged() {
d626bc9b
MS
655 boolean dragged = false;
656 for (UIContext context : overlays) {
657 dragged |= context.mouseDragged(mouseX, mouseY);
658 }
659 if (!dragged) {
0a9f99cc
MS
660 int dx = mouseX - mx;
661 int dy = mouseY - my;
662 mx = mouseX;
663 my = mouseY;
664 eyeA += dx*.003;
665 eyeX = midX + eyeR*sin(eyeA);
666 eyeZ = midZ + eyeR*cos(eyeA);
667 eyeY += dy;
668 }
669}
670
671void mouseReleased() {
d626bc9b
MS
672 for (UIContext context : overlays) {
673 context.mouseReleased(mouseX, mouseY);
674 }
0a9f99cc 675}
bda5421d
MS
676
677void mouseWheel(int delta) {
d626bc9b
MS
678 boolean wheeled = false;
679 for (UIContext context : overlays) {
680 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
681 }
682
683 if (!wheeled) {
0ba6ac44
MS
684 eyeR = constrain(eyeR - delta, -500, -80);
685 eyeX = midX + eyeR*sin(eyeA);
686 eyeZ = midZ + eyeR*cos(eyeA);
687 }
bda5421d 688}