Mercurial > hg > touchkeys
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