X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=_Internals.pde;h=425ea0e3c8a34532ef54f39f10bca368be15ba28;hb=270a8b44cd8296bb40fa74e4fcf2e7f7c4f1192c;hp=a18c95a21ea48d57a05dd3bba15c1e254e9785ae;hpb=6f486736347dd4c851672ce6b0ec32f5d0618cdb;p=SugarCubes.git diff --git a/_Internals.pde b/_Internals.pde index a18c95a..425ea0e 100644 --- a/_Internals.pde +++ b/_Internals.pde @@ -4,7 +4,7 @@ * //\\ //\\ //\\ //\\ * ///\\\ ///\\\ ///\\\ ///\\\ * \\\/// \\\/// \\\/// \\\/// - * \\// \\// \\// \\// + * \\// \\// \\// \\//H * * EXPERTS ONLY!! EXPERTS ONLY!! * @@ -14,22 +14,19 @@ */ import glucose.*; -import glucose.control.*; -import glucose.effect.*; import glucose.model.*; -import glucose.pattern.*; -import glucose.transform.*; -import glucose.transition.*; import heronarts.lx.*; -import heronarts.lx.control.*; import heronarts.lx.effect.*; import heronarts.lx.modulator.*; +import heronarts.lx.parameter.*; import heronarts.lx.pattern.*; +import heronarts.lx.transform.*; import heronarts.lx.transition.*; import ddf.minim.*; import ddf.minim.analysis.*; import processing.opengl.*; import rwmidi.*; +import java.lang.reflect.*; final int VIEWPORT_WIDTH = 900; final int VIEWPORT_HEIGHT = 700; @@ -40,25 +37,87 @@ final float TRAILER_DEPTH = 97; final float TRAILER_HEIGHT = 33; int targetFramerate = 60; - int startMillis, lastMillis; + +// Core engine variables GLucose glucose; -HeronLX lx; -MappingTool mappingTool; +LX lx; LXPattern[] patterns; -LXTransition[] transitions; -LXEffect[] effects; -OverlayUI ui; -ControlUI controlUI; -MappingUI mappingUI; +Effects effects; +MappingTool mappingTool; PandaDriver[] pandaBoards; +PresetManager presetManager; +MidiEngine midiEngine; + +// Display configuration mode boolean mappingMode = false; boolean debugMode = false; DebugUI debugUI; +boolean uiOn = true; +boolean simulationOn = true; +boolean diagnosticsOn = false; +LXPattern restoreToPattern = null; +PImage logo; +float[] hsb = new float[3]; + +// Handles to UI objects +UIContext[] overlays; +UIPatternDeck uiPatternA; +UICrossfader uiCrossfader; +UIMidi uiMidi; +UIMapping uiMapping; +UIDebugText uiDebugText; +UISpeed uiSpeed; // Camera variables float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ; +/** + * Engine construction and initialization. + */ + +LXTransition _transition(GLucose glucose) { + return new DissolveTransition(glucose.lx).setDuration(1000); +} + +LXPattern[] _leftPatterns(GLucose glucose) { + LXPattern[] patterns = patterns(glucose); + for (LXPattern p : patterns) { + p.setTransition(_transition(glucose)); + } + return patterns; +} + +LXPattern[] _rightPatterns(GLucose glucose) { + LXPattern[] patterns = _leftPatterns(glucose); + LXPattern[] rightPatterns = new LXPattern[patterns.length+1]; + int i = 0; + rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose)); + for (LXPattern p : patterns) { + rightPatterns[i++] = p; + } + return rightPatterns; +} + +LXEffect[] _effectsArray(Effects effects) { + List effectList = new ArrayList(); + for (Field f : effects.getClass().getDeclaredFields()) { + try { + Object val = f.get(effects); + if (val instanceof LXEffect) { + effectList.add((LXEffect)val); + } + } catch (IllegalAccessException iax) {} + } + return effectList.toArray(new LXEffect[]{}); +} + +void logTime(String evt) { + int now = millis(); + println(evt + ": " + (now - lastMillis) + "ms"); + lastMillis = now; +} + void setup() { startMillis = lastMillis = millis(); @@ -74,15 +133,25 @@ void setup() { lx = glucose.lx; lx.enableKeyboardTempo(); logTime("Built GLucose engine"); - + // Set the patterns - glucose.lx.setPatterns(patterns = patterns(glucose)); + LXEngine engine = lx.engine; + engine.setPatterns(patterns = _leftPatterns(glucose)); + engine.addDeck(_rightPatterns(glucose)); logTime("Built patterns"); - glucose.lx.addEffects(effects = effects(glucose)); - logTime("Built effects"); - glucose.setTransitions(transitions = transitions(glucose)); + glucose.setTransitions(transitions(glucose)); logTime("Built transitions"); - + glucose.lx.addEffects(_effectsArray(effects = new Effects())); + logTime("Built effects"); + + // Preset manager + presetManager = new PresetManager(); + logTime("Loaded presets"); + + // MIDI devices + midiEngine = new MidiEngine(); + logTime("Setup MIDI devices"); + // Build output driver PandaMapping[] pandaMappings = buildPandaList(); pandaBoards = new PandaDriver[pandaMappings.length]; @@ -92,77 +161,174 @@ void setup() { } mappingTool = new MappingTool(glucose, pandaMappings); logTime("Built PandaDriver"); - + // Build overlay UI - ui = controlUI = new ControlUI(); - mappingUI = new MappingUI(mappingTool); debugUI = new DebugUI(pandaMappings); - logTime("Built overlay UI"); + overlays = new UIContext[] { + uiPatternA = new UIPatternDeck(lx.engine.getDeck(GLucose.LEFT_DECK), "PATTERN A", 4, 4, 140, 324), + new UIBlendMode(4, 332, 140, 86), + new UIEffects(4, 422, 140, 144), + new UITempo(4, 570, 140, 50), + uiSpeed = new UISpeed(4, 624, 140, 50), + + new UIPatternDeck(lx.engine.getDeck(GLucose.RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324), + uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158), + new UIOutput(width-144, 494, 140, 106), - // MIDI devices - for (MidiInputDevice d : RWMidi.getInputDevices()) { - d.createInput(this); - } - SCMidiDevices.initializeStandardDevices(glucose); - logTime("Setup MIDI devices"); + uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86), + uiDebugText = new UIDebugText(148, height-138, width-304, 44), + uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324), + }; + uiMapping.setVisible(false); + logTime("Built overlay UI"); + + // Load logo image + logo = loadImage("data/logo.png"); + // Setup camera - midX = TRAILER_WIDTH/2. + 20; + midX = TRAILER_WIDTH/2.; midY = glucose.model.yMax/2; midZ = TRAILER_DEPTH/2.; eyeR = -290; eyeA = .15; - eyeY = midY + 20; + eyeY = midY + 70; eyeX = midX + eyeR*sin(eyeA); eyeZ = midZ + eyeR*cos(eyeA); + + // Add mouse scrolling event support addMouseWheelListener(new java.awt.event.MouseWheelListener() { public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) { mouseWheel(mwe.getWheelRotation()); }}); - println("Total setup: " + (millis() - startMillis) + "ms"); println("Hit the 'p' key to toggle Panda Board output"); } -void controllerChangeReceived(rwmidi.Controller cc) { - if (debugMode) { - println("CC: " + cc.toString()); - } -} +/** + * Core render loop and drawing functionality. + */ +void draw() { + long drawStart = System.nanoTime(); + + // Draws the simulation and the 2D UI overlay + background(40); -void noteOnReceived(Note note) { - if (debugMode) { - println("Note On: " + note.toString()); + color[] simulationColors; + color[] sendColors; + simulationColors = sendColors = glucose.getColors(); + String displayMode = uiCrossfader.getDisplayMode(); + if (displayMode == "A") { + simulationColors = lx.engine.getDeck(GLucose.LEFT_DECK).getColors(); + } else if (displayMode == "B") { + simulationColors = lx.engine.getDeck(GLucose.RIGHT_DECK).getColors(); } -} - -void noteOffReceived(Note note) { if (debugMode) { - println("Note Off: " + note.toString()); + debugUI.maskColors(simulationColors); + debugUI.maskColors(sendColors); } -} -void logTime(String evt) { - int now = millis(); - println(evt + ": " + (now - lastMillis) + "ms"); - lastMillis = now; + long simulationStart = System.nanoTime(); + if (simulationOn) { + drawSimulation(simulationColors); + } + long simulationNanos = System.nanoTime() - simulationStart; + + // 2D Overlay UI + long uiStart = System.nanoTime(); + drawUI(); + long uiNanos = System.nanoTime() - uiStart; + + long gammaStart = System.nanoTime(); + // Gamma correction here. Apply a cubic to the brightness + // for better representation of dynamic range + for (int i = 0; i < sendColors.length; ++i) { + lx.RGBtoHSB(sendColors[i], hsb); + float b = hsb[2]; + sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b)); + } + long gammaNanos = System.nanoTime() - gammaStart; + + long sendStart = System.nanoTime(); + for (PandaDriver p : pandaBoards) { + p.send(sendColors); + } + long sendNanos = System.nanoTime() - sendStart; + + long drawNanos = System.nanoTime() - drawStart; + + if (diagnosticsOn) { + drawDiagnostics(drawNanos, simulationNanos, uiNanos, gammaNanos, sendNanos); + } } -void draw() { - // Draws the simulation and the 2D UI overlay - background(40); - color[] colors = glucose.getColors(); - if (debugMode) { - debugUI.maskColors(colors); +void drawDiagnostics(long drawNanos, long simulationNanos, long uiNanos, long gammaNanos, long sendNanos) { + float ws = 4 / 1000000.; + int thirtyfps = 1000000000 / 30; + int sixtyfps = 1000000000 / 60; + int x = width - 138; + int y = height - 14; + int h = 10; + noFill(); + stroke(#999999); + rect(x, y, thirtyfps * ws, h); + noStroke(); + int xp = x; + float hv = 0; + for (long val : new long[] {lx.timer.drawNanos, simulationNanos, uiNanos, gammaNanos, sendNanos }) { + fill(lx.hsb(hv % 360, 100, 80)); + rect(xp, y, val * ws, h-1); + hv += 140; + xp += val * ws; + } + noFill(); + stroke(#333333); + line(x+sixtyfps*ws, y+1, x+sixtyfps*ws, y+h-1); + + y = y - 14; + xp = x; + float tw = thirtyfps * ws; + noFill(); + stroke(#999999); + rect(x, y, tw, h); + h = 5; + noStroke(); + for (long val : new long[] { + lx.engine.timer.deckNanos, + lx.engine.timer.copyNanos, + lx.engine.timer.fxNanos}) { + float amt = val / (float) lx.timer.drawNanos; + fill(lx.hsb(hv % 360, 100, 80)); + rect(xp, y, amt * tw, h-1); + hv += 140; + xp += amt * tw; } + + xp = x; + y += h; + hv = 120; + for (long val : new long[] { + lx.engine.getDeck(0).timer.runNanos, + lx.engine.getDeck(1).timer.runNanos, + lx.engine.getDeck(1).getFaderTransition().timer.blendNanos}) { + float amt = val / (float) lx.timer.drawNanos; + fill(lx.hsb(hv % 360, 100, 80)); + rect(xp, y, amt * tw, h-1); + hv += 140; + xp += amt * tw; + } +} +void drawSimulation(color[] simulationColors) { camera( eyeX, eyeY, eyeZ, midX, midY, midZ, 0, -1, 0 ); + translate(0, 40, 0); + noStroke(); fill(#141414); drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.); @@ -174,11 +340,21 @@ void draw() { vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH); vertex(0, 0, TRAILER_DEPTH); endShape(); + + // Draw the logo on the front of platform + pushMatrix(); + translate(0, 0, -1); + float s = .07; + scale(s, -s, s); + image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s); + popMatrix(); noStroke(); - drawBassBox(glucose.model.bassBox); - for (Speaker s : glucose.model.speakers) { - drawSpeaker(s); + if (glucose.model.bassBox.exists) { + drawBassBox(glucose.model.bassBox, false); + } + for (Speaker speaker : glucose.model.speakers) { + drawSpeaker(speaker); } for (Cube c : glucose.model.cubes) { drawCube(c); @@ -187,44 +363,40 @@ void draw() { noFill(); strokeWeight(2); beginShape(POINTS); - for (Point p : glucose.model.points) { - stroke(colors[p.index]); - vertex(p.fx, p.fy, p.fz); + for (LXPoint p : glucose.model.points) { + stroke(simulationColors[p.index]); + vertex(p.x, p.y, p.z); } endShape(); - - // 2D Overlay - camera(); - javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL(); - gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT); - ((PGraphicsOpenGL)g).endGL(); - strokeWeight(1); - drawUI(); - - if (debugMode) { - debugUI.draw(); - } - - // TODO(mcslee): move into GLucose engine - for (PandaDriver p : pandaBoards) { - p.send(colors); - } } -void drawBassBox(BassBox b) { +void drawBassBox(BassBox b, boolean hasSub) { + float in = .15; - - noStroke(); - fill(#191919); - pushMatrix(); - translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.); - box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in); - popMatrix(); + + if (hasSub) { + noStroke(); + fill(#191919); + pushMatrix(); + translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.); + box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in); + popMatrix(); + } noStroke(); fill(#393939); 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); + pushMatrix(); + translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.); + float lastOffset = 0; + for (float offset : BoothFloor.STRIP_OFFSETS) { + translate(offset - lastOffset, 0, 0); + box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in); + lastOffset = offset; + } + popMatrix(); + pushMatrix(); translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in); for (int j = 0; j < 2; ++j) { @@ -273,8 +445,7 @@ void drawSpeaker(Speaker s) { noStroke(); fill(#393939); 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); -} - +} void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) { pushMatrix(); @@ -309,22 +480,76 @@ void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, } void drawUI() { + camera(); + javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL(); + gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT); + ((PGraphicsOpenGL)g).endGL(); + strokeWeight(1); + if (uiOn) { - ui.draw(); - } else { - ui.drawHelpTip(); + for (UIContext context : overlays) { + context.draw(); + } + } + + // Always draw FPS meter + fill(#555555); + textSize(9); + textAlign(LEFT, BASELINE); + text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4); + + if (debugMode) { + debugUI.draw(); } - ui.drawFPS(); } -boolean uiOn = true; -int restoreToIndex = -1; +/** + * Top-level keyboard event handling + */ void keyPressed() { if (mappingMode) { - mappingTool.keyPressed(); + mappingTool.keyPressed(uiMapping); } switch (key) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + if (!midiEngine.isQwertyEnabled()) { + presetManager.select(midiEngine.getFocusedDeck(), key - '1'); + } + break; + + case '!': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 0); + break; + case '@': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 1); + break; + case '#': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 2); + break; + case '$': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 3); + break; + case '%': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 4); + break; + case '^': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 5); + break; + case '&': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 6); + break; + case '*': + if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 7); + break; + case '-': case '_': frameRate(--targetFramerate); @@ -332,27 +557,36 @@ void keyPressed() { case '=': case '+': frameRate(++targetFramerate); - break; + break; + case 'b': + effects.boom.trigger(); + break; case 'd': - debugMode = !debugMode; - println("Debug output: " + (debugMode ? "ON" : "OFF")); + if (!midiEngine.isQwertyEnabled()) { + debugMode = !debugMode; + println("Debug output: " + (debugMode ? "ON" : "OFF")); + } break; case 'm': - mappingMode = !mappingMode; - if (mappingMode) { - LXPattern pattern = lx.getPattern(); - for (int i = 0; i < patterns.length; ++i) { - if (pattern == patterns[i]) { - restoreToIndex = i; - break; - } + if (!midiEngine.isQwertyEnabled()) { + mappingMode = !mappingMode; + uiPatternA.setVisible(!mappingMode); + uiMapping.setVisible(mappingMode); + if (mappingMode) { + restoreToPattern = lx.getPattern(); + lx.setPatterns(new LXPattern[] { mappingTool }); + } else { + lx.setPatterns(patterns); + LXTransition pop = restoreToPattern.getTransition(); + restoreToPattern.setTransition(null); + lx.goPattern(restoreToPattern); + restoreToPattern.setTransition(pop); } - ui = mappingUI; - lx.setPatterns(new LXPattern[] { mappingTool }); - } else { - ui = controlUI; - lx.setPatterns(patterns); - lx.goIndex(restoreToIndex); + } + break; + case 't': + if (!midiEngine.isQwertyEnabled()) { + lx.engine.setThreaded(!lx.engine.isThreaded()); } break; case 'p': @@ -360,28 +594,48 @@ void keyPressed() { p.toggle(); } break; + case 'q': + if (!midiEngine.isQwertyEnabled()) { + diagnosticsOn = !diagnosticsOn; + } + break; + case 's': + if (!midiEngine.isQwertyEnabled()) { + simulationOn = !simulationOn; + } + break; case 'u': - uiOn = !uiOn; + if (!midiEngine.isQwertyEnabled()) { + uiOn = !uiOn; + } break; } } +/** + * Top-level mouse event handling + */ int mx, my; void mousePressed() { - ui.mousePressed(); - if (mouseX < ui.leftPos) { - if (debugMode) { - debugUI.mousePressed(); - } - mx = mouseX; - my = mouseY; + boolean debugged = false; + if (debugMode) { + debugged = debugUI.mousePressed(); } + if (!debugged) { + for (UIContext context : overlays) { + context.mousePressed(mouseX, mouseY); + } + } + mx = mouseX; + my = mouseY; } void mouseDragged() { - if (mouseX > ui.leftPos) { - ui.mouseDragged(); - } else { + boolean dragged = false; + for (UIContext context : overlays) { + dragged |= context.mouseDragged(mouseX, mouseY); + } + if (!dragged) { int dx = mouseX - mx; int dy = mouseY - my; mx = mouseX; @@ -394,16 +648,20 @@ void mouseDragged() { } void mouseReleased() { - ui.mouseReleased(); + for (UIContext context : overlays) { + context.mouseReleased(mouseX, mouseY); + } } - + void mouseWheel(int delta) { - if (mouseX > ui.leftPos) { - ui.mouseWheel(delta); - } else { + boolean wheeled = false; + for (UIContext context : overlays) { + wheeled |= context.mouseWheel(mouseX, mouseY, delta); + } + + if (!wheeled) { eyeR = constrain(eyeR - delta, -500, -80); eyeX = midX + eyeR*sin(eyeA); eyeZ = midZ + eyeR*cos(eyeA); } } -