From: Mark Slee Date: Sun, 26 May 2013 21:09:08 +0000 (-0700) Subject: Update to new libraries, add knobs for effects and transitions X-Git-Url: https://git.piment-noir.org/?p=SugarCubes.git;a=commitdiff_plain;h=3f8be6146c76a48a2e23288555958a26a7c8d643 Update to new libraries, add knobs for effects and transitions --- diff --git a/MarkSlee.pde b/MarkSlee.pde index 2df823a..07c8ce2 100644 --- a/MarkSlee.pde +++ b/MarkSlee.pde @@ -3,10 +3,10 @@ class SpaceTime extends SCPattern { SinLFO pos = new SinLFO(0, 15, 3000); SinLFO rate = new SinLFO(1000, 9000, 13000); SinLFO falloff = new SinLFO(10, 70, 5000); - float sat = 0; - - BasicKnob rateKnob = new BasicKnob("RATE", 0.5); - BasicKnob sizeKnob = new BasicKnob("SIZE", 0.5); + float angle = 0; + + BasicParameter rateParameter = new BasicParameter("RATE", 0.5); + BasicParameter sizeParameter = new BasicParameter("SIZE", 0.5); public SpaceTime(GLucose glucose) { super(glucose); @@ -14,22 +14,22 @@ class SpaceTime extends SCPattern { addModulator(rate).trigger(); addModulator(falloff).trigger(); pos.modulateDurationBy(rate); - addKnob(rateKnob); - addKnob(sizeKnob); + addParameter(rateParameter); + addParameter(sizeParameter); } - - public void onKnobChange(Knob knob) { - if (knob == rateKnob) { - rate.stop().setValue(9000 - 8000*knob.getValuef()); - } else if (knob == sizeKnob) { - falloff.stop().setValue(70 - 60*knob.getValuef()); + + public void onParameterChanged(LXParameter parameter) { + if (parameter == rateParameter) { + rate.stop().setValue(9000 - 8000*parameter.getValuef()); + } else if (parameter == sizeParameter) { + falloff.stop().setValue(70 - 60*parameter.getValuef()); } } void run(int deltaMs) { - sat += deltaMs * 0.00004; - float sVal1 = Strip.list.size() * (0.5 + 0.5*sin(sat)); - float sVal2 = Strip.list.size() * (0.5 + 0.5*cos(sat)); + angle += deltaMs * 0.0007; + float sVal1 = Strip.list.size() * (0.5 + 0.5*sin(angle)); + float sVal2 = Strip.list.size() * (0.5 + 0.5*cos(angle)); float pVal = pos.getValuef(); float fVal = falloff.getValuef(); @@ -39,12 +39,13 @@ class SpaceTime extends SCPattern { int i = 0; for (Point p : strip.points) { colors[p.index] = color( - (lx.getBaseHuef() + s*.2 + i*3) % 360, - min(100, min(abs(s - sVal1), abs(s - sVal2))), + (lx.getBaseHuef() + 360 - p.fy*.2 + p.fz * .3) % 360, + constrain(.4 * min(abs(s - sVal1), abs(s - sVal2)), 20, 100), max(0, 100 - fVal*abs(i - pVal)) ); ++i; } + ++s; } } } @@ -99,21 +100,27 @@ class Swarm extends SCPattern { } class SwipeTransition extends SCTransition { + + final BasicParameter bleed = new BasicParameter("WIDTH", 0.5); + SwipeTransition(GLucose glucose) { super(glucose); setDuration(5000); + addParameter(bleed); } void computeBlend(int[] c1, int[] c2, double progress) { - float bleed = 50.; - float yPos = (float) (-bleed + progress * (255. + bleed)); + float bleedf = 10 + bleed.getValuef() * 200.; + float yPos = (float) (-bleedf + progress * (255. + bleedf)); for (Point p : Point.list) { - float d = (p.fy - yPos) / 50.; + float d = (p.fy - yPos) / bleedf; if (d < 0) { colors[p.index] = c2[p.index]; - } else if (d > 1) { + } + else if (d > 1) { colors[p.index] = c1[p.index]; - } else { + } + else { colors[p.index] = lerpColor(c2[p.index], c1[p.index], d, RGB); } } @@ -126,23 +133,23 @@ class CubeEQ extends SCPattern { private LinearEnvelope[] bandVals = null; private int avgSize; - private final BasicKnob thrsh = new BasicKnob("LVL", 0.35); - private final BasicKnob range = new BasicKnob("RANG", 0.45); - private final BasicKnob edge = new BasicKnob("EDGE", 0.5); - private final BasicKnob speed = new BasicKnob("SPD", 0.5); - private final BasicKnob tone = new BasicKnob("TONE", 0.5); - private final BasicKnob clr = new BasicKnob("CLR", 0.5); + private final BasicParameter thrsh = new BasicParameter("LVL", 0.35); + private final BasicParameter range = new BasicParameter("RANG", 0.45); + private final BasicParameter edge = new BasicParameter("EDGE", 0.5); + private final BasicParameter speed = new BasicParameter("SPD", 0.5); + private final BasicParameter tone = new BasicParameter("TONE", 0.5); + private final BasicParameter clr = new BasicParameter("CLR", 0.5); public CubeEQ(GLucose glucose) { super(glucose); - addKnob(thrsh); - addKnob(range); - addKnob(edge); - addKnob(speed); - addKnob(tone); - addKnob(clr); + addParameter(thrsh); + addParameter(range); + addParameter(edge); + addParameter(speed); + addParameter(tone); + addParameter(clr); } - + protected void onActive() { if (this.fft == null) { this.fft = new FFT(lx.audioInput().bufferSize(), lx.audioInput().sampleRate()); @@ -160,7 +167,7 @@ class CubeEQ extends SCPattern { this.fft.forward(this.lx.audioInput().mix); float toneConst = .35 + .4 * (tone.getValuef() - 0.5); float edgeConst = 2 + 30*(edge.getValuef()*edge.getValuef()*edge.getValuef()); - + for (int i = 0; i < avgSize; ++i) { float value = this.fft.getAvg(i); value = 20*log(1 + sqrt(value)); @@ -169,11 +176,12 @@ class CubeEQ extends SCPattern { value *= 6; if (value > this.bandVals[i].getValue()) { this.bandVals[i].setEndVal(value, 40).trigger(); - } else { + } + else { this.bandVals[i].setEndVal(value, 1000 - 900*speed.getValuef()).trigger(); } } - + float jBase = 120 - 360*thrsh.getValuef(); float jConst = 300.*(1-range.getValuef()); float clrConst = 1.1 + clr.getValuef(); @@ -183,16 +191,87 @@ class CubeEQ extends SCPattern { int avgFloor = (int) avgIndex; float j = jBase + jConst * (p.fz / 128.); float value = lerp( - this.bandVals[avgFloor].getValuef(), - this.bandVals[avgFloor+1].getValuef(), - avgIndex-avgFloor - ); - + this.bandVals[avgFloor].getValuef(), + this.bandVals[avgFloor+1].getValuef(), + avgIndex-avgFloor + ); + float b = constrain(edgeConst * (value - j), 0, 100); colors[p.index] = color( - (480 + lx.getBaseHuef() - min(clrConst*p.fz, 120)) % 360, - 100, - b); + (480 + lx.getBaseHuef() - min(clrConst*p.fz, 120)) % 360, + 100, + b); + } + } +} + +class BoomEffect extends SCEffect { + + final BasicParameter falloff = new BasicParameter("WIDTH", 0.5); + final BasicParameter speed = new BasicParameter("SPD", 0.5); + final BasicParameter bright = new BasicParameter("BRT", 1.0); + final BasicParameter sat = new BasicParameter("SAT", 0.2); + List layers = new ArrayList(); + + class Layer { + LinearEnvelope boom = new LinearEnvelope(-40, 500, 1300); + + Layer() { + addModulator(boom); + trigger(); + } + + void trigger() { + float falloffv = falloffv(); + boom.setRange(-100 / falloffv, 500 + 100/falloffv, 4000 - speed.getValuef() * 3300); + boom.trigger(); + } + + void doApply(int[] colors) { + float brightv = 100 * bright.getValuef(); + float falloffv = falloffv(); + float satv = sat.getValuef() * 100; + float huev = lx.getBaseHuef(); + for (Point p : Point.list) { + colors[p.index] = blendColor( + colors[p.index], + color(huev, satv, constrain(brightv - falloffv*abs(boom.getValuef() - dist(2*p.fx, p.fy, 2*p.fz, 128, 128, 128)), 0, 100)), + ADD); + } + } + } + + BoomEffect(GLucose glucose) { + super(glucose, true); + addParameter(falloff); + addParameter(speed); + addParameter(bright); + addParameter(sat); + } + + public void onEnable() { + for (Layer l : layers) { + if (!l.boom.isRunning()) { + l.trigger(); + return; + } + } + layers.add(new Layer()); + } + + private float falloffv() { + return 20 - 19 * falloff.getValuef(); + } + + public void onTrigger() { + onEnable(); + } + + public void doApply(int[] colors) { + for (Layer l : layers) { + if (l.boom.isRunning()) { + l.doApply(colors); + } } } } diff --git a/SugarCubes.pde b/SugarCubes.pde index 8428367..3a5354c 100644 --- a/SugarCubes.pde +++ b/SugarCubes.pde @@ -39,15 +39,16 @@ LXPattern[] patterns(GLucose glucose) { LXTransition[] transitions(GLucose glucose) { return new LXTransition[] { - new DissolveTransition(lx).setDuration(3000), + new DissolveTransition(lx), new SwipeTransition(glucose), - new FadeTransition(lx).setDuration(2000), + new FadeTransition(lx), }; } LXEffect[] effects(GLucose glucose) { return new LXEffect[] { new FlashEffect(lx), + new BoomEffect(glucose), new DesaturationEffect(lx), }; } diff --git a/_Internals.pde b/_Internals.pde index d040f11..c93129f 100644 --- a/_Internals.pde +++ b/_Internals.pde @@ -6,10 +6,12 @@ import glucose.*; import glucose.control.*; +import glucose.effect.*; import glucose.pattern.*; import glucose.transition.*; import glucose.model.*; import heronarts.lx.*; +import heronarts.lx.control.*; import heronarts.lx.effect.*; import heronarts.lx.pattern.*; import heronarts.lx.modulator.*; @@ -30,7 +32,6 @@ LXPattern[] patterns; LXTransition[] transitions; LXEffect[] effects; OverlayUI ui; -int activeTransitionIndex = 0; void setup() { startMillis = lastMillis = millis(); @@ -38,6 +39,7 @@ void setup() { // Initialize the Processing graphics environment size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL); frameRate(TARGET_FRAMERATE); + noSmooth(); // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement? logTime("Created viewport"); @@ -59,8 +61,8 @@ void setup() { logTime("Built overlay UI"); // MIDI devices - MidiKnobController.initializeStandardDevices(glucose); - logTime("Setup MIDI controllers"); + SCMidiDevices.initializeStandardDevices(glucose); + logTime("Setup MIDI devices"); println("Total setup: " + (millis() - startMillis) + "ms"); } @@ -86,9 +88,15 @@ void drawUI() { } boolean uiOn = true; +boolean knobsOn = true; void keyPressed() { - if (key == 'u') { - uiOn = !uiOn; + switch (key) { + case 'u': + uiOn = !uiOn; + break; + case 'k': + knobsOn = !knobsOn; + break; } } diff --git a/_Overlay.pde b/_Overlay.pde index e212e1e..e21c9cd 100644 --- a/_Overlay.pde +++ b/_Overlay.pde @@ -6,15 +6,17 @@ class OverlayUI { private final PFont titleFont = createFont("Myriad Pro", 10); - private final PFont itemFont = createFont("Myriad Pro", 11); + private final PFont itemFont = createFont("Lucida Grande", 11); private final PFont knobFont = titleFont; private final int w = 140; private final int leftPos; private final int leftTextPos; private final int lineHeight = 20; private final int sectionSpacing = 12; + private final int controlSpacing = 18; private final int tempoHeight = 20; private final int knobSize = 28; + private final float knobIndent = .4; private final int knobSpacing = 6; private final int knobLabelHeight = 14; private final color lightBlue = #666699; @@ -27,23 +29,45 @@ class OverlayUI { private PImage logo; private int firstPatternY; + private int firstPatternKnobY; private int firstTransitionY; + private int firstTransitionKnobY; private int firstEffectY; - private int firstKnobY; + private int firstEffectKnobY; + private int tempoY; private Method patternStateMethod; private Method transitionStateMethod; private Method effectStateMethod; + private final int NUM_TRANSITION_KNOBS = 4; + private final int NUM_EFFECT_KNOBS = 4; + + private int activeTransitionIndex = 0; + private int activeEffectIndex = 0; + + private final VirtualTransitionKnob[] transitionKnobs; + private final VirtualEffectKnob[] effectKnobs; + OverlayUI() { leftPos = width - w; leftTextPos = leftPos + 4; logo = loadImage("logo-sm.png"); - patternNames = classNameArray(patterns); - transitionNames = classNameArray(transitions); - effectNames = classNameArray(effects); + patternNames = classNameArray(patterns, "Pattern"); + transitionNames = classNameArray(transitions, "Transition"); + effectNames = classNameArray(effects, "Effect"); + + transitionKnobs = new VirtualTransitionKnob[NUM_TRANSITION_KNOBS]; + for (int i = 0; i < transitionKnobs.length; ++i) { + transitionKnobs[i] = new VirtualTransitionKnob(i); + } + + effectKnobs = new VirtualEffectKnob[NUM_EFFECT_KNOBS]; + for (int i = 0; i < effectKnobs.length; ++i) { + effectKnobs[i] = new VirtualEffectKnob(i); + } try { patternStateMethod = getClass().getMethod("getState", LXPattern.class); @@ -71,11 +95,8 @@ class OverlayUI { int yPos = 0; firstPatternY = yPos + lineHeight + 6; yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod); - - yPos += sectionSpacing; - yPos = drawObjectList(yPos, "CONTROL", null, null, null); - yPos += 6; - firstKnobY = yPos; + yPos += controlSpacing; + firstPatternKnobY = yPos; int xPos = leftTextPos; for (int i = 0; i < glucose.NUM_PATTERN_KNOBS/2; ++i) { drawKnob(xPos, yPos, knobSize, glucose.patternKnobs[i]); @@ -87,10 +108,26 @@ class OverlayUI { yPos += sectionSpacing; firstTransitionY = yPos + lineHeight + 6; yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod); + yPos += controlSpacing; + firstTransitionKnobY = yPos; + xPos = leftTextPos; + for (int i = 0; i < transitionKnobs.length; ++i) { + drawKnob(xPos, yPos, knobSize, transitionKnobs[i]); + xPos += knobSize + knobSpacing; + } + yPos += knobSize + knobLabelHeight; yPos += sectionSpacing; firstEffectY = yPos + lineHeight + 6; yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod); + yPos += controlSpacing; + firstEffectKnobY = yPos; + xPos = leftTextPos; + for (int i = 0; i < effectKnobs.length; ++i) { + drawKnob(xPos, yPos, knobSize, effectKnobs[i]); + xPos += knobSize + knobSpacing; + } + yPos += knobSize + knobLabelHeight; yPos += sectionSpacing; yPos = drawObjectList(yPos, "TEMPO", null, null, null); @@ -110,7 +147,7 @@ class OverlayUI { text("Tap 'u' to hide UI (~+3FPS)", leftTextPos, height-6); } - public Knob getOrNull(List items, int index) { + public LXParameter getOrNull(List items, int index) { if (index < items.size()) { return items.get(index); } @@ -138,7 +175,12 @@ class OverlayUI { } public int getState(LXEffect e) { - return e.isEnabled() ? STATE_ACTIVE : STATE_DEFAULT; + if (e.isEnabled()) { + return STATE_PENDING; + } else if (effects[activeEffectIndex] == e) { + return STATE_ACTIVE; + } + return STATE_DEFAULT; } public int getState(LXTransition t) { @@ -151,7 +193,7 @@ class OverlayUI { } protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) { - return drawObjectList(yPos, title, items, classNameArray(items), stateMethod); + return drawObjectList(yPos, title, items, classNameArray(items, null), stateMethod); } private int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) { @@ -195,25 +237,42 @@ class OverlayUI { return yPos; } - private void drawKnob(int xPos, int yPos, int knobSize, Knob knob) { - final float knobIndent = .4; + private void drawKnob(int xPos, int yPos, int knobSize, LXParameter knob) { + if (!knobsOn) { + return; + } final float knobValue = knob.getValuef(); String knobLabel = knob.getLabel(); - if (knobLabel.length() > 4) { - knobLabel = knobLabel.substring(0, 4); - } else if (knobLabel.length() == 0) { + if (knobLabel == null) { knobLabel = "-"; + } else if (knobLabel.length() > 4) { + knobLabel = knobLabel.substring(0, 4); } ellipseMode(CENTER); fill(#222222); - arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent)); - + // For some reason this arc call really crushes drawing performance. Presumably + // because openGL is drawing it and when we overlap the second set of arcs it + // does a bunch of depth buffer intersection tests? Ellipse with a trapezoid cut out is faster + // arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent)); + ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize); + + float endArc = HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue; fill(lightGreen); - arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue); - + arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, endArc); + + // Center circle of knob fill(#333333); ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2); + + // Mask notch out of knob + fill(color(0, 0, 30)); + beginShape(); + vertex(xPos + knobSize/2 - 3, yPos + knobSize - 8); + vertex(xPos + knobSize/2 - 5, yPos + knobSize); + vertex(xPos + knobSize/2 + 5, yPos + knobSize); + vertex(xPos + knobSize/2 + 3, yPos + knobSize - 8); + endShape(); fill(0); rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2); @@ -224,62 +283,103 @@ class OverlayUI { } - private String[] classNameArray(Object[] objects) { + private String[] classNameArray(Object[] objects, String suffix) { if (objects == null) { return null; } String[] names = new String[objects.length]; for (int i = 0; i < objects.length; ++i) { - names[i] = className(objects[i]); + names[i] = className(objects[i], suffix); } return names; } - private String className(Object p) { + private String className(Object p, String suffix) { String s = p.getClass().getName(); int li; if ((li = s.lastIndexOf(".")) > 0) { s = s.substring(li + 1); } if (s.indexOf("SugarCubes$") == 0) { - return s.substring("SugarCubes$".length()); + s = s.substring("SugarCubes$".length()); + } + if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) { + s = s.substring(0, li); } return s; } + + class VirtualTransitionKnob extends LXVirtualParameter { + private final int index; + + VirtualTransitionKnob(int index) { + this.index = index; + } + + public LXParameter getRealParameter() { + List parameters = transitions[activeTransitionIndex].getParameters(); + if (index < parameters.size()) { + return parameters.get(index); + } + return null; + } + } + + class VirtualEffectKnob extends LXVirtualParameter { + private final int index; + + VirtualEffectKnob(int index) { + this.index = index; + } + + public LXParameter getRealParameter() { + List parameters = effects[activeEffectIndex].getParameters(); + if (index < parameters.size()) { + return parameters.get(index); + } + return null; + } + } + + private int patternKnobIndex = -1; + private int transitionKnobIndex = -1; + private int effectKnobIndex = -1; - private int knobIndex = -1; private int lastY; private int releaseEffect = -1; private boolean tempoDown = false; public void mousePressed() { lastY = mouseY; - knobIndex = -1; + patternKnobIndex = transitionKnobIndex = effectKnobIndex = -1; releaseEffect = -1; if (mouseY > tempoY) { if (mouseY - tempoY < tempoHeight) { lx.tempo.tap(); tempoDown = true; } + } else if ((mouseY >= firstEffectKnobY) && (mouseY < firstEffectKnobY + knobSize + knobLabelHeight)) { + effectKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); } else if (mouseY > firstEffectY) { int effectIndex = (mouseY - firstEffectY) / lineHeight; if (effectIndex < effects.length) { - if (effects[effectIndex].isMomentary()) { + if (activeEffectIndex == effectIndex) { effects[effectIndex].enable(); releaseEffect = effectIndex; - } else { - effects[effectIndex].toggle(); } + activeEffectIndex = effectIndex; } + } else if ((mouseY >= firstTransitionKnobY) && (mouseY < firstTransitionKnobY + knobSize + knobLabelHeight)) { + transitionKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); } else if (mouseY > firstTransitionY) { int transitionIndex = (mouseY - firstTransitionY) / lineHeight; if (transitionIndex < transitions.length) { activeTransitionIndex = transitionIndex; } - } else if ((mouseY >= firstKnobY) && (mouseY < firstKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) { - knobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); - if (mouseY >= firstKnobY + knobSize + knobLabelHeight + knobSpacing) { - knobIndex += glucose.NUM_PATTERN_KNOBS / 2; + } else if ((mouseY >= firstPatternKnobY) && (mouseY < firstPatternKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) { + patternKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); + if (mouseY >= firstPatternKnobY + knobSize + knobLabelHeight + knobSpacing) { + patternKnobIndex += glucose.NUM_PATTERN_KNOBS / 2; } } else if (mouseY > firstPatternY) { int patternIndex = (mouseY - firstPatternY) / lineHeight; @@ -293,9 +393,15 @@ class OverlayUI { public void mouseDragged() { int dy = lastY - mouseY; lastY = mouseY; - if (knobIndex >= 0 && knobIndex < glucose.NUM_PATTERN_KNOBS) { - Knob k = glucose.patternKnobs[knobIndex]; - k.setValue(k.getValuef() + dy*.01); + if (patternKnobIndex >= 0 && patternKnobIndex < glucose.NUM_PATTERN_KNOBS) { + LXParameter p = glucose.patternKnobs[patternKnobIndex]; + p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); + } else if (effectKnobIndex >= 0 && effectKnobIndex < NUM_EFFECT_KNOBS) { + LXParameter p = effectKnobs[effectKnobIndex]; + p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); + } else if (transitionKnobIndex >= 0 && transitionKnobIndex < NUM_TRANSITION_KNOBS) { + LXParameter p = transitionKnobs[transitionKnobIndex]; + p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); } } @@ -306,6 +412,7 @@ class OverlayUI { releaseEffect = -1; } } + } void mousePressed() { diff --git a/code/GLucose.jar b/code/GLucose.jar index 6ae9494..48c8de6 100644 Binary files a/code/GLucose.jar and b/code/GLucose.jar differ diff --git a/code/HeronLX.jar b/code/HeronLX.jar index 2724cc6..e6ac53e 100644 Binary files a/code/HeronLX.jar and b/code/HeronLX.jar differ