# HG changeset patch # User Andrew McPherson # Date 1428978627 25200 # Node ID 90ce403d0dc575d8c7b531cefd542717ddd01e39 # Parent 2a9e5576905e85db87c221a03dcf5ba4d6e93b07 Added OSC control over all main application functions. OSC messages can be sent to do most of the tasks available from the GUI. diff -r 2a9e5576905e -r 90ce403d0dc5 Source/GUI/KeyboardZoneComponent.cpp --- 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); diff -r 2a9e5576905e -r 90ce403d0dc5 Source/MainApplicationController.cpp --- 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 devices = controller_.availableTouchkeyDevices(); + vector::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 > midiInputs = controller_.availableMIDIInputDevices(); + + OscMessage *response = OscTransmitter::createMessage("/list-midi-in/result", "i", midiInputs.size(), LO_ARGS_END); + + std::vector >::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 > midiOutputs = controller_.availableMIDIOutputDevices(); + + OscMessage *response = OscTransmitter::createMessage("/list-midi-out/result", "i", midiOutputs.size(), LO_ARGS_END); + + std::vector >::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 diff -r 2a9e5576905e -r 90ce403d0dc5 Source/MainApplicationController.h --- 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__) */ diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/Control/TouchkeyControlMappingFactory.cpp --- 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::oscControlMethod(path, types, numValues, values, data); +} // ****** Preset Save/Load ****** diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/Control/TouchkeyControlMappingFactory.h --- 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(); diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/MappingFactory.h --- 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 diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.cpp --- 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::oscControlMethod(path, types, numValues, values, data); +} // ****** Preset Save/Load ****** XmlElement* TouchkeyMultiFingerTriggerMappingFactory::getPreset() { diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.h --- 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); diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.cpp --- 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::oscControlMethod(path, types, numValues, values, data); +} // ****** Preset Save/Load ****** XmlElement* TouchkeyPitchBendMappingFactory::getPreset() { diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/PitchBend/TouchkeyPitchBendMappingFactory.h --- 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(); diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.cpp --- 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() { diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h --- 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(); diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/TouchkeyBaseMappingFactory.h --- 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 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 diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.cpp --- 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::oscControlMethod(path, types, numValues, values, data); +} // ****** Preset Save/Load ****** diff -r 2a9e5576905e -r 90ce403d0dc5 Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.h --- 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(); diff -r 2a9e5576905e -r 90ce403d0dc5 Source/TouchKeys/MidiInputController.cpp --- 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. diff -r 2a9e5576905e -r 90ce403d0dc5 Source/TouchKeys/MidiInputController.h --- 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, diff -r 2a9e5576905e -r 90ce403d0dc5 Source/TouchKeys/MidiKeyboardSegment.cpp --- 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::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::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) { diff -r 2a9e5576905e -r 90ce403d0dc5 Source/TouchKeys/MidiKeyboardSegment.h --- 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) diff -r 2a9e5576905e -r 90ce403d0dc5 Source/TouchKeys/Osc.cpp --- 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); +} diff -r 2a9e5576905e -r 90ce403d0dc5 Source/TouchKeys/Osc.h --- 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 addresses_;