Add auto-scanning plugin API, with initial implementation

Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
falkTX 2023-05-11 04:30:04 +02:00
parent b32f6dbad8
commit 7321be60c6
No known key found for this signature in database
GPG Key ID: CDBAA37ABC74FBA0
4 changed files with 635 additions and 10 deletions

View File

@ -21,6 +21,7 @@
#include "CarlaBackend.h"
#ifdef __cplusplus
using CARLA_BACKEND_NAMESPACE::BinaryType;
using CARLA_BACKEND_NAMESPACE::PluginCategory;
using CARLA_BACKEND_NAMESPACE::PluginType;
#endif
@ -34,16 +35,6 @@ using CARLA_BACKEND_NAMESPACE::PluginType;
* @{
*/
/*!
* TODO.
*/
typedef void* CarlaPipeClientHandle;
/*!
* TODO.
*/
typedef void (*CarlaPipeCallbackFunc)(void* ptr, const char* msg);
/*!
* Information about a cached plugin.
* @see carla_get_cached_plugin_info()
@ -138,6 +129,170 @@ typedef struct _CarlaCachedPluginInfo {
} CarlaCachedPluginInfo;
/* --------------------------------------------------------------------------------------------------------------------
* plugin discovery */
typedef void* CarlaPluginDiscoveryHandle;
/*!
* TODO.
*/
typedef struct _CarlaPluginDiscoveryMetadata {
/*!
* Plugin name.
*/
const char* name;
/*!
* Plugin author/maker.
*/
const char* maker;
/*!
* Plugin category.
*/
PluginCategory category;
/*!
* Plugin hints.
* @see PluginHints
*/
uint hints;
#ifdef __cplusplus
/*!
* C++ constructor.
*/
CARLA_API _CarlaPluginDiscoveryMetadata() noexcept;
CARLA_DECLARE_NON_COPYABLE(_CarlaPluginDiscoveryMetadata)
#endif
} CarlaPluginDiscoveryMetadata;
/*!
* TODO.
*/
typedef struct _CarlaPluginDiscoveryIO {
/*!
* Number of audio inputs.
*/
uint32_t audioIns;
/*!
* Number of audio outputs.
*/
uint32_t audioOuts;
/*!
* Number of CV inputs.
*/
uint32_t cvIns;
/*!
* Number of CV outputs.
*/
uint32_t cvOuts;
/*!
* Number of MIDI inputs.
*/
uint32_t midiIns;
/*!
* Number of MIDI outputs.
*/
uint32_t midiOuts;
/*!
* Number of input parameters.
*/
uint32_t parameterIns;
/*!
* Number of output parameters.
*/
uint32_t parameterOuts;
#ifdef __cplusplus
/*!
* C++ constructor.
*/
CARLA_API _CarlaPluginDiscoveryIO() noexcept;
CARLA_DECLARE_NON_COPYABLE(_CarlaPluginDiscoveryIO)
#endif
} CarlaPluginDiscoveryIO;
/*!
* TODO.
*/
typedef struct _CarlaPluginDiscoveryInfo {
/*!
* Binary type.
*/
BinaryType btype;
/*!
* Plugin type.
*/
PluginType ptype;
/*!
* Plugin filename.
*/
const char* filename;
/*!
* Plugin label/URI/Id.
*/
const char* label;
/*!
* Plugin unique Id.
*/
uint64_t uniqueId;
/*!
* Extra information, not required for load plugins.
*/
CarlaPluginDiscoveryMetadata metadata;
/*!
* Extra information, not required for load plugins.
*/
CarlaPluginDiscoveryIO io;
#ifdef __cplusplus
/*!
* C++ constructor.
*/
CARLA_API _CarlaPluginDiscoveryInfo() noexcept;
CARLA_DECLARE_NON_COPYABLE(_CarlaPluginDiscoveryInfo)
#endif
} CarlaPluginDiscoveryInfo;
/*!
* TODO.
*/
typedef void (*CarlaPluginDiscoveryCallback)(void* ptr, const CarlaPluginDiscoveryInfo* info);
/*!
*/
CARLA_PLUGIN_EXPORT CarlaPluginDiscoveryHandle carla_plugin_discovery_start(const char* discoveryTool,
PluginType ptype,
const char* pluginPath,
CarlaPluginDiscoveryCallback callback,
void* callbackPtr);
/*!
*/
CARLA_PLUGIN_EXPORT bool carla_plugin_discovery_idle(CarlaPluginDiscoveryHandle handle);
/*!
*/
CARLA_PLUGIN_EXPORT void carla_plugin_discovery_stop(CarlaPluginDiscoveryHandle handle);
/* --------------------------------------------------------------------------------------------------------------------
* cached plugins */
@ -222,6 +377,16 @@ CARLA_PLUGIN_EXPORT void carla_juce_cleanup(void);
/* --------------------------------------------------------------------------------------------------------------------
* pipes */
/*!
* TODO.
*/
typedef void* CarlaPipeClientHandle;
/*!
* TODO.
*/
typedef void (*CarlaPipeCallbackFunc)(void* ptr, const char* msg);
/*!
* TODO.
*/

View File

@ -19,6 +19,7 @@ OBJS = \
$(OBJDIR)/Information.cpp.o \
$(OBJDIR)/JUCE.cpp.o \
$(OBJDIR)/PipeClient.cpp.o \
$(OBJDIR)/PluginDiscovery.cpp.o \
$(OBJDIR)/System.cpp.o \
$(OBJDIR)/Windows.cpp.o

View File

@ -0,0 +1,458 @@
/*
* Carla Plugin Host
* Copyright (C) 2011-2023 Filipe Coelho <falktx@falktx.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* For a full copy of the GNU General Public License see the doc/GPL.txt file.
*/
#include "CarlaUtils.h"
#include "CarlaBackendUtils.hpp"
#include "CarlaJuceUtils.hpp"
#include "CarlaPipeUtils.hpp"
#include "water/files/File.h"
#include "water/threads/ChildProcess.h"
#include "water/text/StringArray.h"
namespace CB = CARLA_BACKEND_NAMESPACE;
// --------------------------------------------------------------------------------------------------------------------
static const char* const gPluginsDiscoveryNullCharPtr = "";
_CarlaPluginDiscoveryMetadata::_CarlaPluginDiscoveryMetadata() noexcept
: name(gPluginsDiscoveryNullCharPtr),
maker(gPluginsDiscoveryNullCharPtr),
category(CB::PLUGIN_CATEGORY_NONE),
hints(0x0) {}
_CarlaPluginDiscoveryIO::_CarlaPluginDiscoveryIO() noexcept
: audioIns(0),
audioOuts(0),
cvIns(0),
cvOuts(0),
midiIns(0),
midiOuts(0),
parameterIns(0),
parameterOuts(0) {}
_CarlaPluginDiscoveryInfo::_CarlaPluginDiscoveryInfo() noexcept
: btype(CB::BINARY_NONE),
ptype(CB::PLUGIN_NONE),
filename(gPluginsDiscoveryNullCharPtr),
label(gPluginsDiscoveryNullCharPtr),
uniqueId(0),
metadata() {}
// --------------------------------------------------------------------------------------------------------------------
class CarlaPluginDiscovery : private CarlaPipeServer
{
public:
CarlaPluginDiscovery(const char* const discoveryTool,
const PluginType ptype,
const std::vector<water::File>&& binaries,
const CarlaPluginDiscoveryCallback callback,
void* const callbackPtr)
: fPluginType(ptype),
fCallback(callback),
fCallbackPtr(callbackPtr),
fBinaryIndex(0),
fBinaryCount(binaries.size()),
fBinaries(binaries),
fDiscoveryTool(discoveryTool),
nextLabel(nullptr),
nextMaker(nullptr),
nextName(nullptr)
{
startPipeServer(discoveryTool, getPluginTypeAsString(fPluginType), fBinaries[0].getFullPathName().toRawUTF8());
}
CarlaPluginDiscovery(const char* const discoveryTool,
const PluginType ptype,
const CarlaPluginDiscoveryCallback callback,
void* const callbackPtr)
: fPluginType(ptype),
fCallback(callback),
fCallbackPtr(callbackPtr),
fBinaryIndex(0),
fBinaryCount(1),
nextLabel(nullptr),
nextMaker(nullptr),
nextName(nullptr)
{
startPipeServer(discoveryTool, getPluginTypeAsString(fPluginType), ":all");
}
~CarlaPluginDiscovery()
{
std::free(nextLabel);
std::free(nextMaker);
std::free(nextName);
}
// closePipeServer()
bool idle()
{
if (isPipeRunning())
{
idlePipe();
return true;
}
if (++fBinaryIndex == fBinaryCount)
return false;
startPipeServer(fDiscoveryTool,
getPluginTypeAsString(fPluginType),
fBinaries[fBinaryIndex].getFullPathName().toRawUTF8());
return true;
}
protected:
bool msgReceived(const char* const msg) noexcept
{
if (std::strcmp(msg, "warning") == 0 || std::strcmp(msg, "error") == 0)
{
const char* text = nullptr;
readNextLineAsString(text, false);
carla_stdout("discovery: %s", text);
return true;
}
if (std::strcmp(msg, "init") == 0)
{
const char* _;
readNextLineAsString(_, false);
new (&nextInfo) _CarlaPluginDiscoveryInfo();
return true;
}
if (std::strcmp(msg, "end") == 0)
{
const char* _;
readNextLineAsString(_, false);
if (nextInfo.label == nullptr)
nextInfo.label = gPluginsDiscoveryNullCharPtr;
if (nextInfo.metadata.maker == nullptr)
nextInfo.metadata.maker = gPluginsDiscoveryNullCharPtr;
if (nextInfo.metadata.name == nullptr)
nextInfo.metadata.name = gPluginsDiscoveryNullCharPtr;
if (fDiscoveryTool.isEmpty())
{
char* filename = nullptr;
if (fPluginType == CB::PLUGIN_LV2)
{
do {
const char* const slash = std::strchr(nextLabel, CARLA_OS_SEP);
CARLA_SAFE_ASSERT_BREAK(slash != nullptr);
filename = strdup(nextLabel);
filename[slash - nextLabel] = '\0';
nextInfo.filename = filename;
nextInfo.label = slash + 1;
} while (false);
}
fCallback(fCallbackPtr, &nextInfo);
std::free(filename);
}
else
{
const water::String filename(fBinaries[fBinaryIndex].getFullPathName());
nextInfo.filename = filename.toRawUTF8();
fCallback(fCallbackPtr, &nextInfo);
}
std::free(nextLabel);
nextLabel = nullptr;
std::free(nextMaker);
nextMaker = nullptr;
std::free(nextName);
nextName = nullptr;
return true;
}
if (std::strcmp(msg, "build") == 0)
{
uint8_t btype = 0;
readNextLineAsByte(btype);
nextInfo.btype = static_cast<BinaryType>(btype);
return true;
}
if (std::strcmp(msg, "hints") == 0)
{
readNextLineAsUInt(nextInfo.metadata.hints);
return true;
}
if (std::strcmp(msg, "category") == 0)
{
const char* category = nullptr;
readNextLineAsString(category, false);
nextInfo.metadata.category = getPluginCategoryFromString(category);
return true;
}
if (std::strcmp(msg, "name") == 0)
{
nextInfo.metadata.name = nextName = readNextLineAsString();
return true;
}
if (std::strcmp(msg, "label") == 0)
{
nextInfo.label = nextLabel = readNextLineAsString();
return true;
}
if (std::strcmp(msg, "maker") == 0)
{
nextInfo.metadata.maker = nextMaker = readNextLineAsString();
return true;
}
if (std::strcmp(msg, "uniqueId") == 0)
{
readNextLineAsULong(nextInfo.uniqueId);
return true;
}
if (std::strcmp(msg, "audio.ins") == 0)
{
readNextLineAsUInt(nextInfo.io.audioIns);
return true;
}
if (std::strcmp(msg, "audio.outs") == 0)
{
readNextLineAsUInt(nextInfo.io.audioOuts);
return true;
}
if (std::strcmp(msg, "cv.ins") == 0)
{
readNextLineAsUInt(nextInfo.io.cvIns);
return true;
}
if (std::strcmp(msg, "cv.outs") == 0)
{
readNextLineAsUInt(nextInfo.io.cvOuts);
return true;
}
if (std::strcmp(msg, "midi.ins") == 0)
{
readNextLineAsUInt(nextInfo.io.midiIns);
return true;
}
if (std::strcmp(msg, "midi.outs") == 0)
{
readNextLineAsUInt(nextInfo.io.midiOuts);
return true;
}
if (std::strcmp(msg, "parameters.ins") == 0)
{
readNextLineAsUInt(nextInfo.io.parameterIns);
return true;
}
if (std::strcmp(msg, "parameters.outs") == 0)
{
readNextLineAsUInt(nextInfo.io.parameterOuts);
return true;
}
if (std::strcmp(msg, "exiting") == 0)
{
stopPipeServer(1000);
return true;
}
carla_stdout("discovery: unknown message '%s' received", msg);
return true;
}
private:
const PluginType fPluginType;
const CarlaPluginDiscoveryCallback fCallback;
void* const fCallbackPtr;
uint fBinaryIndex;
const uint fBinaryCount;
const std::vector<water::File> fBinaries;
const CarlaString fDiscoveryTool;
CarlaPluginDiscoveryInfo nextInfo;
char* nextLabel;
char* nextMaker;
char* nextName;
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginDiscovery)
};
// --------------------------------------------------------------------------------------------------------------------
static std::vector<water::File> findBinaries(const char* const pluginPath, const char* const wildcard)
{
CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
if (pluginPath[0] == '\0')
return {};
using water::File;
using water::String;
using water::StringArray;
const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
if (splitPaths.size() == 0)
return {};
std::vector<water::File> ret;
for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
{
const File dir(*it);
std::vector<File> results;
if (dir.findChildFiles(results, File::findFiles|File::ignoreHiddenFiles, true, wildcard) > 0)
{
ret.reserve(ret.size() + results.size());
ret.insert(ret.end(), results.begin(), results.end());
}
}
return ret;
}
static std::vector<water::File> findVST3s(const char* const pluginPath)
{
CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
if (pluginPath[0] == '\0')
return {};
using water::File;
using water::String;
using water::StringArray;
const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
if (splitPaths.size() == 0)
return {};
std::vector<water::File> ret;
for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
{
const File dir(*it);
std::vector<File> results;
if (dir.findChildFiles(results, File::findDirectories|File::findFiles|File::ignoreHiddenFiles, true, "*.vst3") > 0)
{
ret.reserve(ret.size() + results.size());
ret.insert(ret.end(), results.begin(), results.end());
}
}
return ret;
}
CarlaPluginDiscoveryHandle carla_plugin_discovery_start(const char* const discoveryTool,
const PluginType ptype,
const char* const pluginPath,
const CarlaPluginDiscoveryCallback callback,
void* const callbackPtr)
{
CARLA_SAFE_ASSERT_RETURN(discoveryTool != nullptr && discoveryTool[0] != '\0', nullptr);
CARLA_SAFE_ASSERT_RETURN(callback != nullptr, nullptr);
const char* wildcard = nullptr;
switch (ptype)
{
case CB::PLUGIN_NONE:
case CB::PLUGIN_JACK:
case CB::PLUGIN_TYPE_COUNT:
return nullptr;
case CB::PLUGIN_SFZ:
case CB::PLUGIN_JSFX:
{
const CarlaScopedEnvVar csev("CARLA_DISCOVERY_PATH", pluginPath);
return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
}
case CB::PLUGIN_INTERNAL:
case CB::PLUGIN_LV2:
case CB::PLUGIN_AU:
return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
case CB::PLUGIN_LADSPA:
case CB::PLUGIN_DSSI:
case CB::PLUGIN_VST2:
wildcard = "*.so";
break;
case CB::PLUGIN_VST3:
// handled separately
break;
case CB::PLUGIN_CLAP:
wildcard = "*.clap";
break;
case CB::PLUGIN_DLS:
wildcard = "*.dls";
break;
case CB::PLUGIN_GIG:
wildcard = "*.gig";
break;
case CB::PLUGIN_SF2:
wildcard = "*.sf2";
break;
}
const std::vector<water::File> binaries(ptype == CB::PLUGIN_VST3 ? findVST3s(pluginPath)
: findBinaries(pluginPath, wildcard));
if (binaries.size() == 0)
return nullptr;
return new CarlaPluginDiscovery(discoveryTool, ptype, std::move(binaries), callback, callbackPtr);
}
bool carla_plugin_discovery_idle(CarlaPluginDiscoveryHandle handle)
{
return static_cast<CarlaPluginDiscovery*>(handle)->idle();
}
void carla_plugin_discovery_stop(CarlaPluginDiscoveryHandle handle)
{
delete static_cast<CarlaPluginDiscovery*>(handle);
}
// --------------------------------------------------------------------------------------------------------------------

View File

@ -28,6 +28,7 @@
#include "utils/Information.cpp"
#include "utils/JUCE.cpp"
#include "utils/PipeClient.cpp"
#include "utils/PluginDiscovery.cpp"
#include "utils/System.cpp"
#include "utils/Windows.cpp"