2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
9 * EXPERTS ONLY!! EXPERTS ONLY!!
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.
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.*;
30 import ddf.minim.analysis.*;
31 import processing.opengl.*;
34 final int VIEWPORT_WIDTH = 900;
35 final int VIEWPORT_HEIGHT = 700;
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;
42 int targetFramerate = 60;
43 int startMillis, lastMillis;
45 // Core engine variables
49 MappingTool mappingTool;
50 PandaDriver[] pandaBoards;
51 MidiListener midiQwerty;
53 // Display configuration mode
54 boolean mappingMode = false;
55 boolean debugMode = false;
58 LXPattern restoreToPattern = null;
60 // Handles to UI objects
62 UIPatternDeck uiPatternA;
63 UICrossfader uiCrossfader;
66 UIDebugText uiDebugText;
69 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
72 * Engine construction and initialization.
74 LXPattern[] _patterns(GLucose glucose) {
75 LXPattern[] patterns = patterns(glucose);
76 for (LXPattern p : patterns) {
77 p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
82 void logTime(String evt) {
84 println(evt + ": " + (now - lastMillis) + "ms");
89 startMillis = lastMillis = millis();
91 // Initialize the Processing graphics environment
92 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
93 frameRate(targetFramerate);
95 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
96 logTime("Created viewport");
98 // Create the GLucose engine to run the cubes
99 glucose = new GLucose(this, buildModel());
101 lx.enableKeyboardTempo();
102 logTime("Built GLucose engine");
105 Engine engine = lx.engine;
106 engine.setPatterns(patterns = _patterns(glucose));
107 engine.addDeck(_patterns(glucose));
108 logTime("Built patterns");
109 glucose.setTransitions(transitions(glucose));
110 logTime("Built transitions");
111 glucose.lx.addEffects(effects(glucose));
112 logTime("Built effects");
114 // Build output driver
115 PandaMapping[] pandaMappings = buildPandaList();
116 pandaBoards = new PandaDriver[pandaMappings.length];
118 for (PandaMapping pm : pandaMappings) {
119 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
121 mappingTool = new MappingTool(glucose, pandaMappings);
122 logTime("Built PandaDriver");
125 List<MidiListener> midiListeners = new ArrayList<MidiListener>();
126 midiListeners.add(midiQwerty = new MidiListener());
127 for (MidiInputDevice device : RWMidi.getInputDevices()) {
128 boolean enableDevice = device.getName().contains("APC");
129 midiListeners.add(new MidiListener(device).setEnabled(enableDevice));
131 SCMidiDevices.initializeStandardDevices(glucose);
132 logTime("Setup MIDI devices");
135 debugUI = new DebugUI(pandaMappings);
136 overlays = new UIContext[] {
137 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
138 new UIBlendMode(4, 332, 140, 86),
139 new UIEffects(4, 422, 140, 144),
140 new UITempo(4, 570, 140, 50),
141 new UISpeed(4, 624, 140, 50),
143 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
144 uiMidi = new UIMidi(midiListeners, width-144, 332, 140, 160),
145 new UIOutput(width-144, 498, 140, 106),
147 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
149 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
150 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
152 uiMapping.setVisible(false);
153 logTime("Built overlay UI");
156 midX = TRAILER_WIDTH/2.;
157 midY = glucose.model.yMax/2;
158 midZ = TRAILER_DEPTH/2.;
162 eyeX = midX + eyeR*sin(eyeA);
163 eyeZ = midZ + eyeR*cos(eyeA);
164 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
165 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
166 mouseWheel(mwe.getWheelRotation());
169 println("Total setup: " + (millis() - startMillis) + "ms");
170 println("Hit the 'p' key to toggle Panda Board output");
173 public class MidiListener extends AbstractScrollItem {
174 private boolean enabled = false;
175 private final String name;
177 MidiListener(MidiInputDevice d) {
185 NoteMeta(int channel, int number) {
186 this.channel = channel;
187 this.number = number;
191 final Map<Character, NoteMeta> keyToNote = new HashMap<Character, NoteMeta>();
194 name = "QWERTY Keyboard";
219 registerKeyEvent(this);
222 void mapNote(char ch, int channel, int number) {
223 keyToNote.put(ch, new NoteMeta(channel, number));
226 public String getLabel() {
230 public void keyEvent(KeyEvent e) {
231 char c = Character.toLowerCase(e.getKeyChar());
232 NoteMeta nm = keyToNote.get(c);
235 case KeyEvent.KEY_PRESSED:
236 noteOnReceived(new Note(Note.NOTE_ON, nm.channel, nm.number, 127));
238 case KeyEvent.KEY_RELEASED:
239 noteOffReceived(new Note(Note.NOTE_OFF, nm.channel, nm.number, 0));
245 public boolean isEnabled() {
249 public boolean isSelected() {
253 public void onMousePressed() {
254 setEnabled(!enabled);
257 public MidiListener setEnabled(boolean enabled) {
258 if (enabled != this.enabled) {
259 this.enabled = enabled;
265 private SCPattern getFocusedPattern() {
266 return (SCPattern) uiMidi.getFocusedDeck().getActivePattern();
269 void programChangeReceived(ProgramChange pc) {
273 if (uiMidi.logMidi()) {
274 println(getLabel() + " :: Program Change :: " + pc.getNumber());
278 void controllerChangeReceived(rwmidi.Controller cc) {
282 if (uiMidi.logMidi()) {
283 println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue());
285 getFocusedPattern().controllerChangeReceived(cc);
288 void noteOnReceived(Note note) {
292 if (uiMidi.logMidi()) {
293 println(getLabel() + " :: Note On :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
295 getFocusedPattern().noteOnReceived(note);
298 void noteOffReceived(Note note) {
302 if (uiMidi.logMidi()) {
303 println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
305 getFocusedPattern().noteOffReceived(note);
311 * Core render loop and drawing functionality.
314 // Draws the simulation and the 2D UI overlay
316 color[] colors = glucose.getColors();
318 String displayMode = uiCrossfader.getDisplayMode();
319 if (displayMode == "A") {
320 colors = lx.engine.getDeck(0).getColors();
321 } else if (displayMode == "B") {
322 colors = lx.engine.getDeck(1).getColors();
325 debugUI.maskColors(colors);
338 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
343 vertex(TRAILER_WIDTH, 0, 0);
344 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
345 vertex(0, 0, TRAILER_DEPTH);
349 // drawBassBox(glucose.model.bassBox);
350 // for (Speaker s : glucose.model.speakers) {
353 for (Cube c : glucose.model.cubes) {
360 // TODO(mcslee): restore when bassBox/speakers are right again
361 // for (Point p : glucose.model.points) {
362 for (Cube cube : glucose.model.cubes) {
363 for (Point p : cube.points) {
364 stroke(colors[p.index]);
365 vertex(p.fx, p.fy, p.fz);
373 // Send output colors
374 color[] sendColors = glucose.getColors();
376 debugUI.maskColors(colors);
379 // Gamma correction here. Apply a cubic to the brightness
380 // for better representation of dynamic range
381 for (int i = 0; i < colors.length; ++i) {
382 float b = brightness(colors[i]) / 100.f;
385 saturation(colors[i]),
390 // TODO(mcslee): move into GLucose engine
391 for (PandaDriver p : pandaBoards) {
396 void drawBassBox(BassBox b) {
402 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
403 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
408 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);
411 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
412 float lastOffset = 0;
413 for (float offset : BoothFloor.STRIP_OFFSETS) {
414 translate(offset - lastOffset, 0, 0);
415 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
421 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
422 for (int j = 0; j < 2; ++j) {
424 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
425 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
426 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
429 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
434 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
435 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
436 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
437 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
442 void drawCube(Cube c) {
446 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);
449 void drawSpeaker(Speaker s) {
455 translate(s.x, s.y, s.z);
456 rotate(s.ry / 180. * PI, 0, -1, 0);
457 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
458 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
459 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
462 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
467 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);
470 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
473 rotate(rx / 180. * PI, -1, 0, 0);
474 rotate(ry / 180. * PI, 0, -1, 0);
475 rotate(rz / 180. * PI, 0, 0, -1);
476 for (int i = 0; i < 4; ++i) {
477 float wid = (i % 2 == 0) ? xd : zd;
483 vertex(wid - sw, yd);
484 vertex(wid - sw, sw);
490 vertex(wid - sw, yd);
491 vertex(wid - sw, yd - sw);
496 translate(wid, 0, 0);
497 rotate(HALF_PI, 0, -1, 0);
504 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
505 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
506 ((PGraphicsOpenGL)g).endGL();
510 for (UIContext context : overlays) {
515 // Always draw FPS meter
518 textAlign(LEFT, BASELINE);
519 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
527 * Top-level keyboard event handling
531 mappingTool.keyPressed(uiMapping);
536 frameRate(--targetFramerate);
540 frameRate(++targetFramerate);
543 if (!midiQwerty.isEnabled()) {
544 debugMode = !debugMode;
545 println("Debug output: " + (debugMode ? "ON" : "OFF"));
549 if (!midiQwerty.isEnabled()) {
550 mappingMode = !mappingMode;
551 uiPatternA.setVisible(!mappingMode);
552 uiMapping.setVisible(mappingMode);
554 restoreToPattern = lx.getPattern();
555 lx.setPatterns(new LXPattern[] { mappingTool });
557 lx.setPatterns(patterns);
558 LXTransition pop = restoreToPattern.getTransition();
559 restoreToPattern.setTransition(null);
560 lx.goPattern(restoreToPattern);
561 restoreToPattern.setTransition(pop);
566 for (PandaDriver p : pandaBoards) {
577 * Top-level mouse event handling
580 void mousePressed() {
581 boolean debugged = false;
583 debugged = debugUI.mousePressed();
586 for (UIContext context : overlays) {
587 context.mousePressed(mouseX, mouseY);
594 void mouseDragged() {
595 boolean dragged = false;
596 for (UIContext context : overlays) {
597 dragged |= context.mouseDragged(mouseX, mouseY);
600 int dx = mouseX - mx;
601 int dy = mouseY - my;
605 eyeX = midX + eyeR*sin(eyeA);
606 eyeZ = midZ + eyeR*cos(eyeA);
611 void mouseReleased() {
612 for (UIContext context : overlays) {
613 context.mouseReleased(mouseX, mouseY);
617 void mouseWheel(int delta) {
618 boolean wheeled = false;
619 for (UIContext context : overlays) {
620 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
624 eyeR = constrain(eyeR - delta, -500, -80);
625 eyeX = midX + eyeR*sin(eyeA);
626 eyeZ = midZ + eyeR*cos(eyeA);