From 2a9795113b2fdefa8361d3d547053be9b7ecf6ce Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 7 Nov 2019 17:04:18 +0100 Subject: [PATCH] VST3: Plugin discovery --- libs/ardour/ardour/plugin_manager.h | 17 +- libs/ardour/ardour/rc_configuration_vars.h | 1 + libs/ardour/ardour/vst3_scan.h | 86 +++++ libs/ardour/plugin_manager.cc | 402 ++++++++++++++++---- libs/ardour/vst3_scan.cc | 414 +++++++++++++++++++++ libs/ardour/wscript | 2 +- 6 files changed, 840 insertions(+), 82 deletions(-) create mode 100644 libs/ardour/ardour/vst3_scan.h create mode 100644 libs/ardour/vst3_scan.cc diff --git a/libs/ardour/ardour/plugin_manager.h b/libs/ardour/ardour/plugin_manager.h index 191a305b7d..a9f96279fb 100644 --- a/libs/ardour/ardour/plugin_manager.h +++ b/libs/ardour/ardour/plugin_manager.h @@ -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); diff --git a/libs/ardour/ardour/rc_configuration_vars.h b/libs/ardour/ardour/rc_configuration_vars.h index 9c20eeb8fb..7b9f46703e 100644 --- a/libs/ardour/ardour/rc_configuration_vars.h +++ b/libs/ardour/ardour/rc_configuration_vars.h @@ -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) diff --git a/libs/ardour/ardour/vst3_scan.h b/libs/ardour/ardour/vst3_scan.h new file mode 100644 index 0000000000..d37f9019cf --- /dev/null +++ b/libs/ardour/ardour/vst3_scan.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 Robin Gareus + * + * 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 +#include +#include + +#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, + std::vector&); + +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 cb); + +} // namespace ARDOUR + +#endif diff --git a/libs/ardour/plugin_manager.cc b/libs/ardour/plugin_manager.cc index ae31c7e927..e1312db23b 100644 --- a/libs/ardour/plugin_manager.cc +++ b/libs/ardour/plugin_manager.cc @@ -79,7 +79,6 @@ #include #include #include -#include #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 v3i_files; + find_files_matching_regex (v3i_files, dn, "\\.v3i$", false); + for (vector::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 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 diff --git a/libs/ardour/vst3_scan.cc b/libs/ardour/vst3_scan.cc new file mode 100644 index 0000000000..ca9fb6a9b7 --- /dev/null +++ b/libs/ardour/vst3_scan.cc @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2019 Robin Gareus + * + * 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 + +#include "pbd/gstdio_compat.h" +#include +#include + +#ifdef COMPILER_MSVC +#include +#else +#include +#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 (1, bus.channelCount); + } else { + n_channels += bus.channelCount; + } + } + } + return n_channels; +} + +bool +ARDOUR::discover_vst3 (boost::shared_ptr m, std::vector& 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 (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 processor; + if (!(processor = FUnknownPtr (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 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 m = VST3PluginModule::load (module_path); + std::vector nfo; + discover_vst3 (m, nfo); + for (std::vector::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; +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 65764b9d69..367c2b7f95 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -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'):