#include "rack_vst3.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivstcomponent.h"
#include <vector>
#include <string>
#include <algorithm>
#include <cstring>
#if defined(__APPLE__)
#include <CoreFoundation/CoreFoundation.h>
#include <dirent.h>
#include <sys/stat.h>
#elif defined(_WIN32)
#include <windows.h>
#include <shlobj.h>
#else
#include <unistd.h>
#include <pwd.h>
#include <dirent.h>
#include <sys/stat.h>
#endif
using namespace VST3;
using namespace Steinberg;
using namespace Steinberg::Vst;
struct RackVST3Scanner {
std::vector<std::string> search_paths;
};
static std::vector<std::string> scan_directory_for_vst3(const std::string& dir_path) {
std::vector<std::string> vst3_paths;
#if defined(_WIN32)
std::string search_path = dir_path + "\\*.vst3";
WIN32_FIND_DATAA find_data;
HANDLE find_handle = FindFirstFileA(search_path.c_str(), &find_data);
if (find_handle != INVALID_HANDLE_VALUE) {
do {
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
std::string full_path = dir_path + "\\" + find_data.cFileName;
vst3_paths.push_back(full_path);
}
} while (FindNextFileA(find_handle, &find_data));
FindClose(find_handle);
}
#else
DIR* dir = opendir(dir_path.c_str());
if (!dir) {
return vst3_paths;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
std::string name = entry->d_name;
if (name == "." || name == "..") {
continue;
}
if (name.length() > 5 && name.substr(name.length() - 5) == ".vst3") {
std::string full_path = dir_path + "/" + name;
struct stat st;
if (stat(full_path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
vst3_paths.push_back(full_path);
}
}
}
closedir(dir);
#endif
return vst3_paths;
}
static std::vector<std::string> get_default_vst3_paths() {
std::vector<std::string> paths;
#if defined(__APPLE__)
paths.push_back("/Library/Audio/Plug-Ins/VST3");
const char* home = getenv("HOME");
if (home) {
std::string user_path = std::string(home) + "/Library/Audio/Plug-Ins/VST3";
paths.push_back(user_path);
}
#elif defined(_WIN32)
char common_files[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROGRAM_FILES_COMMON, NULL, 0, common_files))) {
std::string path = std::string(common_files) + "\\VST3";
paths.push_back(path);
}
#else
paths.push_back("/usr/lib/vst3");
paths.push_back("/usr/local/lib/vst3");
const char* home = getenv("HOME");
if (home) {
std::string user_path = std::string(home) + "/.vst3";
paths.push_back(user_path);
}
#endif
return paths;
}
static std::string uid_to_string(const VST3::UID& uid) {
return uid.toString();
}
static RackVST3PluginType determine_plugin_type(const std::string& subcategories) {
if (subcategories.find("Instrument") != std::string::npos) {
return RACK_VST3_TYPE_INSTRUMENT;
} else if (subcategories.find("Analyzer") != std::string::npos) {
return RACK_VST3_TYPE_ANALYZER;
} else if (subcategories.find("Spatial") != std::string::npos) {
return RACK_VST3_TYPE_SPATIAL;
} else if (subcategories.find("Fx") != std::string::npos) {
return RACK_VST3_TYPE_EFFECT;
}
return RACK_VST3_TYPE_OTHER;
}
RackVST3Scanner* rack_vst3_scanner_new(void) {
return new(std::nothrow) RackVST3Scanner();
}
void rack_vst3_scanner_free(RackVST3Scanner* scanner) {
delete scanner;
}
int rack_vst3_scanner_add_path(RackVST3Scanner* scanner, const char* path) {
if (!scanner || !path) {
return RACK_VST3_ERROR_INVALID_PARAM;
}
scanner->search_paths.push_back(path);
return RACK_VST3_OK;
}
int rack_vst3_scanner_add_default_paths(RackVST3Scanner* scanner) {
if (!scanner) {
return RACK_VST3_ERROR_INVALID_PARAM;
}
auto paths = get_default_vst3_paths();
scanner->search_paths.insert(scanner->search_paths.end(), paths.begin(), paths.end());
return RACK_VST3_OK;
}
int rack_vst3_scanner_scan(RackVST3Scanner* scanner, RackVST3PluginInfo* plugins, size_t max_plugins) {
if (!scanner) {
return RACK_VST3_ERROR_INVALID_PARAM;
}
bool count_only = (plugins == nullptr);
size_t count = 0;
std::vector<std::string> paths_to_scan = scanner->search_paths;
if (paths_to_scan.empty()) {
paths_to_scan = get_default_vst3_paths();
}
std::vector<std::string> module_paths;
for (const auto& search_path : paths_to_scan) {
auto found_bundles = scan_directory_for_vst3(search_path);
module_paths.insert(module_paths.end(), found_bundles.begin(), found_bundles.end());
}
auto system_modules = Hosting::Module::getModulePaths();
module_paths.insert(module_paths.end(), system_modules.begin(), system_modules.end());
std::sort(module_paths.begin(), module_paths.end());
module_paths.erase(std::unique(module_paths.begin(), module_paths.end()), module_paths.end());
for (const auto& module_path : module_paths) {
std::string error_description;
auto module = Hosting::Module::create(module_path, error_description);
if (!module) {
continue;
}
const auto& factory = module->getFactory();
auto class_infos = factory.classInfos();
for (const auto& class_info : class_infos) {
if (class_info.category() != kVstAudioEffectClass) {
continue;
}
if (count_only) {
count++;
continue;
}
if (count >= max_plugins) {
count++;
continue;
}
RackVST3PluginInfo& info = plugins[count];
std::string name = class_info.name();
strncpy(info.name, name.c_str(), sizeof(info.name) - 1);
info.name[sizeof(info.name) - 1] = '\0';
std::string vendor = class_info.vendor();
if (vendor.empty()) {
vendor = factory.info().vendor();
}
strncpy(info.manufacturer, vendor.c_str(), sizeof(info.manufacturer) - 1);
info.manufacturer[sizeof(info.manufacturer) - 1] = '\0';
strncpy(info.path, module_path.c_str(), sizeof(info.path) - 1);
info.path[sizeof(info.path) - 1] = '\0';
std::string uid_str = uid_to_string(class_info.ID());
strncpy(info.unique_id, uid_str.c_str(), sizeof(info.unique_id) - 1);
info.unique_id[sizeof(info.unique_id) - 1] = '\0';
std::string version_str = class_info.version();
uint32_t version = 0;
if (!version_str.empty()) {
int major = 0, minor = 0, patch = 0, build = 0;
int parsed = sscanf(version_str.c_str(), "%d.%d.%d.%d", &major, &minor, &patch, &build);
if (parsed >= 1) {
auto clamp_byte = [](int val) -> uint8_t {
return static_cast<uint8_t>(std::max(0, std::min(255, val)));
};
uint8_t major_byte = clamp_byte(major);
uint8_t minor_byte = clamp_byte(minor);
uint8_t patch_byte = clamp_byte(patch);
uint8_t build_byte = clamp_byte(build);
version = (static_cast<uint32_t>(major_byte) << 24) |
(static_cast<uint32_t>(minor_byte) << 16) |
(static_cast<uint32_t>(patch_byte) << 8) |
static_cast<uint32_t>(build_byte);
}
}
info.version = version;
std::string subcategories = class_info.subCategoriesString();
info.plugin_type = determine_plugin_type(subcategories);
strncpy(info.category, subcategories.c_str(), sizeof(info.category) - 1);
info.category[sizeof(info.category) - 1] = '\0';
count++;
}
}
return static_cast<int>(count);
}