gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

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:
Msource/jucePluginEditorLib/patchmanager/patchmanager.cpp | 11++++++-----
Msource/jucePluginEditorLib/patchmanager/patchmanager.h | 2+-
Msource/jucePluginEditorLib/pluginEditor.cpp | 17+++--------------
Msource/jucePluginLib/clipboard.cpp | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/jucePluginLib/clipboard.h | 28++++++++++++++++++++++++++++
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); }; }