Skip to content

Commit 85ecf77

Browse files
committed
Work on the patch loader plugin
1 parent a3e4566 commit 85ecf77

File tree

2 files changed

+191
-144
lines changed

2 files changed

+191
-144
lines changed

include/soul/patch/helper_classes/soul_patch_LoaderPlugin.h

Lines changed: 190 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,42 @@ namespace patch
2828
This is a juce::AudioProcessor which can told to dynamically load and run different
2929
patches. The purpose is that you can build a native (VST/AU/etc) plugin with this
3030
class which can then load (and hot-reload) any SOUL patch at runtime.
31+
32+
On startup, the plugin will also check in its folder for any sibling .soulpatch files,
33+
and if it finds exactly one, it'll load it automatically.
34+
35+
The PatchLibrary template is a mechanism for providing different JIT engines.
36+
The PatchLibraryDLL is an example of a class that could be used for this parameter.
3137
*/
38+
template <typename PatchLibrary>
3239
class SOULPatchLoaderPlugin : public juce::AudioProcessor
3340
{
3441
public:
35-
SOULPatchLoaderPlugin() = default;
42+
SOULPatchLoaderPlugin (PatchLibrary&& library)
43+
: patchLibrary (std::move (library))
44+
{
45+
enableAllBuses();
46+
updatePatchName();
47+
checkForSiblingPatch();
48+
}
3649

3750
~SOULPatchLoaderPlugin() override
3851
{
3952
plugin.reset();
4053
patchInstance = nullptr;
4154
}
4255

43-
/** To allow this utility class to be used with either the patch DLL or a static build,
44-
this virtual method abstracts away the loading of a patch.
45-
*/
46-
virtual soul::patch::PatchInstance::Ptr createPatchInstance (const std::string& url) = 0;
47-
48-
/** This allows a sub-class to provide an error message to be shown in the editor if
49-
it needs to report a problem.
50-
*/
51-
virtual std::string getErrorMessage() = 0;
56+
//==============================================================================
57+
/** Sets a new .soulpatch file or URL for the plugin to load. */
58+
void setPatchURL (const std::string& newFileOrURL)
59+
{
60+
if (newFileOrURL != state.getProperty (ids.patchURL).toString().toStdString())
61+
{
62+
state = juce::ValueTree (ids.SOULPatchPlugin);
63+
state.setProperty (ids.patchURL, newFileOrURL.c_str(), nullptr);
64+
updatePatchState();
65+
}
66+
}
5267

5368
//==============================================================================
5469
void prepareToPlay (double sampleRate, int samplesPerBlock) override
@@ -78,8 +93,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
7893
}
7994

8095
//==============================================================================
81-
const juce::String getName() const override { return "SOUL Patch Loader"; }
82-
96+
const juce::String getName() const override { return patchName; }
8397
juce::AudioProcessorEditor* createEditor() override { return new Editor (*this); }
8498
bool hasEditor() const override { return true; }
8599

@@ -143,7 +157,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
143157
}
144158

145159
if (patchInstance == nullptr)
146-
patchInstance = createPatchInstance (stateURL);
160+
patchInstance = patchLibrary.createPatchInstance (stateURL);
147161

148162
if (patchInstance != nullptr)
149163
{
@@ -160,13 +174,13 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
160174
if (plugin == nullptr)
161175
{
162176
auto newPlugin = std::make_unique<soul::patch::SOULPatchAudioProcessor> (patchInstance, getCompilerCache());
163-
newPlugin->askHostToReinitialise = [this] { this->childChanged(); };
177+
newPlugin->askHostToReinitialise = [this] { this->reinitialiseSourcePlugin(); };
164178

165179
if (state.getNumChildren() != 0)
166180
newPlugin->applyNewState (state.getChild (0));
167181

168182
newPlugin->setBusesLayout (getBusesLayout());
169-
newPlugin->prepareToPlay (getSampleRate(), getBlockSize());
183+
preparePluginToPlayIfPossible (*newPlugin);
170184
replaceCurrentPlugin (std::move (newPlugin));
171185
}
172186
else
@@ -177,50 +191,8 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
177191
}
178192
}
179193
}
180-
}
181194

182-
void setPatchURL (const std::string& newURL)
183-
{
184-
if (newURL != state.getProperty (ids.patchURL).toString().toStdString())
185-
{
186-
state = juce::ValueTree (ids.SOULPatchPlugin);
187-
state.setProperty (ids.patchURL, newURL.c_str(), nullptr);
188-
updatePatchState();
189-
}
190-
}
191-
192-
void childChanged()
193-
{
194-
suspendProcessing (true);
195-
196-
if (plugin != nullptr)
197-
{
198-
plugin->setBusesLayout (getBusesLayout());
199-
plugin->reinitialise();
200-
plugin->prepareToPlay (getSampleRate(), getBlockSize());
201-
}
202-
203-
updateHostDisplay();
204-
suspendProcessing (false);
205-
206-
if (auto ed = dynamic_cast<Editor*> (getActiveEditor()))
207-
ed->refreshContent();
208-
}
209-
210-
void replaceCurrentPlugin (std::unique_ptr<soul::patch::SOULPatchAudioProcessor> newPlugin)
211-
{
212-
if (newPlugin.get() != plugin.get())
213-
{
214-
if (auto ed = dynamic_cast<Editor*> (getActiveEditor()))
215-
ed->clearContent();
216-
217-
suspendProcessing (true);
218-
std::swap (plugin, newPlugin);
219-
suspendProcessing (false);
220-
221-
if (auto ed = dynamic_cast<Editor*> (getActiveEditor()))
222-
ed->refreshContent();
223-
}
195+
updatePatchName();
224196
}
225197

226198
//==============================================================================
@@ -276,7 +248,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
276248

277249
if (pluginEditor == nullptr)
278250
{
279-
auto message = owner.getErrorMessage();
251+
auto message = owner.patchLibrary.getErrorMessage();
280252

281253
if (message.empty())
282254
message = "Drag-and-drop a .soulpatch file here to load it";
@@ -322,6 +294,8 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
322294

323295
private:
324296
//==============================================================================
297+
PatchLibrary patchLibrary;
298+
juce::String patchName;
325299
soul::patch::PatchInstance::Ptr patchInstance;
326300
std::unique_ptr<soul::patch::SOULPatchAudioProcessor> plugin;
327301
juce::ValueTree state;
@@ -357,9 +331,166 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
357331
return compilerCache;
358332
}
359333

334+
void preparePluginToPlayIfPossible (soul::patch::SOULPatchAudioProcessor& p)
335+
{
336+
auto rate = getSampleRate();
337+
auto size = getBlockSize();
338+
339+
if (rate > 0 && size > 0)
340+
p.prepareToPlay (rate, size);
341+
}
342+
343+
void reinitialiseSourcePlugin()
344+
{
345+
suspendProcessing (true);
346+
347+
if (plugin != nullptr)
348+
{
349+
plugin->setBusesLayout (getBusesLayout());
350+
plugin->reinitialise();
351+
preparePluginToPlayIfPossible (*plugin);
352+
}
353+
354+
updateHostDisplay();
355+
suspendProcessing (false);
356+
357+
if (auto ed = dynamic_cast<Editor*> (getActiveEditor()))
358+
ed->refreshContent();
359+
}
360+
361+
void replaceCurrentPlugin (std::unique_ptr<soul::patch::SOULPatchAudioProcessor> newPlugin)
362+
{
363+
if (newPlugin.get() != plugin.get())
364+
{
365+
if (auto ed = dynamic_cast<Editor*> (getActiveEditor()))
366+
ed->clearContent();
367+
368+
suspendProcessing (true);
369+
std::swap (plugin, newPlugin);
370+
suspendProcessing (false);
371+
372+
if (auto ed = dynamic_cast<Editor*> (getActiveEditor()))
373+
ed->refreshContent();
374+
}
375+
}
376+
377+
void checkForSiblingPatch()
378+
{
379+
auto pluginDLL = juce::File::getSpecialLocation (juce::File::SpecialLocationType::currentApplicationFile);
380+
auto siblingPatches = pluginDLL.getParentDirectory().findChildFiles (juce::File::findFiles, false, "*.soulpatch");
381+
382+
if (siblingPatches.size() == 1)
383+
setPatchURL (siblingPatches.getFirst().getFullPathName().toStdString());
384+
}
385+
386+
void updatePatchName()
387+
{
388+
if (patchInstance != nullptr)
389+
{
390+
if (auto desc = soul::patch::Description::Ptr (patchInstance->getDescription()))
391+
{
392+
if (std::string_view (desc->name).empty())
393+
{
394+
patchName = desc->name;
395+
return;
396+
}
397+
}
398+
}
399+
400+
patchName = "SOUL Patch Loader";
401+
}
402+
360403
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SOULPatchLoaderPlugin)
361404
};
362405

363406

407+
//==============================================================================
408+
/** This helper class can be used as the template class for SOULPatchLoaderPlugin to make it
409+
find and load the SOUL_PatchLoader DLL as its JIT engine.
410+
*/
411+
struct PatchLibraryDLL
412+
{
413+
PatchLibraryDLL()
414+
{
415+
library->ensureLibraryLoaded (lookForSOULPatchDLL().toStdString());
416+
}
417+
418+
std::string getErrorMessage()
419+
{
420+
if (library->library == nullptr)
421+
return std::string ("Couldn't find or load ") + soul::patch::SOULPatchLibrary::getLibraryFileName();
422+
423+
return {};
424+
}
425+
426+
soul::patch::PatchInstance::Ptr createPatchInstance (const std::string& url)
427+
{
428+
if (library->library != nullptr)
429+
return soul::patch::PatchInstance::Ptr (library->library->createPatchFromFileBundle (url.c_str()));
430+
431+
return {};
432+
}
433+
434+
private:
435+
//==============================================================================
436+
static juce::String lookForSOULPatchDLL()
437+
{
438+
auto dllName = soul::patch::SOULPatchLibrary::getLibraryFileName();
439+
440+
auto pluginDLL = juce::File::getSpecialLocation (juce::File::SpecialLocationType::currentApplicationFile);
441+
auto pluginSibling = pluginDLL.getSiblingFile (dllName);
442+
443+
if (pluginSibling.exists())
444+
return pluginSibling.getFullPathName();
445+
446+
#if JUCE_MAC
447+
auto insideBundle = pluginDLL.getChildFile ("Contents/Resources").getChildFile (dllName);
448+
449+
if (insideBundle.exists())
450+
return insideBundle.getFullPathName();
451+
#endif
452+
453+
auto inAppData = juce::File::getSpecialLocation (juce::File::SpecialLocationType::userApplicationDataDirectory)
454+
.getChildFile ("SOUL").getChildFile (dllName);
455+
456+
if (inAppData.exists())
457+
return inAppData.getFullPathName();
458+
459+
return dllName;
460+
}
461+
462+
struct SharedPatchLibraryHolder
463+
{
464+
SharedPatchLibraryHolder() = default;
465+
466+
void ensureLibraryLoaded (const std::string& patchLoaderLibraryPath)
467+
{
468+
if (library == nullptr)
469+
{
470+
library = std::make_unique<soul::patch::SOULPatchLibrary> (patchLoaderLibraryPath.c_str());
471+
472+
if (library->loadedSuccessfully())
473+
loadedPath = patchLoaderLibraryPath;
474+
else
475+
library.reset();
476+
}
477+
else
478+
{
479+
// This class isn't sophisticated enough to be able to load multiple
480+
// DLLs from different locations at the same time
481+
jassert (loadedPath == patchLoaderLibraryPath);
482+
}
483+
}
484+
485+
std::unique_ptr<soul::patch::SOULPatchLibrary> library;
486+
std::string loadedPath;
487+
488+
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedPatchLibraryHolder)
489+
};
490+
491+
juce::SharedResourcePointer<SharedPatchLibraryHolder> library;
492+
};
493+
494+
364495
} // namespace patch
365496
} // namespace soul

0 commit comments

Comments
 (0)