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 63f3923b8553e8e1831cefb0ee18d590dcaa1df8
parent 5f852def9906f9cdf8fbb91adbf87c0eb6b2fb77
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Mon, 20 May 2024 11:40:17 +0200

support drag & drop of patches to external applications, will export midi files

Diffstat:
Msource/jucePluginEditorLib/patchmanager/savepatchdesc.cpp | 29+++++++++++++++++++++++++++++
Msource/jucePluginEditorLib/patchmanager/savepatchdesc.h | 2++
Msource/jucePluginEditorLib/pluginEditor.cpp | 48+++++++++++++++++++++++++++++++++++++++++++++++-
Msource/jucePluginEditorLib/pluginEditor.h | 4++++
Msource/jucePluginLib/patchdb/db.cpp | 32+++++++++++++++-----------------
Msource/jucePluginLib/patchdb/db.h | 2++
Msource/synthLib/sysexToMidi.cpp | 2++
7 files changed, 101 insertions(+), 18 deletions(-)

diff --git a/source/jucePluginEditorLib/patchmanager/savepatchdesc.cpp b/source/jucePluginEditorLib/patchmanager/savepatchdesc.cpp @@ -18,6 +18,35 @@ namespace jucePluginEditorLib::patchManager return m_patches; } + bool SavePatchDesc::writePatchesToFile(const juce::File& _file) const + { + const auto& patches = getPatches(); + + if(patches.empty()) + return false; + + std::vector<std::vector<uint8_t>> patchesData; + + for (auto& patch : patches) + { + auto data = m_patchManager.prepareSave(patch.second); + if(data.empty()) + return false; + patchesData.emplace_back(std::move(data)); + } + + std::stringstream ss; + + synthLib::SysexToMidi::write(ss, patchesData); + + const auto size = ss.tellp(); + std::vector<char> buffer; + buffer.resize(size); + ss.read(buffer.data(), size); + + return _file.replaceWithData(buffer.data(), size); + } + std::vector<pluginLib::patchDB::PatchPtr> SavePatchDesc::getPatchesFromDragSource(const juce::DragAndDropTarget::SourceDetails& _dragSourceDetails) { const auto* savePatchDesc = fromDragSource(_dragSourceDetails); diff --git a/source/jucePluginEditorLib/patchmanager/savepatchdesc.h b/source/jucePluginEditorLib/patchmanager/savepatchdesc.h @@ -29,6 +29,8 @@ namespace jucePluginEditorLib::patchManager bool isPartValid() const { return m_part != InvalidPart; } bool hasPatches() const { return !getPatches().empty(); } + bool writePatchesToFile(const juce::File& _file) const; + static const SavePatchDesc* fromDragSource(const juce::DragAndDropTarget::SourceDetails& _source) { return dynamic_cast<const SavePatchDesc*>(_source.description.getObject()); diff --git a/source/jucePluginEditorLib/pluginEditor.cpp b/source/jucePluginEditorLib/pluginEditor.cpp @@ -8,6 +8,7 @@ #include "../synthLib/sysexToMidi.h" #include "patchmanager/patchmanager.h" +#include "patchmanager/savepatchdesc.h" namespace jucePluginEditorLib { @@ -20,7 +21,11 @@ namespace jucePluginEditorLib showDisclaimer(); } - Editor::~Editor() = default; + Editor::~Editor() + { + for (const auto& file : m_dragAndDropFiles) + file.deleteFile(); + } void Editor::loadPreset(const std::function<void(const juce::File&)>& _callback) { @@ -189,6 +194,47 @@ namespace jucePluginEditorLib } } + bool Editor::shouldDropFilesWhenDraggedExternally(const juce::DragAndDropTarget::SourceDetails& sourceDetails, juce::StringArray& files, bool& canMoveFiles) + { + const auto* savePatchDesc = patchManager::SavePatchDesc::fromDragSource(sourceDetails); + + if(!savePatchDesc || !savePatchDesc->hasPatches()) + return false; + + // try to create human-readable filename first + const auto patch = savePatchDesc->getPatches().begin()->second; + + auto patchFileName = patch->getName(); + + while(!patchFileName.empty() && patchFileName.back() == ' ') + patchFileName.pop_back(); + + patchFileName = m_processor.getProperties().name + "_" + patchManager::PatchManager::createValidFilename(patchFileName) + ".mid"; + + const auto pathName = juce::File::getSpecialLocation(juce::File::tempDirectory).getFullPathName().toStdString() + "/" + patchFileName; + + auto file = juce::File(pathName); + + if(file.hasWriteAccess()) + { + m_dragAndDropFiles.emplace_back(file); + } + else + { + // failed, create temp file + const auto& tempFile = m_dragAndDropTempFiles.emplace_back(std::make_shared<juce::TemporaryFile>(patchFileName)); + file = tempFile->getFile(); + } + + if(!savePatchDesc->writePatchesToFile(file)) + return false; + + files.add(file.getFullPathName()); + + canMoveFiles = true; + return true; + } + void Editor::onDisclaimerFinished() const { if(!synthLib::isRunningUnderRosetta()) diff --git a/source/jucePluginEditorLib/pluginEditor.h b/source/jucePluginEditorLib/pluginEditor.h @@ -60,6 +60,8 @@ namespace jucePluginEditorLib void showDisclaimer() const; + bool shouldDropFilesWhenDraggedExternally(const juce::DragAndDropTarget::SourceDetails& sourceDetails, juce::StringArray& files, bool& canMoveFiles) override; + private: void onDisclaimerFinished() const; @@ -80,5 +82,7 @@ namespace jucePluginEditorLib std::unique_ptr<juce::FileChooser> m_fileChooser; std::unique_ptr<patchManager::PatchManager> m_patchManager; std::vector<uint8_t> m_instanceConfig; + std::vector<std::shared_ptr<juce::TemporaryFile>> m_dragAndDropTempFiles; + std::vector<juce::File> m_dragAndDropFiles; }; } diff --git a/source/jucePluginLib/patchdb/db.cpp b/source/jucePluginLib/patchdb/db.cpp @@ -17,23 +17,6 @@ namespace pluginLib::patchDB { static constexpr bool g_cacheEnabled = true; - namespace - { - std::string createValidFilename(const std::string& _name) - { - std::string result; - result.reserve(_name.size()); - - for (const char c : _name) - { - if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) - result += c; - else - result += '_'; - } - return result; - } - } DB::DB(juce::File _dir) : m_settingsDir(std::move(_dir)) , m_jsonFileName(m_settingsDir.getChildFile("patchmanagerdb.json")) @@ -91,6 +74,21 @@ namespace pluginLib::patchDB _mods->updateCache(); } + std::string DB::createValidFilename(const std::string& _name) + { + std::string result; + result.reserve(_name.size()); + + for (const char c : _name) + { + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + result += c; + else + result += '_'; + } + return result; + } + DataSourceNodePtr DB::addDataSource(const DataSource& _ds, const bool _save, const DataSourceLoadedCallback& _callback) { const auto needsSave = _save && _ds.origin == DataSourceOrigin::Manual && _ds.type != SourceType::Rom; diff --git a/source/jucePluginLib/patchdb/db.h b/source/jucePluginLib/patchdb/db.h @@ -82,6 +82,8 @@ namespace pluginLib::patchDB static void assign(const PatchPtr& _patch, const PatchModificationsPtr& _mods); + static std::string createValidFilename(const std::string& _name); + protected: DataSourceNodePtr addDataSource(const DataSource& _ds, bool _save, const DataSourceLoadedCallback& = [](bool , std::shared_ptr<DataSourceNode>) {}); diff --git a/source/synthLib/sysexToMidi.cpp b/source/synthLib/sysexToMidi.cpp @@ -48,8 +48,10 @@ namespace synthLib writeBuf(_dst, {0xff,0x2f,0x00}); // end of track + const auto end = _dst.tellp(); _dst.seekp(trackChunkBegin); writeUInt32(_dst, static_cast<uint32_t>(trackChunkLength)); + _dst.seekp(end); } void SysexToMidi::writeBuf(std::ostream& _dst, const std::vector<uint8_t>& _data)