From: Mark Slee Date: Wed, 25 Sep 2013 00:28:53 +0000 (-0700) Subject: Refactor MIDI stuff so deck focusing is listenable and controllable X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=d6ac1ee83fec42f9c5ba4a14248879b541f1f58d;p=SugarCubes.git Refactor MIDI stuff so deck focusing is listenable and controllable --- diff --git a/DanUtil.pde b/DanUtil.pde index d3f9ac0..c27eaed 100644 --- a/DanUtil.pde +++ b/DanUtil.pde @@ -121,7 +121,6 @@ public class DGlobals { void controllerChangeReceived(rwmidi.Controller cc) { if (cc.getCC() == 7 && btwn(cc.getChannel(),0,7)) { Sliders[cc.getChannel()] = 1.*cc.getValue()/127.; } - else if (cc.getCC() == 15 && cc.getChannel() == 0) { lx.engine.getDeck(1).getCrossfader().setValue( 1.*cc.getValue()/127.); } @@ -157,7 +156,6 @@ public class DGlobals { double Tap1 = 0; double getNow() { return millis() + 1000*second() + 60*1000*minute() + 3600*1000*hour(); } - void noteOffReceived(Note note) { if (CurPat == null) return; int row = DG.mapRow(note.getPitch()), col = note.getChannel(); diff --git a/MarkSlee.pde b/MarkSlee.pde index 7738419..3a9b530 100644 --- a/MarkSlee.pde +++ b/MarkSlee.pde @@ -421,13 +421,15 @@ public class PianoKeyPattern extends SCPattern { return base[index % base.length]; } - public void noteOnReceived(Note note) { + public boolean noteOnReceived(Note note) { LinearEnvelope env = getEnvelope(note.getPitch()); env.setEndVal(min(1, env.getValuef() + (note.getVelocity() / 127.)), getAttackTime()).start(); + return true; } - public void noteOffReceived(Note note) { + public boolean noteOffReceived(Note note) { getEnvelope(note.getPitch()).setEndVal(0, getReleaseTime()).start(); + return true; } public void run(double deltaMs) { diff --git a/_Internals.pde b/_Internals.pde index 19b4ed6..60a5f32 100644 --- a/_Internals.pde +++ b/_Internals.pde @@ -48,8 +48,7 @@ HeronLX lx; LXPattern[] patterns; MappingTool mappingTool; PandaDriver[] pandaBoards; -SCMidiInput midiQwertyKeys; -SCMidiInput midiQwertyAPC; +MidiEngine midiEngine; // Display configuration mode boolean mappingMode = false; @@ -124,14 +123,7 @@ void setup() { logTime("Built PandaDriver"); // MIDI devices - List midiControllers = new ArrayList(); - midiControllers.add(midiQwertyKeys = new SCMidiInput(SCMidiInput.KEYS)); - midiControllers.add(midiQwertyAPC = new SCMidiInput(SCMidiInput.APC)); - for (MidiInputDevice device : RWMidi.getInputDevices()) { - boolean enableDevice = device.getName().contains("APC"); - midiControllers.add(new SCMidiInput(device).setEnabled(enableDevice)); - } - SCMidiDevices.initializeStandardDevices(glucose); + midiEngine = new MidiEngine(); logTime("Setup MIDI devices"); // Build overlay UI @@ -144,7 +136,7 @@ void setup() { new UISpeed(4, 624, 140, 50), new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324), - uiMidi = new UIMidi(midiControllers, width-144, 332, 140, 158), + uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158), new UIOutput(width-144, 494, 140, 106), uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86), @@ -167,6 +159,8 @@ void setup() { 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()); @@ -176,221 +170,6 @@ void setup() { println("Hit the 'p' key to toggle Panda Board output"); } -public interface SCMidiInputListener { - public void onEnabled(SCMidiInput controller, boolean enabled); -} - -public 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; - 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(); - - final List listeners = new ArrayList(); - - public SCMidiInput addListener(SCMidiInputListener l) { - listeners.add(l); - return this; - } - - public SCMidiInput removeListener(SCMidiInputListener l) { - listeners.remove(l); - return this; - } - - SCMidiInput(MidiInputDevice d) { - mode = MIDI; - d.createInput(this); - name = d.getName().replace("Unknown vendor",""); - } - - SCMidiInput(int mode) { - this.mode = mode; - switch (mode) { - case APC: - name = "QWERTY (APC Mode)"; - mapAPC(); - break; - default: - case KEYS: - name = "QWERTY (Key Mode)"; - mapKeys(); - break; - } - } - - private void mapAPC() { - mapNote('1', 0, 53); - mapNote('2', 1, 53); - mapNote('3', 2, 53); - mapNote('4', 3, 53); - mapNote('5', 4, 53); - mapNote('6', 5, 53); - mapNote('q', 0, 54); - mapNote('w', 1, 54); - mapNote('e', 2, 54); - mapNote('r', 3, 54); - mapNote('t', 4, 54); - mapNote('y', 5, 54); - mapNote('a', 0, 55); - mapNote('s', 1, 55); - mapNote('d', 2, 55); - mapNote('f', 3, 55); - mapNote('g', 4, 55); - mapNote('h', 5, 55); - mapNote('z', 0, 56); - mapNote('x', 1, 56); - mapNote('c', 2, 56); - mapNote('v', 3, 56); - mapNote('b', 4, 56); - mapNote('n', 5, 56); - registerKeyEvent(this); - } - - private void mapKeys() { - int note = 48; - mapNote('a', 1, note++); - mapNote('w', 1, note++); - mapNote('s', 1, note++); - mapNote('e', 1, note++); - mapNote('d', 1, note++); - mapNote('f', 1, note++); - mapNote('t', 1, note++); - mapNote('g', 1, note++); - mapNote('y', 1, note++); - mapNote('h', 1, note++); - mapNote('u', 1, note++); - mapNote('j', 1, note++); - 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; - } - char c = Character.toLowerCase(e.getKeyChar()); - NoteMeta nm = keyToNote.get(c); - if (nm != null) { - switch (e.getID()) { - case KeyEvent.KEY_PRESSED: - noteOnReceived(new Note(Note.NOTE_ON, nm.channel, nm.number + octaveShift*12, 127)); - break; - case KeyEvent.KEY_RELEASED: - noteOffReceived(new Note(Note.NOTE_OFF, nm.channel, nm.number + octaveShift*12, 0)); - break; - } - } - if ((mode == KEYS) && (e.getID() == KeyEvent.KEY_PRESSED)) { - switch (c) { - case 'z': - octaveShift = constrain(octaveShift-1, -4, 4); - break; - case 'x': - octaveShift = constrain(octaveShift+1, -4, 4); - break; - } - } - } - - public boolean isEnabled() { - return enabled; - } - - public boolean isSelected() { - return enabled; - } - - public void onMousePressed() { - setEnabled(!enabled); - } - - public SCMidiInput setEnabled(boolean enabled) { - if (enabled != this.enabled) { - this.enabled = enabled; - for (SCMidiInputListener l : listeners) { - l.onEnabled(this, enabled); - } - } - return this; - } - - private SCPattern getFocusedPattern() { - Engine.Deck focusedDeck = (uiMidi != null) ? uiMidi.getFocusedDeck() : lx.engine.getDefaultDeck(); - return (SCPattern) focusedDeck.getActivePattern(); - } - - private boolean logMidi() { - return (uiMidi != null) && uiMidi.logMidi(); - } - - void programChangeReceived(ProgramChange pc) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Program Change :: " + pc.getNumber()); - } - } - - void controllerChangeReceived(rwmidi.Controller cc) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue()); - } - getFocusedPattern().controllerChangeReceived(cc); - } - - void noteOnReceived(Note note) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Note On :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity()); - } - getFocusedPattern().noteOnReceived(note); - } - - void noteOffReceived(Note note) { - if (!enabled) { - return; - } - if (logMidi()) { - println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity()); - } - getFocusedPattern().noteOffReceived(note); - } - -} - /** * Core render loop and drawing functionality. */ @@ -633,13 +412,13 @@ void keyPressed() { frameRate(++targetFramerate); break; case 'd': - if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) { + if (!midiEngine.isQwertyEnabled()) { debugMode = !debugMode; println("Debug output: " + (debugMode ? "ON" : "OFF")); } break; case 'm': - if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) { + if (!midiEngine.isQwertyEnabled()) { mappingMode = !mappingMode; uiPatternA.setVisible(!mappingMode); uiMapping.setVisible(mappingMode); @@ -661,7 +440,7 @@ void keyPressed() { } break; case 'u': - if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) { + if (!midiEngine.isQwertyEnabled()) { uiOn = !uiOn; } break; diff --git a/_MIDI.pde b/_MIDI.pde new file mode 100644 index 0000000..4b3f313 --- /dev/null +++ b/_MIDI.pde @@ -0,0 +1,459 @@ +/** + * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND + * + * //\\ //\\ //\\ //\\ + * ///\\\ ///\\\ ///\\\ ///\\\ + * \\\/// \\\/// \\\/// \\\/// + * \\// \\// \\// \\// + * + * EXPERTS ONLY!! EXPERTS ONLY!! + * + * This file defines the MIDI mapping interfaces. This shouldn't + * need editing unless you're adding top level support for a + * specific MIDI device of some sort. Generally, all MIDI devices + * will just work with the default configuration, and you can + * set your SCPattern class to respond to the controllers that you + * care about. + */ + +interface MidiEngineListener { + public void onFocusedDeck(int deckIndex); +} + +class MidiEngine { + + private final List listeners = new ArrayList(); + private final List midiControllers = new ArrayList(); + + public MidiEngine addListener(MidiEngineListener l) { + listeners.add(l); + return this; + } + + public MidiEngine removeListener(MidiEngineListener l) { + listeners.remove(l); + return this; + } + + private SCMidiInput midiQwertyKeys; + private SCMidiInput midiQwertyAPC; + + private int activeDeckIndex = 0; + + public MidiEngine() { + + midiControllers.add(midiQwertyKeys = new SCMidiInput(SCMidiInput.KEYS)); + midiControllers.add(midiQwertyAPC = new SCMidiInput(SCMidiInput.APC)); + for (MidiInputDevice device : RWMidi.getInputDevices()) { + if (device.getName().contains("APC")) { + midiControllers.add(new APC40MidiInput(device).setEnabled(true)); + } else if (device.getName().contains("SLIDER/KNOB KORG")) { + midiControllers.add(new KorgNanoKontrolMidiInput(device).setEnabled(true)); + } else { + midiControllers.add(new SCMidiInput(device)); + } + } + } + + public List getControllers() { + return this.midiControllers; + } + + public MidiEngine setFocusedDeck(int deckIndex) { + if (this.activeDeckIndex != deckIndex) { + this.activeDeckIndex = deckIndex; + for (MidiEngineListener listener : listeners) { + listener.onFocusedDeck(deckIndex); + } + } + return this; + } + + public Engine.Deck getFocusedDeck() { + return lx.engine.getDeck(activeDeckIndex); + } + + public boolean isQwertyEnabled() { + return midiQwertyKeys.isEnabled() || midiQwertyAPC.isEnabled(); + } +} + +public interface SCMidiInputListener { + public void onEnabled(SCMidiInput controller, boolean enabled); +} + +public 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; + 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(); + + final List listeners = new ArrayList(); + + public SCMidiInput addListener(SCMidiInputListener l) { + listeners.add(l); + return this; + } + + public SCMidiInput removeListener(SCMidiInputListener l) { + listeners.remove(l); + return this; + } + + SCMidiInput(MidiInputDevice d) { + mode = MIDI; + d.createInput(this); + name = d.getName().replace("Unknown vendor",""); + } + + SCMidiInput(int mode) { + this.mode = mode; + switch (mode) { + case APC: + name = "QWERTY (APC Mode)"; + mapAPC(); + break; + default: + case KEYS: + name = "QWERTY (Key Mode)"; + mapKeys(); + break; + } + } + + private void mapAPC() { + mapNote('1', 0, 53); + mapNote('2', 1, 53); + mapNote('3', 2, 53); + mapNote('4', 3, 53); + mapNote('5', 4, 53); + mapNote('6', 5, 53); + mapNote('q', 0, 54); + mapNote('w', 1, 54); + mapNote('e', 2, 54); + mapNote('r', 3, 54); + mapNote('t', 4, 54); + mapNote('y', 5, 54); + mapNote('a', 0, 55); + mapNote('s', 1, 55); + mapNote('d', 2, 55); + mapNote('f', 3, 55); + mapNote('g', 4, 55); + mapNote('h', 5, 55); + mapNote('z', 0, 56); + mapNote('x', 1, 56); + mapNote('c', 2, 56); + mapNote('v', 3, 56); + mapNote('b', 4, 56); + mapNote('n', 5, 56); + registerKeyEvent(this); + } + + private void mapKeys() { + int note = 48; + mapNote('a', 1, note++); + mapNote('w', 1, note++); + mapNote('s', 1, note++); + mapNote('e', 1, note++); + mapNote('d', 1, note++); + mapNote('f', 1, note++); + mapNote('t', 1, note++); + mapNote('g', 1, note++); + mapNote('y', 1, note++); + mapNote('h', 1, note++); + mapNote('u', 1, note++); + mapNote('j', 1, note++); + 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; + } + char c = Character.toLowerCase(e.getKeyChar()); + NoteMeta nm = keyToNote.get(c); + if (nm != null) { + switch (e.getID()) { + case KeyEvent.KEY_PRESSED: + noteOnReceived(new Note(Note.NOTE_ON, nm.channel, nm.number + octaveShift*12, 127)); + break; + case KeyEvent.KEY_RELEASED: + noteOffReceived(new Note(Note.NOTE_OFF, nm.channel, nm.number + octaveShift*12, 0)); + break; + } + } + if ((mode == KEYS) && (e.getID() == KeyEvent.KEY_PRESSED)) { + switch (c) { + case 'z': + octaveShift = constrain(octaveShift-1, -4, 4); + break; + case 'x': + octaveShift = constrain(octaveShift+1, -4, 4); + break; + } + } + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isSelected() { + return enabled; + } + + public void onMousePressed() { + setEnabled(!enabled); + } + + public SCMidiInput setEnabled(boolean enabled) { + if (enabled != this.enabled) { + this.enabled = enabled; + for (SCMidiInputListener l : listeners) { + l.onEnabled(this, enabled); + } + } + return this; + } + + protected SCPattern getFocusedPattern() { + return (SCPattern) midiEngine.getFocusedDeck().getActivePattern(); + } + + private boolean logMidi() { + return (uiMidi != null) && uiMidi.logMidi(); + } + + 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.getCC() + ":" + cc.getValue()); + } + if (!getFocusedPattern().controllerChangeReceived(cc)) { + handleControllerChange(cc); + } + } + + 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); + } + } + + 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); + } + } + + // 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); + } + + protected void handleControllerChange(rwmidi.Controller cc) { + int number = cc.getCC(); + switch (number) { + // Crossfader + case 15: + lx.engine.getDeck(1).getCrossfader().setValue(cc.getValue() / 127.); + break; + } + + int parameterIndex = -1; + if (number >= 48 && number <= 55) { + parameterIndex = number - 48; + } else if (number >= 16 && number <= 19) { + parameterIndex = 8 + (number-16); + } + if (parameterIndex >= 0) { + List parameters = getFocusedPattern().getParameters(); + if (parameterIndex < parameters.size()) { + parameters.get(parameterIndex).setValue(cc.getValue() / 127.); + } + } + + if (number >= 20 && number <= 23) { + int effectIndex = number - 20; + List parameters = glucose.getSelectedEffect().getParameters(); + if (effectIndex < parameters.size()) { + parameters.get(effectIndex).setValue(cc.getValue() / 127.); + } + } + } + + protected void handleNoteOn(Note note) { + switch (note.getPitch()) { + case 94: // right bank + midiEngine.setFocusedDeck(1); + break; + case 95: // left bank + midiEngine.setFocusedDeck(0); + break; + case 96: // up bank + if (shiftOn) { + glucose.incrementSelectedEffectBy(1); + } else { + midiEngine.getFocusedDeck().goNext(); + } + break; + case 97: // down bank + if (shiftOn) { + glucose.incrementSelectedEffectBy(-1); + } else { + midiEngine.getFocusedDeck().goPrev(); + } + break; + + case 98: // shift + shiftOn = true; + break; + + case 99: // tap tempo + lx.tempo.tap(); + break; + case 100: // nudge+ + lx.tempo.setBpm(lx.tempo.bpm() + (shiftOn ? 1 : .1)); + break; + case 101: // nudge- + lx.tempo.setBpm(lx.tempo.bpm() - (shiftOn ? 1 : .1)); + break; + + case 91: // play + case 93: // rec + releaseEffect = glucose.getSelectedEffect(); + if (releaseEffect.isMomentary()) { + releaseEffect.enable(); + } else { + releaseEffect.toggle(); + } + break; + + case 92: // stop + glucose.getSelectedEffect().disable(); + break; + } + } + + protected void handleNoteOff(Note note) { + switch (note.getPitch()) { + case 93: // rec + if (releaseEffect != null) { + if (releaseEffect.isMomentary()) { + releaseEffect.disable(); + } + } + break; + + case 98: // shift + shiftOn = false; + break; + } + } +} + +class KorgNanoKontrolMidiInput extends SCMidiInput { + + KorgNanoKontrolMidiInput(MidiInputDevice d) { + super(d); + } + + protected void handleControllerChange(rwmidi.Controller cc) { + int number = cc.getCC(); + if (number >= 16 && number <= 23) { + int parameterIndex = number - 16; + List parameters = getFocusedPattern().getParameters(); + if (parameterIndex < parameters.size()) { + parameters.get(parameterIndex).setValue(cc.getValue() / 127.); + } + } + + 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: + midiEngine.getFocusedDeck().goPrev(); + break; + // Right chevron + case 44: + midiEngine.getFocusedDeck().goNext(); + break; + } + } + } +} + diff --git a/_UIImplementation.pde b/_UIImplementation.pde index a12c189..df3a7e4 100644 --- a/_UIImplementation.pde +++ b/_UIImplementation.pde @@ -483,16 +483,21 @@ class UIMidi extends UIWindow { private final UIToggleSet deckMode; private final UIButton logMode; - UIMidi(List midiControllers, float x, float y, float w, float h) { + UIMidi(final MidiEngine midiEngine, float x, float y, float w, float h) { super("MIDI", x, y, w, h); + // Processing compiler doesn't seem to get that list of class objects also conform to interface List scrollItems = new ArrayList(); - for (SCMidiInput mc : midiControllers) { + for (SCMidiInput mc : midiEngine.getControllers()) { scrollItems.add(mc); } final UIScrollList scrollList; (scrollList = new UIScrollList(1, titleHeight, w-2, 100)).setItems(scrollItems).addToContainer(this); - (deckMode = new UIToggleSet(4, 130, 90, 20)).setOptions(new String[] { "A", "B" }).addToContainer(this); + (deckMode = new UIToggleSet(4, 130, 90, 20) { + protected void onToggle(String value) { + midiEngine.setFocusedDeck(value == "A" ? 0 : 1); + } + }).setOptions(new String[] { "A", "B" }).addToContainer(this); (logMode = new UIButton(98, 130, w-103, 20)).setLabel("LOG").addToContainer(this); SCMidiInputListener listener = new SCMidiInputListener() { @@ -500,9 +505,15 @@ class UIMidi extends UIWindow { scrollList.redraw(); } }; - for (SCMidiInput mc : midiControllers) { + for (SCMidiInput mc : midiEngine.getControllers()) { mc.addListener(listener); } + + midiEngine.addListener(new MidiEngineListener() { + public void onFocusedDeck(int deckIndex) { + deckMode.setValue(deckIndex == 0 ? "A" : "B"); + } + }); } diff --git a/code/GLucose.jar b/code/GLucose.jar index 26b508f..7b558bb 100755 Binary files a/code/GLucose.jar and b/code/GLucose.jar differ