From 85ecf77ee4ae009b716973d4d1e3f683fecebb6e Mon Sep 17 00:00:00 2001 From: jules Date: Tue, 21 Jul 2020 11:10:43 +0100 Subject: [PATCH] Work on the patch loader plugin --- .../helper_classes/soul_patch_LoaderPlugin.h | 249 +++++++++++++----- tools/plugin/Source/PatchLoaderPlugin.cpp | 86 +----- 2 files changed, 191 insertions(+), 144 deletions(-) diff --git a/include/soul/patch/helper_classes/soul_patch_LoaderPlugin.h b/include/soul/patch/helper_classes/soul_patch_LoaderPlugin.h index 867fa35..c85ce52 100644 --- a/include/soul/patch/helper_classes/soul_patch_LoaderPlugin.h +++ b/include/soul/patch/helper_classes/soul_patch_LoaderPlugin.h @@ -28,11 +28,24 @@ namespace patch This is a juce::AudioProcessor which can told to dynamically load and run different patches. The purpose is that you can build a native (VST/AU/etc) plugin with this class which can then load (and hot-reload) any SOUL patch at runtime. + + On startup, the plugin will also check in its folder for any sibling .soulpatch files, + and if it finds exactly one, it'll load it automatically. + + The PatchLibrary template is a mechanism for providing different JIT engines. + The PatchLibraryDLL is an example of a class that could be used for this parameter. */ +template class SOULPatchLoaderPlugin : public juce::AudioProcessor { public: - SOULPatchLoaderPlugin() = default; + SOULPatchLoaderPlugin (PatchLibrary&& library) + : patchLibrary (std::move (library)) + { + enableAllBuses(); + updatePatchName(); + checkForSiblingPatch(); + } ~SOULPatchLoaderPlugin() override { @@ -40,15 +53,17 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor patchInstance = nullptr; } - /** To allow this utility class to be used with either the patch DLL or a static build, - this virtual method abstracts away the loading of a patch. - */ - virtual soul::patch::PatchInstance::Ptr createPatchInstance (const std::string& url) = 0; - - /** This allows a sub-class to provide an error message to be shown in the editor if - it needs to report a problem. - */ - virtual std::string getErrorMessage() = 0; + //============================================================================== + /** Sets a new .soulpatch file or URL for the plugin to load. */ + void setPatchURL (const std::string& newFileOrURL) + { + if (newFileOrURL != state.getProperty (ids.patchURL).toString().toStdString()) + { + state = juce::ValueTree (ids.SOULPatchPlugin); + state.setProperty (ids.patchURL, newFileOrURL.c_str(), nullptr); + updatePatchState(); + } + } //============================================================================== void prepareToPlay (double sampleRate, int samplesPerBlock) override @@ -78,8 +93,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor } //============================================================================== - const juce::String getName() const override { return "SOUL Patch Loader"; } - + const juce::String getName() const override { return patchName; } juce::AudioProcessorEditor* createEditor() override { return new Editor (*this); } bool hasEditor() const override { return true; } @@ -143,7 +157,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor } if (patchInstance == nullptr) - patchInstance = createPatchInstance (stateURL); + patchInstance = patchLibrary.createPatchInstance (stateURL); if (patchInstance != nullptr) { @@ -160,13 +174,13 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor if (plugin == nullptr) { auto newPlugin = std::make_unique (patchInstance, getCompilerCache()); - newPlugin->askHostToReinitialise = [this] { this->childChanged(); }; + newPlugin->askHostToReinitialise = [this] { this->reinitialiseSourcePlugin(); }; if (state.getNumChildren() != 0) newPlugin->applyNewState (state.getChild (0)); newPlugin->setBusesLayout (getBusesLayout()); - newPlugin->prepareToPlay (getSampleRate(), getBlockSize()); + preparePluginToPlayIfPossible (*newPlugin); replaceCurrentPlugin (std::move (newPlugin)); } else @@ -177,50 +191,8 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor } } } - } - void setPatchURL (const std::string& newURL) - { - if (newURL != state.getProperty (ids.patchURL).toString().toStdString()) - { - state = juce::ValueTree (ids.SOULPatchPlugin); - state.setProperty (ids.patchURL, newURL.c_str(), nullptr); - updatePatchState(); - } - } - - void childChanged() - { - suspendProcessing (true); - - if (plugin != nullptr) - { - plugin->setBusesLayout (getBusesLayout()); - plugin->reinitialise(); - plugin->prepareToPlay (getSampleRate(), getBlockSize()); - } - - updateHostDisplay(); - suspendProcessing (false); - - if (auto ed = dynamic_cast (getActiveEditor())) - ed->refreshContent(); - } - - void replaceCurrentPlugin (std::unique_ptr newPlugin) - { - if (newPlugin.get() != plugin.get()) - { - if (auto ed = dynamic_cast (getActiveEditor())) - ed->clearContent(); - - suspendProcessing (true); - std::swap (plugin, newPlugin); - suspendProcessing (false); - - if (auto ed = dynamic_cast (getActiveEditor())) - ed->refreshContent(); - } + updatePatchName(); } //============================================================================== @@ -276,7 +248,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor if (pluginEditor == nullptr) { - auto message = owner.getErrorMessage(); + auto message = owner.patchLibrary.getErrorMessage(); if (message.empty()) message = "Drag-and-drop a .soulpatch file here to load it"; @@ -322,6 +294,8 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor private: //============================================================================== + PatchLibrary patchLibrary; + juce::String patchName; soul::patch::PatchInstance::Ptr patchInstance; std::unique_ptr plugin; juce::ValueTree state; @@ -357,9 +331,166 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor return compilerCache; } + void preparePluginToPlayIfPossible (soul::patch::SOULPatchAudioProcessor& p) + { + auto rate = getSampleRate(); + auto size = getBlockSize(); + + if (rate > 0 && size > 0) + p.prepareToPlay (rate, size); + } + + void reinitialiseSourcePlugin() + { + suspendProcessing (true); + + if (plugin != nullptr) + { + plugin->setBusesLayout (getBusesLayout()); + plugin->reinitialise(); + preparePluginToPlayIfPossible (*plugin); + } + + updateHostDisplay(); + suspendProcessing (false); + + if (auto ed = dynamic_cast (getActiveEditor())) + ed->refreshContent(); + } + + void replaceCurrentPlugin (std::unique_ptr newPlugin) + { + if (newPlugin.get() != plugin.get()) + { + if (auto ed = dynamic_cast (getActiveEditor())) + ed->clearContent(); + + suspendProcessing (true); + std::swap (plugin, newPlugin); + suspendProcessing (false); + + if (auto ed = dynamic_cast (getActiveEditor())) + ed->refreshContent(); + } + } + + void checkForSiblingPatch() + { + auto pluginDLL = juce::File::getSpecialLocation (juce::File::SpecialLocationType::currentApplicationFile); + auto siblingPatches = pluginDLL.getParentDirectory().findChildFiles (juce::File::findFiles, false, "*.soulpatch"); + + if (siblingPatches.size() == 1) + setPatchURL (siblingPatches.getFirst().getFullPathName().toStdString()); + } + + void updatePatchName() + { + if (patchInstance != nullptr) + { + if (auto desc = soul::patch::Description::Ptr (patchInstance->getDescription())) + { + if (std::string_view (desc->name).empty()) + { + patchName = desc->name; + return; + } + } + } + + patchName = "SOUL Patch Loader"; + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SOULPatchLoaderPlugin) }; +//============================================================================== +/** This helper class can be used as the template class for SOULPatchLoaderPlugin to make it + find and load the SOUL_PatchLoader DLL as its JIT engine. +*/ +struct PatchLibraryDLL +{ + PatchLibraryDLL() + { + library->ensureLibraryLoaded (lookForSOULPatchDLL().toStdString()); + } + + std::string getErrorMessage() + { + if (library->library == nullptr) + return std::string ("Couldn't find or load ") + soul::patch::SOULPatchLibrary::getLibraryFileName(); + + return {}; + } + + soul::patch::PatchInstance::Ptr createPatchInstance (const std::string& url) + { + if (library->library != nullptr) + return soul::patch::PatchInstance::Ptr (library->library->createPatchFromFileBundle (url.c_str())); + + return {}; + } + +private: + //============================================================================== + static juce::String lookForSOULPatchDLL() + { + auto dllName = soul::patch::SOULPatchLibrary::getLibraryFileName(); + + auto pluginDLL = juce::File::getSpecialLocation (juce::File::SpecialLocationType::currentApplicationFile); + auto pluginSibling = pluginDLL.getSiblingFile (dllName); + + if (pluginSibling.exists()) + return pluginSibling.getFullPathName(); + + #if JUCE_MAC + auto insideBundle = pluginDLL.getChildFile ("Contents/Resources").getChildFile (dllName); + + if (insideBundle.exists()) + return insideBundle.getFullPathName(); + #endif + + auto inAppData = juce::File::getSpecialLocation (juce::File::SpecialLocationType::userApplicationDataDirectory) + .getChildFile ("SOUL").getChildFile (dllName); + + if (inAppData.exists()) + return inAppData.getFullPathName(); + + return dllName; + } + + struct SharedPatchLibraryHolder + { + SharedPatchLibraryHolder() = default; + + void ensureLibraryLoaded (const std::string& patchLoaderLibraryPath) + { + if (library == nullptr) + { + library = std::make_unique (patchLoaderLibraryPath.c_str()); + + if (library->loadedSuccessfully()) + loadedPath = patchLoaderLibraryPath; + else + library.reset(); + } + else + { + // This class isn't sophisticated enough to be able to load multiple + // DLLs from different locations at the same time + jassert (loadedPath == patchLoaderLibraryPath); + } + } + + std::unique_ptr library; + std::string loadedPath; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedPatchLibraryHolder) + }; + + juce::SharedResourcePointer library; +}; + + } // namespace patch } // namespace soul diff --git a/tools/plugin/Source/PatchLoaderPlugin.cpp b/tools/plugin/Source/PatchLoaderPlugin.cpp index 75c6ba1..e2c189b 100644 --- a/tools/plugin/Source/PatchLoaderPlugin.cpp +++ b/tools/plugin/Source/PatchLoaderPlugin.cpp @@ -10,93 +10,9 @@ #include "../../../include/soul/patch/helper_classes/soul_patch_LoaderPlugin.h" -//============================================================================== -/** This subclass of SOULPatchLoaderPlugin just adds functionality to find and load - the SOUL_PatchLoader DLL as its JIT engine. -*/ -struct SOULPatchLoaderPlugin_FromDLL : public soul::patch::SOULPatchLoaderPlugin -{ - SOULPatchLoaderPlugin_FromDLL() - { - library->ensureLibraryLoaded (lookForSOULPatchDLL().toStdString()); - } - - std::string getErrorMessage() override - { - if (library->library == nullptr) - return std::string ("Couldn't find or load ") + soul::patch::SOULPatchLibrary::getLibraryFileName(); - - return {}; - } - - soul::patch::PatchInstance::Ptr createPatchInstance (const std::string& url) override - { - if (library->library != nullptr) - return soul::patch::PatchInstance::Ptr (library->library->createPatchFromFileBundle (url.c_str())); - - return {}; - } - - static juce::String lookForSOULPatchDLL() - { - auto dllName = soul::patch::SOULPatchLibrary::getLibraryFileName(); - - auto pluginDLL = juce::File::getSpecialLocation (juce::File::SpecialLocationType::currentApplicationFile); - auto pluginSibling = pluginDLL.getSiblingFile (dllName); - - if (pluginSibling.exists()) - return pluginSibling.getFullPathName(); - - #if JUCE_MAC - auto insideBundle = pluginDLL.getChildFile ("Contents/Resources").getChildFile (dllName); - - if (insideBundle.exists()) - return insideBundle.getFullPathName(); - #endif - - auto inAppData = juce::File::getSpecialLocation (juce::File::SpecialLocationType::userApplicationDataDirectory) - .getChildFile ("SOUL").getChildFile (dllName); - - if (inAppData.exists()) - return inAppData.getFullPathName(); - - return dllName; - } - - struct SharedPatchLibraryHolder - { - SharedPatchLibraryHolder() = default; - - void ensureLibraryLoaded (const std::string& patchLoaderLibraryPath) - { - if (library == nullptr) - { - library = std::make_unique (patchLoaderLibraryPath.c_str()); - - if (library->loadedSuccessfully()) - loadedPath = patchLoaderLibraryPath; - else - library.reset(); - } - else - { - // This class isn't sophisticated enough to be able to load multiple - // DLLs from different locations at the same time - jassert (loadedPath == patchLoaderLibraryPath); - } - } - - std::unique_ptr library; - std::string loadedPath; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedPatchLibraryHolder) - }; - - juce::SharedResourcePointer library; -}; //============================================================================== juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { - return new SOULPatchLoaderPlugin_FromDLL(); + return new soul::patch::SOULPatchLoaderPlugin ({}); }