@@ -28,27 +28,42 @@ namespace patch
28
28
This is a juce::AudioProcessor which can told to dynamically load and run different
29
29
patches. The purpose is that you can build a native (VST/AU/etc) plugin with this
30
30
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.
31
37
*/
38
+ template <typename PatchLibrary>
32
39
class SOULPatchLoaderPlugin : public juce ::AudioProcessor
33
40
{
34
41
public:
35
- SOULPatchLoaderPlugin () = default ;
42
+ SOULPatchLoaderPlugin (PatchLibrary&& library)
43
+ : patchLibrary (std::move (library))
44
+ {
45
+ enableAllBuses ();
46
+ updatePatchName ();
47
+ checkForSiblingPatch ();
48
+ }
36
49
37
50
~SOULPatchLoaderPlugin () override
38
51
{
39
52
plugin.reset ();
40
53
patchInstance = nullptr ;
41
54
}
42
55
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
+ }
52
67
53
68
// ==============================================================================
54
69
void prepareToPlay (double sampleRate, int samplesPerBlock) override
@@ -78,8 +93,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
78
93
}
79
94
80
95
// ==============================================================================
81
- const juce::String getName () const override { return " SOUL Patch Loader" ; }
82
-
96
+ const juce::String getName () const override { return patchName; }
83
97
juce::AudioProcessorEditor* createEditor () override { return new Editor (*this ); }
84
98
bool hasEditor () const override { return true ; }
85
99
@@ -143,7 +157,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
143
157
}
144
158
145
159
if (patchInstance == nullptr )
146
- patchInstance = createPatchInstance (stateURL);
160
+ patchInstance = patchLibrary. createPatchInstance (stateURL);
147
161
148
162
if (patchInstance != nullptr )
149
163
{
@@ -160,13 +174,13 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
160
174
if (plugin == nullptr )
161
175
{
162
176
auto newPlugin = std::make_unique<soul::patch::SOULPatchAudioProcessor> (patchInstance, getCompilerCache ());
163
- newPlugin->askHostToReinitialise = [this ] { this ->childChanged (); };
177
+ newPlugin->askHostToReinitialise = [this ] { this ->reinitialiseSourcePlugin (); };
164
178
165
179
if (state.getNumChildren () != 0 )
166
180
newPlugin->applyNewState (state.getChild (0 ));
167
181
168
182
newPlugin->setBusesLayout (getBusesLayout ());
169
- newPlugin-> prepareToPlay ( getSampleRate (), getBlockSize () );
183
+ preparePluginToPlayIfPossible (*newPlugin );
170
184
replaceCurrentPlugin (std::move (newPlugin));
171
185
}
172
186
else
@@ -177,50 +191,8 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
177
191
}
178
192
}
179
193
}
180
- }
181
194
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 ();
224
196
}
225
197
226
198
// ==============================================================================
@@ -276,7 +248,7 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
276
248
277
249
if (pluginEditor == nullptr )
278
250
{
279
- auto message = owner.getErrorMessage ();
251
+ auto message = owner.patchLibrary . getErrorMessage ();
280
252
281
253
if (message.empty ())
282
254
message = " Drag-and-drop a .soulpatch file here to load it" ;
@@ -322,6 +294,8 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
322
294
323
295
private:
324
296
// ==============================================================================
297
+ PatchLibrary patchLibrary;
298
+ juce::String patchName;
325
299
soul::patch::PatchInstance::Ptr patchInstance;
326
300
std::unique_ptr<soul::patch::SOULPatchAudioProcessor> plugin;
327
301
juce::ValueTree state;
@@ -357,9 +331,166 @@ class SOULPatchLoaderPlugin : public juce::AudioProcessor
357
331
return compilerCache;
358
332
}
359
333
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
+
360
403
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SOULPatchLoaderPlugin)
361
404
};
362
405
363
406
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
+
364
495
} // namespace patch
365
496
} // namespace soul
0 commit comments