Implement midi UI properly, no manual redraw, have UIObject listen to model
[SugarCubes.git] / _Internals.pde
index 044d650de195f7883980342ac25d8ba419c73bcd..b6d835ceb923b9c00becba042121ed217526b8ed 100644 (file)
@@ -48,7 +48,8 @@ HeronLX lx;
 LXPattern[] patterns;
 MappingTool mappingTool;
 PandaDriver[] pandaBoards;
-MidiListener midiQwerty;
+SCMidiInput midiQwertyKeys;
+SCMidiInput midiQwertyAPC;
 
 // Display configuration mode
 boolean mappingMode = false;
@@ -56,6 +57,7 @@ boolean debugMode = false;
 DebugUI debugUI;
 boolean uiOn = true;
 LXPattern restoreToPattern = null;
+PImage logo;
 
 // Handles to UI objects
 UIContext[] overlays;
@@ -122,11 +124,12 @@ void setup() {
   logTime("Built PandaDriver");
 
   // MIDI devices
-  List<MidiListener> midiListeners = new ArrayList<MidiListener>();
-  midiListeners.add(midiQwerty = new MidiListener());
+  List<SCMidiInput> midiControllers = new ArrayList<SCMidiInput>();
+  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");
-    midiListeners.add(new MidiListener(device).setEnabled(enableDevice));
+    midiControllers.add(new SCMidiInput(device).setEnabled(enableDevice));
   }
   SCMidiDevices.initializeStandardDevices(glucose);
   logTime("Setup MIDI devices");
@@ -141,8 +144,8 @@ void setup() {
     new UISpeed(4, 624, 140, 50),
         
     new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
-    uiMidi = new UIMidi(midiListeners, width-144, 332, 140, 160),
-    new UIOutput(width-144, 498, 140, 106),
+    uiMidi = new UIMidi(midiControllers, width-144, 332, 140, 158),
+    new UIOutput(width-144, 494, 140, 106),
     
     uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
     
@@ -151,7 +154,10 @@ void setup() {
   };
   uiMapping.setVisible(false);
   logTime("Built overlay UI");
-    
+
+  // Load logo image
+  logo = loadImage("data/logo.png");
+  
   // Setup camera
   midX = TRAILER_WIDTH/2.;
   midY = glucose.model.yMax/2;
@@ -170,15 +176,21 @@ void setup() {
   println("Hit the 'p' key to toggle Panda Board output");
 }
 
-public class MidiListener extends AbstractScrollItem {
-  private boolean enabled = false;
-  private final String name;
+public interface SCMidiInputListener {
+  public void onEnabled(SCMidiInput controller, boolean enabled);
+}
+
+public class SCMidiInput extends AbstractScrollItem {
   
-  MidiListener(MidiInputDevice d) {
-    d.createInput(this);
-    name = d.getName();
-  }
+  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;
@@ -189,9 +201,41 @@ public class MidiListener extends AbstractScrollItem {
   }
   
   final Map<Character, NoteMeta> keyToNote = new HashMap<Character, NoteMeta>();
+    
+  final List<SCMidiInputListener> listeners = new ArrayList<SCMidiInputListener>();
+  
+  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();
+  }
+  
+  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;
+    }
+  }
   
-  MidiListener() {
-    name = "QWERTY Keyboard";
+  private void mapAPC() {
     mapNote('1', 0, 53);
     mapNote('2', 1, 53);
     mapNote('3', 2, 53);
@@ -219,6 +263,26 @@ public class MidiListener extends AbstractScrollItem {
     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));
   }
@@ -228,15 +292,28 @@ public class MidiListener extends AbstractScrollItem {
   }
   
   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, 127));
+          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, 0));
+          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;
       }
     }
@@ -254,23 +331,30 @@ public class MidiListener extends AbstractScrollItem {
     setEnabled(!enabled);
   }
   
-  public MidiListener setEnabled(boolean enabled) {
+  public SCMidiInput setEnabled(boolean enabled) {
     if (enabled != this.enabled) {
       this.enabled = enabled;
-      uiMidi.redraw();
+      for (SCMidiInputListener l : listeners) {
+        l.onEnabled(this, enabled);
+      }
     }
     return this;
   }
   
   private SCPattern getFocusedPattern() {
-    return (SCPattern) uiMidi.getFocusedDeck().getActivePattern();
+    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 (uiMidi.logMidi()) {
+    if (logMidi()) {
       println(getLabel() + " :: Program Change :: " + pc.getNumber());
     }
   }
@@ -279,7 +363,7 @@ public class MidiListener extends AbstractScrollItem {
     if (!enabled) {
       return;
     }
-    if (uiMidi.logMidi()) {
+    if (logMidi()) {
       println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue());
     }
     getFocusedPattern().controllerChangeReceived(cc);
@@ -289,7 +373,7 @@ public class MidiListener extends AbstractScrollItem {
     if (!enabled) {
       return;
     }
-    if (uiMidi.logMidi()) {
+    if (logMidi()) {
       println(getLabel() + " :: Note On  :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
     }
     getFocusedPattern().noteOnReceived(note);
@@ -299,7 +383,7 @@ public class MidiListener extends AbstractScrollItem {
     if (!enabled) {
       return;
     }
-    if (uiMidi.logMidi()) {
+    if (logMidi()) {
       println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
     }
     getFocusedPattern().noteOffReceived(note);
@@ -344,6 +428,14 @@ void draw() {
   vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
   vertex(0, 0, TRAILER_DEPTH);
   endShape();
+
+  // Draw the logo on the front of platform  
+  pushMatrix();
+  translate(0, 0, -1);
+  float s = .07;
+  scale(s, -s, s);
+  image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
+  popMatrix();
   
   noStroke();
 //  drawBassBox(glucose.model.bassBox);
@@ -523,6 +615,7 @@ void drawUI() {
   }
 }
 
+
 /**
  * Top-level keyboard event handling
  */
@@ -538,15 +631,15 @@ void keyPressed() {
     case '=':
     case '+':
       frameRate(++targetFramerate);
-      break;
+      break;      
     case 'd':
-      if (!midiQwerty.isEnabled()) {
+      if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
         debugMode = !debugMode;
         println("Debug output: " + (debugMode ? "ON" : "OFF"));
       }
       break;
     case 'm':
-      if (!midiQwerty.isEnabled()) {
+      if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
         mappingMode = !mappingMode;
         uiPatternA.setVisible(!mappingMode);
         uiMapping.setVisible(mappingMode);
@@ -568,7 +661,9 @@ void keyPressed() {
       }
       break;
     case 'u':
-      uiOn = !uiOn;
+      if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
+        uiOn = !uiOn;
+      }
       break;
   }
 }