From e358d9ee889780d6f161eda68929ebcc1fc03c68 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 20 May 2023 01:54:53 +0200 Subject: [PATCH] Auto-scan binary plugins with cache Signed-off-by: falkTX --- README.md | 13 +- carla | 2 +- dpf | 2 +- plugins/Common/IldaeilBasePlugin.hpp | 1 + plugins/Common/IldaeilPlugin.cpp | 30 ++++- plugins/Common/IldaeilUI.cpp | 173 ++++++++++++++++++++++----- 6 files changed, 178 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 571b87a..2b3c759 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ DISTRHO Ildaeil is mini-plugin host working as a plugin, allowing one-to-one plugin format reusage. The idea is to load it as a plugin inside your DAW and then the other "real" plugin inside Ildaeil. -This allows, for example, a VST3 host to load LV2 plugins. +This allows, for example, a VST3 host to load LV2 plugins and vice-versa. The Ildaeil name comes from the korean 일대일, which means "one to one". @@ -31,24 +31,23 @@ The current formats Ildaeil can work as are: And it can load the following plugin formats: - Internal (from Carla) +- LADSPA +- DSSI - LV2 +- VST2 +- VST3 +- CLAP - JSFX With a few extra formats through the "Load from file..." action: -- CLAP plugins -- VST2 plugins - Audio files (through internal audio file player, synced to transport) - MIDI files (through internal MIDI file player, synced to transport) -- SFZ files (through internal SFZero) ## Goals Later on, in theory, it should be able to load the following plugin formats: -- LADSPA -- DSSI -- VST3 - AU (macOS only) Eventually the following files could be loaded too: diff --git a/carla b/carla index ad7def4..e4e1f7d 160000 --- a/carla +++ b/carla @@ -1 +1 @@ -Subproject commit ad7def4bd0dbd9c2470a72cae79089a0a17dad7f +Subproject commit e4e1f7d158f7b41b7dd55d86a32449932623abc0 diff --git a/dpf b/dpf index 1736c84..b4460be 160000 --- a/dpf +++ b/dpf @@ -1 +1 @@ -Subproject commit 1736c842c5c6d609fabce8f46de3e732d17e9c90 +Subproject commit b4460beb094502c868dda5991e356623b13ce658 diff --git a/plugins/Common/IldaeilBasePlugin.hpp b/plugins/Common/IldaeilBasePlugin.hpp index 8302561..dda7d47 100644 --- a/plugins/Common/IldaeilBasePlugin.hpp +++ b/plugins/Common/IldaeilBasePlugin.hpp @@ -58,6 +58,7 @@ public: // -------------------------------------------------------------------------------------------------------------------- +const char* ildaeilConfigDir(); void ildaeilProjectLoadedFromDSP(void* ui); void ildaeilParameterChangeForUI(void* ui, uint32_t index, float value); void ildaeilCloseUI(void* ui); diff --git a/plugins/Common/IldaeilPlugin.cpp b/plugins/Common/IldaeilPlugin.cpp index 19d0d0d..8796f21 100644 --- a/plugins/Common/IldaeilPlugin.cpp +++ b/plugins/Common/IldaeilPlugin.cpp @@ -40,7 +40,7 @@ static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key static void host_ui_closed(NativeHostHandle handle); static const char* host_ui_open_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter); static const char* host_ui_save_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter); -static intptr_t host_dispatcher(NativeHostHandle handle, NativeHostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt); +static intptr_t host_dispatcher(NativeHostHandle h, NativeHostDispatcherOpcode op, int32_t, intptr_t, void*, float); // -------------------------------------------------------------------------------------------------------------------- @@ -48,13 +48,11 @@ Mutex IldaeilBasePlugin::sPluginInfoLoadMutex; // -------------------------------------------------------------------------------------------------------------------- -#ifndef CARLA_OS_WIN static water::String getHomePath() { static water::String path(water::File::getSpecialLocation(water::File::userHomeDirectory).getFullPathName()); return path; } -#endif static const char* getPathForLADSPA() { @@ -262,6 +260,32 @@ const char* IldaeilBasePlugin::getPluginPath(const PluginType ptype) // -------------------------------------------------------------------------------------------------------------------- +const char* ildaeilConfigDir() +{ + static water::String configDir; + + if (configDir.isEmpty()) + { + #if defined(CARLA_OS_WASM) + configDir = "/userfiles"; + #elif defined(CARLA_OS_MAC) + configDir = getHomePath() + "/Documents/Ildaeil"; + #elif defined(CARLA_OS_WIN) + configDir = water::File::getSpecialLocation(water::File::winMyDocuments).getFullPathName() + "\\Ildaeil"; + #else + if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME")) + configDir = xdgEnv; + else + configDir = getHomePath() + "/.config"; + configDir += "/Ildaeil"; + #endif + } + + return configDir.toRawUTF8(); +} + +// -------------------------------------------------------------------------------------------------------------------- + class IldaeilPlugin : public IldaeilBasePlugin { #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0 diff --git a/plugins/Common/IldaeilUI.cpp b/plugins/Common/IldaeilUI.cpp index 8e70970..bef7540 100644 --- a/plugins/Common/IldaeilUI.cpp +++ b/plugins/Common/IldaeilUI.cpp @@ -29,6 +29,11 @@ // IDE helper #include "DearImGui.hpp" +#include "water/files/File.h" +#include "water/files/FileInputStream.h" +#include "water/files/FileOutputStream.h" +#include "water/memory/MemoryBlock.h" + #include #include @@ -160,7 +165,6 @@ class IldaeilUI : public UI, PluginType fNextPluginType; uint fPluginId; int fPluginSelected; - bool fPluginScanningFinished; bool fPluginHasCustomUI; bool fPluginHasEmbedUI; bool fPluginHasFileOpen; @@ -211,7 +215,6 @@ public: fNextPluginType(fPluginType), fPluginId(0), fPluginSelected(-1), - fPluginScanningFinished(false), fPluginHasCustomUI(false), fPluginHasEmbedUI(false), fPluginHasFileOpen(false), @@ -790,22 +793,19 @@ protected: if (fRunnerData.needsReinit) { fRunnerData.needsReinit = false; - fPluginScanningFinished = false; { const MutexLocker cml(fPluginsMutex); fPlugins.clear(); } - { - const MutexLocker cml(fPlugin->sPluginInfoLoadMutex); - - d_stdout("Will scan plugins now..."); - fRunnerData.handle = carla_plugin_discovery_start(fPlugin->fDiscoveryTool, - fPluginType, - IldaeilBasePlugin::getPluginPath(fPluginType), - _binaryPluginSearchCallback, this); - } + d_stdout("Will scan plugins now..."); + fRunnerData.handle = carla_plugin_discovery_start(fPlugin->fDiscoveryTool, + fPluginType, + IldaeilBasePlugin::getPluginPath(fPluginType), + _binaryPluginSearchCallback, + _binaryPluginCheckCacheCallback, + this); if (fDrawingState == kDrawingLoading) { @@ -816,37 +816,74 @@ protected: if (fRunnerData.handle == nullptr) { d_stdout("Nothing found!"); - fPluginScanningFinished = true; return false; } } - DISTRHO_SAFE_ASSERT_RETURN(!fPluginScanningFinished, false); DISTRHO_SAFE_ASSERT_RETURN(fRunnerData.handle != nullptr, false); - bool ok; - { - const MutexLocker cml(fPlugin->sPluginInfoLoadMutex); - ok = carla_plugin_discovery_idle(fRunnerData.handle); - } - - if (ok) + if (carla_plugin_discovery_idle(fRunnerData.handle)) return true; // stop here - { - const MutexLocker cml(fPlugin->sPluginInfoLoadMutex); - d_stdout("Found %lu plugins!", (ulong)fPlugins.size()); - carla_plugin_discovery_stop(fRunnerData.handle); - fRunnerData.handle = nullptr; - } + d_stdout("Found %lu plugins!", (ulong)fPlugins.size()); + carla_plugin_discovery_stop(fRunnerData.handle); + fRunnerData.handle = nullptr; - fPluginScanningFinished = true; return false; } - void binaryPluginSearchCallback(const CarlaPluginDiscoveryInfo* const info) + void binaryPluginSearchCallback(const CarlaPluginDiscoveryInfo* const info, const char* const sha1sum) { + // save plugin info into cache + if (sha1sum != nullptr) + { + const water::String configDir(ildaeilConfigDir()); + const water::File cacheFile(configDir + CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR + sha1sum); + + if (cacheFile.create().ok()) + { + water::FileOutputStream stream(cacheFile); + + if (stream.openedOk()) + { + if (info != nullptr) + { + stream.writeString(getBinaryTypeAsString(info->btype)); + stream.writeString(getPluginTypeAsString(info->ptype)); + stream.writeString(info->filename); + stream.writeString(info->label); + stream.writeInt64(info->uniqueId); + stream.writeString(info->metadata.name); + stream.writeString(info->metadata.maker); + stream.writeString(getPluginCategoryAsString(info->metadata.category)); + stream.writeInt(info->metadata.hints); + stream.writeCompressedInt(info->io.audioIns); + stream.writeCompressedInt(info->io.audioOuts); + stream.writeCompressedInt(info->io.cvIns); + stream.writeCompressedInt(info->io.cvOuts); + stream.writeCompressedInt(info->io.midiIns); + stream.writeCompressedInt(info->io.midiOuts); + stream.writeCompressedInt(info->io.parameterIns); + stream.writeCompressedInt(info->io.parameterOuts); + } + } + else + { + d_stderr("Failed to write cache file for %s%s%s", + ildaeilConfigDir(), CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR, sha1sum); + } + } + else + { + d_stderr("Failed to write cache file directories for %s%s%s", + ildaeilConfigDir(), CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR, sha1sum); + } + } + + if (info == nullptr) + return; + if (info->io.cvIns != 0 || info->io.cvOuts != 0) return; if (info->io.midiIns != 0 && info->io.midiIns != 1) @@ -911,9 +948,83 @@ protected: fPlugins.push_back(pinfo); } - static void _binaryPluginSearchCallback(void* ptr, const CarlaPluginDiscoveryInfo* info) + static void _binaryPluginSearchCallback(void* const ptr, + const CarlaPluginDiscoveryInfo* const info, + const char* const sha1sum) + { + static_cast(ptr)->binaryPluginSearchCallback(info, sha1sum); + } + + bool binaryPluginCheckCacheCallback(const char* const filename, const char* const sha1sum) + { + if (sha1sum == nullptr) + return false; + + const water::String configDir(ildaeilConfigDir()); + const water::File cacheFile(configDir + CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR + sha1sum); + + if (cacheFile.existsAsFile()) + { + water::FileInputStream stream(cacheFile); + + if (stream.openedOk()) + { + while (! stream.isExhausted()) + { + CarlaPluginDiscoveryInfo info = {}; + + // read back everything the same way and order as we wrote it + info.btype = getBinaryTypeFromString(stream.readString().toRawUTF8()); + info.ptype = getPluginTypeFromString(stream.readString().toRawUTF8()); + const water::String pfilename(stream.readString()); + const water::String label(stream.readString()); + info.uniqueId = stream.readInt64(); + const water::String name(stream.readString()); + const water::String maker(stream.readString()); + info.metadata.category = getPluginCategoryFromString(stream.readString().toRawUTF8()); + info.metadata.hints = stream.readInt(); + info.io.audioIns = stream.readCompressedInt(); + info.io.audioOuts = stream.readCompressedInt(); + info.io.cvIns = stream.readCompressedInt(); + info.io.cvOuts = stream.readCompressedInt(); + info.io.midiIns = stream.readCompressedInt(); + info.io.midiOuts = stream.readCompressedInt(); + info.io.parameterIns = stream.readCompressedInt(); + info.io.parameterOuts = stream.readCompressedInt(); + + // string stuff + info.filename = pfilename.toRawUTF8(); + info.label = label.toRawUTF8(); + info.metadata.name = name.toRawUTF8(); + info.metadata.maker = maker.toRawUTF8(); + + // check sha1 collisions + if (pfilename != filename) + { + d_stderr("Cache hash collision for %s: \"%s\" vs \"%s\"", + sha1sum, pfilename.toRawUTF8(), filename); + return false; + } + + // purposefully not passing sha1sum, to not override cache file + binaryPluginSearchCallback(&info, nullptr); + } + + return true; + } + else + { + d_stderr("Failed to read cache file for %s%s%s", + ildaeilConfigDir(), CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR, sha1sum); + } + } + + return false; + } + + static bool _binaryPluginCheckCacheCallback(void* const ptr, const char* const filename, const char* const sha1) { - static_cast(ptr)->binaryPluginSearchCallback(info); + return static_cast(ptr)->binaryPluginCheckCacheCallback(filename, sha1); } void onImGuiDisplay() override