From 65ed4183bd90603972fe9f1358aae6440908cac9 Mon Sep 17 00:00:00 2001 From: jd-13 Date: Wed, 3 Mar 2021 23:49:18 +0000 Subject: [PATCH] Store XML parameter values with names and as internal (not normalised) values, also provide migration from previous format --- WECore/CoreJUCEPlugin/CoreAudioProcessor.h | 127 +++++++++++++-------- 1 file changed, 80 insertions(+), 47 deletions(-) diff --git a/WECore/CoreJUCEPlugin/CoreAudioProcessor.h b/WECore/CoreJUCEPlugin/CoreAudioProcessor.h index 949097e1..2e3c13c1 100644 --- a/WECore/CoreJUCEPlugin/CoreAudioProcessor.h +++ b/WECore/CoreJUCEPlugin/CoreAudioProcessor.h @@ -106,13 +106,28 @@ namespace WECore::JUCEPlugin { std::function setter); /** @} */ + /** + * Override this and return a vector of parameter names corresponding to the order that + * parameters were stored in using the legacy schema. + */ + virtual std::vector _provideParamNamesForMigration() = 0; + + /** + * Override this to migrate saved parameter values from normalised to internal. + */ + virtual void _migrateParamValues(std::vector& paramValues) = 0; + private: + // Increment this after changing how parameter states are stored + static constexpr int PARAMS_SCHEMA_VERSION {1}; + /** * Stores a setter and getter for a parameter. Used when persisting parameter values to XML * and restoring values from XML. */ struct ParameterInterface { + juce::String name; std::function getter; std::function setter; }; @@ -145,48 +160,47 @@ namespace WECore::JUCEPlugin { inline std::vector _stringToFloatVector(const juce::String sFloatCSV) const; + inline std::unique_ptr _migrateParameters( + std::unique_ptr rootElement); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CoreAudioProcessor) }; void CoreAudioProcessor::getStateInformation(juce::MemoryBlock& destData) { - // Compile the list of parameter values - std::vector paramValues; + // Build the XML + juce::XmlElement rootElement("Root"); + + // Set the XML params version + rootElement.setAttribute("SchemaVersion", PARAMS_SCHEMA_VERSION); + + // Store the parameters + juce::XmlElement* paramsElement = rootElement.createNewChildElement("Params"); for (const ParameterInterface& param : _paramsList) { - paramValues.push_back(param.getter()); + paramsElement->setAttribute(param.name, param.getter()); } - // Build the XML - juce::XmlElement root("Root"); - juce::XmlElement *el = root.createNewChildElement("AllUserParam"); - - el->addTextElement(juce::String(_floatVectorToString(paramValues))); - copyXmlToBinary(root, destData); + copyXmlToBinary(rootElement, destData); } void CoreAudioProcessor::setStateInformation(const void* data, int sizeInBytes) { - std::unique_ptr pRoot(getXmlFromBinary(data, sizeInBytes)); + std::unique_ptr rootElement(getXmlFromBinary(data, sizeInBytes)); // Parse the XML - if (pRoot != NULL) { - forEachXmlChildElement((*pRoot), pChild) { - if (pChild->hasTagName("AllUserParam")) { + if (rootElement != nullptr) { - // Read the values into a float array - juce::String sFloatCSV = pChild->getAllSubText(); - const std::vector readParamValues = _stringToFloatVector(sFloatCSV); - - // Pass each value read from XML to a setter, being careful not to go out of - // range on either of the vectors - // - // For this to work it relies the order not changing between plugin versions, - // otherwise parameter values written by an old version will be assigned to the - // wrong parameters in a new version (new parameters are ok though) - for (int idx {0}; idx < _paramsList.size(); idx++) { - if (idx < readParamValues.size()) { - _paramsList[idx].setter(readParamValues[idx]); - } + // If state was saved using an old plugin we need to migrate the XML data + if (rootElement->getIntAttribute("SchemaVersion", 0) < PARAMS_SCHEMA_VERSION) { + rootElement = _migrateParameters(std::move(rootElement)); + } + + // Iterate through our list of parameters, restoring them from the XML attributes + juce::XmlElement* paramsElement = rootElement->getChildByName("Params"); + if (paramsElement != nullptr) { + for (const ParameterInterface& param : _paramsList) { + if (paramsElement->hasAttribute(param.name)) { + param.setter(paramsElement->getDoubleAttribute(param.name)); } } } @@ -201,8 +215,9 @@ namespace WECore::JUCEPlugin { std::function setter) { param = new juce::AudioParameterFloat(name, name, {0.0f, 1.0f, precision}, range->InternalToNormalised(defaultValue)); - ParameterInterface interface = {[¶m]() { return param->get(); }, - setter}; + ParameterInterface interface = {name, + [¶m, range]() { return range->NormalisedToInternal(param->get()); }, + [setter, range](float val) { setter(range->InternalToNormalised(val)); }}; _paramsList.push_back(interface); param->addListener(&_parameterBroadcaster); @@ -216,7 +231,8 @@ namespace WECore::JUCEPlugin { std::function setter) { param = new juce::AudioParameterInt(name, name, range->minValue, range->maxValue, defaultValue); - ParameterInterface interface = {[¶m]() { return param->get(); }, + ParameterInterface interface = {name, + [¶m]() { return param->get(); }, [setter](float val) { setter(static_cast(val)); }}; _paramsList.push_back(interface); @@ -230,7 +246,8 @@ namespace WECore::JUCEPlugin { std::function setter) { param = new juce::AudioParameterBool(name, name, defaultValue); - ParameterInterface interface = {[¶m]() { return param->get(); }, + ParameterInterface interface = {name, + [¶m]() { return param->get(); }, [setter](float val) { setter(static_cast(val)); }}; _paramsList.push_back(interface); @@ -238,22 +255,6 @@ namespace WECore::JUCEPlugin { addParameter(param); } - juce::String CoreAudioProcessor::_floatVectorToString(const std::vector& fData) const { - juce::String result {""}; - - if (fData.size() < 1) { - return result; - } - - for (int iii {0}; iii < (fData.size() - 1); iii++) { - result << juce::String(fData[iii])<<","; - } - - result << juce::String(fData[fData.size() - 1]); - - return result; - } - std::vector CoreAudioProcessor::_stringToFloatVector(const juce::String sFloatCSV) const { juce::StringArray tokenizer; tokenizer.addTokens(sFloatCSV, ",",""); @@ -266,4 +267,36 @@ namespace WECore::JUCEPlugin { return values; } + + std::unique_ptr CoreAudioProcessor::_migrateParameters(std::unique_ptr rootElement) { + const int schemaVersion {rootElement->getIntAttribute("SchemaVersion", 0)}; + + std::unique_ptr retVal = std::make_unique("Root"); + + if (schemaVersion == 0) { + // This is an original parameter schema - parameters are normalised values in a single string + + forEachXmlChildElement((*rootElement), childElement) { + if (childElement->hasTagName("AllUserParam")) { + + // Read the values into a float array + juce::String sFloatCSV = childElement->getAllSubText(); + std::vector readParamValues = _stringToFloatVector(sFloatCSV); + _migrateParamValues(readParamValues); + + std::vector paramNames = _provideParamNamesForMigration(); + + juce::XmlElement* paramsElement = retVal->createNewChildElement("Params"); + + for (int idx {0}; idx < paramNames.size(); idx++) { + if (idx < readParamValues.size()) { + paramsElement->setAttribute(paramNames[idx], readParamValues[idx]); + } + } + } + } + } + + return retVal; + } }