Fix up a couple minor APC mapping issues
[SugarCubes.git] / _MIDI.pde
index 4b3f3136ee9c7c03ce9835fb4d5552cd52d83590..323efeeec641c8ad7412d89d68b1d19b0ec543e9 100644 (file)
--- a/_MIDI.pde
+++ b/_MIDI.pde
@@ -50,7 +50,13 @@ class MidiEngine {
       } else if (device.getName().contains("SLIDER/KNOB KORG")) {
         midiControllers.add(new KorgNanoKontrolMidiInput(device).setEnabled(true));
       } else {
-        midiControllers.add(new SCMidiInput(device));
+        boolean enabled = device.getName().contains("KEYBOARD KORG") || device.getName().contains("Bus 1 Apple");
+        midiControllers.add(new SCMidiInput(device).setEnabled(enabled));
+      }
+    }
+    for (MidiOutputDevice device : RWMidi.getOutputDevices()) {
+      if (device.getName().contains("APC")) {
+        new APC40MidiOutput(this, device);
       }
     }
   }
@@ -348,26 +354,52 @@ public class APC40MidiInput extends SCMidiInput {
     }
   }
 
+  private long tap1 = 0;
+
+  private boolean lbtwn(long a, long b, long c) {
+    return a >= b && a <= c;
+  }
+
   protected void handleNoteOn(Note note) {
-    switch (note.getPitch()) {
-    case 94: // right bank
-      midiEngine.setFocusedDeck(1);
+    int nPitch = note.getPitch(), nChan = note.getChannel();
+    switch (nPitch) {
+               
+    case 82: // scene 1
+      EFF_boom.trigger();
+      break;
+      
+    case 83: // scene 2
+      EFF_flash.trigger();
+      break;
+      
+    case 90:
+      // dan's dirty tapping mechanism
+      lx.tempo.trigger();
+      tap1 = millis();
       break;
-    case 95: // left bank
+
+    case 91: // play
+    case 97: // left bank
       midiEngine.setFocusedDeck(0);
       break;
-    case 96: // up bank
+
+    case 93: // rec
+    case 96: // right bank
+      midiEngine.setFocusedDeck(1);
+      break;
+
+    case 94: // up bank
       if (shiftOn) {
-        glucose.incrementSelectedEffectBy(1);
+        glucose.incrementSelectedEffectBy(-1);
       } else {
-        midiEngine.getFocusedDeck().goNext();
+        midiEngine.getFocusedDeck().goPrev();
       }
       break;
-    case 97: // down bank
+    case 95: // down bank
       if (shiftOn) {
-        glucose.incrementSelectedEffectBy(-1);
+        glucose.incrementSelectedEffectBy(1);
       } else {
-        midiEngine.getFocusedDeck().goPrev();
+        midiEngine.getFocusedDeck().goNext();
       }
       break;
 
@@ -385,8 +417,7 @@ public class APC40MidiInput extends SCMidiInput {
       lx.tempo.setBpm(lx.tempo.bpm() - (shiftOn ? 1 : .1));
       break;
 
-    case 91: // play
-    case 93: // rec
+    case 62: // Detail View / red 5
       releaseEffect = glucose.getSelectedEffect(); 
       if (releaseEffect.isMomentary()) {
         releaseEffect.enable();
@@ -395,25 +426,39 @@ public class APC40MidiInput extends SCMidiInput {
       }
       break;
 
-    case 92: // stop
+    case 63: // rec quantize / red 6
       glucose.getSelectedEffect().disable();
       break;
     }
   }
 
   protected void handleNoteOff(Note note) {
-    switch (note.getPitch()) {
-    case 93: // rec
+    int nPitch = note.getPitch(), nChan = note.getChannel();
+    switch (nPitch) {
+    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");
+      }
+      break;
+
+    case 63: // rec quantize / RED 6
       if (releaseEffect != null) {
         if (releaseEffect.isMomentary()) {
           releaseEffect.disable();
         }
       }
       break;
-               
+
     case 98: // shift
       shiftOn = false;
-       break;
+      break;
     }
   }
 }
@@ -457,3 +502,112 @@ class KorgNanoKontrolMidiInput extends SCMidiInput {
   }
 }
 
+class APC40MidiOutput implements LXParameter.Listener {
+  
+  private final MidiEngine midiEngine;
+  private final MidiOutput output;
+  private LXPattern focusedPattern = null;
+  private LXEffect focusedEffect = null;
+  
+  APC40MidiOutput(MidiEngine midiEngine, MidiOutputDevice device) {
+    this.midiEngine = midiEngine;
+    output = device.createOutput();
+    midiEngine.addListener(new MidiEngineListener() {
+      public void onFocusedDeck(int deckIndex) {
+        resetPatternParameters();
+      }
+    });
+    glucose.addEffectListener(new GLucose.EffectListener() {
+      public void effectSelected(LXEffect effect) {
+        resetEffectParameters();
+      }
+    });
+    Engine.Listener deckListener = new Engine.AbstractListener() {
+      public void patternDidChange(Engine.Deck deck, LXPattern pattern) {
+        resetPatternParameters();
+      }
+    };
+    for (Engine.Deck d : lx.engine.getDecks()) {
+      d.addListener(deckListener);
+    }
+    resetParameters();
+  }
+
+  private void resetParameters() {
+    resetPatternParameters();
+    resetEffectParameters();
+  }
+  
+  private void resetPatternParameters() {
+    LXPattern newPattern = midiEngine.getFocusedDeck().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);
+    }
+  }
+  
+  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;
+    }
+  }
+}