X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=_Internals.pde;h=b593f3ef07b9c4aa7db088430348d3da9c653ae7;hb=77c62639984c01295a20c7710d089e857f76a0b0;hp=46c39390dcf93ac1dfe29deaee243eba0c36efa5;hpb=cc9fcf4be00a99376a2083ddfbcf589f94ee6785;p=SugarCubes.git diff --git a/_Internals.pde b/_Internals.pde index 46c3939..b593f3e 100644 --- a/_Internals.pde +++ b/_Internals.pde @@ -4,7 +4,7 @@ * //\\ //\\ //\\ //\\ * ///\\\ ///\\\ ///\\\ ///\\\ * \\\/// \\\/// \\\/// \\\/// - * \\// \\// \\// \\// + * \\// \\// \\// \\//H * * EXPERTS ONLY!! EXPERTS ONLY!! * @@ -13,127 +13,507 @@ * for general animation work. */ -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.model.*; import heronarts.lx.modulator.*; +import heronarts.lx.parameter.*; import heronarts.lx.pattern.*; +import heronarts.lx.transform.*; import heronarts.lx.transition.*; +import heronarts.lx.ui.*; +import heronarts.lx.ui.component.*; +import heronarts.lx.ui.control.*; import ddf.minim.*; import ddf.minim.analysis.*; import processing.opengl.*; -import java.lang.reflect.*; import rwmidi.*; +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +static final int VIEWPORT_WIDTH = 900; +static final int VIEWPORT_HEIGHT = 700; + +static final int LEFT_DECK = 0; +static final int RIGHT_DECK = 1; -final int VIEWPORT_WIDTH = 900; -final int VIEWPORT_HEIGHT = 700; -final int TARGET_FRAMERATE = 45; +// The trailer is measured from the outside of the black metal (but not including the higher welded part on the front) +static final float TRAILER_WIDTH = 192; +static final float TRAILER_DEPTH = 192; +static final float TRAILER_HEIGHT = 33; +int targetFramerate = 60; int startMillis, lastMillis; -GLucose glucose; -HeronLX lx; + +// Core engine variables +LX lx; +Model model; LXPattern[] patterns; LXTransition[] transitions; -LXEffect[] effects; -OverlayUI ui; +Effects effects; +LXEffect[] effectsArr; +DiscreteParameter selectedEffect; +MappingTool mappingTool; +GrizzlyOutput[] grizzlies; +PresetManager presetManager; +MidiEngine midiEngine; +// Display configuration mode +boolean mappingMode = false; boolean debugMode = false; +boolean simulationOn = true; +boolean diagnosticsOn = false; +LXPattern restoreToPattern = null; +PImage logo; +float[] hsb = new float[3]; + +// Handles to UI objects +UIPatternDeck uiPatternA; +UICrossfader uiCrossfader; +UIMidi uiMidi; +UIMapping uiMapping; +UIDebugText uiDebugText; +UISpeed uiSpeed; + +/** + * Engine construction and initialization. + */ + +LXTransition _transition(LX lx) { + return new DissolveTransition(lx).setDuration(1000); +} + +LXPattern[] _leftPatterns(LX lx) { + LXPattern[] patterns = patterns(lx); + for (LXPattern p : patterns) { + p.setTransition(_transition(lx)); + } + return patterns; +} + +LXPattern[] _rightPatterns(LX lx) { + LXPattern[] patterns = _leftPatterns(lx); + LXPattern[] rightPatterns = new LXPattern[patterns.length+1]; + int i = 0; + rightPatterns[i++] = new BlankPattern(lx).setTransition(_transition(lx)); + 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[]{}); +} + +LXEffect getSelectedEffect() { + return effectsArr[selectedEffect.getValuei()]; +} + +void logTime(String evt) { + int now = millis(); + println(evt + ": " + (now - lastMillis) + "ms"); + lastMillis = now; +} void setup() { startMillis = lastMillis = millis(); // Initialize the Processing graphics environment size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL); - frameRate(TARGET_FRAMERATE); + frameRate(targetFramerate); noSmooth(); // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement? logTime("Created viewport"); - // Create the GLucose engine to run the cubes - glucose = new GLucose(this, new SCMapping()); - lx = glucose.lx; - lx.enableKeyboardTempo(); - logTime("Built GLucose engine"); + // Create the model + model = buildModel(); + logTime("Built Model"); + // LX engine + lx = new LX(this, model); + lx.enableKeyboardTempo(); + logTime("Built LX engine"); + // Set the patterns - glucose.lx.setPatterns(patterns = patterns(glucose)); + LXEngine engine = lx.engine; + engine.setPatterns(patterns = _leftPatterns(lx)); + engine.addDeck(_rightPatterns(lx)); logTime("Built patterns"); - glucose.lx.addEffects(effects = effects(glucose)); - logTime("Built effects"); - glucose.setTransitions(transitions = transitions(glucose)); + + // Transitions + transitions = transitions(lx); + lx.engine.getDeck(RIGHT_DECK).setFaderTransition(transitions[0]); logTime("Built transitions"); - // Build overlay UI - ui = new OverlayUI(); - logTime("Built overlay UI"); - + // Effects + lx.addEffects(effectsArr = _effectsArray(effects = new Effects())); + selectedEffect = new DiscreteParameter("EFFECT", effectsArr.length); + logTime("Built effects"); + + // Preset manager + presetManager = new PresetManager(); + logTime("Loaded presets"); + // MIDI devices - for (MidiInputDevice d : RWMidi.getInputDevices()) { - d.createInput(this); - } - SCMidiDevices.initializeStandardDevices(glucose); + midiEngine = new MidiEngine(); logTime("Setup MIDI devices"); - - println("Total setup: " + (millis() - startMillis) + "ms"); -} -void controllerChangeReceived(rwmidi.Controller cc) { - if (debugMode) { - println("CC: " + cc.toString()); + // Build output driver + grizzlies = new GrizzlyOutput[]{}; + try { + grizzlies = buildGrizzlies(); + for (LXOutput output : grizzlies) { + lx.addOutput(output); + } + } catch (Exception x) { + x.printStackTrace(); } -} + logTime("Built Grizzly Outputs"); -void noteOnReceived(Note note) { - if (debugMode) { - println("Note On: " + note.toString()); + // Mapping tool + mappingTool = new MappingTool(lx); + logTime("Built Mapping Tool"); + + // Build overlay UI + UILayer[] layers = new UILayer[] { + // Camera layer + new UICameraLayer(lx.ui) + .setCenter(model.cx, model.cy, model.cz) + .setRadius(290).addComponent(new UICubesLayer()), + + // Left controls + uiPatternA = new UIPatternDeck(lx.ui, lx.engine.getDeck(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), + + // Right controls + new UIPatternDeck(lx.ui, lx.engine.getDeck(RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324), + uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158), + new UIOutput(grizzlies, width-144, 494, 140, 106), + + // Crossfader + uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86), + + // Overlays + uiDebugText = new UIDebugText(148, height-138, width-304, 44), + uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324) + }; + uiMapping.setVisible(false); + for (UILayer layer : layers) { + lx.ui.addLayer(layer); } + logTime("Built UI"); + + // Load logo image + logo = loadImage("data/logo.png"); + logTime("Loaded logo image"); + + println("Total setup: " + (millis() - startMillis) + "ms"); + println("Hit the 'o' key to toggle live output"); } -void noteOffReceived(Note note) { - if (debugMode) { - println("Note Off: " + note.toString()); +public SCPattern getPattern() { + return (SCPattern) lx.getPattern(); +} + +/** + * Subclass of LXPattern specific to sugar cubes. These patterns + * get access to the state and geometry, and have some + * little helpers for interacting with the model. + */ +public static abstract class SCPattern extends LXPattern { + + protected SCPattern(LX lx) { + super(lx); + } + + /** + * Reset this pattern to its default state. + */ + public final void reset() { + for (LXParameter parameter : getParameters()) { + parameter.reset(); + } + onReset(); + } + + /** + * Subclasses may override to add additional reset functionality. + */ + protected /*abstract*/ void onReset() {} + + /** + * Invoked by the engine when a grid controller button press occurs + * + * @param row Row index on the gird + * @param col Column index on the grid + * @return True if the event was consumed, false otherwise + */ + public boolean gridPressed(int row, int col) { + return false; + } + + /** + * Invoked by the engine when a grid controller button release occurs + * + * @param row Row index on the gird + * @param col Column index on the grid + * @return True if the event was consumed, false otherwise + */ + public boolean gridReleased(int row, int col) { + return false; + } + + /** + * Invoked by engine when this pattern is focused an a midi note is received. + * + * @param note + * @return True if the pattern has consumed this note, false if the top-level + * may handle it + */ + public boolean noteOn(rwmidi.Note note) { + return false; + } + + /** + * Invoked by engine when this pattern is focused an a midi note off is received. + * + * @param note + * @return True if the pattern has consumed this note, false if the top-level + * may handle it + */ + public boolean noteOff(rwmidi.Note note) { + return false; + } + + /** + * Invoked by engine when this pattern is focused an a controller is received + * + * @param note + * @return True if the pattern has consumed this controller, false if the top-level + * may handle it + */ + public boolean controllerChange(rwmidi.Controller controller) { + return false; } } -void logTime(String evt) { - int now = millis(); - println(evt + ": " + (now - lastMillis) + "ms"); - lastMillis = now; -} +long simulationNanos = 0; +/** + * Core render loop and drawing functionality. + */ void draw() { - // The glucose engine deals with the core simulation here, we don't need - // to do anything specific. This method just needs to exist. + long drawStart = System.nanoTime(); + + // Set background + background(40); + + // Send colors + color[] sendColors = lx.getColors(); + 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; + + // Always draw FPS meter + drawFPS(); + + // TODO(mcslee): fix + long drawNanos = System.nanoTime() - drawStart; + long uiNanos = 0; + + if (diagnosticsOn) { + drawDiagnostics(drawNanos, simulationNanos, uiNanos, gammaNanos); + } } -void drawUI() { - if (uiOn) { - ui.draw(); - } else { - ui.drawHelpTip(); +void drawDiagnostics(long drawNanos, long simulationNanos, long uiNanos, long gammaNanos) { + 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, lx.timer.outputNanos }) { + 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; } - ui.drawFPS(); + + 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 drawFPS() { + // Always draw FPS meter + fill(#555555); + textSize(9); + textAlign(LEFT, BASELINE); + text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4); } -boolean uiOn = true; -boolean knobsOn = true; + +/** + * Top-level keyboard event handling + */ void keyPressed() { + if (mappingMode) { + 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); + break; + case '=': + case '+': + frameRate(++targetFramerate); + 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': + 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); + } + } + break; + case 't': + if (!midiEngine.isQwertyEnabled()) { + lx.engine.setThreaded(!lx.engine.isThreaded()); + } break; - case 'u': - uiOn = !uiOn; + case 'o': + case 'p': + for (LXOutput output : grizzlies) { + output.enabled.toggle(); + } + break; + case 'q': + if (!midiEngine.isQwertyEnabled()) { + diagnosticsOn = !diagnosticsOn; + } + break; + case 's': + if (!midiEngine.isQwertyEnabled()) { + simulationOn = !simulationOn; + } break; } } -