From 3f8be6146c76a48a2e23288555958a26a7c8d643 Mon Sep 17 00:00:00 2001 From: Mark Slee Date: Sun, 26 May 2013 14:09:08 -0700 Subject: [PATCH] Update to new libraries, add knobs for effects and transitions --- MarkSlee.pde | 171 +++++++++++++++++++++++++++++++------------ SugarCubes.pde | 5 +- _Internals.pde | 18 +++-- _Overlay.pde | 183 +++++++++++++++++++++++++++++++++++++---------- code/GLucose.jar | Bin 16044 -> 13798 bytes code/HeronLX.jar | Bin 55699 -> 60851 bytes 6 files changed, 286 insertions(+), 91 deletions(-) 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 6ae9494db03c46ef1760edf5d567daf30ae4c0f4..48c8de6a9c2ad6f105c054cd2a66ee553cbbe686 100644 GIT binary patch delta 4321 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 Cq<8NC diff --git a/code/HeronLX.jar b/code/HeronLX.jar index 2724cc6f9ac1e35c17c8e5a0db2f16386395f128..e6ac53e47ae04653ee7759bff74f262a8011fd44 100644 GIT binary patch delta 12940 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 -- 2.34.1