X-Git-Url: https://git.piment-noir.org/?p=SugarCubes.git;a=blobdiff_plain;f=_MIDI.pde;h=7673f7ffd87d6cadf3630ed7abfa64efc0e50c7e;hp=e26050f20c42fc12ec39e25e9bfc645abb7afc73;hb=8f4e6c99775f2724edf3cec488860eb68b06491c;hpb=4214e9a2908a28344f1e90f98a645ebc2efa0fae diff --git a/_MIDI.pde b/_MIDI.pde index e26050f..7673f7f 100644 --- a/_MIDI.pde +++ b/_MIDI.pde @@ -22,6 +22,7 @@ interface MidiEngineListener { class MidiEngine { + public final GridController grid; private final List listeners = new ArrayList(); private final List midiControllers = new ArrayList(); @@ -41,17 +42,44 @@ class MidiEngine { private int activeDeckIndex = 0; public MidiEngine() { - - midiControllers.add(midiQwertyKeys = new SCMidiInput(SCMidiInput.KEYS)); - midiControllers.add(midiQwertyAPC = new SCMidiInput(SCMidiInput.APC)); + grid = new GridController(this); + midiControllers.add(midiQwertyKeys = new VirtualKeyMidiInput(this, VirtualKeyMidiInput.KEYS)); + midiControllers.add(midiQwertyAPC = new VirtualKeyMidiInput(this, VirtualKeyMidiInput.APC)); + int apcCount = 0; + for (MidiInputDevice device : RWMidi.getInputDevices()) { + if (device.getName().contains("APC")) { + ++apcCount; + } + } + + int apcIndex = 0; for (MidiInputDevice device : RWMidi.getInputDevices()) { if (device.getName().contains("APC")) { - midiControllers.add(new APC40MidiInput(device).setEnabled(true)); + int apcDeck = -1; + if (apcCount > 1 && apcIndex < 2) { + apcDeck = apcIndex++; + } + midiControllers.add(new APC40MidiInput(this, device, apcDeck).setEnabled(true)); } else if (device.getName().contains("SLIDER/KNOB KORG")) { - midiControllers.add(new KorgNanoKontrolMidiInput(device).setEnabled(true)); + midiControllers.add(new KorgNanoKontrolMidiInput(this, device).setEnabled(true)); + } else if (device.getName().contains("Arturia MINILAB")) { + midiControllers.add(new ArturiaMinilabMidiInput(this, device).setEnabled(true)); } else { - boolean enabled = device.getName().contains("KEYBOARD KORG") || device.getName().contains("Bus 1 Apple"); - midiControllers.add(new SCMidiInput(device).setEnabled(enabled)); + boolean enabled = + device.getName().contains("KEYBOARD KORG") || + device.getName().contains("Bus 1 Apple"); + midiControllers.add(new GenericDeviceMidiInput(this, device).setEnabled(enabled)); + } + } + + apcIndex = 0; + for (MidiOutputDevice device : RWMidi.getOutputDevices()) { + if (device.getName().contains("APC")) { + int apcDeck = -1; + if (apcCount > 1 && apcIndex < 2) { + apcDeck = apcIndex++; + } + new APC40MidiOutput(this, device, apcDeck); } } } @@ -60,6 +88,14 @@ class MidiEngine { return this.midiControllers; } + public LXDeck getFocusedDeck() { + return lx.engine.getDeck(activeDeckIndex); + } + + public SCPattern getFocusedPattern() { + return (SCPattern) getFocusedDeck().getActivePattern(); + } + public MidiEngine setFocusedDeck(int deckIndex) { if (this.activeDeckIndex != deckIndex) { this.activeDeckIndex = deckIndex; @@ -70,10 +106,6 @@ class MidiEngine { return this; } - public Engine.Deck getFocusedDeck() { - return lx.engine.getDeck(activeDeckIndex); - } - public boolean isQwertyEnabled() { return midiQwertyKeys.isEnabled() || midiQwertyAPC.isEnabled(); } @@ -83,30 +115,20 @@ public interface SCMidiInputListener { public void onEnabled(SCMidiInput controller, boolean enabled); } -public class SCMidiInput extends AbstractScrollItem { +public abstract class SCMidiInput extends AbstractScrollItem { - public static final int MIDI = 0; - public static final int KEYS = 1; - public static final int APC = 2; - - private boolean enabled = false; + protected boolean enabled = false; private final String name; - private final int mode; - private int octaveShift = 0; - class NoteMeta { - int channel; - int number; - NoteMeta(int channel, int number) { - this.channel = channel; - this.number = number; - } - } - - final Map keyToNote = new HashMap(); + protected final MidiEngine midiEngine; final List listeners = new ArrayList(); + protected SCMidiInput(MidiEngine midiEngine, String name) { + this.midiEngine = midiEngine; + this.name = name; + } + public SCMidiInput addListener(SCMidiInputListener l) { listeners.add(l); return this; @@ -117,25 +139,132 @@ public class SCMidiInput extends AbstractScrollItem { return this; } - SCMidiInput(MidiInputDevice d) { - mode = MIDI; - d.createInput(this); - name = d.getName().replace("Unknown vendor",""); + public String getLabel() { + return name; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isSelected() { + return enabled; + } + + public void onMousePressed() { + setEnabled(!enabled); } - SCMidiInput(int mode) { + public SCMidiInput setEnabled(boolean enabled) { + if (enabled != this.enabled) { + this.enabled = enabled; + for (SCMidiInputListener l : listeners) { + l.onEnabled(this, enabled); + } + } + return this; + } + + private boolean logMidi() { + return (uiMidi != null) && uiMidi.logMidi(); + } + + protected SCPattern getTargetPattern() { + return midiEngine.getFocusedPattern(); + } + + final void programChangeReceived(ProgramChange pc) { + if (!enabled) { + return; + } + if (logMidi()) { + println(getLabel() + " :: Program Change :: " + pc.getNumber()); + } + handleProgramChange(pc); + } + + final void controllerChangeReceived(rwmidi.Controller cc) { + if (!enabled) { + return; + } + if (logMidi()) { + println(getLabel() + " :: Controller :: " + cc.getChannel() + " :: " + cc.getCC() + ":" + cc.getValue()); + } + if (!handleControllerChange(cc)) { + getTargetPattern().controllerChange(cc); + } + } + + final void noteOnReceived(Note note) { + if (!enabled) { + return; + } + if (logMidi()) { + println(getLabel() + " :: Note On :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity()); + } + if (!handleNoteOn(note)) { + getTargetPattern().noteOn(note); + } + } + + final void noteOffReceived(Note note) { + if (!enabled) { + return; + } + if (logMidi()) { + println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity()); + } + if (!handleNoteOff(note)) { + getTargetPattern().noteOff(note); + } + } + + protected void setNormalized(LXParameter parameter, float value) { + if (parameter != null) { + if (parameter instanceof BasicParameter) { + ((BasicParameter)parameter).setNormalized(value); + } else { + parameter.setValue(value); + } + } + } + + // Subclasses may implement these to map top-level functionality + protected boolean handleProgramChange(ProgramChange pc) { return false; } + protected boolean handleControllerChange(rwmidi.Controller cc) { return false; } + protected boolean handleNoteOn(Note note) { return false; } + protected boolean handleNoteOff(Note note) { return false; } +} + +public class VirtualKeyMidiInput extends SCMidiInput { + + public static final int KEYS = 1; + public static final int APC = 2; + + private final int mode; + + private int octaveShift = 0; + + class NoteMeta { + int channel; + int number; + NoteMeta(int channel, int number) { + this.channel = channel; + this.number = number; + } + } + + final Map keyToNote = new HashMap(); + + VirtualKeyMidiInput(MidiEngine midiEngine, int mode) { + super(midiEngine, "QWERTY (" + (mode == APC ? "APC" : "Key") + " Mode)"); this.mode = mode; - switch (mode) { - case APC: - name = "QWERTY (APC Mode)"; + if (mode == APC) { mapAPC(); - break; - default: - case KEYS: - name = "QWERTY (Key Mode)"; + } else { mapKeys(); - break; } + registerKeyEvent(this); } private void mapAPC() { @@ -163,7 +292,6 @@ public class SCMidiInput extends AbstractScrollItem { mapNote('v', 3, 56); mapNote('b', 4, 56); mapNote('n', 5, 56); - registerKeyEvent(this); } private void mapKeys() { @@ -183,17 +311,12 @@ public class SCMidiInput extends AbstractScrollItem { mapNote('k', 1, note++); mapNote('o', 1, note++); mapNote('l', 1, note++); - registerKeyEvent(this); } void mapNote(char ch, int channel, int number) { keyToNote.put(ch, new NoteMeta(channel, number)); } - - public String getLabel() { - return name; - } - + public void keyEvent(KeyEvent e) { if (!enabled) { return; @@ -221,110 +344,122 @@ public class SCMidiInput extends AbstractScrollItem { } } } +} - public boolean isEnabled() { - return enabled; - } - - public boolean isSelected() { - return enabled; +public class GenericDeviceMidiInput extends SCMidiInput { + GenericDeviceMidiInput(MidiEngine midiEngine, MidiInputDevice d) { + super(midiEngine, d.getName().replace("Unknown vendor","")); + d.createInput(this); } +} - public void onMousePressed() { - setEnabled(!enabled); - } +public class APC40MidiInput extends GenericDeviceMidiInput { - public SCMidiInput setEnabled(boolean enabled) { - if (enabled != this.enabled) { - this.enabled = enabled; - for (SCMidiInputListener l : listeners) { - l.onEnabled(this, enabled); - } - } - return this; + private boolean shiftOn = false; + private LXEffect releaseEffect = null; + final private LXDeck targetDeck; + + APC40MidiInput(MidiEngine midiEngine, MidiInputDevice d) { + this(midiEngine, d, -1); } - - protected SCPattern getFocusedPattern() { - return (SCPattern) midiEngine.getFocusedDeck().getActivePattern(); + + APC40MidiInput(MidiEngine midiEngine, MidiInputDevice d, int deckIndex) { + super(midiEngine, d); + targetDeck = (deckIndex < 0) ? null : lx.engine.getDecks().get(deckIndex); } - - private boolean logMidi() { - return (uiMidi != null) && uiMidi.logMidi(); + + protected LXDeck getTargetDeck() { + return (targetDeck != null) ? targetDeck : midiEngine.getFocusedDeck(); } - - final void programChangeReceived(ProgramChange pc) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Program Change :: " + pc.getNumber()); + + protected SCPattern getTargetPattern() { + if (targetDeck != null) { + return (SCPattern) (targetDeck.getActivePattern()); } - handleProgramChange(pc); + return super.getTargetPattern(); } - final void controllerChangeReceived(rwmidi.Controller cc) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue()); - } - if (!getFocusedPattern().controllerChangeReceived(cc)) { - handleControllerChange(cc); + private class GridPosition { + public final int row, col; + GridPosition(int r, int c) { + row = r; + col = c; } } - - final void noteOnReceived(Note note) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Note On :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity()); - } - if (!getFocusedPattern().noteOnReceived(note)) { - handleNoteOn(note); + + private GridPosition getGridPosition(Note note) { + int channel = note.getChannel(); + int pitch = note.getPitch(); + if (channel < 8) { + if (pitch >= 53 && pitch <=57) return new GridPosition(pitch-53, channel); } + return null; } - final void noteOffReceived(Note note) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity()); - } - if (!getFocusedPattern().noteOffReceived(note)) { - handleNoteOff(note); + private boolean handleGridNoteOn(Note note) { + GridPosition p = getGridPosition(note); + if (p != null) { + return midiEngine.grid.gridPressed(p.row, p.col); } + return false; } - // Subclasses may implement these to map top-level functionality - protected void handleProgramChange(ProgramChange pc) { - } - protected void handleControllerChange(rwmidi.Controller cc) { - } - protected void handleNoteOn(Note note) { - } - protected void handleNoteOff(Note note) { - } -} - -public class APC40MidiInput extends SCMidiInput { - - private boolean shiftOn = false; - private LXEffect releaseEffect = null; - - APC40MidiInput(MidiInputDevice d) { - super(d); + private boolean handleGridNoteOff(Note note) { + GridPosition p = getGridPosition(note); + if (p != null) { + return midiEngine.grid.gridReleased(p.row, p.col); + } + return false; } - protected void handleControllerChange(rwmidi.Controller cc) { + protected boolean handleControllerChange(rwmidi.Controller cc) { + int channel = cc.getChannel(); int number = cc.getCC(); + float value = cc.getValue() / 127.; switch (number) { + + case 7: + switch (channel) { + case 0: + uiSpeed.speed.setNormalized(0.5 - value*0.5); + return true; + case 1: + effects.colorFucker.desat.setNormalized(value); + return true; + case 2: + effects.colorFucker.desat.setNormalized(value); + return true; + case 3: + effects.blur.amount.setNormalized(value); + return true; + case 4: + effects.quantize.amount.setNormalized(value); + return true; + } + break; + + // Master bright + case 14: + effects.colorFucker.level.setNormalized(value); + return true; + // Crossfader case 15: - lx.engine.getDeck(1).getCrossfader().setValue(cc.getValue() / 127.); - break; + lx.engine.getDeck(GLucose.RIGHT_DECK).getFader().setNormalized(value); + return true; + + // Cue level + case 47: + float val = effects.colorFucker.hueShift.getValuef(); + int cv = cc.getValue(); + if (cv < 64) { + cv = 64 + cv; + } else { + cv = cv - 64; + } + val += (cv - 64) / 500.; + effects.colorFucker.hueShift.setNormalized((val+1) % 1); + return true; } int parameterIndex = -1; @@ -334,9 +469,10 @@ public class APC40MidiInput extends SCMidiInput { parameterIndex = 8 + (number-16); } if (parameterIndex >= 0) { - List parameters = getFocusedPattern().getParameters(); + List parameters = getTargetPattern().getParameters(); if (parameterIndex < parameters.size()) { - parameters.get(parameterIndex).setValue(cc.getValue() / 127.); + setNormalized(parameters.get(parameterIndex), value); + return true; } } @@ -344,144 +480,500 @@ public class APC40MidiInput extends SCMidiInput { int effectIndex = number - 20; List parameters = glucose.getSelectedEffect().getParameters(); if (effectIndex < parameters.size()) { - parameters.get(effectIndex).setValue(cc.getValue() / 127.); + setNormalized(parameters.get(effectIndex), value); + return true; } } + + return false; } - + private long tap1 = 0; - private double Tap1 = 0; - private double getNow() { return millis() + 1000*second() + 60*1000*minute() + 3600*1000*hour(); } - private boolean dbtwn (double a,double b,double c) { return a >= b && a <= c; } + private boolean lbtwn(long a, long b, long c) { + return a >= b && a <= c; + } - protected void handleNoteOn(Note note) { - int nPitch = note.getPitch(), nChan = note.getChannel(); + protected boolean handleNoteOn(Note note) { + if (handleGridNoteOn(note)) { + return true; + } + + int nPitch = note.getPitch(); + int nChan = note.getChannel(); switch (nPitch) { - - case 82: EFF_boom .trigger(); break; // BOOM! - case 83: EFF_flash .trigger(); break; // Flash - - case 90: lx.tempo.trigger(); Tap1 = getNow(); break; // dan's dirty tapping mechanism - - case 91: // play - case 95: // bank - midiEngine.setFocusedDeck(0); + + case 49: // SOLO/CUE + switch (nChan) { + case 4: + effects.colorFucker.mono.setNormalized(1); + return true; + case 5: + effects.colorFucker.invert.setNormalized(1); + return true; + case 6: + lx.cycleBaseHue(60000); + return true; + } break; + + case 82: // scene 1 + effects.boom.trigger(); + return true; + + case 83: // scene 2 + effects.flash.trigger(); + return true; + + case 84: // scene 3 + getTargetPattern().reset(); + return true; + + case 90: + // dan's dirty tapping mechanism + lx.tempo.trigger(); + tap1 = millis(); + return true; + case 91: // play + if (shiftOn) { + midiEngine.setFocusedDeck(GLucose.LEFT_DECK); + } else { + uiCrossfader.setDisplayMode("A"); + } + return true; + + case 92: // stop + uiCrossfader.setDisplayMode("COMP"); + return true; + case 93: // rec - case 94: // right bank - midiEngine.setFocusedDeck(1); - break; - - case 96: // up bank if (shiftOn) { - glucose.incrementSelectedEffectBy(1); + midiEngine.setFocusedDeck(GLucose.RIGHT_DECK); } else { - midiEngine.getFocusedDeck().goNext(); + uiCrossfader.setDisplayMode("B"); } - break; - case 97: // down bank + return true; + + case 94: // up bank if (shiftOn) { glucose.incrementSelectedEffectBy(-1); } else { - midiEngine.getFocusedDeck().goPrev(); + getTargetDeck().goPrev(); } - break; + return true; + + case 95: // down bank + if (shiftOn) { + glucose.incrementSelectedEffectBy(1); + } else { + getTargetDeck().goNext(); + } + return true; case 98: // shift shiftOn = true; - break; + return true; case 99: // tap tempo lx.tempo.tap(); - break; + return true; + case 100: // nudge+ lx.tempo.setBpm(lx.tempo.bpm() + (shiftOn ? 1 : .1)); - break; + return true; + case 101: // nudge- lx.tempo.setBpm(lx.tempo.bpm() - (shiftOn ? 1 : .1)); - break; + return true; - case 62: // Detail View + case 62: // Detail View / red 5 releaseEffect = glucose.getSelectedEffect(); if (releaseEffect.isMomentary()) { releaseEffect.enable(); } else { releaseEffect.toggle(); } - break; + return true; - case 63: // rec quantize + case 63: // rec quantize / red 6 glucose.getSelectedEffect().disable(); - break; + return true; } + + return false; } - protected void handleNoteOff(Note note) { - int nPitch = note.getPitch(), nChan = note.getChannel(); + protected boolean handleNoteOff(Note note) { + if (handleGridNoteOff(note)) { + return true; + } + + int nPitch = note.getPitch(); + int nChan = note.getChannel(); + switch (nPitch) { - case 90: - if (dbtwn(getNow() - Tap1,5000,300*1000)) { // hackish tapping mechanism - double bpm = 32.*60000./(getNow()-Tap1); - while (bpm < 20) bpm*=2; - while (bpm > 40) bpm/=2; - lx.tempo.setBpm(bpm); lx.tempo.trigger(); Tap1=0; println("Tap Set - " + bpm + " bpm"); - } - break; + + case 49: // SOLO/CUE + switch (nChan) { + case 4: + effects.colorFucker.mono.setNormalized(0); + return true; + case 5: + effects.colorFucker.invert.setNormalized(0); + return true; + case 6: + lx.setBaseHue(lx.getBaseHue()); + return true; + } + break; + case 52: // CLIP STOP + if (nChan < PresetManager.NUM_PRESETS) { + if (shiftOn) { + presetManager.store(getTargetDeck(), nChan); + } else { + presetManager.select(getTargetDeck(), nChan); + } + } + return true; + + case 90: // SEND C + long tapDelta = millis() - tap1; + if (lbtwn(tapDelta,5000,300*1000)) { // hackish tapping mechanism + double bpm = 32.*60000./(tapDelta); + while (bpm < 20) bpm*=2; + while (bpm > 40) bpm/=2; + lx.tempo.setBpm(bpm); + lx.tempo.trigger(); + tap1 = 0; + println("Tap Set - " + bpm + " bpm"); + } + return true; - case 63: // rec quantize + case 63: // rec quantize / RED 6 if (releaseEffect != null) { if (releaseEffect.isMomentary()) { releaseEffect.disable(); } } - break; + return true; case 98: // shift shiftOn = false; - break; + return true; } + + return false; } } -class KorgNanoKontrolMidiInput extends SCMidiInput { +class KorgNanoKontrolMidiInput extends GenericDeviceMidiInput { - KorgNanoKontrolMidiInput(MidiInputDevice d) { - super(d); + KorgNanoKontrolMidiInput(MidiEngine midiEngine, MidiInputDevice d) { + super(midiEngine, d); } - protected void handleControllerChange(rwmidi.Controller cc) { + protected boolean handleControllerChange(rwmidi.Controller cc) { int number = cc.getCC(); if (number >= 16 && number <= 23) { int parameterIndex = number - 16; - List parameters = getFocusedPattern().getParameters(); + List parameters = midiEngine.getFocusedPattern().getParameters(); if (parameterIndex < parameters.size()) { - parameters.get(parameterIndex).setValue(cc.getValue() / 127.); + setNormalized(parameters.get(parameterIndex), cc.getValue() / 127.); + return true; } } if (cc.getValue() == 127) { switch (number) { - // Left track - case 58: - midiEngine.setFocusedDeck(0); - break; - // Right track - case 59: - midiEngine.setFocusedDeck(1); - break; - // Left chevron - case 43: + + case 58: // Left track + midiEngine.setFocusedDeck(GLucose.LEFT_DECK); + return true; + + case 59: // Right track + midiEngine.setFocusedDeck(GLucose.RIGHT_DECK); + return true; + + case 43: // Left chevron midiEngine.getFocusedDeck().goPrev(); - break; - // Right chevron - case 44: + return true; + + case 44: // Right chevron midiEngine.getFocusedDeck().goNext(); + return true; + } + } + + return false; + } +} + +class APC40MidiOutput implements LXParameterListener, GridOutput { + + private final MidiEngine midiEngine; + private final MidiOutput output; + private LXPattern focusedPattern = null; + private LXEffect focusedEffect = null; + private final LXDeck targetDeck; + + APC40MidiOutput(MidiEngine midiEngine, MidiOutputDevice device) { + this(midiEngine, device, -1); + } + + APC40MidiOutput(MidiEngine midiEngine, MidiOutputDevice device, int deckIndex) { + this.midiEngine = midiEngine; + output = device.createOutput(); + targetDeck = (deckIndex < 0) ? null : lx.engine.getDecks().get(deckIndex); + setDPatternOutputs(); + if (targetDeck != null) { + midiEngine.addListener(new MidiEngineListener() { + public void onFocusedDeck(int deckIndex) { + resetPatternParameters(); + for (int i = 0; i < 8; ++i) { + output.sendNoteOn(i, 52, 0); + } + } + }); + } + glucose.addEffectListener(new GLucose.EffectListener() { + public void effectSelected(LXEffect effect) { + resetEffectParameters(); + } + }); + LXDeck.Listener deckListener = new LXDeck.AbstractListener() { + public void patternDidChange(LXDeck deck, LXPattern pattern) { + if (deck == getTargetDeck()) { + resetPatternParameters(); + } + } + }; + for (LXDeck d : lx.engine.getDecks()) { + if (targetDeck == null || d == targetDeck) { + d.addListener(deckListener); + } + } + presetManager.addListener(new PresetListener() { + public void onPresetSelected(LXDeck deck, Preset preset) { + if (deck == getTargetDeck()) { + for (int i = 0; i < 8; ++i) { + output.sendNoteOn(i, 52, (preset.index == i) ? 1 : 0); + } + } + } + public void onPresetDirty(LXDeck deck, Preset preset) { + if (deck == getTargetDeck()) { + output.sendNoteOn(preset.index, 52, 2); + } + } + public void onPresetStored(LXDeck deck, Preset preset) { + onPresetSelected(deck, preset); + } + }); + resetParameters(); + midiEngine.grid.addOutput(this); + + lx.cycleBaseHue(60000); + output.sendNoteOn(6, 49, 127); + + // Turn off the track selection lights and preset selectors + for (int i = 0; i < 8; ++i) { + output.sendNoteOn(i, 51, 0); + output.sendNoteOn(i, 52, 0); + } + + // Turn off the MASTER selector + output.sendNoteOn(0, 80, 0); + } + + private void setDPatternOutputs() { + for (LXDeck deck : lx.engine.getDecks()) { + if (targetDeck == null || deck == targetDeck) { + for (LXPattern pattern : deck.getPatterns()) { + if (pattern instanceof DPat) { + ((DPat)pattern).setAPCOutput(output); + } + } + } + } + } + + protected LXDeck getTargetDeck() { + return (targetDeck != null) ? targetDeck : midiEngine.getFocusedDeck(); + } + + private void resetParameters() { + resetPatternParameters(); + resetEffectParameters(); + } + + private void resetPatternParameters() { + LXPattern newPattern = getTargetDeck().getActivePattern(); + if (newPattern == focusedPattern) { + return; + } + if (focusedPattern != null) { + for (LXParameter p : focusedPattern.getParameters()) { + ((LXListenableParameter) p).removeListener(this); + } + } + focusedPattern = newPattern; + int i = 0; + for (LXParameter p : focusedPattern.getParameters()) { + ((LXListenableParameter) p).addListener(this); + sendKnob(i++, p); + } + while (i < 12) { + sendKnob(i++, 0); + } + if (focusedPattern instanceof DPat) { + ((DPat)focusedPattern).updateLights(); + } else { + for (int j = 0; j < 8; ++j) { + output.sendNoteOn(j, 48, 0); + } + for (int row = 0; row < 7; ++row) { + for (int col = 0; col < 8; ++col) { + setGridState(row, col, 0); + } + } + } + } + + private void resetEffectParameters() { + LXEffect newEffect = glucose.getSelectedEffect(); + if (newEffect == focusedEffect) { + return; + } + if (focusedEffect != null) { + for (LXParameter p : focusedPattern.getParameters()) { + ((LXListenableParameter) p).removeListener(this); + } + } + focusedEffect = newEffect; + int i = 0; + for (LXParameter p : focusedEffect.getParameters()) { + ((LXListenableParameter) p).addListener(this); + sendKnob(12 + i++, p); + } + while (i < 4) { + sendKnob(12 + i++, 0); + } + } + + private void sendKnob(int i, LXParameter p) { + sendKnob(i, (int) (p.getValuef() * 127.)); + } + + private void sendKnob(int i, int value) { + if (i < 8) { + output.sendController(0, 48+i, value); + } else if (i < 16) { + output.sendController(0, 8+i, value); + } + } + + public void onParameterChanged(LXParameter parameter) { + int i = 0; + for (LXParameter p : focusedPattern.getParameters()) { + if (p == parameter) { + sendKnob(i, p); + break; + } + ++i; + } + i = 12; + for (LXParameter p : focusedEffect.getParameters()) { + if (p == parameter) { + sendKnob(i, p); + break; + } + ++i; + } + } + + public void setGridState(int row, int col, int state) { + if (col < 8 && row < 5) { + output.sendNoteOn(col, 53+row, state); + } + } +} + +class ArturiaMinilabMidiInput extends GenericDeviceMidiInput { + ArturiaMinilabMidiInput(MidiEngine midiEngine, MidiInputDevice d) { + super(midiEngine, d); + } + + protected boolean handleControllerChange(rwmidi.Controller cc) { + int parameterIndex = -1; + switch (cc.getCC()) { + case 7: parameterIndex = 0; break; + case 74: parameterIndex = 1; break; + case 71: parameterIndex = 2; break; + case 76: parameterIndex = 3; break; + case 114: parameterIndex = 4; break; + case 18: parameterIndex = 5; break; + case 19: parameterIndex = 6; break; + case 16: parameterIndex = 7; break; + + case 75: + float val = effects.colorFucker.hueShift.getValuef(); + val += (cc.getValue() - 64) / 256.; + effects.colorFucker.hueShift.setNormalized((val+1) % 1); break; + } + if (parameterIndex >= 0) { + List parameters = midiEngine.getFocusedPattern().getParameters(); + if (parameterIndex < parameters.size()) { + LXParameter p = parameters.get(parameterIndex); + float curVal = p.getValuef(); + curVal += (cc.getValue() - 64) / 127.; + setNormalized(p, constrain(curVal, 0, 1)); } } + return false; + } +} + +interface GridOutput { + public static final int OFF = 0; + public static final int GREEN = 1; + public static final int GREEN_BLINK = 2; + public static final int RED = 3; + public static final int RED_BLINK = 4; + public static final int YELLOW = 5; + public static final int YELLOW_BLINK = 6; + public static final int ON = 127; + + public void setGridState(int row, int col, int state); +} + +class GridController { + private final List outputs = new ArrayList(); + + private final MidiEngine midiEngine; + + GridController(MidiEngine midiEngine) { + this.midiEngine = midiEngine; + } + + public void addOutput(GridOutput output) { + outputs.add(output); + } + + public boolean gridPressed(int row, int col) { + return midiEngine.getFocusedPattern().gridPressed(row, col); + } + + public boolean gridReleased(int row, int col) { + return midiEngine.getFocusedPattern().gridReleased(row, col); + } + + public void setState(int row, int col, int state) { + for (GridOutput g : outputs) { + g.setGridState(row, col, state); + } } }