changeset 33:e8965409903e

First (incomplete) start on save/load presets.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Thu, 20 Mar 2014 23:18:41 +0000
parents 5f25a9b0139e
children 20c28a319dee
files Source/GUI/MainWindow.cpp Source/GUI/MainWindow.h Source/MainApplicationController.cpp Source/MainApplicationController.h Source/Mappings/Control/TouchkeyControlMappingFactory.cpp Source/Mappings/Control/TouchkeyControlMappingFactory.h Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h Source/Mappings/MappingFactory.h Source/Mappings/MappingFactorySplitter.cpp Source/Mappings/MappingFactorySplitter.h Source/Mappings/TouchkeyBaseMappingFactory.h Source/TouchKeys/MidiInputController.cpp Source/TouchKeys/MidiInputController.h Source/TouchKeys/MidiKeyboardSegment.cpp Source/TouchKeys/MidiKeyboardSegment.h
diffstat 15 files changed, 370 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/Source/GUI/MainWindow.cpp	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/GUI/MainWindow.cpp	Thu Mar 20 23:18:41 2014 +0000
@@ -97,12 +97,10 @@
     PopupMenu menu;
     
     if(menuIndex == 0) { // File
-        menu.addCommandItem(&commandManager_, kCommandNewPreset);
+        menu.addCommandItem(&commandManager_, kCommandClearPreset);
         menu.addSeparator();
         menu.addCommandItem(&commandManager_, kCommandOpenPreset);
-        menu.addSeparator();
         menu.addCommandItem(&commandManager_, kCommandSavePreset);
-        menu.addCommandItem(&commandManager_, kCommandSavePresetAs);
 #ifndef JUCE_MAC
         menu.addSeparator();
         menu.addCommandItem(&commandManager_, StandardApplicationCommandIDs::quit);
@@ -153,8 +151,9 @@
     // this returns the set of all commands that this target can perform..
     const CommandID ids[] = {
         // File
-        kCommandNewPreset, kCommandOpenPreset, kCommandSavePreset,
-        kCommandSavePresetAs,
+        kCommandClearPreset,
+        kCommandOpenPreset,
+        kCommandSavePreset,
         // Edit
         StandardApplicationCommandIDs::undo,
         StandardApplicationCommandIDs::redo,
@@ -190,29 +189,23 @@
     switch (commandID) {
             
         // *** File Menu ***
-        case kCommandNewPreset:
-            result.setInfo("New Preset", "Clears the current settings", presetsCategory, 0);
+        case kCommandClearPreset:
+            result.setInfo("Clear Settings", "Clears the current settings", presetsCategory, 0);
             result.setTicked(false);
             result.setActive(false);
-            result.addDefaultKeypress ('N', ModifierKeys::commandModifier);
             break;
         case kCommandOpenPreset:
             result.setInfo("Open Preset...", "Opens an existing preset", presetsCategory, 0);
             result.setTicked(false);
-            result.setActive(false);
+            result.setActive(true);
             result.addDefaultKeypress ('O', ModifierKeys::commandModifier);
             break;
         case kCommandSavePreset:
-            result.setInfo("Save Preset", "Saves the current preset", presetsCategory, 0);
+            result.setInfo("Save Preset...", "Saves the current preset", presetsCategory, 0);
             result.setTicked(false);
-            result.setActive(false);
+            result.setActive(true);
             result.addDefaultKeypress ('S', ModifierKeys::commandModifier);
             break;
-        case kCommandSavePresetAs:
-            result.setInfo("Save Preset As...", "Saves the current preset with a new name", presetsCategory, 0);
-            result.setTicked (false);
-            result.setActive(false);
-            break;
         // Quit command is handled by JuceApplication
             
         // *** Edit Menu ***
@@ -307,7 +300,13 @@
 bool MainWindow::perform(const InvocationInfo& info) {
     switch (info.commandID)
     {
-        case kCommandNewPreset:            
+        case kCommandClearPreset:
+            break;
+        case kCommandOpenPreset:
+            controller_.loadPresetWithDialog();
+            break;
+        case kCommandSavePreset:
+            controller_.savePresetWithDialog();
             break;
         case kCommandRescanDevices:
             controller_.tellDevicesToUpdate();
--- a/Source/GUI/MainWindow.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/GUI/MainWindow.h	Thu Mar 20 23:18:41 2014 +0000
@@ -39,10 +39,9 @@
     enum CommandIDs
     {
         // File menu
-        kCommandNewPreset    = 0x2001,
+        kCommandClearPreset    = 0x2001,
         kCommandOpenPreset,
         kCommandSavePreset,
-        kCommandSavePresetAs,
         
         // Edit menu
         // (all standard)
--- a/Source/MainApplicationController.cpp	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/MainApplicationController.cpp	Thu Mar 20 23:18:41 2014 +0000
@@ -462,6 +462,80 @@
     return false;
 }
 
+// Save the current settings to an XML file
+// Returns true on success
+bool MainApplicationController::savePresetToFile(const char *filename) {
+    File outputFile(filename);
+    
+    return savePresetHelper(outputFile);
+}
+
+// Load settings from a saved XML file
+// Returns true on success
+bool MainApplicationController::loadPresetFromFile(const char *filename) {
+    File inputFile(filename);
+
+    return loadPresetHelper(inputFile);
+}
+
+#ifndef TOUCHKEYS_NO_GUI
+// Present the user with a Save dialog and then save the preset
+bool MainApplicationController::savePresetWithDialog() {
+    FileChooser myChooser ("Save preset...",
+                           File::nonexistent, // File::getSpecialLocation (File::userHomeDirectory),
+                           "*.tkpreset");
+    if(myChooser.browseForFileToSave(true)) {
+        File outputFile(myChooser.getResult());
+        return savePresetHelper(outputFile);
+    }
+    // User clicked cancel...
+    return true;
+}
+
+
+// Present the user with a Load dialog and then save the preset
+bool MainApplicationController::loadPresetWithDialog() {
+    FileChooser myChooser ("Select a preset...",
+                           File::nonexistent, // File::getSpecialLocation (File::userHomeDirectory),
+                           "*.tkpreset");
+    if(myChooser.browseForFileToOpen()) {
+        return loadPresetHelper(myChooser.getResult());
+    }
+    // User clicked cancel...
+    return true;
+}
+#endif
+
+bool MainApplicationController::loadPresetHelper(File const& inputFile) {
+    if(!inputFile.existsAsFile())
+        return false;
+    
+    // Load the XML element from the file and check that it is valid
+    XmlDocument document(inputFile);
+    ScopedPointer<XmlElement> mainElement = document.getDocumentElement();
+    
+    if(mainElement == 0)
+        return false;
+    if(mainElement->getTagName() != "TouchKeysPreset")
+        return false;
+    XmlElement *segmentsElement = mainElement->getChildByName("KeyboardSegments");
+    if(segmentsElement == 0)
+        return false;
+        
+    // Load the preset from this element
+    return midiInputController_.loadSegmentPreset(segmentsElement);
+}
+
+bool MainApplicationController::savePresetHelper(File& outputFile) {
+    XmlElement mainElement("TouchKeysPreset");
+    mainElement.setAttribute("format", "0.1");
+    
+    XmlElement* segmentsElement = midiInputController_.getSegmentPreset();
+    mainElement.addChildElement(segmentsElement);
+    
+    return mainElement.writeToFile(outputFile, "");
+}
+
 #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST
 // Start testing the TouchKeys sensors. Returns true on success.
 bool MainApplicationController::touchkeySensorTestStart(const char *path, int firstKey) {
--- a/Source/MainApplicationController.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/MainApplicationController.h	Thu Mar 20 23:18:41 2014 +0000
@@ -330,6 +330,17 @@
     // 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.
+    bool savePresetToFile(const char *filename);
+    bool loadPresetFromFile(const char *filename);
+    
+#ifndef TOUCHKEYS_NO_GUI
+    bool savePresetWithDialog();
+    bool loadPresetWithDialog();
+#endif
+    
 #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST
     // *** TouchKeys sensor testing methods ***
     // Start testing the TouchKeys sensors
@@ -353,6 +364,9 @@
     static int midiNoteNumberForName(std::string const& name);
     
 private:
+    bool savePresetHelper(File& outputFile);
+    bool loadPresetHelper(File const& inputFile);
+    
     // TouchKeys objects
     PianoKeyboard keyboardController_;
     MidiInputController midiInputController_;
--- a/Source/Mappings/Control/TouchkeyControlMappingFactory.cpp	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingFactory.cpp	Thu Mar 20 23:18:41 2014 +0000
@@ -223,6 +223,17 @@
     return new TouchkeyControlMappingShortEditor(*this);
 }
 
+// ****** Preset Save/Load ******
+XmlElement* TouchkeyControlMappingFactory::getPreset() {
+    XmlElement* preset = new XmlElement("MappingFactory");
+    preset->setAttribute("type", "Control");
+    return preset;
+}
+
+bool TouchkeyControlMappingFactory::loadPreset(XmlElement const* preset) {
+    return true;
+}
+
 // ***** Private Methods *****
 
 void TouchkeyControlMappingFactory::initializeMappingParameters(int noteNumber, TouchkeyControlMapping *mapping) {
--- a/Source/Mappings/Control/TouchkeyControlMappingFactory.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingFactory.h	Thu Mar 20 23:18:41 2014 +0000
@@ -91,6 +91,10 @@
     bool hasExtendedEditor() { return false; }
     MappingEditorComponent* createExtendedEditor() { return nullptr; }
     
+    // ****** Preset Save/Load ******
+    XmlElement* getPreset();
+    bool loadPreset(XmlElement const* preset);
+    
 private:
     // ***** Private Methods *****
     void initializeMappingParameters(int noteNumber, TouchkeyControlMapping *mapping);
--- a/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h	Thu Mar 20 23:18:41 2014 +0000
@@ -83,8 +83,6 @@
         globalOffsetCents_ = offsetCents;
     }
     
-
-    
 private:
     // ***** Private Methods *****
     void initializeMappingParameters(int noteNumber, TouchkeyKeyDivisionMapping *mapping);
--- a/Source/Mappings/MappingFactory.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/Mappings/MappingFactory.h	Thu Mar 20 23:18:41 2014 +0000
@@ -134,6 +134,13 @@
     virtual bool hasExtendedEditor() { return false; }
     virtual MappingEditorComponent* createExtendedEditor() { return nullptr; }
     
+    // ****** Preset Save/Load ******
+    // These methods generate XML settings files and reload values from them
+    // The specific implementation is up to the subclass
+    
+    virtual XmlElement* getPreset() = 0;
+    virtual bool loadPreset(XmlElement const* preset) = 0;
+    
 protected:
 	// ***** Member Variables *****
 	
--- a/Source/Mappings/MappingFactorySplitter.cpp	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/Mappings/MappingFactorySplitter.cpp	Thu Mar 20 23:18:41 2014 +0000
@@ -142,6 +142,17 @@
     factories_.clear();
 }
 
+// Generate XML element with preset settings
+XmlElement* MappingFactorySplitter::getPreset() {
+    XmlElement *preset = new XmlElement("MappingFactory");
+    preset->setAttribute("type", "Splitter");
+    return preset;
+}
+
+bool MappingFactorySplitter::loadPreset(XmlElement const* preset) {
+    return true;
+}
+
 // Touch becomes active on a key where it wasn't previously
 void MappingFactorySplitter::touchBegan(int noteNumber, bool midiNoteIsOn, bool keyMotionActive,
                 Node<KeyTouchFrame>* touchBuffer,
--- a/Source/Mappings/MappingFactorySplitter.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/Mappings/MappingFactorySplitter.h	Thu Mar 20 23:18:41 2014 +0000
@@ -67,6 +67,13 @@
     void addFactory(MappingFactory* factory);
     void removeFactory(MappingFactory* factory);
     void removeAllFactories();
+    
+    // ****** Preset Save/Load ******
+    // These methods generate XML settings files and reload values from them
+    // The specific implementation is up to the subclass
+    
+    XmlElement* getPreset();
+    bool loadPreset(XmlElement const* preset);
 
     // ***** State Updaters *****
     
--- a/Source/Mappings/TouchkeyBaseMappingFactory.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/Mappings/TouchkeyBaseMappingFactory.h	Thu Mar 20 23:18:41 2014 +0000
@@ -228,6 +228,18 @@
         activeNotes_ = notes;
     }
     
+    // ****** Preset Save/Load ******
+    
+    // These generate XML settings files and reload settings from them
+    
+    virtual XmlElement* getPreset() {
+        XmlElement* presetElement = new XmlElement("MappingFactory");
+        presetElement->setAttribute("type", "Unknown");
+        return presetElement;
+    }
+    
+    virtual bool loadPreset(XmlElement const* preset) { return true; }
+    
     // ***** State Updaters *****
     
     // These are called by PianoKey whenever certain events occur that might
--- a/Source/TouchKeys/MidiInputController.cpp	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/TouchKeys/MidiInputController.cpp	Thu Mar 20 23:18:41 2014 +0000
@@ -27,7 +27,7 @@
 #include "MidiOutputController.h"
 #include "../Mappings/MappingFactory.h"
 
-#define DEBUG_MIDI_INPUT_CONTROLLER
+#undef DEBUG_MIDI_INPUT_CONTROLLER
 #undef MIDI_INPUT_CONTROLLER_DEBUG_RAW
 
 // Constructor
@@ -350,6 +350,55 @@
         segments_[i]->allNotesOff();
 }
 
+// Return the current preset as an XmlElement, suitable for
+// saving to file. This element will need to be deleted when finished
+
+XmlElement* MidiInputController::getSegmentPreset() {
+    ScopedLock sl(segmentsMutex_);
+    XmlElement* controllerElement = new XmlElement("KeyboardSegments");
+    
+    // Add settings for each zone to the element
+    for(int i = 0; i < segments_.size(); i++) {
+        XmlElement* segmentElement = segments_[i]->getPreset();
+        controllerElement->addChildElement(segmentElement);
+    }
+    
+    // Return the element
+    return controllerElement;
+}
+
+// Load keyboard segments from a preset; returns true on success
+
+bool MidiInputController::loadSegmentPreset(XmlElement const* preset) {
+    ScopedLock sl(segmentsMutex_);
+
+    for(int i = 0; i < segments_.size(); i++)
+        delete segments_[i];
+    segments_.clear();
+    
+    XmlElement *element = preset->getChildByName("Segment");
+    while(element != 0) {
+        // Create a new segment and populate its values
+        MidiKeyboardSegment *segment = new MidiKeyboardSegment(keyboard_);
+        segment->setMidiOutputController(midiOutputController_);
+        
+        // Load settings for this particular segment
+        if(!segment->loadPreset(element)) {
+            delete segment;
+            for(int i = 0; i < segments_.size(); i++)
+                delete segments_[i];
+            segments_.clear();
+            return false;
+        }
+    
+        segments_.push_back(segment);
+        element = element->getNextElementWithTagName("Segment");
+    }
+    
+    segmentUniqueIdentifier_++;
+    return true;
+}
+
 // 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	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/TouchKeys/MidiInputController.h	Thu Mar 20 23:18:41 2014 +0000
@@ -113,6 +113,10 @@
     void removeSegment(MidiKeyboardSegment* segment);
     void removeAllSegments();
     
+    // Preset save/load for keyboard segments
+    XmlElement* getSegmentPreset();
+    bool loadSegmentPreset(XmlElement const* preset);
+    
     // Juce MIDI callbacks
     void handleIncomingMidiMessage(MidiInput* source, const MidiMessage& message);
     void handlePartialSysexMessage(MidiInput* source,
--- a/Source/TouchKeys/MidiKeyboardSegment.cpp	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/TouchKeys/MidiKeyboardSegment.cpp	Thu Mar 20 23:18:41 2014 +0000
@@ -27,6 +27,13 @@
 #include "MidiKeyboardSegment.h"
 #include "MidiOutputController.h"
 #include "../Mappings/MappingFactory.h"
+#include "../Mappings/Vibrato/TouchkeyVibratoMappingFactory.h"
+#include "../Mappings/PitchBend/TouchkeyPitchBendMappingFactory.h"
+#include "../Mappings/Control/TouchkeyControlMappingFactory.h"
+#include "../Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h"
+#include "../Mappings/OnsetAngle/TouchkeyOnsetAngleMappingFactory.h"
+#include "../Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMappingFactory.h"
+#include "../Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h"
 #include "OscMidiConverter.h"
 #include <algorithm>
 #include <string>
@@ -533,6 +540,150 @@
     return mappingFactories_;
 }
 
+// Get an XML element describing current settings (for saving presets)
+// This element will need to be released (or added to another XML element
+// that is released) by the caller
+XmlElement* MidiKeyboardSegment::getPreset() {
+    XmlElement* segmentElement = new XmlElement("Segment");
+
+    // Add segment settings
+    PropertySet properties;
+    properties.setValue("outputPort", outputPortNumber_);
+    properties.setValue("mode", mode_);
+    properties.setValue("channelMask", (int)channelMask_);
+    properties.setValue("noteMin", noteMin_);
+    properties.setValue("noteMax", noteMax_);
+    properties.setValue("outputChannelLowest", outputChannelLowest_);
+    properties.setValue("outputTransposition", outputTransposition_);
+    properties.setValue("damperPedalEnabled", damperPedalEnabled_);
+    properties.setValue("touchkeysStandaloneMode", touchkeyStandaloneMode_);
+    properties.setValue("usesKeyboardChannelPressure", usesKeyboardChannelPressure_);
+    properties.setValue("usesKeyboardPitchWheel", usesKeyboardPitchWheel_);
+    properties.setValue("usesKeyboardModWheel", usesKeyboardModWheel_);
+    properties.setValue("usesKeyboardMidiControllers", usesKeyboardMidiControllers_);
+    properties.setValue("pitchWheelRange", pitchWheelRange_);
+    properties.setValue("retransmitMaxPolyphony", retransmitMaxPolyphony_);
+    properties.setValue("useVoiceStealing", useVoiceStealing_);
+
+    segmentElement->addChildElement(properties.createXml("Properties"));
+    
+    // Go through mapping factories and add their settings
+    vector<MappingFactory*>::iterator it;
+    for(it = mappingFactories_.begin(); it != mappingFactories_.end(); ++it) {
+        XmlElement* factoryElement = (*it)->getPreset();
+        segmentElement->addChildElement(factoryElement);
+    }
+    
+    return segmentElement;
+}
+
+// Load settings from an XML element
+bool MidiKeyboardSegment::loadPreset(XmlElement const* preset) {
+    removeAllMappingFactories();
+    
+    XmlElement *propertiesElement = preset->getChildByName("Properties");
+    if(propertiesElement == 0)
+        return false;
+    
+    // Load segment settings
+    PropertySet properties;
+    properties.restoreFromXml(*propertiesElement);
+    
+    if(!properties.containsKey("outputPort"))
+        return false;
+    outputPortNumber_ = properties.getIntValue("outputPort");
+    if(!properties.containsKey("mode"))
+        return false;
+    mode_ = properties.getIntValue("mode");
+    if(!properties.containsKey("channelMask"))
+        return false;
+    channelMask_ = properties.getIntValue("channelMask");
+    if(!properties.containsKey("noteMin"))
+        return false;
+    noteMin_ = properties.getIntValue("noteMin");
+    if(!properties.containsKey("noteMax"))
+        return false;
+    noteMax_ = properties.getIntValue("noteMax");
+    if(!properties.containsKey("outputChannelLowest"))
+        return false;
+    outputChannelLowest_ = properties.getIntValue("outputChannelLowest");
+    if(!properties.containsKey("outputTransposition"))
+        return false;
+    outputTransposition_ = properties.getIntValue("outputTransposition");
+    if(!properties.containsKey("damperPedalEnabled"))
+        return false;
+    damperPedalEnabled_ = properties.getBoolValue("damperPedalEnabled");
+    if(!properties.containsKey("touchkeysStandaloneMode"))
+        return false;
+    touchkeyStandaloneMode_ = properties.getBoolValue("touchkeysStandaloneMode");
+    if(!properties.containsKey("usesKeyboardChannelPressure"))
+        return false;
+    usesKeyboardChannelPressure_ = properties.getBoolValue("usesKeyboardChannelPressure");
+    if(!properties.containsKey("usesKeyboardPitchWheel"))
+        return false;
+    usesKeyboardPitchWheel_ = properties.getBoolValue("usesKeyboardPitchWheel");
+    if(!properties.containsKey("usesKeyboardModWheel"))
+        return false;
+    usesKeyboardModWheel_ = properties.getBoolValue("usesKeyboardModWheel");
+    if(!properties.containsKey("usesKeyboardMidiControllers"))
+        return false;
+    usesKeyboardMidiControllers_ = properties.getBoolValue("usesKeyboardMidiControllers");
+    if(!properties.containsKey("pitchWheelRange"))
+        return false;
+    pitchWheelRange_ = properties.getDoubleValue("pitchWheelRange");
+    if(!properties.containsKey("retransmitMaxPolyphony"))
+        return false;
+    retransmitMaxPolyphony_ = properties.getIntValue("retransmitMaxPolyphony");
+    if(!properties.containsKey("useVoiceStealing"))
+        return false;
+    useVoiceStealing_ = properties.getBoolValue("useVoiceStealing");
+    
+    // Load each mapping factory
+    XmlElement *element = preset->getChildByName("MappingFactory");
+    
+    while(element != 0) {
+        if(!element->hasAttribute("type"))
+            return false;
+        
+        // Create a new factory whose type depends on the XML tag
+        MappingFactory *factory;
+        String const& factoryType = element->getStringAttribute("type");
+        
+        if(factoryType == "Control")
+            factory = new TouchkeyControlMappingFactory(keyboard_, *this);
+        else if(factoryType == "Vibrato")
+            factory = new TouchkeyVibratoMappingFactory(keyboard_, *this);
+        else if(factoryType == "PitchBend")
+            factory = new TouchkeyPitchBendMappingFactory(keyboard_, *this);
+        else if(factoryType == "KeyDivision")
+            factory = new TouchkeyKeyDivisionMappingFactory(keyboard_, *this);
+        else if(factoryType == "MultiFingerTrigger")
+            factory = new TouchkeyMultiFingerTriggerMappingFactory(keyboard_, *this);
+        else if(factoryType == "OnsetAngle")
+            factory = new TouchkeyOnsetAngleMappingFactory(keyboard_, *this);
+        else if(factoryType == "ReleaseAngle")
+            factory = new TouchkeyReleaseAngleMappingFactory(keyboard_, *this);
+        else {
+            // Type unknown or unsupported; ignore and continue
+            element = element->getNextElementWithTagName("MappingFactory");
+            continue;
+        }
+        
+        // Tell factory to load its settings from this element
+        if(!factory->loadPreset(element)) {
+            delete factory;
+            return false;
+        }
+        
+        // Add factory; don't autogenerate name as it will be saved
+        addMappingFactory(factory, false);
+        
+        element = element->getNextElementWithTagName("MappingFactory");
+    }
+    
+    return true;
+}
+
 // Mode-specific MIDI handlers.  These methods handle incoming MIDI data according to the rules
 // defined by a particular mode of operation.
 
--- a/Source/TouchKeys/MidiKeyboardSegment.h	Thu Mar 20 00:18:35 2014 +0000
+++ b/Source/TouchKeys/MidiKeyboardSegment.h	Thu Mar 20 23:18:41 2014 +0000
@@ -228,6 +228,14 @@
     
     // 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)
+    XmlElement* getPreset();
+    
+    // Load settings from an XML element
+    bool loadPreset(XmlElement const* preset);
 
 private:
 	// Mode-specific MIDI input handlers