changeset 49:90ce403d0dc5

Added OSC control over all main application functions. OSC messages can be sent to do most of the tasks available from the GUI.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Mon, 13 Apr 2015 19:30:27 -0700
parents 2a9e5576905e
children 114427cb39f0
files Source/GUI/KeyboardZoneComponent.cpp Source/MainApplicationController.cpp Source/MainApplicationController.h Source/Mappings/Control/TouchkeyControlMappingFactory.cpp Source/Mappings/Control/TouchkeyControlMappingFactory.h Source/Mappings/MappingFactory.h Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.cpp Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.h Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.cpp Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.h Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.cpp Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h Source/Mappings/TouchkeyBaseMappingFactory.h Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.cpp Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.h Source/TouchKeys/MidiInputController.cpp Source/TouchKeys/MidiInputController.h Source/TouchKeys/MidiKeyboardSegment.cpp Source/TouchKeys/MidiKeyboardSegment.h Source/TouchKeys/Osc.cpp Source/TouchKeys/Osc.h
diffstat 21 files changed, 1141 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/Source/GUI/KeyboardZoneComponent.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/GUI/KeyboardZoneComponent.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -619,9 +619,9 @@
 
     PopupMenu menu;
 
-    for(int i = 0; i < controller_->numberOfMappingFactories(); i++) {
-        if(controller_->experimentalMappingsEnabled() || !controller_->mappingIsExperimental(i))
-            menu.addItem(i + 1, controller_->mappingFactoryNameForIndex(i));
+    for(int i = 0; i < MidiKeyboardSegment::numberOfMappingFactories(); i++) {
+        if(controller_->experimentalMappingsEnabled() || !MidiKeyboardSegment::mappingIsExperimental(i))
+            menu.addItem(i + 1, MidiKeyboardSegment::mappingFactoryNameForIndex(i));
     }
 
     menu.showMenuAsync(PopupMenu::Options().withTargetComponent(addMappingButton),
@@ -657,7 +657,7 @@
 
     // Items are numbered from 1 in the menu but from 0 in the array in the controller
     if(result >= 1) {
-        MappingFactory *newFactory = controller_->createMappingFactoryForIndex(result - 1, *keyboardSegment_);
+        MappingFactory *newFactory = keyboardSegment_->createMappingFactoryForIndex(result - 1);
 
         if(newFactory != 0) {
             keyboardSegment_->addMappingFactory(newFactory, true);
--- a/Source/MainApplicationController.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/MainApplicationController.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -95,6 +95,9 @@
     // Set up an initial OSC transmit host/port if none has been loaded
     if(oscTransmitter_.addresses().size() == 0)
         oscTransmitter_.addAddress(kDefaultOscTransmitHost, kDefaultOscTransmitPort);
+    
+    // Listen for control messages by OSC
+    mainOscController_ = new MainApplicationOSCController(*this, oscReceiver_);
 }
 
 MainApplicationController::~MainApplicationController() {
@@ -103,6 +106,7 @@
         touchkeySensorTestStop();
 #endif
     removeAllOscListeners();
+    delete mainOscController_;
 }
 
 // Actions here run in the JUCE initialise() method once the application is loaded
@@ -736,96 +740,55 @@
 
 // OSC handler method
 bool MainApplicationController::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) {
-	if(!strcmp(path, "/midi/noteon") && touchkeyAutodetecting_ && numValues > 0) {
-        // std::cout << "/midi/noteon\n";
-        // Found a MIDI note. Look for a unique touch on this pitch class to
-        // determine which octave the keyboard is set to
-        if(types[0] != 'i')
-            return false;   // Ill-formed message
-        int midiNote = values[0]->i;
-        if(midiNote < 0 || midiNote > 127)
-            return false;
-        
-        // Go through each octave and see if a touch is present
-        int midiTestNote = midiNote % 12;
-        int count = 0;
-        int lastFoundTouchNote = 0;
-        while(midiTestNote <= 127) {
-            if(keyboardController_.key(midiTestNote) != 0) {
-                if(keyboardController_.key(midiTestNote)->touchIsActive()) {
-                    count++;
-                    lastFoundTouchNote = midiTestNote;
+    std::cout << path << endl;
+    
+	if(!strcmp(path, "/midi/noteon")) {
+        if(touchkeyAutodetecting_ && numValues > 0) {
+            // std::cout << "/midi/noteon\n";
+            // Found a MIDI note. Look for a unique touch on this pitch class to
+            // determine which octave the keyboard is set to
+            if(types[0] != 'i')
+                return false;   // Ill-formed message
+            int midiNote = values[0]->i;
+            if(midiNote < 0 || midiNote > 127)
+                return false;
+            
+            // Go through each octave and see if a touch is present
+            int midiTestNote = midiNote % 12;
+            int count = 0;
+            int lastFoundTouchNote = 0;
+            while(midiTestNote <= 127) {
+                if(keyboardController_.key(midiTestNote) != 0) {
+                    if(keyboardController_.key(midiTestNote)->touchIsActive()) {
+                        count++;
+                        lastFoundTouchNote = midiTestNote;
+                    }
                 }
-            }
-            midiTestNote += 12;
-        }
-        
-        // We return success if exactly one note had a touch on this pitch class
-        if(count == 1) {
-            int noteDifference = lastFoundTouchNote - midiNote;
-            int currentMinNote = touchkeyController_.lowestMidiNote();
-
-            // std::cout << "Found difference of " << noteDifference << std::endl;
-
-            currentMinNote -= noteDifference;
-            if(currentMinNote >= 0 && currentMinNote <= 127) {
-                touchkeyController_.setLowestMidiNote(currentMinNote);
-                applicationProperties_.getUserSettings()->setValue("TouchKeysLowestMIDINote", currentMinNote);
+                midiTestNote += 12;
             }
             
-            touchkeyDeviceStopAutodetecting();
+            // We return success if exactly one note had a touch on this pitch class
+            if(count == 1) {
+                int noteDifference = lastFoundTouchNote - midiNote;
+                int currentMinNote = touchkeyController_.lowestMidiNote();
+
+                // std::cout << "Found difference of " << noteDifference << std::endl;
+
+                currentMinNote -= noteDifference;
+                if(currentMinNote >= 0 && currentMinNote <= 127) {
+                    touchkeyController_.setLowestMidiNote(currentMinNote);
+                    applicationProperties_.getUserSettings()->setValue("TouchKeysLowestMIDINote", currentMinNote);
+                }
+                
+                touchkeyDeviceStopAutodetecting();
+            }
+            return false; // Others may still want to handle this message
         }
-        return false; // Others may still want to handle this message
     }
     
     return false;
 }
 
-// Factores to use
-const int kNumMappingFactoryTypes = 7;
-const char* kMappingFactoryNames[kNumMappingFactoryTypes] = {"Control", "Vibrato", "Pitch Bend", "Split Key", "Multi-Finger Trigger", "Onset Angle", "Release Angle"};
-
-// Return the number of mapping factory types available
-int MainApplicationController::numberOfMappingFactories() {
-    return kNumMappingFactoryTypes;
-}
-
-// Return the name of the given mapping factory type
-String MainApplicationController::mappingFactoryNameForIndex(int index) {
-    if(index < 0 || index >= kNumMappingFactoryTypes)
-        return String();
-    return kMappingFactoryNames[index];
-}
-
-// Return a new object of the given mapping factory type
-MappingFactory* MainApplicationController::createMappingFactoryForIndex(int index, MidiKeyboardSegment& segment) {
-    switch(index) {
-        case 0:
-            return new TouchkeyControlMappingFactory(keyboardController_, segment);
-        case 1:
-            return new TouchkeyVibratoMappingFactory(keyboardController_, segment);
-        case 2:
-            return new TouchkeyPitchBendMappingFactory(keyboardController_, segment);
-        case 3:
-            return new TouchkeyKeyDivisionMappingFactory(keyboardController_, segment);
-        case 4:
-            return new TouchkeyMultiFingerTriggerMappingFactory(keyboardController_, segment);
-        case 5:
-            return new TouchkeyOnsetAngleMappingFactory(keyboardController_, segment);
-        case 6:
-            return new TouchkeyReleaseAngleMappingFactory(keyboardController_, segment);
-        default:
-            return 0;
-    }
-}
-
-// Return whethera  given mapping is experimental or not
-bool MainApplicationController::mappingIsExperimental(int index) {
-    if(index > 2 && index != 4)
-        return true;
-    return false;
-}
-
 // Save the current settings to an XML file
 // Returns true on success
 bool MainApplicationController::savePresetToFile(const char *filename) {
@@ -1162,13 +1125,16 @@
     if(keyOffset < 0) // Shouldn't happen...
         keyOffset = 0;
     
-    if(!touchkeyController_.startRawDataCollection(keyOffset / 12, keyOffset % 12, 3, 2)) { // FIXME: check these values
+    if(!touchkeyController_.startRawDataCollection(keyOffset / 12, keyOffset % 12, 3, 2)) {
         touchkeyErrorMessage_ = "Failed to start";
         touchkeyErrorOccurred_ = true;
         return false;
     }
     
+    keyboardTesterWindow_->addToDesktop(keyboardTesterWindow_->getDesktopWindowStyleFlags()
+                                         | ComponentPeer::windowHasCloseButton);
     keyboardTesterWindow_->setVisible(true);
+    keyboardTesterWindow_->toFront(true);
     
     touchkeyErrorMessage_ = "";
     touchkeyErrorOccurred_ = false;
@@ -1322,4 +1288,328 @@
     if(noteNumber < 0 || noteNumber > 127)
         return -1;
     return noteNumber;
+}
+
+
+// ***** External OSC Control *****
+
+bool MainApplicationOSCController::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) {
+    if(!strncmp(path, "/control", 8)) {
+        // OSC messages that start with /touchkeys/control are used to control the operation of the
+        // software and mappings
+        
+        // First check if the message belongs to one of the segments
+        if(!strncmp(path, "/control/segment", 16) && strlen(path) > 16) {
+            // Pick out which segment based on the following number: e.g. /control/segment0/...
+            
+            std::string subpath(&path[16]);
+            int separatorLoc = subpath.find_first_of('/');
+            if(separatorLoc == std::string::npos || separatorLoc == subpath.length() - 1) {
+                // Malformed input (no slash or it's the last character): ignore
+                return false;
+            }
+            std::stringstream segmentNumberSStream(subpath.substr(0, separatorLoc));
+            
+            int segmentNumber = 0;
+            segmentNumberSStream >> segmentNumber;
+            
+            if(segmentNumber < 0)  // Unknown segment number
+                return false;
+            
+            // Pass this message onto the corresponding segment in MidiInputController
+            // If the segment doesn't exist, it will return false. All further handling is
+            // done within MidiInputController with its corresponding mutex locked so
+            // the segments can't change while this message is processed.
+            
+            subpath = subpath.substr(separatorLoc); // Start at the '/'
+            
+            if(subpath == "/set-midi-out") {
+                // Special case for setting the MIDI output, which is done in the main controller
+                // rather than in the segment itself
+
+                if(numValues >= 1) {
+                    if(types[0] == 'i') {
+                        MidiKeyboardSegment* segment = controller_.midiInputController_.segment(segmentNumber);
+                        if(segment == 0)
+                            oscControlTransmitResult(1); // Failure response
+                        else {
+                            if(values[0]->i < 0) {
+                                // Negative value means disable
+                                controller_.disableMIDIOutputPort(segment->outputPort());
+                            }
+                            else {
+                                controller_.enableMIDIOutputPort(segment->outputPort(), values[0]->i);
+                            }
+                            oscControlTransmitResult(0);
+                        }
+                    }
+#ifndef JUCE_WINDOWS
+                    else if(types[0] == 's') {
+                        MidiKeyboardSegment* segment = controller_.midiInputController_.segment(segmentNumber);
+                        if(segment == 0)
+                            oscControlTransmitResult(1); // Failure response
+                        else {
+                            if(!strcmp(&values[0]->s, "virtual")) {
+                                char st[20];
+                                snprintf(st, 20, "TouchKeys %d", segment->outputPort());
+                                controller_.enableMIDIOutputVirtualPort(segment->outputPort(), st);
+                                oscControlTransmitResult(0);
+                            }
+                        }
+                    }
+#endif
+                }
+            }
+            else {
+                // All other segment messages are handled within MidiKeyboardSegment
+                
+                OscMessage* response = controller_.midiInputController_.oscControlMessageForSegment(segmentNumber, subpath.c_str(), types, numValues, values, data);
+                if(response != 0) {
+                    // Add the right prefix to the response. If it is a simple result status,
+                    // then give it the generic prefix. Otherwise add the zone beforehand
+                    if(!strcmp(response->path(), "/result"))
+                        response->prependPath("/touchkeys/control");
+                    else {
+                        char prefix[28];
+#ifdef _MSC_VER
+                        _snprintf_s(prefix, 28, _TRUNCATE, "/touchkeys/control/segment%d", segmentNumber);
+#else
+                        snprintf(prefix, 28,  "/touchkeys/control/segment%d", segmentNumber);
+#endif
+                        response->prependPath(prefix);
+                    }
+                    
+                    // Send the message and free it
+                    controller_.oscTransmitter_.sendMessage(response->path(), response->type(), response->message());
+                    delete response;
+                }
+            }
+        }
+        else if(!strcmp(path, "/control/preset-load")) {
+            // Load preset from file
+            // Argument 0 is the path to the file
+            
+            if(numValues > 0) {
+                if(types[0] == 's') {
+                    bool result = controller_.loadPresetFromFile(&values[0]->s);
+                    
+                    // Send back a message on success/failure
+                    oscControlTransmitResult(result == true ? 0 : 1);
+                    return true;
+                }
+                else if(types[0] == 'i') {
+                    // TODO: take a second form of the message which has a numerical
+                    //       input for selecting presets
+                }
+            }
+            return false;
+        }
+        else if(!strcmp(path, "/control/preset-save")) {
+            // Save preset to file
+            
+            if(numValues > 0) {
+                if(types[0] == 's') {
+                    bool result = controller_.savePresetToFile(&values[0]->s);
+                    
+                    // Send back a message on success/failure
+                    oscControlTransmitResult(result == true ? 0 : 1);
+                    return true;
+                }
+                else if(types[0] == 'i') {
+                    // TODO: take a second form of the message which has a numerical
+                    //       input for selecting presets
+                }
+            }
+            return false;
+        }
+        else if(!strcmp(path, "/control/preset-clear")) {
+            // Clear everything in preset
+            controller_.clearPreset();
+            oscControlTransmitResult(0);
+            return true;
+        }
+        else if(!strcmp(path, "/control/tk-list-devices")) {
+            // Return a list of TouchKeys devices
+            
+            OscMessage *response = OscTransmitter::createMessage("/touchkeys/control/tk-list-devices/result", "i",
+                                                                controller_.availableTouchkeyDevices().size(), LO_ARGS_END);
+            
+            vector<std::string> devices = controller_.availableTouchkeyDevices();
+            vector<string>::iterator it;
+            for(it = devices.begin(); it != devices.end(); ++it) {
+                lo_message_add_string(response->message(), it->c_str());
+            }
+            
+            controller_.oscTransmitter_.sendMessage(response->path(), response->type(), response->message());
+            delete response;
+            
+            return true;
+        }
+        else if(!strcmp(path, "/control/tk-start")) {
+            // Start the TouchKeys device with the given path
+            if(numValues > 0) {
+                if(types[0] == 's') {
+                    char *device = &values[0]->s;
+                    bool result = controller_.touchkeyDeviceStartupSequence(device);
+                    
+                    oscControlTransmitResult(result == true ? 0 : 1);
+                    return true;
+                }
+            }
+        }
+        else if(!strcmp(path, "/control/tk-stop")) {
+            // Stop TouchKeys
+            if(controller_.touchkeyDeviceIsOpen()) {
+                controller_.closeTouchkeyDevice();
+                oscControlTransmitResult(0);
+            }
+            else {
+                // Not running, can't close
+                oscControlTransmitResult(1);
+            }
+            return true;
+        }
+        else if(!strcmp(path, "/control/tk-set-lowest-midi-note")) {
+            // Set TouchKeys octave such that the lowest key is at the
+            // given note. Only 'C' notes are valid.
+            
+            if(numValues > 0) {
+                if(types[0] == 'i') {
+                    int note = values[0]->i;
+                    
+                    if(note % 12 == 0 && note >= 12 && note < 127) {
+                        controller_.touchkeyDeviceSetLowestMidiNote(note);
+                        oscControlTransmitResult(0);
+                    }
+                    else {
+                        // Invalid note
+                        oscControlTransmitResult(1);
+                    }
+                    return true;
+                }
+            }
+        }
+        else if(!strcmp(path, "/control/tk-autodetect")) {
+            // Autodetect lowest TouchKeys octave
+            controller_.touchkeyDeviceAutodetectLowestMidiNote();
+            oscControlTransmitResult(0);
+            return true;
+        }
+        else if(!strcmp(path, "/control/tk-autodetect-stop")) {
+            // Stop autodetecting TouchKeys octave
+            controller_.touchkeyDeviceStopAutodetecting();
+            oscControlTransmitResult(0);
+            return true;
+        }
+        else if(!strcmp(path, "/control/list-midi-in")) {
+            // List available MIDI input devices
+            std::vector<std::pair<int, std::string> > midiInputs = controller_.availableMIDIInputDevices();
+            
+            OscMessage *response = OscTransmitter::createMessage("/list-midi-in/result", "i", midiInputs.size(), LO_ARGS_END);
+            
+            std::vector<std::pair<int, std::string> >::iterator it;
+            for(it = midiInputs.begin(); it != midiInputs.end(); ++it) {
+                lo_message_add_int32(response->message(), it->first);
+                lo_message_add_string(response->message(), it->second.c_str());
+            }
+            
+            controller_.oscTransmitter_.sendMessage(response->path(), response->type(), response->message());
+            delete response;
+            
+            return true;
+        }
+        else if(!strcmp(path, "/control/list-midi-out")) {
+            // List available MIDI output devices
+            std::vector<std::pair<int, std::string> > midiOutputs = controller_.availableMIDIOutputDevices();
+            
+            OscMessage *response = OscTransmitter::createMessage("/list-midi-out/result", "i", midiOutputs.size(), LO_ARGS_END);
+            
+            std::vector<std::pair<int, std::string> >::iterator it;
+            for(it = midiOutputs.begin(); it != midiOutputs.end(); ++it) {
+                lo_message_add_int32(response->message(), it->first);
+                lo_message_add_string(response->message(), it->second.c_str());
+            }
+            
+            controller_.oscTransmitter_.sendMessage(response->path(), response->type(), response->message());
+            delete response;
+            
+            return true;
+        }
+        else if(!strcmp(path, "/control/set-midi-in-keyboard")) {
+            // Set MIDI input device for keyboard
+            if(numValues > 0) {
+                if(types[0] == 'i') {
+                    if(controller_.midiTouchkeysStandaloneModeIsEnabled())
+                        controller_.midiTouchkeysStandaloneModeDisable();
+                    if(values[0]->i < 0) {
+                        // Negative means disable
+                        controller_.disablePrimaryMIDIInputPort();
+                    }
+                    else {
+                        controller_.enableMIDIInputPort(values[0]->i, true);
+                    }
+                    
+                    oscControlTransmitResult(0);
+                    return true;
+                }
+                else if(types[0] == 's') {
+                    if(!strncmp(&values[0]->s, "stand", 5)) {
+                        // Enable TouchKeys standalone mode in place of MIDI input
+                        controller_.disablePrimaryMIDIInputPort();
+                        controller_.midiTouchkeysStandaloneModeEnable();
+                        
+                        oscControlTransmitResult(0);
+                        return true;
+                    }
+                }
+            }
+        }
+        else if(!strcmp(path, "/control/set-midi-in-aux")) {
+            // Set MIDI auxiliary input device
+            if(numValues > 0) {
+                if(types[0] == 'i') {
+                    controller_.disableAllMIDIInputPorts(true);
+                    if(values[0]->i >= 0) {
+                        // Negative values mean leave the port disabled
+                        controller_.enableMIDIInputPort(values[0]->i, false);
+                    }
+                    
+                    oscControlTransmitResult(0);
+                    return true;
+                }
+            }
+            
+        }
+        else if(!strcmp(path, "/control/add-segment")) {
+            // Add a new keyboard segment
+            if(controller_.midiSegmentsCount() >= 8) {
+                // Max of 8 segments possible
+                oscControlTransmitResult(1);
+            }
+            else {
+                controller_.midiSegmentAdd();
+                oscControlTransmitResult(0);
+            }
+            return true;
+        }
+        else if(!strcmp(path, "/control/delete-segment")) {
+            // Remove a keyboard segment by number
+            if(numValues > 0) {
+                if(types[0] == 'i') {
+                    int segmentNumber = values[0]->i;
+                    
+                    bool result = controller_.midiInputController_.removeSegment(segmentNumber);
+                    oscControlTransmitResult(result == true ? 0 : 1);
+                    return true;
+                }
+            }
+        }
+    }
+    
+    return false;
+}
+
+// Send back an OSC message to indicate the result of a control command
+void MainApplicationOSCController::oscControlTransmitResult(int result) {
+    controller_.oscTransmitter_.sendMessage("/touchkeys/control/result", "i", result, LO_ARGS_END);
 }
\ No newline at end of file
--- a/Source/MainApplicationController.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/MainApplicationController.h	Mon Apr 13 19:30:27 2015 -0700
@@ -62,10 +62,11 @@
 const char kDefaultOscTransmitPort[] = "8000";
 const int kDefaultOscReceivePort = 8001;
 
-class InterfaceSelectorComponent;
-
+class MainApplicationOSCController;
 
 class MainApplicationController : public OscHandler {
+    friend class MainApplicationOSCController;
+    
 public:
     // *** Constructor ***
     MainApplicationController();
@@ -263,23 +264,11 @@
 	bool oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data);
     
     // *** Mapping methods ***
-    // Return the number of mapping factory types available
-    int numberOfMappingFactories();
-    
-    // Return the name of a given mapping factory type
-    String mappingFactoryNameForIndex(int index);
-    
-    // Create a new mapping factory of the given type, attached to
-    // the supplied segment
-    MappingFactory* createMappingFactoryForIndex(int index, MidiKeyboardSegment& segment);
- 
+
     // Whether experimental (not totally finished/tested) mappings are available
     bool experimentalMappingsEnabled() { return experimentalMappingsEnabled_; }
     void setExperimentalMappingsEnabled(bool enable) { experimentalMappingsEnabled_ = enable; }
     
-    // Whether a given mapping is experimental
-    bool mappingIsExperimental(int index);
-    
     // *** Preset Save/Load ***
     // These methods save the current settings to file or load settings
     // from a file. They return true on success.
@@ -356,6 +345,7 @@
     ApplicationProperties applicationProperties_;
     
     // TouchKeys objects
+    MainApplicationOSCController *mainOscController_;
     PianoKeyboard keyboardController_;
     MidiInputController midiInputController_;
     MidiOutputController midiOutputController_;
@@ -399,4 +389,31 @@
     std::string loggingDirectory_;
 };
 
+
+// Separate class for handling external OSC control messages since
+// one class cannot have two receivers. This one is for all external
+// OSC messages which OscHandler on MainApplicationController is for
+// internally-generated messages via the PianoKeyboard class.
+
+class MainApplicationOSCController : public OscHandler {
+public:
+    MainApplicationOSCController(MainApplicationController& controller,
+                                 OscMessageSource& source) :
+    controller_(controller), source_(source) {
+        setOscController(&source_);
+        addOscListener("/control*");
+    }
+    
+    // *** OSC handler method (different from OSC device selection) ***
+    
+    bool oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data);
+    
+private:
+    // Reply to OSC messages with a status
+    void oscControlTransmitResult(int result);
+    
+    MainApplicationController& controller_;
+    OscMessageSource& source_;
+};
+
 #endif /* defined(__TouchKeys__MainApplicationController__) */
--- a/Source/Mappings/Control/TouchkeyControlMappingFactory.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingFactory.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -60,14 +60,20 @@
 }
 
 void TouchkeyControlMappingFactory::setInputParameter(int inputParameter) {
-    inputParameter_ = inputParameter;
+    if(inputParameter >= 1 && inputParameter < TouchkeyControlMapping::kInputParameterMaxValue)
+        inputParameter_ = inputParameter;
 }
 
 void TouchkeyControlMappingFactory::setInputType(int inputType) {
-    inputType_ = inputType;
+    if(inputType >= 1 && inputType < TouchkeyControlMapping::kTypeMaxValue)
+        inputType_ = inputType;
 }
 
 void TouchkeyControlMappingFactory::setController(int controller) {
+    if(midiControllerNumber_ < 1 ||
+       midiControllerNumber_ >= MidiKeyboardSegment::kControlMax)
+        return;
+    
     // Before changing the controller, check if we were going to or from the pitch wheel.
     // If so, we should scale the value to or from a 14-bit value
     if(midiControllerNumber_ == MidiKeyboardSegment::kControlPitchWheel &&
@@ -216,6 +222,7 @@
                       -1, use14BitControl_, outOfRangeBehavior_);
 }
 
+#ifndef TOUCHKEYS_NO_GUI
 // ***** GUI Support *****
 MappingEditorComponent* TouchkeyControlMappingFactory::createBasicEditor() {
     return new TouchkeyControlMappingShortEditor(*this);
@@ -224,6 +231,155 @@
 MappingEditorComponent* TouchkeyControlMappingFactory::createExtendedEditor() {
     return new TouchkeyControlMappingExtendedEditor(*this);
 }
+#endif
+
+// ****** OSC Control Support ******
+OscMessage* TouchkeyControlMappingFactory::oscControlMethod(const char *path, const char *types,
+                                                            int numValues, lo_arg **values, void *data) {
+    if(!strcmp(path, "/set-input-parameter")) {
+        // Change the input parameter for the control mapping
+        if(numValues > 0) {
+            if(types[0] == 'i') {
+                setInputParameter(values[0]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-input-type")) {
+        // Change the input type (absolute/relative)
+        if(numValues > 0) {
+            if(types[0] == 'i') {
+                setInputType(values[0]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-input-range-min")) {
+        // Change the input range
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setRangeInputMin(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-input-range-max")) {
+        // Change the input range
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setRangeInputMax(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-input-range-center")) {
+        // Change the input range
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setRangeInputCenter(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-output-range-min")) {
+        // Change the output range
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setRangeOutputMin(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-output-range-max")) {
+        // Change the output range
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setRangeOutputMax(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-output-default")) {
+        // Change the output range
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setRangeOutputDefault(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-out-of-range-behavior")) {
+        // Change how out-of-range inputs are handled
+        if(numValues > 0) {
+            if(types[0] == 'i') {
+                setOutOfRangeBehavior(values[0]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-midi-controller")) {
+        // Set the MIDI output CC, including pitchwheel etc. and 14-bit options
+        if(numValues > 0) {
+            if(types[0] == 'i') {
+                if(numValues >= 2)
+                    if(types[1] == 'i')
+                        setUses14BitControl(values[1]->i != 0);
+                
+                setController(values[0]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-threshold")) {
+        // Set the threshold for relative activations
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setThreshold(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-ignores-multiple-fingers")) {
+        // Change whether two or three finger touches are ignored
+        if(numValues >= 2) {
+            if(types[0] == 'i' && types[1] == 'i') {
+                setIgnoresTwoFingers(values[0]->i);
+                setIgnoresThreeFingers(values[1]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-direction")) {
+        // Set the direction of the mapping (normal/reverse/absolute val)
+        if(numValues > 0) {
+            if(types[0] == 'i') {
+                setDirection(values[0]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+            else if(types[0] == 's') {
+                const char *str = &values[0]->s;
+                
+                if(!strncmp(str, "norm", 4)) {
+                    setDirection(TouchkeyControlMapping::kDirectionPositive);
+                    return OscTransmitter::createSuccessMessage();
+                }
+                else if(!strncmp(str, "rev", 3)) {
+                    setDirection(TouchkeyControlMapping::kDirectionNegative);
+                    return OscTransmitter::createSuccessMessage();
+                }
+                if(!strncmp(str, "always", 6) || !strncmp(str, "both", 4)) {
+                    setDirection(TouchkeyControlMapping::kDirectionBoth);
+                    return OscTransmitter::createSuccessMessage();
+                }
+                else
+                    return OscTransmitter::createFailureMessage();
+            }
+        }
+    }
+    
+    // If no match, check the base class
+    return TouchkeyBaseMappingFactory<TouchkeyControlMapping>::oscControlMethod(path, types, numValues, values, data);
+}
 
 
 // ****** Preset Save/Load ******
--- a/Source/Mappings/Control/TouchkeyControlMappingFactory.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingFactory.h	Mon Apr 13 19:30:27 2015 -0700
@@ -89,11 +89,17 @@
     void setOutOfRangeBehavior(int behavior);
     void setUses14BitControl(bool use);
     
+#ifndef TOUCHKEYS_NO_GUI
     // ***** GUI Support *****
     bool hasBasicEditor() { return true; }
     MappingEditorComponent* createBasicEditor();
     bool hasExtendedEditor() { return true; }
     MappingEditorComponent* createExtendedEditor();
+#endif
+    
+    // ****** OSC Control Support ******
+    OscMessage* oscControlMethod(const char *path, const char *types,
+                                 int numValues, lo_arg **values, void *data);
     
     // ****** Preset Save/Load ******
     XmlElement* getPreset();
--- a/Source/Mappings/MappingFactory.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/MappingFactory.h	Mon Apr 13 19:30:27 2015 -0700
@@ -62,6 +62,7 @@
     
     // Specific name for this particular factory
     virtual string const getName() { return ""; }
+    virtual string const getShortName() { return ""; }
     virtual void setName(const string& name) {}
     
     virtual Mapping* mapping(int noteNumber) = 0;      // Look up a mapping with the given note number
@@ -124,6 +125,7 @@
     // Notification from key that a note is about to be sent out
     virtual void noteWillBegin(int noteNumber, int midiChannel, int midiVelocity) = 0;
     
+#ifndef TOUCHKEYS_NO_GUI
     // ***** GUI Support *****
     // There are two types of editors for a mapping: one is a small editor that fits in the
     // list view for adjusting the most important parameters, the other goes in a window of
@@ -133,6 +135,16 @@
     virtual MappingEditorComponent* createBasicEditor() { return nullptr; }
     virtual bool hasExtendedEditor() { return false; }
     virtual MappingEditorComponent* createExtendedEditor() { return nullptr; }
+#endif
+    
+    // ****** OSC Control ******
+    // As an alternative to GUI control, the mapping factories can receive OSC messages
+    // from the keyboard segment to which they are attached.
+    virtual OscMessage* oscControlMethod(const char *path, const char *types,
+                                         int numValues, lo_arg **values, void *data) {
+        // Nothing to do here in this virtual base class
+        return 0;
+    }
     
     // ****** Preset Save/Load ******
     // These methods generate XML settings files and reload values from them
--- a/Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -102,10 +102,21 @@
     triggerOffNoteVel_ = velocity;
 }
 
+#ifndef TOUCHKEYS_NO_GUI
 // ***** GUI Support *****
 MappingEditorComponent* TouchkeyMultiFingerTriggerMappingFactory::createBasicEditor() {
     return new TouchkeyMultiFingerTriggerMappingShortEditor(*this);
 }
+#endif
+
+// ****** OSC Control Support ******
+OscMessage* TouchkeyMultiFingerTriggerMappingFactory::oscControlMethod(const char *path, const char *types,
+                                                              int numValues, lo_arg **values, void *data) {
+    // TODO
+    
+    // If no match, check the base class
+    return TouchkeyBaseMappingFactory<TouchkeyMultiFingerTriggerMapping>::oscControlMethod(path, types, numValues, values, data);
+}
 
 // ****** Preset Save/Load ******
 XmlElement* TouchkeyMultiFingerTriggerMappingFactory::getPreset() {
--- a/Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.h	Mon Apr 13 19:30:27 2015 -0700
@@ -73,12 +73,17 @@
     void setTriggerOnNoteVelocity(int velocity);
     void setTriggerOffNoteVelocity(int velocity);
     
+#ifndef TOUCHKEYS_NO_GUI
     // ***** GUI Support *****
     bool hasBasicEditor() { return true; }
     MappingEditorComponent* createBasicEditor();
     bool hasExtendedEditor() { return false; }
     MappingEditorComponent* createExtendedEditor() { return nullptr; }
+#endif
     
+    // ****** OSC Control Support ******
+    OscMessage* oscControlMethod(const char *path, const char *types,
+                                 int numValues, lo_arg **values, void *data);
     // ****** Preset Save/Load ******
     XmlElement* getPreset();
     bool loadPreset(XmlElement const* preset);
--- a/Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -121,10 +121,68 @@
     bendIgnoresThreeFingers_ = ignoresThree;
 }
 
+#ifndef TOUCHKEYS_NO_GUI
 // ***** GUI Support *****
 MappingEditorComponent* TouchkeyPitchBendMappingFactory::createBasicEditor() {
     return new TouchkeyPitchBendMappingShortEditor(*this);
 }
+#endif
+
+// ****** OSC Control Support ******
+OscMessage* TouchkeyPitchBendMappingFactory::oscControlMethod(const char *path, const char *types,
+                                                            int numValues, lo_arg **values, void *data) {
+    if(!strcmp(path, "/set-bend-range")) {
+        // Change the range of the pitch bend in semitones
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setBendRange(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-bend-threshold")) {
+        // Change the threshold to activate the pitch bend [in semitones]
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setBendThresholdSemitones(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-bend-fixed-endpoints")) {
+        // Enable fixed endpoints on the pitch bend
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                float fixedEndpointBufferAtEnd = 0;
+                if(numValues >= 2) {
+                    if(types[1] == 'f') {
+                        fixedEndpointBufferAtEnd = values[1]->f;
+                    }
+                }
+                setBendFixedEndpoints(values[0]->f, fixedEndpointBufferAtEnd);
+                
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-bend-variable-endpoints")) {
+        // Enable variable endpoints on the pitch bend
+        setBendVariableEndpoints();
+        return OscTransmitter::createSuccessMessage();
+    }
+    else if(!strcmp(path, "/set-bend-ignores-multiple-fingers")) {
+        // Change whether the bend ignores two or three fingers
+        if(numValues >= 2) {
+            if(types[0] == 'i' && types[1] == 'i') {
+                setBendIgnoresMultipleFingers(values[0]->i, values[1]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    
+    // If no match, check the base class
+    return TouchkeyBaseMappingFactory<TouchkeyPitchBendMapping>::oscControlMethod(path, types, numValues, values, data);
+}
 
 // ****** Preset Save/Load ******
 XmlElement* TouchkeyPitchBendMappingFactory::getPreset() {
--- a/Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.h	Mon Apr 13 19:30:27 2015 -0700
@@ -68,11 +68,17 @@
     void setBendVariableEndpoints();
     void setBendIgnoresMultipleFingers(bool ignoresTwo, bool ignoresThree);
     
+#ifndef TOUCHKEYS_NO_GUI
     // ***** GUI Support *****
     bool hasBasicEditor() { return true; }
     MappingEditorComponent* createBasicEditor();
     bool hasExtendedEditor() { return false; }
     MappingEditorComponent* createExtendedEditor() { return nullptr; }
+#endif
+    
+    // ****** OSC Control Support ******
+    OscMessage* oscControlMethod(const char *path, const char *types,
+                                 int numValues, lo_arg **values, void *data);
 
     // ****** Preset Save/Load ******
     XmlElement* getPreset();
--- a/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -192,11 +192,14 @@
     }
 }
 
+#ifndef TOUCHKEYS_NO_GUI
 // ***** GUI Support *****
 
 MappingEditorComponent* TouchkeyReleaseAngleMappingFactory::createExtendedEditor() {
     return new TouchkeyReleaseAngleMappingExtendedEditor(*this);
 }
+#endif
+
 
 // ****** Preset Save/Load ******
 XmlElement* TouchkeyReleaseAngleMappingFactory::getPreset() {
--- a/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h	Mon Apr 13 19:30:27 2015 -0700
@@ -87,11 +87,13 @@
     }
     void setCurrentConfiguration(int index);
     
+#ifndef TOUCHKEYS_NO_GUI
     // ***** GUI Support *****
     bool hasBasicEditor() { return false; }
     MappingEditorComponent* createBasicEditor() { return nullptr; }
     bool hasExtendedEditor() { return true; }
     MappingEditorComponent* createExtendedEditor();
+#endif
     
     // ****** Preset Save/Load ******
     XmlElement* getPreset();
--- a/Source/Mappings/TouchkeyBaseMappingFactory.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/TouchkeyBaseMappingFactory.h	Mon Apr 13 19:30:27 2015 -0700
@@ -53,7 +53,7 @@
 	// Default constructor, containing a reference to the PianoKeyboard class.
     TouchkeyBaseMappingFactory(PianoKeyboard &keyboard, MidiKeyboardSegment& segment) :
       MappingFactory(keyboard), keyboardSegment_(segment), midiConverter_(0),
-      controlName_(""),
+      controlName_(""), shortControlName_(""),
       inputRangeMin_(0.0), inputRangeMax_(1.0), inputRangeCenter_(0.0),
       outOfRangeBehavior_(OscMidiConverter::kOutOfRangeClip),
       use14BitControl_(false),
@@ -207,10 +207,13 @@
     }
     
     virtual string const getName() { return controlName_; }
+    virtual string const getShortName() { return shortControlName_; }
     
     virtual void setName(const string& name) {
         if(name == "")
             return;
+        shortControlName_ = name;
+        
         std::stringstream ss;
         
         // Remove listener on previous name (if any)
@@ -347,6 +350,38 @@
         midiConverter_->clearLastValues(midiChannel, true);
         //midiConverter_->sendDefaultValue(midiChannel);
     }
+    
+    // ****** OSC Control ******
+    // As an alternative to GUI control, the mapping factories can receive OSC messages
+    // from the keyboard segment to which they are attached.
+    virtual OscMessage* oscControlMethod(const char *path, const char *types,
+                                  int numValues, lo_arg **values, void *data) {
+        if(!strcmp(path, "/set-bypass")) {
+            // Enable/disable suspend mapping
+            if(numValues > 0) {
+                if(types[0] == 'i') {
+                    if(values[0]->i != 0)
+                        setBypassed(true);
+                    else
+                        setBypassed(false);
+                    return OscTransmitter::createSuccessMessage();
+                }
+            }
+        }
+        else if(!strcmp(path, "/set-active-notes")) {
+            // Set which notes it applies to
+            // Bitmask: lower 12 bits of the number for pitch classes 0-11
+            if(numValues > 0) {
+                if(types[0] == 'i') {
+                    setActiveNotes((values[0]->i) & 0x0FFF);
+                    
+                    return OscTransmitter::createSuccessMessage();
+                }
+            }
+        }
+        
+        return 0;
+    }
 
     
 protected:
@@ -446,7 +481,8 @@
     std::map<int, MappingType*> mappings_;         // Collection of active mappings
     CriticalSection mappingsMutex_;                // Mutex protecting mappings from changes
     
-    std::string controlName_;                           // Name of the mapping
+    std::string controlName_;                           // Name of the mapping in long..
+    std::string shortControlName_;                      // ... and short forms
     float inputRangeMin_, inputRangeMax_;               // Input ranges
     float inputRangeCenter_;      
     int outOfRangeBehavior_;                            // What happens to out of range inputs
--- a/Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -156,10 +156,65 @@
     vibratoTimeout_ = timeout;
 }
 
+#ifndef TOUCHKEYS_NO_GUI
 // ***** GUI Support *****
 MappingEditorComponent* TouchkeyVibratoMappingFactory::createBasicEditor() {
     return new TouchkeyVibratoMappingShortEditor(*this);
 }
+#endif
+
+// ****** OSC Control Support ******
+OscMessage* TouchkeyVibratoMappingFactory::oscControlMethod(const char *path, const char *types,
+                                                            int numValues, lo_arg **values, void *data) {
+    if(!strcmp(path, "/set-vibrato-control")) {
+        // Change the vibrato control
+        if(numValues > 0) {
+            if(types[0] == 'i') {
+                setVibratoControl(values[0]->i);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-vibrato-range")) {
+        // Change the vibrato range in semitones
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setVibratoRange(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-vibrato-prescaler")) {
+        // Change the vibrato prescaler
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setVibratoPrescaler(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-vibrato-timeout")) {
+        // Change the vibrato timeout
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setVibratoTimeout(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-vibrato-threshold")) {
+        // Change the vibrato threshold
+        if(numValues > 0) {
+            if(types[0] == 'f') {
+                setVibratoThreshold(values[0]->f);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    
+    // If no match, check the base class
+    return TouchkeyBaseMappingFactory<TouchkeyVibratoMapping>::oscControlMethod(path, types, numValues, values, data);
+}
 
 
 // ****** Preset Save/Load ******
--- a/Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.h	Mon Apr 13 19:30:27 2015 -0700
@@ -71,11 +71,17 @@
     void setVibratoThresholds(float thresholdX, float thresholdY, float ratioX, float ratioY, bool updateCurrent = false);
     void setVibratoTimeout(timestamp_diff_type timeout, bool updateCurrent = false);
     
+#ifndef TOUCHKEYS_NO_GUI
     // ***** GUI Support *****
     bool hasBasicEditor() { return true; }
     MappingEditorComponent* createBasicEditor();
     bool hasExtendedEditor() { return false; }
     MappingEditorComponent* createExtendedEditor() { return nullptr; }
+#endif
+    
+    // ****** OSC Control Support ******
+    OscMessage* oscControlMethod(const char *path, const char *types,
+                                 int numValues, lo_arg **values, void *data);
     
     // ****** Preset Save/Load ******
     XmlElement* getPreset();
--- a/Source/TouchKeys/MidiInputController.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/TouchKeys/MidiInputController.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -326,30 +326,34 @@
     return segment;
 }
 
-// Remove a segment by index or by object
-void MidiInputController::removeSegment(int index) {
+// Remove a segment by index or by object. Returns true if segment existed
+bool MidiInputController::removeSegment(int index) {
     ScopedLock sl(segmentsMutex_);
     
     if(index < 0 || index >= segments_.size())
-        return;
+        return false;
     
     MidiKeyboardSegment* segment = segments_[index];
     delete segment;
     segments_.erase(segments_.begin() + index);
     segmentUniqueIdentifier_++;
+    return true;
 }
 
-void MidiInputController::removeSegment(MidiKeyboardSegment* segment) {
+bool MidiInputController::removeSegment(MidiKeyboardSegment* segment) {
     ScopedLock sl(segmentsMutex_);
+    bool found = false;
     
     for(int i = 0; i < segments_.size(); i++) {
         if(segments_[i] == segment) {
             delete segment;
             segments_.erase(segments_.begin() + i);
+            found = true;
             break;
         }
     }
     segmentUniqueIdentifier_++;
+    return found;
 }
 
 void MidiInputController::removeAllSegments() {
@@ -419,6 +423,16 @@
     return true;
 }
 
+// OSC handling for keyboard segments
+OscMessage* MidiInputController::oscControlMessageForSegment(int segment, const char *path, const char *types,
+                                                      int numValues, lo_arg **values, void *data) {
+    ScopedLock sl(segmentsMutex_);
+    if(segment < 0 || segment >= segments_.size())
+        return 0;
+
+    return segments_[segment]->oscControlMethod(path, types, numValues, values, data);
+}
+
 // This gets called every time MIDI data becomes available on any input controller. source tells
 // us where the message came from, and may be 0 if being called internally.
 
--- a/Source/TouchKeys/MidiInputController.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/TouchKeys/MidiInputController.h	Mon Apr 13 19:30:27 2015 -0700
@@ -113,14 +113,17 @@
     MidiKeyboardSegment* addSegment(int outputPortNumber, int noteMin = 0, int noteMax = 127, int channelMask = 0xFFFF);
     
     // Remove a segment by index or by object
-    void removeSegment(int index);
-    void removeSegment(MidiKeyboardSegment* segment);
+    bool removeSegment(int index);
+    bool removeSegment(MidiKeyboardSegment* segment);
     void removeAllSegments();
     
     // Preset save/load for keyboard segments
     XmlElement* getSegmentPreset();
     bool loadSegmentPreset(XmlElement const* preset);
     
+    // OSC handling for keyboard segments
+    OscMessage* oscControlMessageForSegment(int segment, const char *path, const char *types, int numValues, lo_arg **values, void *data);
+    
     // Juce MIDI callbacks
     void handleIncomingMidiMessage(MidiInput* source, const MidiMessage& message);
     void handlePartialSysexMessage(MidiInput* source,
--- a/Source/TouchKeys/MidiKeyboardSegment.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/TouchKeys/MidiKeyboardSegment.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -44,6 +44,11 @@
 const int MidiKeyboardSegment::kMidiControllerDamperPedal = 64;
 const int MidiKeyboardSegment::kPedalActiveValue = 64;
 
+// Factores to use
+const int kNumMappingFactoryTypes = 7;
+const char* kMappingFactoryNames[kNumMappingFactoryTypes] = {"Control", "Vibrato", "Pitch Bend", "Split Key", "Multi-Finger Trigger", "Onset Angle", "Release Angle"};
+
+
 // Constructor
 MidiKeyboardSegment::MidiKeyboardSegment(PianoKeyboard& keyboard)
 : keyboard_(keyboard), outputPortNumber_(0), mappingFactorySplitter_(keyboard),
@@ -391,6 +396,247 @@
 	return true;
 }
 
+// Control method via OSC. This comes in via MainApplicationController to MidiInputController
+// and is used specifically for querying and modifying the status of the zone and its mappings,
+// as opposed to the more frequent OSC messages to oscHandlerMethod() which provide touch and
+// MIDI data. Return true if message was successfully handled.
+OscMessage* MidiKeyboardSegment::oscControlMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) {
+    // First check if this message is destined for a mapping within the segment
+    // e.g. /mapping/my_mapping_name/message_for_mapping
+    if(!strncmp(path, "/mapping/", 9) && strlen(path) > 9) {
+        std::string subpath(&path[9]);
+        
+        int separatorLoc = subpath.find_first_of('/');
+        if(separatorLoc == std::string::npos || separatorLoc == subpath.length() - 1) {
+            // Malformed input (no slash or it's the last character): ignore
+            return 0;
+        }
+        
+        // Find the name of the mapping in the nextsegment
+        std::string mappingName = subpath.substr(0, separatorLoc);
+        
+        // Look for a matching factory. TODO: this should probably be mutex-protected
+        vector<MappingFactory*>::iterator it;
+        for(it = mappingFactories_.begin(); it != mappingFactories_.end(); ++it) {
+            if((*it)->getShortName() == mappingName) {
+                std::string mappingAction = subpath.substr(separatorLoc);
+                
+                if(mappingAction == "/delete") {
+                    removeMappingFactory(*it);
+                    return OscTransmitter::createSuccessMessage();
+                }
+                else {
+                    // Pass message to mapping factory here
+                    OscMessage *response = (*it)->oscControlMethod(mappingAction.c_str(), types, numValues, values, data);
+                    
+                    // Prepend the mapping name to the response except in case of simple status response
+                    if(response == 0)
+                        return 0;
+                    else if(!strcmp(response->path(), "/result"))
+                        return response;
+                    response->prependPath(mappingName.c_str());
+                    response->prependPath("/mapping/");
+                    return response;
+                }
+            }
+        }
+    }
+    else if(!strcmp(path, "/list-mappings")) {
+        // Return a list of mapping names and types
+        // TODO: this should be mutex-protected
+        
+        OscMessage *response = OscTransmitter::createMessage("/list-mappings/result", "i", mappingFactories_.size(), LO_ARGS_END);
+        
+        vector<MappingFactory*>::iterator it;
+        for(it = mappingFactories_.begin(); it != mappingFactories_.end(); ++it) {
+            lo_message_add_string(response->message(), (*it)->getShortName().c_str());
+        }
+        
+        return response;
+    }
+    else if(!strcmp(path, "/add-mapping")) {
+        // Add a new mapping of a given type
+        if(numValues >= 1) {
+            if(types[0] == 'i') {
+                int type = values[0]->i;
+                
+                if(type < 0 || type >= kNumMappingFactoryTypes)
+                    return OscTransmitter::createFailureMessage();
+ 
+                // Create mapping factory of the requested type
+                MappingFactory *newFactory = createMappingFactoryForIndex(type);
+                if(newFactory == 0)
+                    return OscTransmitter::createFailureMessage();
+ 
+                // Add the mapping factory to this segment, autogenerating the
+                // name unless it is specified
+                if(numValues >= 2) {
+                    if(types[1] == 's') {
+                        // Set the name as it was passed in
+                        newFactory->setName(&values[1]->s);
+                        addMappingFactory(newFactory, false);
+                    }
+                    else
+                        addMappingFactory(newFactory, true);
+                }
+                else
+                    addMappingFactory(newFactory, true);
+                
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-range")) {
+        // Set the MIDI note range
+        if(numValues >= 2) {
+            if(types[0] == 'i' && types[1] == 'i') {
+                int rangeLow = values[0]->i;
+                int rangeHigh = values[1]->i;
+                
+                if(rangeLow < 0 || rangeLow > 127 || rangeHigh < 0 || rangeHigh > 127)
+                    return OscTransmitter::createFailureMessage();
+                if(rangeLow > rangeHigh) {
+                    // Swap values so lowest one is always first
+                    int temp = rangeLow;
+                    rangeLow = rangeHigh;
+                    rangeHigh = temp;
+                }
+                
+                setNoteRange(rangeLow, rangeHigh);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-transpose")) {
+        // Set the transposition of the output
+        if(numValues >= 1) {
+            if(types[0] == 'i') {
+                int transpose = values[0]->i;
+                
+                if(transpose < -48 || transpose > 48)
+                    return OscTransmitter::createFailureMessage();
+
+                setOutputTransposition(transpose);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-transpose-octave-up")) {
+        // Set the transposition of the output
+        int transpose = outputTransposition() + 12;
+        if(transpose > 48)
+            transpose = 48;
+        setOutputTransposition(transpose);
+
+        return OscTransmitter::createSuccessMessage();
+    }
+    else if(!strcmp(path, "/set-transpose-octave-down")) {
+        // Set the transposition of the output
+        int transpose = outputTransposition() - 12;
+        if(transpose < -48)
+            transpose = -48;
+        setOutputTransposition(transpose);
+        
+        return OscTransmitter::createSuccessMessage();
+    }
+    else if(!strcmp(path, "/set-controller-pass")) {
+        // Set which controllers to pass through
+        // Arguments: (channel pressure), (pitch wheel), (mod wheel), (other CCs)
+        
+        if(numValues >= 4) {
+            if(types[0] == 'i' && types[1] == 'i' && types[2] == 'i' && types[3] == 'i') {
+                setUsesKeyboardChannelPressure(values[0]->i != 0);
+                setUsesKeyboardPitchWheel(values[1]->i != 0);
+                setUsesKeyboardModWheel(values[2]->i != 0);
+                setUsesKeyboardMIDIControllers(values[3]->i != 0);
+                
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-pitchwheel-range")) {
+        // Set the MIDI pitchwheel range in semitones
+        if(numValues >= 1) {
+            if(types[0] == 'i') {
+                int range = values[0]->i;
+                
+                setMidiPitchWheelRange(range);
+                return OscTransmitter::createSuccessMessage();
+            }
+            else if(types[0] == 'f') {
+                float range = values[0]->f;
+                
+                setMidiPitchWheelRange(range);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/send-pitchwheel-range")) {
+        // Send an RPN value with the current pitchwheel range
+        sendMidiPitchWheelRange();
+        return OscTransmitter::createSuccessMessage();
+    }
+    else if(!strcmp(path, "/set-midi-mode")) {
+        // Set the MIDI mode (mono, poly etc.)
+        if(numValues >= 1) {
+            if(types[0] == 's') {
+                char *mode = &values[0]->s;
+                
+                if(!strcmp(mode, "off"))
+                    setModeOff();
+                else if(!strncmp(mode, "pass", 4))
+                    setModePassThrough();
+                else if(!strncmp(mode, "mono", 4))
+                    setModeMonophonic();
+                else if(!strncmp(mode, "poly", 4))
+                    setModePolyphonic();
+                else
+                    return OscTransmitter::createFailureMessage();
+
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-midi-channels")) {
+        // Set the MIDI channels
+        if(numValues >= 2) {
+            if(types[0] == 'i' && types[1] == 'i') {
+                int channelLow = values[0]->i;
+                int channelHigh = values[1]->i;
+                
+                if(channelLow < 1 || channelLow > 16 || channelHigh < 1 || channelHigh > 16)
+                    return OscTransmitter::createFailureMessage();
+                if(channelLow > channelHigh) {
+                    // Swap values so lowest one is always first
+                    int temp = channelLow;
+                    channelLow = channelHigh;
+                    channelHigh = temp;
+                }
+                
+                setOutputChannelLowest(channelLow - 1); // 1-16 --> 0-15 indexing
+                int polyphony = channelHigh - channelLow + 1;
+                if(polyphony < 1)
+                    polyphony = 1;
+                setPolyphony(polyphony);
+                
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+    else if(!strcmp(path, "/set-midi-stealing")) {
+        // Set whether MIDI voice stealing is enabled
+        if(numValues >= 1) {
+            if(types[0] == 'i') {
+                setVoiceStealingEnabled(values[0]->i != 0);
+                return OscTransmitter::createSuccessMessage();
+            }
+        }
+    }
+
+    // No match
+    return 0;
+}
+
 // Acquire an OSC-MIDI converter. If a converter for this control already exists,
 // return it. If not, create it. This method keeps track of how many objects have
 // acquired the converter. When all acquirers have released ihe converter, it is
@@ -449,6 +695,47 @@
 #endif
 }
 
+// Return the number of mapping factory types available
+int MidiKeyboardSegment::numberOfMappingFactories() {
+    return kNumMappingFactoryTypes;
+}
+
+// Return the name of the given mapping factory type
+String MidiKeyboardSegment::mappingFactoryNameForIndex(int index) {
+    if(index < 0 || index >= kNumMappingFactoryTypes)
+        return String();
+    return kMappingFactoryNames[index];
+}
+
+// Return a new object of the given mapping factory type
+MappingFactory* MidiKeyboardSegment::createMappingFactoryForIndex(int index) {
+    switch(index) {
+        case 0:
+            return new TouchkeyControlMappingFactory(keyboard_, *this);
+        case 1:
+            return new TouchkeyVibratoMappingFactory(keyboard_, *this);
+        case 2:
+            return new TouchkeyPitchBendMappingFactory(keyboard_, *this);
+        case 3:
+            return new TouchkeyKeyDivisionMappingFactory(keyboard_, *this);
+        case 4:
+            return new TouchkeyMultiFingerTriggerMappingFactory(keyboard_, *this);
+        case 5:
+            return new TouchkeyOnsetAngleMappingFactory(keyboard_, *this);
+        case 6:
+            return new TouchkeyReleaseAngleMappingFactory(keyboard_, *this);
+        default:
+            return 0;
+    }
+}
+
+// Return whethera  given mapping is experimental or not
+bool MidiKeyboardSegment::mappingIsExperimental(int index) {
+    if(index > 2 && index != 4)
+        return true;
+    return false;
+}
+
 // Create a new mapping factory for this segment. A pointer should be passed in
 // of a newly-allocated object. It will be released upon removal.
 void MidiKeyboardSegment::addMappingFactory(MappingFactory* factory, bool autoGenerateName) {
--- a/Source/TouchKeys/MidiKeyboardSegment.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/TouchKeys/MidiKeyboardSegment.h	Mon Apr 13 19:30:27 2015 -0700
@@ -204,6 +204,10 @@
     // OSC method: used to get touch callback data from the keyboard
 	bool oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data);
     
+    // OSC control method: called separately via the MidiInputController to manipulate
+    // control parameters of this object
+    OscMessage* oscControlMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data);
+    
     // **** Mapping-related methods *****
     
     // OSC-MIDI converters: request and release methods. The acquire method
@@ -213,6 +217,20 @@
     OscMidiConverter* acquireOscMidiConverter(int controlId);
     void releaseOscMidiConverter(int controlId);
     
+    // *** Mapping methods ***
+    // Return the number of mapping factory types available
+    static int numberOfMappingFactories();
+    
+    // Return the name of a given mapping factory type
+    static String mappingFactoryNameForIndex(int index);
+    
+    // Whether a given mapping is experimental
+    static bool mappingIsExperimental(int index);
+    
+    // Create a new mapping factory of the given type, attached to
+    // the supplied segment
+    MappingFactory* createMappingFactoryForIndex(int index);
+    
     // Create a new mapping factory for this segment. A pointer should be passed in
     // of a newly-allocated object. It will be released upon removal.
     void addMappingFactory(MappingFactory* factory, bool autoGenerateName = false);
@@ -232,8 +250,6 @@
     // Return a unique identifier of the mapping state, so we know when something has changed
     int mappingFactoryUniqueIdentifier() { return mappingFactoryUniqueIdentifier_; }
     
-
-    
     // **** Preset methods ****
     
     // Get an XML element describing current settings (for saving presets)
--- a/Source/TouchKeys/Osc.cpp	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/TouchKeys/Osc.cpp	Mon Apr 13 19:30:27 2015 -0700
@@ -558,4 +558,16 @@
 OscTransmitter::~OscTransmitter()
 {
 	clearAddresses();
-}
\ No newline at end of file
+}
+
+OscMessage* OscTransmitter::createMessage(const char * path, const char * type, ...)
+{
+    va_list v;
+    
+    va_start(v, type);
+    lo_message msg = lo_message_new();
+    lo_message_add_varargs(msg, type, v);
+    va_end(v);
+    
+    return new OscMessage(path, type, msg);
+}
--- a/Source/TouchKeys/Osc.h	Mon Jan 05 18:06:43 2015 +0000
+++ b/Source/TouchKeys/Osc.h	Mon Apr 13 19:30:27 2015 -0700
@@ -171,6 +171,33 @@
 	string globalPrefix_;					// Prefix for all OSC paths	
 };
 
+// Simple class to hold a message alongw ith a path and a type
+class OscMessage
+{
+public:
+    OscMessage(const char *path, const char *type, lo_message& message)
+    : path_(path), type_(type), message_(message) {}
+    
+    ~OscMessage() {
+        lo_message_free(message_);
+    }
+    
+    const char *path() { return path_.c_str(); }
+    const char *type() { return type_.c_str(); }
+    lo_message message() { return message_; }
+    
+    // Add a prefix to the message path
+    void prependPath(const char *prefix) {
+        path_.insert(0, prefix); // TODO: check that this is right
+    }
+    
+private:
+    std::string path_;
+    std::string type_;
+    lo_message message_;
+};
+
+
 class OscTransmitter
 {
 public:
@@ -193,6 +220,11 @@
 	void setDebugMessages(bool debug) { debugMessages_ = debug; }
 	
 	~OscTransmitter();
+    
+    // Static methods
+    static OscMessage* createMessage(const char * path, const char * type, ...);
+    static OscMessage* createSuccessMessage() { return createMessage("/result", "i", 0, LO_ARGS_END); }
+    static OscMessage* createFailureMessage() { return createMessage("/result", "i", 1, LO_ARGS_END); }
 	
 private:
 	vector<lo_address> addresses_;