VST3: Plugin discovery

This commit is contained in:
Robin Gareus 2019-11-07 17:04:18 +01:00
parent 84a86fa21a
commit 2a9795113b
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
6 changed files with 840 additions and 82 deletions

View File

@ -42,10 +42,15 @@ namespace ARDOUR {
class Plugin;
#ifdef VST3_SUPPORT
struct VST3Info;
#endif
class LIBARDOUR_API PluginManager : public boost::noncopyable {
public:
static PluginManager& instance();
static std::string scanner_bin_path;
static std::string vst3_scanner_bin_path;
~PluginManager ();
@ -65,6 +70,8 @@ public:
void clear_vst_blacklist ();
void clear_au_cache ();
void clear_au_blacklist ();
void clear_vst3_cache ();
void clear_vst3_blacklist ();
const std::string get_default_windows_vst_path() const { return windows_vst_path; }
const std::string get_default_lxvst_path() const { return lxvst_path; }
@ -77,8 +84,8 @@ public:
*/
static std::string plugin_type_name (const PluginType, bool short_name = true);
bool cancelled () { return _cancel_scan; }
bool no_timeout () { return _cancel_timeout; }
bool cancelled () const { return _cancel_scan; }
bool no_timeout () const { return _cancel_timeout; }
void reset_stats ();
void stats_use_plugin (PluginInfoPtr const&);
@ -228,6 +235,8 @@ private:
void detect_name_ambiguities (ARDOUR::PluginInfoList*);
void detect_type_ambiguities (ARDOUR::PluginInfoList&);
void conceal_duplicates (ARDOUR::PluginInfoList*, ARDOUR::PluginInfoList*);
void load_statuses ();
void load_tags ();
void load_stats ();
@ -261,6 +270,10 @@ private:
int vst3_discover_from_path (std::string const& path, bool cache_only = false);
int vst3_discover (std::string const& path, bool cache_only = false);
#ifdef VST3_SUPPORT
void vst3_plugin (std::string const& module_path, VST3Info const&);
bool run_vst3_scanner_app (std::string bundle_path) const;
#endif
int lxvst_discover_from_path (std::string path, bool cache_only = false);
int lxvst_discover (std::string path, bool cache_only = false);

View File

@ -227,6 +227,7 @@ CONFIG_VARIABLE (bool, use_macvst, "use-macvst", true)
CONFIG_VARIABLE (bool, discover_vst_on_start, "discover-vst-on-start", false)
CONFIG_VARIABLE (bool, verbose_plugin_scan, "verbose-plugin-scan", false)
CONFIG_VARIABLE (bool, conceal_lv1_if_lv2_exists, "conceal-lv1-if-lv2-exists", true)
CONFIG_VARIABLE (bool, conceal_vst2_if_vst3_exists, "conceal-vst2-if-vst3-exists", true)
CONFIG_VARIABLE (int, vst_scan_timeout, "vst-scan-timeout", 1200) /* deciseconds, per plugin, <= 0 no timeout */
CONFIG_VARIABLE (bool, discover_audio_units, "discover-audio-units", false)
CONFIG_VARIABLE (bool, ask_replace_instrument, "ask-replace-instrument", true)

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2019 Robin Gareus <robin@gareus.org>
*
* 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
* (at your option) 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _ardour_vst3_scan_h_
#define _ardour_vst3_scan_h_
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include "pbd/xml++.h"
#include "ardour/libardour_visibility.h"
namespace ARDOUR {
class VST3PluginModule;
}
namespace ARDOUR {
struct VST3Info {
VST3Info ()
: index (0)
, n_inputs (0)
, n_outputs (0)
, n_aux_inputs (0)
, n_aux_outputs (0)
, n_midi_inputs (0)
, n_midi_outputs (0)
{}
VST3Info (XMLNode const&);
XMLNode& state () const;
int index;
std::string uid;
std::string name;
std::string vendor;
std::string category;
std::string version;
std::string sdk_version;
std::string url;
std::string email;
int n_inputs;
int n_outputs;
int n_aux_inputs;
int n_aux_outputs;
int n_midi_inputs;
int n_midi_outputs;
};
LIBARDOUR_API extern bool
discover_vst3 (boost::shared_ptr<VST3PluginModule>,
std::vector<VST3Info>&);
LIBARDOUR_API extern std::string
module_path_vst3 (std::string const& path);
LIBARDOUR_API extern std::string
vst3_cache_file (std::string const& module_path);
LIBARDOUR_API extern std::string
vst3_valid_cache_file (std::string const& module_path, bool verbose = false);
LIBARDOUR_API extern bool
vst3_scan_and_cache (std::string const& module_path, std::string const& bundle_path, boost::function<void (std::string const&, VST3Info const&)> cb);
} // namespace ARDOUR
#endif

View File

@ -79,7 +79,6 @@
#include <glibmm/miscutils.h>
#include <glibmm/pattern.h>
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#include "pbd/convert.h"
#include "pbd/file_utils.h"
@ -115,9 +114,10 @@
#endif
#ifdef VST3_SUPPORT
#include "pbd/basename.h"
#include "ardour/system_exec.h"
#include "ardour/vst3_module.h"
#include "ardour/vst3_plugin.h"
#include "ardour/vst3_scan.h"
#endif
#include "pbd/error.h"
@ -133,6 +133,7 @@ using namespace std;
PluginManager* PluginManager::_instance = 0;
std::string PluginManager::scanner_bin_path = "";
std::string PluginManager::vst3_scanner_bin_path = "";
PluginManager&
PluginManager::instance()
@ -158,7 +159,7 @@ PluginManager::PluginManager ()
char* s;
string lrdf_path;
#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT
#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT || defined VST3_SUPPORT
// source-tree (ardev, etc)
PBD::Searchpath vstsp(Glib::build_filename(ARDOUR::ardour_dll_directory(), "fst"));
@ -170,6 +171,7 @@ PluginManager::PluginManager ()
vstsp += ARDOUR::ardour_dll_directory();
#endif
#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT
if (!PBD::find_file (vstsp,
#ifdef PLATFORM_WINDOWS
#ifdef DEBUGGABLE_SCANNER_APP
@ -187,7 +189,20 @@ PluginManager::PluginManager ()
, scanner_bin_path)) {
PBD::warning << "VST scanner app (ardour-vst-scanner) not found in path " << vstsp.to_string() << endmsg;
}
#endif // VST2
#ifdef VST3_SUPPORT
if (!PBD::find_file (vstsp,
#ifdef PLATFORM_WINDOWS
"ardour-vst3-scanner.exe"
#else
"ardour-vst3-scanner"
#endif
, vst3_scanner_bin_path)) {
PBD::warning << "VST3 scanner app (ardour-vst3-scanner) not found in path " << vstsp.to_string() << endmsg;
}
#endif // VST3_SUPPORT
#endif // any VST
load_statuses ();
load_tags ();
@ -349,6 +364,25 @@ PluginManager::detect_type_ambiguities (PluginInfoList& pil)
}
}
void
PluginManager::conceal_duplicates (ARDOUR::PluginInfoList* old, ARDOUR::PluginInfoList* nu)
{
if (!old) {
return;
}
for (PluginInfoList::const_iterator i = old->begin(); i != old->end(); ++i) {
for (PluginInfoList::const_iterator j = nu->begin(); j != nu->end(); ++j) {
if ((*i)->creator == (*j)->creator && (*i)->name == (*j)->name) {
PluginStatus ps ((*i)->type, (*i)->unique_id, Concealed);
if (find (statuses.begin(), statuses.end(), ps) == statuses.end()) {
statuses.erase (ps);
statuses.insert (ps);
}
}
}
}
}
void
PluginManager::refresh (bool cache_only)
{
@ -369,25 +403,10 @@ PluginManager::refresh (bool cache_only)
BootMessage (_("Scanning LV2 Plugins"));
lv2_refresh ();
if (Config->get_conceal_lv1_if_lv2_exists()) {
for (PluginInfoList::const_iterator i = _ladspa_plugin_info->begin(); i != _ladspa_plugin_info->end(); ++i) {
for (PluginInfoList::const_iterator j = _lv2_plugin_info->begin(); j != _lv2_plugin_info->end(); ++j) {
if ((*i)->creator == (*j)->creator && (*i)->name == (*j)->name) {
PluginStatus ps (LADSPA, (*i)->unique_id, Concealed);
if (find (statuses.begin(), statuses.end(), ps) == statuses.end()) {
statuses.erase (ps);
statuses.insert (ps);
}
}
}
}
} else {
for (PluginStatusList::iterator i = statuses.begin(); i != statuses.end();) {
PluginStatusList::iterator j = i++;
if ((*j).status == Concealed) {
statuses.erase (j);
}
}
bool conceal_lv1 = Config->get_conceal_lv1_if_lv2_exists();
if (conceal_lv1) {
conceal_duplicates (_ladspa_plugin_info, _lv2_plugin_info);
}
#ifdef WINDOWS_VST_SUPPORT
@ -428,20 +447,16 @@ PluginManager::refresh (bool cache_only)
#endif //Native Mac VST SUPPORT
#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT)
if (!cache_only) {
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), VST_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
gchar *bl = NULL;
if (g_file_get_contents(fn.c_str (), &bl, NULL, NULL)) {
if (Config->get_verbose_plugin_scan()) {
PBD::info << _("VST Blacklist: ") << fn << "\n" << bl << "-----" << endmsg;
} else {
PBD::info << _("VST Blacklist:") << "\n" << bl << "-----" << endmsg;
}
g_free (bl);
}
if (!cache_only) {
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), VST_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
try {
std::string bl = Glib::file_get_contents (fn);
PBD::info << _("VST 2 Blacklist: ") << "\n" << bl << "-----" << endmsg;
} catch (Glib::FileError const& err) {
}
}
}
#endif
#ifdef VST3_SUPPORT
@ -451,6 +466,25 @@ PluginManager::refresh (bool cache_only)
BootMessage (_("Discovering VST3 Plugins"));
}
vst3_refresh (cache_only);
if (!cache_only) {
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "vst3_blacklist.txt");
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
try {
std::string bl = Glib::file_get_contents (fn);
PBD::info << _("VST 3 Blacklist: ") << "\n" << bl << "-----" << endmsg;
} catch (Glib::FileError const& err) {
}
}
}
bool conceal_vst2 = Config->get_conceal_vst2_if_vst3_exists();
if (conceal_vst2) {
conceal_duplicates (_windows_vst_plugin_info, _vst3_plugin_info);
conceal_duplicates (_lxvst_plugin_info, _vst3_plugin_info);
conceal_duplicates (_mac_vst_plugin_info, _vst3_plugin_info);
}
#else
bool conceal_vst2 = false;
#endif
#ifdef AUDIOUNIT_SUPPORT
@ -462,6 +496,22 @@ PluginManager::refresh (bool cache_only)
au_refresh (cache_only);
#endif
/* unset concealed plugins */
if (!conceal_lv1 || !conceal_vst2) {
for (PluginStatusList::iterator i = statuses.begin(); i != statuses.end();) {
PluginStatusList::iterator j = i++;
if ((*j).status != Concealed) {
continue;
}
if (!conceal_lv1 && (*j).type == LADSPA) {
statuses.erase (j);
}
if (!conceal_vst2 && ((*j).type == Windows_VST || (*j).type == LXVST || (*j).type == MacVST)) {
statuses.erase (j);
}
}
}
BootMessage (_("Plugin Scan Complete..."));
PluginListChanged (); /* EMIT SIGNAL */
PluginScanMessage(X_("closeme"), "", false);
@ -1447,7 +1497,88 @@ PluginManager::lxvst_discover (string path, bool cache_only)
#endif // LXVST_SUPPORT
void
PluginManager::clear_vst3_cache ()
{
#ifdef VST3_SUPPORT
string dn = Glib::build_filename (ARDOUR::user_cache_directory(), "vst");
vector<string> v3i_files;
find_files_matching_regex (v3i_files, dn, "\\.v3i$", false);
for (vector<string>::iterator i = v3i_files.begin(); i != v3i_files.end (); ++i) {
::g_unlink(i->c_str());
}
#endif
}
void
PluginManager::clear_vst3_blacklist ()
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), "vst3_blacklist.txt");
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
::g_unlink(fn.c_str());
}
}
#ifdef VST3_SUPPORT
static void vst3_blacklist (string const& module_path)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), "vst3_blacklist.txt");
FILE* f = NULL;
if (! (f = g_fopen (fn.c_str (), "a"))) {
PBD::error << string_compose (_("Cannot write to VST3 blacklist file '%1'"), fn) << endmsg;
return;
}
fprintf (f, "%s\n", module_path.c_str ());
::fclose (f);
}
static void vst3_whitelist (string module_path)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), "vst3_blacklist.txt");
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return;
}
::g_unlink (fn.c_str ());
module_path += "\n"; // add separator
const size_t rpl = bl.find (module_path);
if (rpl != string::npos) {
bl.replace (rpl, module_path.size (), "");
}
if (bl.empty ()) {
return;
}
Glib::file_set_contents (fn, bl);
}
static bool vst3_is_blacklisted (string const& module_path)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), "vst3_blacklist.txt");
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return false;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return false;
}
return bl.find (module_path + "\n") != string::npos;
}
static bool vst3_filter (const string& str, void*)
{
return str[0] != '.' && (str.length() > 4 && str.find (".vst3") == (str.length() - 5));
}
void
PluginManager::vst3_refresh (bool cache_only)
@ -1468,11 +1599,6 @@ PluginManager::vst3_refresh (bool cache_only)
#endif
}
static bool vst3_filter (const string& str, void*)
{
return str[0] != '.' && (str.length() > 4 && str.find (".vst3") == (str.length() - 5));
}
int
PluginManager::vst3_discover_from_path (string const& path, bool cache_only)
{
@ -1496,63 +1622,181 @@ PluginManager::vst3_discover_from_path (string const& path, bool cache_only)
return cancelled() ? -1 : 0;
}
static std::string vst3_bindir () {
#ifdef __APPLE__
return "MacOS";
#elif defined PLATFORM_WINDOWS
# if defined __x86_64__ || defined _M_X64
return "x86_64-win";
# else
return "x86-win";
# endif
#else // Linux
# if defined __x86_64__ || defined _M_X64
return "x86_64-linux";
# elif defined __i386__ || defined _M_IX86
return "i386-linux";
#endif
// ARM, PPC ?
#endif
return "";
}
void
PluginManager::vst3_plugin (string const& module_path, VST3Info const& i)
{
PluginInfoPtr info (new VST3PluginInfo ());
static std::string vst3_suffix () {
#ifdef __APPLE__
return "";
#elif defined PLATFORM_WINDOWS
return ".vst3";
#else // Linux
return ".so";
#endif
info->path = module_path;
info->index = i.index;
info->unique_id = i.uid;
info->name = i.name;
info->category = i.category; // TODO process split at "|" -> tags
info->creator = i.vendor;
info->n_inputs = ChanCount();
info->n_outputs = ChanCount();
info->n_inputs.set_audio (i.n_inputs + i.n_aux_inputs);
info->n_inputs.set_midi (i.n_midi_inputs);
info->n_outputs.set_audio (i.n_outputs + i.n_aux_outputs);
info->n_outputs.set_midi (i.n_midi_outputs);
_vst3_plugin_info->push_back (info);
if (!info->category.empty ()) {
set_tags (info->type, info->unique_id, info->category, info->name, FromPlug);
}
}
int
PluginManager::vst3_discover (string const& path, bool cache_only)
{
string module_path;
if (!Glib::file_test (path, Glib::FILE_TEST_IS_DIR)) {
module_path = path;
} else {
module_path = Glib::build_filename (path, "Contents",
vst3_bindir (), PBD::basename_nosuffix (path) + vst3_suffix ());
}
if (!Glib::file_test (module_path, Glib::FILE_TEST_IS_REGULAR)) {
cerr << "VST3 not a valid bundle: '" << module_path << "'\n";
string module_path = module_path_vst3 (path);
if (module_path.empty ()) {
return -1;
}
if (vst3_is_blacklisted (module_path)) {
return -1;
}
ARDOUR::PluginScanMessage(_("VST3"), module_path, !(cache_only || cancelled()));
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("VST3: discover %1 (%2)\n", path, module_path));
try {
boost::shared_ptr<VST3PluginModule> m = VST3PluginModule::load (module_path);
} catch (...) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load VST3 at '%1'\n", path));
if (!cache_only && vst3_scanner_bin_path.empty ()) {
/* direct scan in the host's process */
vst3_blacklist (module_path);
if (! vst3_scan_and_cache (module_path, path, sigc::mem_fun (*this, &PluginManager::vst3_plugin))) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load VST3 at '%1'\n", path));
return -1;
}
vst3_whitelist (module_path);
return 0;
}
string cache_file = vst3_valid_cache_file (module_path);
bool run_scan = false;
XMLTree tree;
if (cache_file.empty ()) {
run_scan = true;
} else if (tree.read (cache_file)) {
/* valid cache file was found, now check version */
int cf_version = 0;
if (!tree.root()->get_property ("version", cf_version) || cf_version < 1) {
run_scan = true;
}
} else {
/* failed to parse XML */
run_scan = true;
}
if (!cache_only && run_scan) {
/* re/generate cache file */
vst3_blacklist (module_path);
if (!run_vst3_scanner_app (path)) {
return -1;
}
cache_file = vst3_valid_cache_file (module_path);
if (cache_file.empty ()) {
return -1;
}
/* re-read cache file */
if (!tree.read (cache_file)) {
error << string_compose (_("Cannot parse VST3 cache file '%1' for plugin '%2'"), cache_file, module_path) << endmsg;
return -1;
}
run_scan = false; // mark as scanned
}
if (cache_file.empty () || run_scan) {
/* cache file does not exist and cache_only == true,
* or cache file is invalid (scan needed)
*/
return -1;
}
std::string module;
if (!tree.root()->get_property ("module", module) || module != module_path) {
error << string_compose (_("Invalid VST3 cache file '%1' for plugin '%2'"), cache_file, module_path) << endmsg;
return -1;
}
vst3_whitelist (module_path);
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
try {
VST3Info nfo (**i);
vst3_plugin (module_path, nfo);
} catch (...) {
error << string_compose (_("Corrupt VST3 cache file '%1' for plugin '%2'"), cache_file, module_path) << endmsg;
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load VST3 at '%1'\n", path));
continue;
}
}
return 0;
}
static void vst3_scanner_log (std::string msg, std::string bundle_path)
{
PBD::error << string_compose ("VST<%1> scanner: %2", bundle_path, msg) << endmsg;
}
bool
PluginManager::run_vst3_scanner_app (std::string bundle_path) const
{
char **argp= (char**) calloc (5, sizeof (char*));
argp[0] = strdup (vst3_scanner_bin_path.c_str ());
argp[1] = strdup ("-q");
argp[2] = strdup ("-f");
argp[3] = strdup (bundle_path.c_str ());
argp[4] = 0;
ARDOUR::SystemExec scanner (vst3_scanner_bin_path, argp);
PBD::ScopedConnection c;
scanner.ReadStdout.connect_same_thread (c, boost::bind (&vst3_scanner_log, _1, bundle_path));
if (scanner.start (ARDOUR::SystemExec::MergeWithStdin)) {
PBD::error << string_compose (_("Cannot launch VST scanner app '%1': %2"), scanner_bin_path, strerror (errno)) << endmsg;
return false;
}
int timeout = Config->get_vst_scan_timeout(); // deciseconds
bool notime = (timeout <= 0);
ARDOUR::PluginScanTimeout (timeout);
while (scanner.is_running () && (notime || timeout > 0)) {
if (!notime && !no_timeout ()) {
if (timeout % 5 == 0) {
ARDOUR::PluginScanTimeout (timeout);
}
--timeout;
}
ARDOUR::GUIIdle ();
Glib::usleep (100000);
if (cancelled () || (!notime && timeout == 0)) {
scanner.terminate ();
/* may be partially written */
std::string module_path = module_path_vst3 (bundle_path);
if (!module_path.empty ()) {
g_unlink (vst3_cache_file (module_path).c_str ());
}
vst3_whitelist (module_path);
return false;
}
}
return true;
}
#endif // VST3_SUPPORT

414
libs/ardour/vst3_scan.cc Normal file
View File

@ -0,0 +1,414 @@
/*
* Copyright (C) 2019 Robin Gareus <robin@gareus.org>
*
* 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
* (at your option) 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <iostream>
#include "pbd/gstdio_compat.h"
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#ifdef COMPILER_MSVC
#include <sys/utime.h>
#else
#include <utime.h>
#endif
#include "vst3/vst3.h"
#include "pbd/basename.h"
#include "pbd/error.h"
#include "pbd/failed_constructor.h"
#include "ardour/filesystem_paths.h"
#include "ardour/vst3_module.h"
#include "ardour/vst3_host.h"
#include "ardour/vst3_scan.h"
using namespace std;
using namespace Steinberg;
static int32
count_channels (Vst::IComponent* c, Vst::MediaType media, Vst::BusDirection dir, Vst::BusType type)
{
/* see also libs/ardour/vst3_plugin.cc VST3PI::count_channels */
int32 n_busses = c->getBusCount (media, dir);
int32 n_channels = 0;
for (int32 i = 0; i < n_busses; ++i) {
Vst::BusInfo bus;
if (c->getBusInfo (media, dir, i, bus) == kResultTrue && bus.busType == type) {
#if 1
if ((type == Vst::kMain && i != 0) || (type == Vst::kAux && i != 1)) {
/* For now allow we only support one main bus, and one aux-bus.
* Also an aux-bus by itself is currently N/A.
*/
std::cerr << "VST3: Ignored extra bus\n";
continue;
}
#endif
if (media == Vst::kEvent) {
/* MIDI Channel count -> MIDI input */
return std::min<int32> (1, bus.channelCount);
} else {
n_channels += bus.channelCount;
}
}
}
return n_channels;
}
bool
ARDOUR::discover_vst3 (boost::shared_ptr<VST3PluginModule> m, std::vector<VST3Info>& rv)
{
using namespace std;
GetFactoryProc fp = (GetFactoryProc)m->fn_ptr ("GetPluginFactory");
if (!fp) {
cerr << "Failed to find 'GetPluginFactory' function\n";
return false;
}
IPluginFactory* factory = fp ();
if (!factory) {
cerr << "Failed to get VST3 plug-in factory\n";
return false;
}
PFactoryInfo fi;
if (factory->getFactoryInfo (&fi) != kResultTrue) {
cerr << "Failed to get VST3 factory info\n";
return false;
}
//cout << "FactoryInfo: '" << fi.vendor << "' '" << fi.url << "' '" << fi.email << "'" << "\n";
IPluginFactory2* factory2 = FUnknownPtr<IPluginFactory2> (factory);
int32 class_cnt = factory->countClasses ();
for (int32 i = 0; i < class_cnt; ++i) {
PClassInfo ci;
if (factory->getClassInfo (i, &ci) == kResultTrue) {
if (strcmp (ci.category, kVstAudioEffectClass)) {
continue;
}
VST3Info nfo;
TUID uid;
//cout << "FOUND: " << i << " '" << ci.name << "' '" << ci.category << "'" << "\n";
/* pre-fill with factory settings */
nfo.vendor = strlen (fi.vendor) == 0 ? "Unknown" : fi.vendor;
nfo.url = fi.url;
nfo.email = fi.email;
PClassInfo2 ci2;
if (factory2 && factory2->getClassInfo2 (i, &ci2) == kResultTrue) {
memcpy (uid, ci2.cid, sizeof (TUID));
nfo.name = ci2.name;
if (strlen (ci2.vendor) > 0) {
nfo.vendor = ci2.vendor;
}
nfo.category = ci2.subCategories;
nfo.version = ci2.version;
nfo.sdk_version = ci2.sdkVersion;
} else {
memcpy (uid, ci.cid, sizeof (TUID));
nfo.name = ci.name;
nfo.version = "0.0.0";
nfo.sdk_version = "VST 3";
}
nfo.index = i;
{
char suid[33] = "";
FUID::fromTUID (uid).toString (suid);
nfo.uid = suid;
}
Vst::IComponent* component;
if (factory->createInstance (uid, Vst::IComponent::iid, (void**)&component) != kResultTrue) {
cerr << "Failed to create VST3 component\n";
continue;
}
// TODO init params
if (component->initialize (HostApplication::getHostContext ()) != kResultOk) {
cerr << "Failed to initialize VST3 component\n";
//component->terminate();
continue;
}
FUnknownPtr<Vst::IAudioProcessor> processor;
if (!(processor = FUnknownPtr<Vst::IAudioProcessor> (component))) {
cerr << "VST3: No valid processor";
//controller->terminate();
component->terminate ();
continue;
}
if (processor->canProcessSampleSize (Vst::kSample32) != kResultTrue) {
cerr << "VST3: Cannot process 32bit float";
component->terminate ();
continue;
}
nfo.n_inputs = count_channels (component, Vst::kAudio, Vst::kInput, Vst::kMain);
nfo.n_aux_inputs = count_channels (component, Vst::kAudio, Vst::kInput, Vst::kAux);
nfo.n_outputs = count_channels (component, Vst::kAudio, Vst::kOutput, Vst::kMain);
nfo.n_aux_outputs = count_channels (component, Vst::kAudio, Vst::kOutput, Vst::kAux);
nfo.n_midi_inputs = count_channels (component, Vst::kEvent, Vst::kInput, Vst::kMain);
nfo.n_midi_outputs = count_channels (component, Vst::kEvent, Vst::kOutput, Vst::kMain);
//controller->terminate();
component->terminate ();
rv.push_back (nfo);
}
}
return true;
}
static std::string vst3_bindir () {
#ifdef __APPLE__
return "MacOS";
#elif defined PLATFORM_WINDOWS
# if defined __x86_64__ || defined _M_X64
return "x86_64-win";
# else
return "x86-win";
# endif
#else // Linux
# if defined __x86_64__ || defined _M_X64
return "x86_64-linux";
# elif defined __i386__ || defined _M_IX86
return "i386-linux";
# elif defined __aarch64__
return "aarch64-linux";
# elif defined __arm__ && defined ARM_NEON_SUPPORT
return "armv7hl-linux";
# elif defined __arm__
return "armv7l-linux";
#else
// other ARM, linux-PPC ?
return "*-linux"; // XXX
#endif
#endif
return "";
}
static std::string vst3_suffix () {
#ifdef __APPLE__
return "";
#elif defined PLATFORM_WINDOWS
return ".vst3";
#else // Linux
return ".so";
#endif
}
std::string
ARDOUR::module_path_vst3 (string const& path)
{
string module_path;
if (!Glib::file_test (path, Glib::FILE_TEST_IS_DIR)) {
module_path = path;
} else {
module_path = Glib::build_filename (path, "Contents",
vst3_bindir (), PBD::basename_nosuffix (path) + vst3_suffix ());
}
if (!Glib::file_test (module_path, Glib::FILE_TEST_IS_REGULAR)) {
cerr << "VST3 not a valid bundle: '" << module_path << "'\n";
return "";
}
return module_path;
}
static string
vst3_info_cache_dir ()
{
string dir = Glib::build_filename (ARDOUR::user_cache_directory (), "vst");
/* if the directory doesn't exist, try to create it */
if (!Glib::file_test (dir, Glib::FILE_TEST_IS_DIR)) {
if (g_mkdir (dir.c_str (), 0700)) {
PBD::fatal << "Cannot create VST info folder '" << dir << "'" << endmsg;
}
}
return dir;
}
#include "sha1.c"
string
ARDOUR::vst3_cache_file (std::string const& module_path)
{
char hash[41];
Sha1Digest s;
sha1_init (&s);
sha1_write (&s, (const uint8_t *) module_path.c_str(), module_path.size());
sha1_result_hash (&s, hash);
return Glib::build_filename (vst3_info_cache_dir (), std::string (hash) + std::string (".v3i"));
}
string
ARDOUR::vst3_valid_cache_file (std::string const& module_path, bool verbose)
{
string const cache_file = ARDOUR::vst3_cache_file (module_path);
if (!Glib::file_test (cache_file, Glib::FileTest (Glib::FILE_TEST_EXISTS | Glib::FILE_TEST_IS_REGULAR))) {
return "";
}
if (verbose) {
PBD::info << "Found cache file: '" << cache_file <<"'" << endmsg;
}
GStatBuf sb_vst;
GStatBuf sb_v3i;
if (g_stat (module_path.c_str(), &sb_vst) == 0 && g_stat (cache_file.c_str (), &sb_v3i) == 0) {
if (sb_vst.st_mtime < sb_v3i.st_mtime) {
/* plugin is older than cache file */
if (verbose) {
PBD::info << "Cache file is up-to-date." << endmsg;
}
return cache_file;
} else if (verbose) {
PBD::info << "Stale cache." << endmsg;
}
}
return "";
}
static void
touch_cachefile (std::string const& module_path, std::string const& cache_file)
{
GStatBuf sb_vst;
GStatBuf sb_v3i;
if (g_stat (module_path.c_str(), &sb_vst) == 0 && g_stat (cache_file.c_str (), &sb_v3i) == 0) {
struct utimbuf utb;
utb.actime = sb_v3i.st_atime;
utb.modtime = std::max (sb_vst.st_mtime, sb_v3i.st_mtime);
g_utime (cache_file.c_str (), &utb);
}
}
static bool
vst3_save_cache_file (std::string const& module_path, XMLNode* root)
{
string const cache_file = ARDOUR::vst3_cache_file (module_path);
XMLTree tree;
tree.set_root (root);
if (!tree.write (cache_file)) {
PBD::error << "Could not save VST3 plugin cache to: " << cache_file << endmsg;
return false;
} else {
touch_cachefile (module_path, cache_file);
return true;
}
}
bool
ARDOUR::vst3_scan_and_cache (std::string const& module_path, std::string const& bundle_path, boost::function<void (std::string const&, VST3Info const&)> cb)
{
XMLNode* root = new XMLNode ("VST3Cache");
root->set_property ("version", 1);
root->set_property ("bundle", bundle_path);
root->set_property ("module", module_path);
try {
boost::shared_ptr<VST3PluginModule> m = VST3PluginModule::load (module_path);
std::vector<VST3Info> nfo;
discover_vst3 (m, nfo);
for (std::vector<VST3Info>::const_iterator i = nfo.begin(); i != nfo.end(); ++i) {
cb (module_path, *i);
root->add_child_nocopy (i->state ());
}
} catch (...) {
cerr << "Cannot load VST3 module: '" << module_path << "'\n";
delete root;
return false;
}
return vst3_save_cache_file (module_path, root);
}
using namespace ARDOUR;
VST3Info::VST3Info (XMLNode const& node)
: index (0)
, n_inputs (0)
, n_outputs (0)
, n_aux_inputs (0)
, n_aux_outputs (0)
, n_midi_inputs (0)
, n_midi_outputs (0)
{
bool err = false;
if (node.name() != "VST3Info") {
throw failed_constructor ();
}
err |= !node.get_property ("index", index);
err |= !node.get_property ("uid", uid);
err |= !node.get_property ("name", name);
err |= !node.get_property ("vendor", vendor);
err |= !node.get_property ("category", category);
err |= !node.get_property ("version", version);
err |= !node.get_property ("sdk-version", sdk_version);
err |= !node.get_property ("url", url);
err |= !node.get_property ("email", email);
err |= !node.get_property ("n_inputs", n_inputs);
err |= !node.get_property ("n_outputs", n_outputs);
err |= !node.get_property ("n_aux_inputs", n_aux_inputs);
err |= !node.get_property ("n_aux_outputs", n_aux_outputs);
err |= !node.get_property ("n_midi_inputs", n_midi_inputs);
err |= !node.get_property ("n_midi_outputs", n_midi_outputs);
if (err) {
throw failed_constructor ();
}
}
XMLNode&
VST3Info::state () const
{
XMLNode* node = new XMLNode("VST3Info");
node->set_property ("index", index);
node->set_property ("uid", uid);
node->set_property ("name", name);
node->set_property ("vendor", vendor);
node->set_property ("category", category);
node->set_property ("version", version);
node->set_property ("sdk-version", sdk_version);
node->set_property ("url", url);
node->set_property ("email", email);
node->set_property ("n_inputs", n_inputs);
node->set_property ("n_outputs", n_outputs);
node->set_property ("n_aux_inputs", n_aux_inputs);
node->set_property ("n_aux_outputs", n_aux_outputs);
node->set_property ("n_midi_inputs", n_midi_inputs);
node->set_property ("n_midi_outputs", n_midi_outputs);
return *node;
}

View File

@ -460,7 +460,7 @@ def build(bld):
obj.defines += [ 'MACVST_SUPPORT' ]
if bld.is_defined('VST3_SUPPORT'):
obj.source += [ 'vst3_plugin.cc', 'vst3_module.cc', 'vst3_host.cc' ]
obj.source += [ 'vst3_plugin.cc', 'vst3_module.cc', 'vst3_host.cc', 'vst3_scan.cc' ]
obj.defines += [ 'VST3_SUPPORT' ]
if bld.is_defined('HAVE_COREAUDIO'):