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 26b413633aef8727dcef1a3f1ccd5121d44e28f6
parent ce7cc01b895f1fedbcc06d0d76d8d3fdb9d49778
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Tue, 21 May 2024 01:00:04 +0200

support copy/paste of patches via ctrl+c/v in text mode for easy sharing in forums etc

Diffstat:
Msource/jucePluginEditorLib/patchmanager/datasourcetreeitem.cpp | 14++++++++++++++
Msource/jucePluginEditorLib/patchmanager/patchmanager.cpp | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/jucePluginEditorLib/patchmanager/patchmanager.h | 6++++++
Msource/jucePluginEditorLib/pluginEditor.cpp | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/jucePluginEditorLib/pluginEditor.h | 5+++++
Msource/jucePluginEditorLib/pluginEditorState.cpp | 41+++++++++++++++++++++++++++++++++++++++--
6 files changed, 230 insertions(+), 2 deletions(-)

diff --git a/source/jucePluginEditorLib/patchmanager/datasourcetreeitem.cpp b/source/jucePluginEditorLib/patchmanager/datasourcetreeitem.cpp @@ -161,6 +161,20 @@ namespace jucePluginEditorLib::patchManager } })); } + + if(m_dataSource->type == pluginLib::patchDB::SourceType::LocalStorage) + { + const auto clipboardPatches = getPatchManager().getPatchesFromClipboard(); + + if(!clipboardPatches.empty()) + { + menu.addSeparator(); + menu.addItem("Paste from Clipboard", [this, clipboardPatches] + { + getPatchManager().copyPatchesToLocalStorage(m_dataSource, clipboardPatches, -1); + }); + } + } menu.showMenuAsync({}); } diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.cpp b/source/jucePluginEditorLib/patchmanager/patchmanager.cpp @@ -14,6 +14,10 @@ #include "../../jucePluginLib/types.h" +#include "../../synthLib/os.h" + +#include "dsp56kEmu/logging.h" + #include "juce_gui_extra/misc/juce_ColourSelector.h" namespace jucePluginEditorLib::patchManager @@ -872,4 +876,114 @@ namespace jucePluginEditorLib::patchManager _item->getOwnerView()->scrollToKeepItemVisible(_item); } + + std::vector<pluginLib::patchDB::PatchPtr> PatchManager::getPatchesFromString(const std::string& _text) + { + if(_text.empty()) + return {}; + + auto text = synthLib::lowercase(_text); + + while(true) + { + const auto pos = text.find_first_of(" \n\r\t"); + if(pos == std::string::npos) + break; + text = text.substr(0,pos) + text.substr(pos+1); + } + + const auto posF0 = text.find("f0"); + if(posF0 == std::string::npos) + return {}; + + const auto posF7 = text.rfind("f7"); + if(posF7 == std::string::npos) + return {}; + + if(posF7 <= posF0) + return {}; + + const auto dataString = text.substr(posF0, posF7 + 2 - posF0); + + if(dataString.size() & 1) + return {}; + + std::vector<uint8_t> data; + data.reserve(dataString.size()>>1); + + for(size_t i=0; i<dataString.size(); i+=2) + { + char temp[3]{0,0,0}; + temp[0] = dataString[i]; + temp[1] = dataString[i+1]; + + const auto c = strtoul(temp, nullptr, 16); + if(c < 0 || c > 255) + return {}; + data.push_back(static_cast<uint8_t>(c)); + } + + pluginLib::patchDB::DataList results; + parseFileData(results, data); + + if(results.empty()) + return {}; + + std::vector<pluginLib::patchDB::PatchPtr> patches; + + for (auto& result : results) + { + if(const auto patch = initializePatch(std::move(result))) + patches.push_back(patch); + } + + return patches; + } + + std::vector<pluginLib::patchDB::PatchPtr> PatchManager::getPatchesFromClipboard() + { + return getPatchesFromString(juce::SystemClipboard::getTextFromClipboard().toStdString()); + } + + bool PatchManager::activatePatchFromString(const std::string& _text) + { + const auto patches = getPatchesFromString(_text); + + if(patches.size() != 1) + return false; + + return activatePatch(patches.front(), getCurrentPart()); + } + + bool PatchManager::activatePatchFromClipboard() + { + return activatePatchFromString(juce::SystemClipboard::getTextFromClipboard().toStdString()); + } + + std::string PatchManager::toString(const pluginLib::patchDB::PatchPtr& _patch, const uint32_t _bytesPerLine/* = 32*/) const + { + if(!_patch) + return {}; + + const auto data = prepareSave(_patch); + + if(data.empty()) + return {}; + + std::stringstream ss; + + for(size_t i=0; i<data.size();) + { + if(i) + ss << '\n'; + for(size_t j=0; j<_bytesPerLine && i<data.size(); ++j, ++i) + { + if(j) + ss << ' '; + ss << HEXN(static_cast<uint32_t>(data[i]), 2); + } + } + + return ss.str(); + } } diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.h b/source/jucePluginEditorLib/patchmanager/patchmanager.h @@ -85,6 +85,12 @@ namespace jucePluginEditorLib::patchManager std::string getTagTypeName(pluginLib::patchDB::TagType _type) const; void setTagTypeName(pluginLib::patchDB::TagType _type, const std::string& _name); + std::vector<pluginLib::patchDB::PatchPtr> getPatchesFromString(const std::string& _text); + 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; + private: bool selectPatch(uint32_t _part, int _offset); diff --git a/source/jucePluginEditorLib/pluginEditor.cpp b/source/jucePluginEditorLib/pluginEditor.cpp @@ -227,6 +227,58 @@ namespace jucePluginEditorLib return true; } + void Editor::copyCurrentPatchToClipboard() const + { + // copy patch of current part to Clipboard + const auto p = m_patchManager->requestPatchForPart(m_patchManager->getCurrentPart()); + + if(!p) + return; + + const auto patchAsString = m_patchManager->toString(p); + + if(patchAsString.empty()) + return; + + const auto time = juce::Time::getCurrentTime(); + + std::stringstream ss; + ss << getProcessor().getProperties().name << " - 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()); + } + + bool Editor::replaceCurrentPatchFromClipboard() const + { + return m_patchManager->activatePatchFromClipboard(); + } + + bool Editor::keyPressed(const juce::KeyPress& _key) + { + if(_key.getModifiers().isCommandDown()) + { + switch(_key.getKeyCode()) + { + case 'c': + case 'C': + copyCurrentPatchToClipboard(); + return true; + case 'v': + case 'V': + if(replaceCurrentPatchFromClipboard()) + return true; + break; + default: + return genericUI::Editor::keyPressed(_key); + } + } + return genericUI::Editor::keyPressed(_key); + } + void Editor::onDisclaimerFinished() const { if(!synthLib::isRunningUnderRosetta()) diff --git a/source/jucePluginEditorLib/pluginEditor.h b/source/jucePluginEditorLib/pluginEditor.h @@ -62,7 +62,12 @@ namespace jucePluginEditorLib bool shouldDropFilesWhenDraggedExternally(const juce::DragAndDropTarget::SourceDetails& sourceDetails, juce::StringArray& files, bool& canMoveFiles) override; + void copyCurrentPatchToClipboard() const; + bool replaceCurrentPatchFromClipboard() const; + private: + bool keyPressed(const juce::KeyPress& _key) override; + void onDisclaimerFinished() const; const char* getResourceByFilename(const std::string& _name, uint32_t& _dataSize) override; diff --git a/source/jucePluginEditorLib/pluginEditorState.cpp b/source/jucePluginEditorLib/pluginEditorState.cpp @@ -1,11 +1,15 @@ #include "pluginEditorState.h" +#include "pluginEditor.h" #include "pluginProcessor.h" -#include "../synthLib/os.h" -#include "dsp56kEmu/logging.h" +#include "patchmanager/patchmanager.h" + +#include "../synthLib/os.h" #include "../juceUiLib/editor.h" +#include "dsp56kEmu/logging.h" + namespace jucePluginEditorLib { PluginEditorState::PluginEditorState(Processor& _processor, pluginLib::Controller& _controller, std::vector<Skin> _includedSkins) @@ -322,6 +326,39 @@ void PluginEditorState::openMenu() menu.addSubMenu("Panic", panicMenu); } + if(auto* editor = dynamic_cast<Editor*>(getEditor())) + { + menu.addSeparator(); + +#ifdef JUCE_MAC + const std::string ctrlName = "Cmd"; +#else + const std::string ctrlName = "Ctrl"; +#endif + + { + juce::PopupMenu::Item item("Copy current Patch to Clipboard"); + item.shortcutKeyDescription = ctrlName + "+C"; + item.action = [editor] + { + editor->copyCurrentPatchToClipboard(); + }; + menu.addItem(item); + } + + auto patches = editor->getPatchManager()->getPatchesFromClipboard(); + if(!patches.empty()) + { + juce::PopupMenu::Item item("Replace current Patch from Clipboard"); + item.shortcutKeyDescription = ctrlName + "+V"; + item.action = [editor] + { + editor->replaceCurrentPatchFromClipboard(); + }; + menu.addItem(item); + } + } + { const auto allowAdvanced = config.getBoolValue("allow_advanced_options", false);