commit 4b1da5c976c9c2c97af72f9b69e58e3b1c03344c
parent 038bc5719df9ad1947ca7e2fab926678aa7dcf8f
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Thu, 30 May 2024 15:33:40 +0200
create new clipboard format for patches that also supports individual parameters, use it in patch manager to copy complete patches
Diffstat:
5 files changed, 181 insertions(+), 20 deletions(-)
diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.cpp b/source/jucePluginEditorLib/patchmanager/patchmanager.cpp
@@ -11,6 +11,7 @@
#include "tree.h"
#include "../pluginEditor.h"
+#include "../pluginProcessor.h"
#include "../../jucePluginLib/types.h"
#include "../../jucePluginLib/clipboard.h"
@@ -880,14 +881,14 @@ namespace jucePluginEditorLib::patchManager
std::vector<pluginLib::patchDB::PatchPtr> PatchManager::getPatchesFromString(const std::string& _text)
{
- pluginLib::patchDB::DataList results = pluginLib::Clipboard::getSysexFromString(_text);
+ auto data = pluginLib::Clipboard::getDataFromString(m_editor.getProcessor(), _text);
- if(results.empty())
+ if(data.sysex.empty())
return {};
std::vector<pluginLib::patchDB::PatchPtr> patches;
- for (auto& result : results)
+ for (auto& result : data.sysex)
{
if(const auto patch = initializePatch(std::move(result)))
patches.push_back(patch);
@@ -916,13 +917,13 @@ namespace jucePluginEditorLib::patchManager
return activatePatchFromString(juce::SystemClipboard::getTextFromClipboard().toStdString());
}
- std::string PatchManager::toString(const pluginLib::patchDB::PatchPtr& _patch, const uint32_t _bytesPerLine/* = 32*/) const
+ std::string PatchManager::toString(const pluginLib::patchDB::PatchPtr& _patch) const
{
if(!_patch)
return {};
const auto data = prepareSave(_patch);
- return pluginLib::Clipboard::midiDataToString(data, _bytesPerLine);
+ return pluginLib::Clipboard::createJsonString(m_editor.getProcessor(), {}, {}, data);
}
}
diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.h b/source/jucePluginEditorLib/patchmanager/patchmanager.h
@@ -89,7 +89,7 @@ namespace jucePluginEditorLib::patchManager
std::vector<pluginLib::patchDB::PatchPtr> getPatchesFromClipboard();
bool activatePatchFromString(const std::string& _text);
bool activatePatchFromClipboard();
- std::string toString(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _bytesPerLine = 32) const;
+ std::string toString(const pluginLib::patchDB::PatchPtr& _patch) const;
private:
bool selectPatch(uint32_t _part, int _offset);
diff --git a/source/jucePluginEditorLib/pluginEditor.cpp b/source/jucePluginEditorLib/pluginEditor.cpp
@@ -3,7 +3,7 @@
#include "pluginProcessor.h"
#include "../jucePluginLib/parameterbinding.h"
-#include "../jucePluginLib/pluginVersion.h"
+#include "../jucePluginLib/clipboard.h"
#include "../synthLib/os.h"
#include "../synthLib/sysexToMidi.h"
@@ -238,19 +238,8 @@ namespace jucePluginEditorLib
const auto patchAsString = m_patchManager->toString(p);
- if(patchAsString.empty())
- return;
-
- const auto time = juce::Time::getCurrentTime();
-
- std::stringstream ss;
- ss << getProcessor().getProperties().name << " " << pluginLib::Version::getVersionString() << " - Patch copied at " << time.formatted("%Y.%m.%d %H:%M") << time.getUTCOffsetString(true);
- ss << '\n';
- ss << "Patch '" << p->getName() << "' data:\n";
- ss << "```\n";
- ss << patchAsString << '\n';
- ss << "```";
- juce::SystemClipboard::copyTextToClipboard(ss.str());
+ if(!patchAsString.empty())
+ juce::SystemClipboard::copyTextToClipboard(patchAsString);
}
bool Editor::replaceCurrentPatchFromClipboard() const
diff --git a/source/jucePluginLib/clipboard.cpp b/source/jucePluginLib/clipboard.cpp
@@ -5,8 +5,12 @@
#include <sstream>
+#include "pluginVersion.h"
+#include "processor.h"
#include "dsp56kEmu/logging.h"
+#include "juce_core/juce_core.h"
+
namespace pluginLib
{
std::string Clipboard::midiDataToString(const std::vector<uint8_t>& _data, const uint32_t _bytesPerLine/* = 32*/)
@@ -82,4 +86,143 @@ namespace pluginLib
return results;
}
+
+ std::string Clipboard::parametersToString(Processor& _processor, const std::vector<std::string>& _parameters, const std::string& _regionId)
+ {
+ if(_parameters.empty())
+ return {};
+
+ return createJsonString(_processor, _parameters, _regionId, {});
+ }
+
+ std::string Clipboard::createJsonString(Processor& _processor, const std::vector<std::string>& _parameters, const std::string& _regionId, const std::vector<uint8_t>& _sysex)
+ {
+ if(_parameters.empty() && _sysex.empty())
+ return {};
+
+ const auto json = juce::ReferenceCountedObjectPtr<juce::DynamicObject>(new juce::DynamicObject());
+
+ json->setProperty("plugin", juce::String(_processor.getProperties().name));
+ json->setProperty("pluginVersion", juce::String(Version::getVersionString()));
+ json->setProperty("pluginVersionNumber", juce::String(Version::getVersionNumber()));
+
+ json->setProperty("formatVersion", 1);
+
+ if(!_regionId.empty())
+ json->setProperty("region", juce::String(_regionId));
+
+ const auto& c = _processor.getController();
+ const auto part = c.getCurrentPart();
+
+ if(!_parameters.empty())
+ {
+ const auto params = juce::ReferenceCountedObjectPtr<juce::DynamicObject>(new juce::DynamicObject());
+
+ for (const auto& param : _parameters)
+ {
+ const auto paramIdx = c.getParameterIndexByName(param);
+ const auto* p = c.getParameter(paramIdx, part);
+
+ if(!p)
+ continue;
+
+ params->setProperty(juce::String(p->getDescription().name), p->getUnnormalizedValue());
+ }
+
+ json->setProperty("parameters", params.get());
+ }
+
+ if(!_sysex.empty())
+ json->setProperty("sysex", juce::String(midiDataToString(_sysex, static_cast<uint32_t>(_sysex.size()))));
+
+ const auto result = juce::JSON::toString(json.get());
+
+ return result.toStdString();
+ }
+
+ Clipboard::Data Clipboard::getDataFromString(Processor& _processor, const std::string& _text)
+ {
+ if(_text.empty())
+ return {};
+
+ const auto json = juce::JSON::parse(juce::String(_text));
+
+ Data data;
+
+ auto parseRawMidi = [&]()
+ {
+ data.sysex = getSysexFromString(_text);
+ return data;
+ };
+
+ data.pluginName = json["plugin"].toString().toStdString();
+
+ // for this plugin or another plugin?
+ if(data.pluginName != _processor.getProperties().name)
+ return parseRawMidi();
+
+ data.pluginVersionNumber = static_cast<int>(json["pluginVersionNumber"]);
+
+ // version cannot be lower than when we first added the feature
+ if(data.pluginVersionNumber < 10315)
+ return parseRawMidi();
+
+ data.formatVersion = static_cast<int>(json["formatVersion"]);
+
+ // we only support version 1 atm
+ if(data.formatVersion != 1)
+ return parseRawMidi();
+
+ data.pluginVersionString = json["pluginVersion"].toString().toStdString();
+
+ data.parameterRegion = json["region"].toString().toStdString();
+
+ const auto* params = json["parameters"].getDynamicObject();
+
+ if(params)
+ {
+ const auto& c = _processor.getController();
+
+ const auto& props = params->getProperties();
+ for (const auto& it : props)
+ {
+ const auto name = it.name.toString().toStdString();
+ const int value = it.value;
+
+ // something is very wrong if a parameter exists twice
+ if(data.parameterValues.find(name) != data.parameterValues.end())
+ return parseRawMidi();
+
+ // also if a parameter value is out of range
+ if(value < 0 || value > 127)
+ return parseRawMidi();
+
+ // gracefully ignore parameters that we are not aware of. Parameters might change in the future or whatever
+ if(c.getParameterIndexByName(name) == Controller::InvalidParameterIndex)
+ continue;
+
+ data.parameterValues.insert(std::make_pair(name, static_cast<uint8_t>(value)));
+ }
+
+ if(!data.parameterValues.empty())
+ {
+ for (const auto& it : data.parameterValues)
+ {
+ const auto& paramName = it.first;
+
+ const auto regionIds = c.getRegionIdsForParameter(paramName);
+
+ for (const auto& regionId : regionIds)
+ data.parameterValuesByRegion[regionId].insert(it);
+ }
+ }
+ }
+
+ const auto sysex = json["sysex"].toString().toStdString();
+
+ if(!sysex.empty())
+ data.sysex = getSysexFromString(sysex);
+
+ return data;
+ }
}
diff --git a/source/jucePluginLib/clipboard.h b/source/jucePluginLib/clipboard.h
@@ -1,15 +1,43 @@
#pragma once
#include <cstdint>
+#include <map>
#include <string>
#include <vector>
namespace pluginLib
{
+ class Processor;
+
class Clipboard
{
public:
+ struct Data
+ {
+ using ParameterValues = std::map<std::string,uint8_t>;
+
+ std::string pluginName;
+ std::string pluginVersionString;
+ uint32_t pluginVersionNumber;
+ uint32_t formatVersion;
+
+ std::string parameterRegion;
+
+ ParameterValues parameterValues;
+ std::map<std::string,ParameterValues> parameterValuesByRegion;
+
+ std::vector<std::vector<uint8_t>> sysex;
+
+ bool empty() const
+ {
+ return sysex.empty() && parameterValues.empty();
+ }
+ };
+
static std::string midiDataToString(const std::vector<uint8_t>& _data, uint32_t _bytesPerLine = 32);
static std::vector<std::vector<uint8_t>> getSysexFromString(const std::string& _text);
+ static std::string parametersToString(Processor& _processor, const std::vector<std::string>& _parameters, const std::string& _regionId);
+ static std::string createJsonString(Processor& _processor, const std::vector<std::string>& _parameters, const std::string& _regionId, const std::vector<uint8_t>& _sysex);
+ static Data getDataFromString(Processor& _processor, const std::string& _text);
};
}