andrewm@0: /* andrewm@0: TouchKeys: multi-touch musical keyboard control software andrewm@0: Copyright (c) 2013 Andrew McPherson andrewm@0: andrewm@0: This program is free software: you can redistribute it and/or modify andrewm@0: it under the terms of the GNU General Public License as published by andrewm@0: the Free Software Foundation, either version 3 of the License, or andrewm@0: (at your option) any later version. andrewm@0: andrewm@0: This program is distributed in the hope that it will be useful, andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrewm@0: GNU General Public License for more details. andrewm@0: andrewm@0: You should have received a copy of the GNU General Public License andrewm@0: along with this program. If not, see . andrewm@0: andrewm@0: ===================================================================== andrewm@0: andrewm@0: TouchkeyBaseMappingFactory.h: base factory class specifically for andrewm@0: TouchKeys mappings. It provides a collection of useful methods for andrewm@0: creating and destroying individual mappings on touch/MIDI onset and andrewm@0: release, as well as parameter adjustment code and OSC to MIDI conversion. andrewm@0: This is a template class that must be created with a specific Mapping andrewm@0: subclass. andrewm@0: */ andrewm@0: andrewm@0: #ifndef __TouchKeys__TouchkeyBaseMappingFactory__ andrewm@0: #define __TouchKeys__TouchkeyBaseMappingFactory__ andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include "MappingFactory.h" andrewm@0: #include "../TouchKeys/OscMidiConverter.h" andrewm@0: #include "../TouchKeys/MidiOutputController.h" andrewm@0: #include "../TouchKeys/MidiKeyboardSegment.h" andrewm@0: #include "MappingScheduler.h" andrewm@0: andrewm@0: #undef DEBUG_TOUCHKEY_BASE_MAPPING_FACTORY andrewm@0: andrewm@0: // Base class for mapping factories that meet the following criteria: andrewm@0: // * MIDI and TouchKeys data (no continuous angle) andrewm@0: // * Mappings begin when either or touch or MIDI starts and end when both finish andrewm@0: // * Each mapping object affects a single note andrewm@0: andrewm@0: template andrewm@0: class TouchkeyBaseMappingFactory : public MappingFactory { andrewm@0: andrewm@0: public: andrewm@0: // ***** Constructor ***** andrewm@0: andrewm@0: // Default constructor, containing a reference to the PianoKeyboard class. andrewm@0: TouchkeyBaseMappingFactory(PianoKeyboard &keyboard, MidiKeyboardSegment& segment) : andrewm@0: MappingFactory(keyboard), keyboardSegment_(segment), midiConverter_(0), andrewm@49: controlName_(""), shortControlName_(""), andrewm@0: inputRangeMin_(0.0), inputRangeMax_(1.0), inputRangeCenter_(0.0), andrewm@0: outOfRangeBehavior_(OscMidiConverter::kOutOfRangeClip), andrewm@41: use14BitControl_(false), andrewm@0: midiControllerNumber_(-1), bypassed_(false), activeNotes_(0x0FFF) {} andrewm@0: andrewm@0: // ***** Destructor ***** andrewm@0: andrewm@0: virtual ~TouchkeyBaseMappingFactory() { andrewm@0: removeAllMappings(); andrewm@0: if(midiConverter_ != 0 && controlName_ != "") andrewm@0: midiConverter_->removeControl(controlName_.c_str()); andrewm@0: if(midiControllerNumber_ >= 0) { andrewm@0: keyboardSegment_.releaseOscMidiConverter(midiControllerNumber_); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // ***** Accessors / Modifiers ***** andrewm@0: andrewm@0: // Return the keyboard segment associated with this factory andrewm@0: MidiKeyboardSegment& segment() { return keyboardSegment_; } andrewm@0: andrewm@0: // Look up a mapping with the given note number andrewm@0: virtual MappingType* mapping(int noteNumber) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: if(mappings_.count(noteNumber) == 0) andrewm@0: return 0; andrewm@0: return mappings_[noteNumber]; andrewm@0: } andrewm@0: andrewm@0: // Return a list of all active notes andrewm@0: virtual std::vector activeMappings() { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: std::vector keys; andrewm@0: typename std::map::iterator it = mappings_.begin(); andrewm@0: while(it != mappings_.end()) { andrewm@0: int nextKey = (it++)->first; andrewm@0: keys.push_back(nextKey); andrewm@0: } andrewm@0: return keys; andrewm@0: } andrewm@0: andrewm@0: // Remove all active mappings andrewm@0: virtual void removeAllMappings() { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: typename std::map::iterator it = mappings_.begin(); andrewm@0: andrewm@0: while(it != mappings_.end()) { andrewm@0: // Delete everybody in the container andrewm@0: MappingType *mapping = it->second; andrewm@0: #ifdef NEW_MAPPING_SCHEDULER andrewm@0: mapping->disengage(true); andrewm@0: //keyboard_.mappingScheduler().unscheduleAndDelete(mapping); andrewm@0: #else andrewm@0: mapping->disengage(); andrewm@0: delete mapping; andrewm@0: #endif andrewm@0: it++; andrewm@0: } andrewm@0: andrewm@0: // Now clear the container andrewm@0: mappings_.clear(); andrewm@0: } andrewm@0: andrewm@0: // Callback from mapping to say it's finished andrewm@0: virtual void mappingFinished(int noteNumber) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: removeMapping(noteNumber); andrewm@0: } andrewm@0: andrewm@0: // Suspend messages from a particular note andrewm@0: virtual void suspendMapping(int noteNumber) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: if(mappings_.count(noteNumber) == 0) andrewm@0: return; andrewm@0: mappings_[noteNumber]->suspend(); andrewm@0: } andrewm@0: andrewm@0: // Suspend messages from all notes andrewm@0: virtual void suspendAllMappings() { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: typename std::map::iterator it = mappings_.begin(); andrewm@0: andrewm@0: while(it != mappings_.end()) { andrewm@0: //std::cout << "suspending mapping on note " << it->first << std::endl; andrewm@0: it->second->suspend(); andrewm@0: it++; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Resume messages from a particular note andrewm@0: virtual void resumeMapping(int noteNumber, bool resend) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: if(mappings_.count(noteNumber) == 0) andrewm@0: return; andrewm@0: //std::cout << "resuming mapping on note " << noteNumber << std::endl; andrewm@0: mappings_[noteNumber]->resume(resend); andrewm@0: } andrewm@0: andrewm@0: // Resume messages on all notes andrewm@0: virtual void resumeAllMappings(bool resend) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: typename std::map::iterator it = mappings_.begin(); andrewm@0: andrewm@0: while(it != mappings_.end()) { andrewm@0: it->second->resume(resend); andrewm@0: it++; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Whether this mapping is bypassed andrewm@0: virtual int bypassed() { andrewm@0: return bypassed_ ? kBypassOn : kBypassOff; andrewm@0: } andrewm@0: andrewm@0: // Set whether the mapping is bypassed or not andrewm@0: virtual void setBypassed(bool bypass) { andrewm@0: bypassed_ = bypass; andrewm@0: } andrewm@0: andrewm@0: // ***** Class-Specific Methods ***** andrewm@0: andrewm@0: virtual void setMidiParameters(int controller, float inputMinValue, float inputMaxValue, float inputCenterValue, andrewm@0: int outputDefaultValue = -1, int outputMinValue = -1, int outputMaxValue = -1, andrewm@0: int outputCenterValue = -1, bool use14BitControl = false, andrewm@0: int outOfRangeBehavior = OscMidiConverter::kOutOfRangeClip) { andrewm@0: if(controller < 0) andrewm@0: return; andrewm@0: andrewm@0: inputRangeMin_ = inputMinValue; andrewm@0: inputRangeMax_ = inputMaxValue; andrewm@0: inputRangeCenter_ = inputCenterValue; andrewm@0: outOfRangeBehavior_ = outOfRangeBehavior; andrewm@41: use14BitControl_ = use14BitControl; andrewm@0: andrewm@0: // Remove listener on previous name (if any) andrewm@0: //midiConverter_.removeAllControls(); andrewm@0: if(midiControllerNumber_ >= 0 && controller != midiControllerNumber_) { andrewm@0: keyboardSegment_.releaseOscMidiConverter(midiControllerNumber_); andrewm@0: midiConverter_ = keyboardSegment_.acquireOscMidiConverter(controller); andrewm@0: } andrewm@0: else if(midiControllerNumber_ < 0 || midiConverter_ == 0) { andrewm@0: midiConverter_ = keyboardSegment_.acquireOscMidiConverter(controller); andrewm@0: } andrewm@0: midiControllerNumber_ = controller; andrewm@0: andrewm@0: midiConverter_->setMidiMessageType(outputDefaultValue, outputMinValue, outputMaxValue, outputCenterValue, use14BitControl); andrewm@0: andrewm@0: // Add listener for new name andrewm@0: if(controlName_ != "") andrewm@0: midiConverter_->addControl(controlName_.c_str(), 1, inputRangeMin_, inputRangeMax_, inputRangeCenter_, outOfRangeBehavior_); andrewm@0: } andrewm@0: andrewm@0: virtual string const getName() { return controlName_; } andrewm@49: virtual string const getShortName() { return shortControlName_; } andrewm@0: andrewm@0: virtual void setName(const string& name) { andrewm@0: if(name == "") andrewm@0: return; andrewm@49: shortControlName_ = name; andrewm@49: andrewm@0: std::stringstream ss; andrewm@0: andrewm@0: // Remove listener on previous name (if any) andrewm@0: if(midiConverter_ != 0 && controlName_ != "") andrewm@0: midiConverter_->removeControl(controlName_.c_str()); andrewm@0: andrewm@0: ss << "/touchkeys/mapping/segment" << (int)keyboardSegment_.outputPort() << "/" << name; andrewm@0: controlName_ = ss.str(); andrewm@0: andrewm@0: // Add listener for new name andrewm@0: if(midiConverter_ != 0) andrewm@0: midiConverter_->addControl(controlName_.c_str(), 1, inputRangeMin_, inputRangeMax_, inputRangeCenter_, outOfRangeBehavior_); andrewm@0: } andrewm@0: andrewm@0: // Set which keys should have this mapping enable andrewm@0: virtual void setActiveNotes(unsigned int notes) { andrewm@0: activeNotes_ = notes; andrewm@0: } andrewm@0: andrewm@33: // ****** Preset Save/Load ****** andrewm@33: andrewm@33: // These generate XML settings files and reload settings from them andrewm@33: andrewm@33: virtual XmlElement* getPreset() { andrewm@34: PropertySet properties; andrewm@34: storeCommonProperties(properties); andrewm@34: andrewm@34: XmlElement* presetElement = properties.createXml("MappingFactory"); andrewm@33: presetElement->setAttribute("type", "Unknown"); andrewm@33: return presetElement; andrewm@33: } andrewm@33: andrewm@34: virtual bool loadPreset(XmlElement const* preset) { andrewm@34: if(preset == 0) andrewm@34: return false; andrewm@34: andrewm@34: PropertySet properties; andrewm@34: properties.restoreFromXml(*preset); andrewm@34: andrewm@34: if(!loadCommonProperties(properties)) andrewm@34: return false; andrewm@34: return true; andrewm@34: } andrewm@33: andrewm@0: // ***** State Updaters ***** andrewm@0: andrewm@0: // These are called by PianoKey whenever certain events occur that might andrewm@0: // merit the start and stop of a mapping. What is done with them depends on andrewm@0: // the particular factory subclass. andrewm@0: andrewm@0: // Touch becomes active on a key where it wasn't previously andrewm@0: virtual void touchBegan(int noteNumber, bool midiNoteIsOn, bool keyMotionActive, andrewm@0: Node* touchBuffer, andrewm@0: Node* positionBuffer, andrewm@0: KeyPositionTracker* positionTracker) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: // Add a new mapping if one doesn't exist already andrewm@0: if(mappings_.count(noteNumber) == 0) { andrewm@0: #ifdef DEBUG_TOUCHKEY_BASE_MAPPING_FACTORY andrewm@0: std::cout << "Note " << noteNumber << ": adding mapping (touch)\n"; andrewm@0: #endif andrewm@0: int moduloNoteNumber = noteNumber % 12; andrewm@0: if((activeNotes_ & (1 << moduloNoteNumber)) && !bypassed_) andrewm@0: addMapping(noteNumber, touchBuffer, positionBuffer, positionTracker); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Touch ends on a key where it wasn't previously andrewm@0: virtual void touchEnded(int noteNumber, bool midiNoteIsOn, bool keyMotionActive, andrewm@0: Node* touchBuffer, andrewm@0: Node* positionBuffer, andrewm@0: KeyPositionTracker* positionTracker) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: // If a mapping exists but the MIDI note is off, remove the mapping andrewm@0: if(mappings_.count(noteNumber) != 0 && !midiNoteIsOn) { andrewm@0: #ifdef DEBUG_TOUCHKEY_BASE_MAPPING_FACTORY andrewm@0: std::cout << "Note " << noteNumber << ": removing mapping (touch)\n"; andrewm@0: #endif andrewm@0: if(mappings_[noteNumber]->requestFinish()) andrewm@0: removeMapping(noteNumber); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // MIDI note on for a key andrewm@0: virtual void midiNoteOn(int noteNumber, bool touchIsOn, bool keyMotionActive, andrewm@0: Node* touchBuffer, andrewm@0: Node* positionBuffer, andrewm@0: KeyPositionTracker* positionTracker) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: // Add a new mapping if one doesn't exist already andrewm@0: if(mappings_.count(noteNumber) == 0) { andrewm@0: #ifdef DEBUG_TOUCHKEY_BASE_MAPPING_FACTORY andrewm@0: std::cout << "Note " << noteNumber << ": adding mapping (MIDI)\n"; andrewm@0: #endif andrewm@0: int moduloNoteNumber = noteNumber % 12; andrewm@0: if((activeNotes_ & (1 << moduloNoteNumber)) && !bypassed_) andrewm@0: addMapping(noteNumber, touchBuffer, positionBuffer, positionTracker); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // MIDI note off for a key andrewm@0: virtual void midiNoteOff(int noteNumber, bool touchIsOn, bool keyMotionActive, andrewm@0: Node* touchBuffer, andrewm@0: Node* positionBuffer, andrewm@0: KeyPositionTracker* positionTracker) { andrewm@0: ScopedLock sl(mappingsMutex_); andrewm@0: // If a mapping exists but the touch is off, remove the mapping andrewm@0: if(mappings_.count(noteNumber) != 0 && !touchIsOn) { andrewm@0: #ifdef DEBUG_TOUCHKEY_BASE_MAPPING_FACTORY andrewm@0: std::cout << "Note " << noteNumber << ": removing mapping (MIDI)\n"; andrewm@0: #endif andrewm@0: if(mappings_[noteNumber]->requestFinish()) andrewm@0: removeMapping(noteNumber); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Subclasses of this one won't care about these two methods: andrewm@0: andrewm@0: // Key goes active from continuous key position andrewm@0: virtual void keyMotionActive(int noteNumber, bool midiNoteIsOn, bool touchIsOn, andrewm@0: Node* touchBuffer, andrewm@0: Node* positionBuffer, andrewm@0: KeyPositionTracker* positionTracker) {} andrewm@0: // Key goes idle from continuous key position andrewm@0: virtual void keyMotionIdle(int noteNumber, bool midiNoteIsOn, bool touchIsOn, andrewm@0: Node* touchBuffer, andrewm@0: Node* positionBuffer, andrewm@0: KeyPositionTracker* positionTracker) {} andrewm@0: andrewm@0: // But we do use this one to send out default values: andrewm@0: virtual void noteWillBegin(int noteNumber, int midiChannel, int midiVelocity) { andrewm@0: if(midiConverter_ == 0) andrewm@0: return; andrewm@0: midiConverter_->clearLastValues(midiChannel, true); andrewm@0: //midiConverter_->sendDefaultValue(midiChannel); andrewm@0: } andrewm@49: andrewm@49: // ****** OSC Control ****** andrewm@49: // As an alternative to GUI control, the mapping factories can receive OSC messages andrewm@49: // from the keyboard segment to which they are attached. andrewm@49: virtual OscMessage* oscControlMethod(const char *path, const char *types, andrewm@49: int numValues, lo_arg **values, void *data) { andrewm@49: if(!strcmp(path, "/set-bypass")) { andrewm@49: // Enable/disable suspend mapping andrewm@49: if(numValues > 0) { andrewm@49: if(types[0] == 'i') { andrewm@49: if(values[0]->i != 0) andrewm@49: setBypassed(true); andrewm@49: else andrewm@49: setBypassed(false); andrewm@49: return OscTransmitter::createSuccessMessage(); andrewm@49: } andrewm@49: } andrewm@49: } andrewm@49: else if(!strcmp(path, "/set-active-notes")) { andrewm@49: // Set which notes it applies to andrewm@49: // Bitmask: lower 12 bits of the number for pitch classes 0-11 andrewm@49: if(numValues > 0) { andrewm@49: if(types[0] == 'i') { andrewm@49: setActiveNotes((values[0]->i) & 0x0FFF); andrewm@49: andrewm@49: return OscTransmitter::createSuccessMessage(); andrewm@49: } andrewm@49: } andrewm@49: } andrewm@49: andrewm@49: return 0; andrewm@49: } andrewm@0: andrewm@0: andrewm@0: protected: andrewm@0: // ***** Protected Methods ***** andrewm@0: andrewm@0: // This method should be set by the subclass to initialize the parameters of andrewm@0: // a new mapping. andrewm@0: virtual void initializeMappingParameters(int noteNumber, MappingType *mapping) {} andrewm@0: andrewm@34: // This method adds the common mapping properties to the given PropertySet andrewm@34: void storeCommonProperties(PropertySet& properties) { andrewm@34: properties.setValue("controlName", String(controlName_)); andrewm@34: properties.setValue("inputRangeMin", inputRangeMin_); andrewm@34: properties.setValue("inputRangeMax", inputRangeMax_); andrewm@34: properties.setValue("inputRangeCenter", inputRangeCenter_); andrewm@34: properties.setValue("outOfRangeBehavior", outOfRangeBehavior_); andrewm@34: properties.setValue("midiControllerNumber", midiControllerNumber_); andrewm@34: properties.setValue("bypassed", bypassed_); andrewm@34: properties.setValue("activeNotes", (int)activeNotes_); andrewm@34: } andrewm@34: andrewm@34: // This method loads the common mapping properties from the given PropertySet andrewm@34: bool loadCommonProperties(PropertySet const& properties) { andrewm@34: if(!properties.containsKey("controlName") || andrewm@34: !properties.containsKey("inputRangeMin") || andrewm@34: !properties.containsKey("inputRangeMax") || andrewm@34: !properties.containsKey("inputRangeCenter") || andrewm@34: !properties.containsKey("outOfRangeBehavior") || andrewm@34: !properties.containsKey("midiControllerNumber") || andrewm@34: !properties.containsKey("bypassed") || andrewm@34: !properties.containsKey("activeNotes")) { andrewm@34: return false; andrewm@34: } andrewm@34: andrewm@34: // Setting the MIDI controller number needs to be done with andrewm@34: // the setMidiParameters() method which will update midiControllerNumber_ andrewm@34: int tempMidiController = 1; andrewm@34: andrewm@34: controlName_ = properties.getValue("controlName").toUTF8(); andrewm@34: inputRangeMin_ = properties.getDoubleValue("inputRangeMin"); andrewm@34: inputRangeMax_ = properties.getDoubleValue("inputRangeMax"); andrewm@34: inputRangeCenter_ = properties.getDoubleValue("inputRangeCenter"); andrewm@34: outOfRangeBehavior_ = properties.getIntValue("outOfRangeBehavior"); andrewm@34: tempMidiController = properties.getIntValue("midiControllerNumber"); andrewm@34: bypassed_ = properties.getBoolValue("bypassed"); andrewm@34: activeNotes_ = properties.getIntValue("activeNotes"); andrewm@34: andrewm@34: setMidiParameters(tempMidiController, inputRangeMin_, inputRangeMax_, inputRangeCenter_); andrewm@34: andrewm@34: return true; andrewm@34: } andrewm@34: andrewm@0: private: andrewm@0: // ***** Private Methods ***** andrewm@0: andrewm@0: // Add a new mapping andrewm@0: void addMapping(int noteNumber, andrewm@0: Node* touchBuffer, andrewm@0: Node* positionBuffer, andrewm@0: KeyPositionTracker* positionTracker) { andrewm@0: // TODO: mutex andrewm@0: removeMapping(noteNumber); // Free any mapping that's already present on this note andrewm@0: andrewm@0: MappingType *mapping = new MappingType(keyboard_, this, noteNumber, touchBuffer, andrewm@0: positionBuffer, positionTracker); andrewm@0: andrewm@0: // Set parameters andrewm@0: mapping->setName(controlName_); andrewm@0: initializeMappingParameters(noteNumber, mapping); andrewm@0: andrewm@0: // Save the mapping andrewm@0: mappings_[noteNumber] = mapping; andrewm@0: andrewm@0: // Finally, engage the new mapping andrewm@0: mapping->engage(); andrewm@0: } andrewm@0: andrewm@0: void removeMapping(int noteNumber) { andrewm@0: // TODO: mutex andrewm@0: if(mappings_.count(noteNumber) == 0) andrewm@0: return; andrewm@0: MappingType* mapping = mappings_[noteNumber]; andrewm@0: #ifdef NEW_MAPPING_SCHEDULER andrewm@0: mapping->disengage(true); andrewm@0: //keyboard_.mappingScheduler().unscheduleAndDelete(mapping); andrewm@0: #else andrewm@0: mapping->disengage(); andrewm@0: delete mapping; andrewm@0: #endif andrewm@0: mappings_.erase(noteNumber); andrewm@0: } andrewm@0: andrewm@0: protected: andrewm@0: // State variables andrewm@0: MidiKeyboardSegment& keyboardSegment_; // Segment of the keyboard that this mapping addresses andrewm@0: OscMidiConverter *midiConverter_; // Object to convert OSC messages to MIDI andrewm@0: std::map mappings_; // Collection of active mappings andrewm@0: CriticalSection mappingsMutex_; // Mutex protecting mappings from changes andrewm@0: andrewm@49: std::string controlName_; // Name of the mapping in long.. andrewm@49: std::string shortControlName_; // ... and short forms andrewm@0: float inputRangeMin_, inputRangeMax_; // Input ranges andrewm@0: float inputRangeCenter_; andrewm@0: int outOfRangeBehavior_; // What happens to out of range inputs andrewm@41: bool use14BitControl_; // Whether to use a 14-bit control andrewm@0: andrewm@0: int midiControllerNumber_; // Which controller to use andrewm@0: bool bypassed_; // Whether the mapping has been bypassed by UI andrewm@0: unsigned int activeNotes_; // Indication of which notes out of the 12 to use andrewm@0: }; andrewm@0: andrewm@0: #endif /* defined(__TouchKeys__TouchkeyBaseMappingFactory__) */