From ef06f01c7b9784d25a78ca397ee14bec07cf942d Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 3 Jun 2021 18:19:18 +0200 Subject: [PATCH] VST2: new scanner implementation --- gtk2_ardour/arvst2 | 49 +++ libs/ardour/ardour/vst2_scan.h | 81 ++++ libs/ardour/vst2_scan.cc | 756 +++++++++++++++++++++++++++++++++ libs/fst/scanner.cc | 175 -------- libs/fst/vst2-scanner.cc | 270 ++++++++++++ libs/fst/wscript | 9 +- 6 files changed, 1161 insertions(+), 179 deletions(-) create mode 100755 gtk2_ardour/arvst2 create mode 100644 libs/ardour/ardour/vst2_scan.h create mode 100644 libs/ardour/vst2_scan.cc delete mode 100644 libs/fst/scanner.cc create mode 100644 libs/fst/vst2-scanner.cc diff --git a/gtk2_ardour/arvst2 b/gtk2_ardour/arvst2 new file mode 100755 index 0000000000..ab79995e06 --- /dev/null +++ b/gtk2_ardour/arvst2 @@ -0,0 +1,49 @@ +#!/bin/sh +TOP=`dirname "$0"`/.. +. $TOP/build/gtk2_ardour/ardev_common_waf.sh +export UBUNTU_MENUPROXY="" + +if [ $# -gt 0 ] ; then + case $1 in + -g|--gdb) DBG=gdb; shift ;; + esac + case $1 in + --valgrind) DBG=valgrind; shift ;; + esac + case $1 in + --callgrind) DBG=callgrind; shift ;; + esac +fi + +if test -z "$DBG"; then + exec $TOP/build/libs/fst/ardour-vst-scanner "$@" +fi + +if test "$DBG" = "valgrind"; then + export ARDOUR_RUNNING_UNDER_VALGRIND=TRUE + exec valgrind \ + --error-limit=no --num-callers=50 \ + --tool=memcheck \ + --track-origins=yes \ + --leak-check=full --show-leak-kinds=all \ + --suppressions=${TOP}/tools/valgrind.supp \ + $TOP/build/libs/fst/ardour-vst-scanner "$@" +fi + +if test "$DBG" = "callgrind"; then + exec valgrind \ + --error-limit=no --num-callers=50 \ + --tool=callgrind \ + --separate-callers=3 \ + --separate-threads=yes \ + --collect-systime=yes \ + --collect-jumps=yes \ + $TOP/build/libs/fst/ardour-vst-scanner "$@" +fi + +if test -n "`which gdb`"; then + exec gdb --args $TOP/build/libs/fst/ardour-vst-scanner "$@" +fi +if test -n "`which lldb`"; then + exec lldb -- $TOP/build/libs/fst/ardour-vst-scanner "$@" +fi diff --git a/libs/ardour/ardour/vst2_scan.h b/libs/ardour/ardour/vst2_scan.h new file mode 100644 index 0000000000..9edcd13566 --- /dev/null +++ b/libs/ardour/ardour/vst2_scan.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 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_vst2_scan_h_ +#define _ardour_vst2_scan_h_ + +#include +#include +#include + +#include "pbd/xml++.h" + +#include "ardour/libardour_visibility.h" +#include "ardour/types.h" + +namespace ARDOUR { + +struct VST2Info { + VST2Info () + : id (0) + , n_inputs (0) + , n_outputs (0) + , n_midi_inputs (0) + , n_midi_outputs (0) + , is_instrument (false) + , can_process_replace (false) + , has_editor (false) + {} + + VST2Info (XMLNode const&); + XMLNode& state () const; + + int32_t id; + std::string name; + std::string creator; // vendor; + std::string category; + std::string version; + + int n_inputs; + int n_outputs; + int n_midi_inputs; + int n_midi_outputs; + + bool is_instrument; + bool can_process_replace; + bool has_editor; +}; + +LIBARDOUR_API extern std::string +vst2_arch (); + +LIBARDOUR_API extern std::string +vst2_id_to_str (int32_t); + +LIBARDOUR_API extern std::string +vst2_cache_file (std::string const& path); + +LIBARDOUR_API extern std::string +vst2_valid_cache_file (std::string const& path, bool verbose = false, bool* is_new = NULL); + +LIBARDOUR_API extern bool +vst2_scan_and_cache (std::string const& path, ARDOUR::PluginType, boost::function cb, bool verbose = false); + +} // namespace ARDOUR + +#endif diff --git a/libs/ardour/vst2_scan.cc b/libs/ardour/vst2_scan.cc new file mode 100644 index 0000000000..8e8500f9e6 --- /dev/null +++ b/libs/ardour/vst2_scan.cc @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2021 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 +#include + +#include +#include "pbd/gstdio_compat.h" + +#include +#include + +#ifdef COMPILER_MSVC +#include +#else +#include +#endif + +#ifdef PLATFORM_WINDOWS +#include +#include +#include +#include +#include +#else +#include +#endif + +#include "pbd/basename.h" +#include "pbd/error.h" +#include "pbd/failed_constructor.h" + +#include "ardour/filesystem_paths.h" +#include "ardour/linux_vst_support.h" +#include "ardour/mac_vst_support.h" +#include "ardour/vst_types.h" +#include "ardour/vst2_scan.h" + +#ifdef WINDOWS_VST_SUPPORT +#include +#endif + +#include "pbd/i18n.h" + +using namespace std; + +/* ID for shell plugins */ +static int vstfx_current_loading_id = 0; +static bool vstfx_verbose_log = false; + +/* **************************************************************************** + * VST system-under-test methods + */ + +static +bool vstfx_midi_input (VSTState* vstfx) +{ + AEffect* plugin = vstfx->plugin; + + if ((plugin->flags & effFlagsIsSynth) + || (plugin->dispatcher (plugin, effCanDo, 0, 0, const_cast ("receiveVstEvents"), 0.0f) > 0) + || (plugin->dispatcher (plugin, effCanDo, 0, 0, const_cast ("receiveVstMidiEvent"), 0.0f) > 0) + || (plugin->dispatcher (plugin, effCanDo, 0, 0, const_cast ("receiveVstMidiEvents"), 0.0f) > 0) + ) { + return true; + } + + return false; +} + +static +bool vstfx_midi_output (VSTState* vstfx) +{ + AEffect* plugin = vstfx->plugin; + + int const vst_version = plugin->dispatcher (plugin, effGetVstVersion, 0, 0, 0, 0.0f); + + if (vst_version >= 2) { + + if ( (plugin->dispatcher (plugin, effCanDo, 0, 0, const_cast ("sendVstEvents"), 0.0f) > 0) + || (plugin->dispatcher (plugin, effCanDo, 0, 0, const_cast ("sendVstMidiEvent"), 0.0f) > 0) + || (plugin->dispatcher (plugin, effCanDo, 0, 0, const_cast ("sendVstMidiEvents"), 0.0f) > 0) + ) { + return true; + } + } + + return false; +} + +/** simple 'dummy' audiomaster callback to instantiate the plugin + * and query information + */ +static intptr_t +simple_master_callback (AEffect *, int32_t opcode, int32_t, intptr_t, void *ptr, float) +{ + const char* vstfx_can_do_strings[] = { + "supplyIdle", + "sendVstTimeInfo", + "sendVstEvents", + "sendVstMidiEvent", + "receiveVstEvents", + "receiveVstMidiEvent", + "supportShell", + "shellCategory", + "shellCategorycurID", + "sizeWindow" + }; + const int vstfx_can_do_string_count = 9; + + if (opcode == audioMasterVersion) { + return 2400; + } + else if (opcode == audioMasterCanDo) { + intptr_t rv = 0; + for (int i = 0; i < vstfx_can_do_string_count; i++) { + if (! strcmp (vstfx_can_do_strings[i], (const char*)ptr)) { + rv = 1; + break; + } + } + if (vstfx_verbose_log) { + PBD::info << string_compose ("Callback CanDo '%1': %2", (const char*)ptr, rv ? "yes" : "no") << endmsg; + } + return rv; + } + else if (opcode == audioMasterCurrentId) { + return vstfx_current_loading_id; + } + else { + if (vstfx_verbose_log) { + PBD::info << string_compose ("Callback opcode = %1 (ignored)", opcode) << endmsg; + } + return 0; + } +} + +/** query VST Info */ +static void +vstfx_parse_vst_state (ARDOUR::VST2Info& nfo, VSTState* vstfx, bool verbose) +{ + assert (vstfx); + /* We need to init the creator because some plugins + * fail to implement getVendorString, and so won't stuff the + * string with any name */ + + char creator[65] = "Unknown"; + char name[65] = ""; + + AEffect* plugin = vstfx->plugin; + + plugin->dispatcher (plugin, effGetEffectName, 0, 0, name, 0); + + if (strlen (name) == 0) { + plugin->dispatcher (plugin, effGetProductString, 0, 0, name, 0); + } + + if (strlen (name) == 0) { + nfo.name = vstfx->handle->name; + } else { + nfo.name = name; + } + + /*If the plugin doesn't bother to implement GetVendorString we will + * have pre-stuffed the string with 'Unknown' */ + + plugin->dispatcher (plugin, effGetVendorString, 0, 0, creator, 0); + + /* Some plugins DO implement GetVendorString, but DON'T put a name in it + * so if its just a zero length string we replace it with 'Unknown' */ + + if (strlen (creator) == 0) { + nfo.creator = "Unknown"; + } else { + nfo.creator = creator; + } + + switch (plugin->dispatcher (plugin, effGetPlugCategory, 0, 0, 0, 0)) + { + case kPlugCategEffect: nfo.category = "Effect"; break; + case kPlugCategSynth: nfo.category = "Instrument"; break; + case kPlugCategAnalysis: nfo.category = "Analyser"; break; + case kPlugCategMastering: nfo.category = "Mastering"; break; + case kPlugCategSpacializer: nfo.category = "Spatial"; break; + case kPlugCategRoomFx: nfo.category = "RoomFx"; break; + case kPlugSurroundFx: nfo.category = "SurroundFx"; break; + case kPlugCategRestoration: nfo.category = "Restoration"; break; + case kPlugCategOfflineProcess: nfo.category = "Offline"; break; + case kPlugCategShell: nfo.category = "Shell"; break; + case kPlugCategGenerator: nfo.category = "Generator"; break; + default: nfo.category = "Unknown"; break; + } + + nfo.id = plugin->uniqueID; + nfo.n_inputs = plugin->numInputs; + nfo.n_outputs = plugin->numOutputs; + nfo.has_editor = plugin->flags & effFlagsHasEditor ? true : false; + nfo.can_process_replace = plugin->flags & effFlagsCanReplacing ? true : false; + nfo.n_midi_inputs = vstfx_midi_input (vstfx) ? 1 : 0; + nfo.n_midi_outputs = vstfx_midi_output (vstfx) ? 1 : 0; + nfo.is_instrument = (plugin->flags & effFlagsIsSynth) ? 1 : 0; + +#ifdef __APPLE__ + if (nfo.has_editor) { + /* we only support Cocoa UIs (just like Reaper) */ + nfo.has_editor = (plugin->dispatcher (plugin, effCanDo, 0, 0, const_cast ("hasCockosViewAsConfig"), 0.0f) & 0xffff0000) == 0xbeef0000; + } +#endif + +#if 0 + for (int i = 0; i < plugin->numParams; ++i) { + char name[VestigeMaxLabelLen]; + char label[VestigeMaxLabelLen]; + + /* Not all plugins give parameters labels as well as names */ + strcpy (name, "No Name"); + strcpy (label, "No Label"); + + plugin->dispatcher (plugin, effGetParamName, i, 0, name, 0); + plugin->dispatcher (plugin, 6 /* effGetParamLabel */, i, 0, label, 0); + + nfp.param[name] = label; + } +#endif +} + +static void +vst2_close (VSTState* vstfx, ARDOUR::PluginType type) +{ + /* mark as used, prevent *_close from unloading the plugin */ + VSTHandle* h = vstfx->handle; + h->plugincnt++; + + switch (type) { +#ifdef WINDOWS_VST_SUPPORT + case ARDOUR::Windows_VST: + fst_close (vstfx); + break; +#endif +#ifdef LXVST_SUPPORT + case ARDOUR::LXVST: + vstfx_close (vstfx); + break; +#endif +#ifdef MACVST_SUPPORT + case ARDOUR::MacVST: + mac_vst_close (vstfx); + break; +#endif + default: + assert (0); + break; + } + h->plugincnt--; +} + +static void +vst2_unload (VSTHandle* h, ARDOUR::PluginType type) +{ + switch (type) { +#ifdef WINDOWS_VST_SUPPORT + case ARDOUR::Windows_VST: + fst_unload (&h); + break; +#endif +#ifdef LXVST_SUPPORT + case ARDOUR::LXVST: + vstfx_unload (h); + break; +#endif +#ifdef MACVST_SUPPORT + case ARDOUR::MacVST: + mac_vst_unload (h); + break; +#endif + default: + assert (0); + break; + } +} + +static VSTHandle* +vstfx_load (const char* dllpath, ARDOUR::PluginType type) +{ + VSTHandle* h = NULL; + switch (type) { +#ifdef WINDOWS_VST_SUPPORT + case ARDOUR::Windows_VST: + h = fst_load (dllpath); + break; +#endif +#ifdef LXVST_SUPPORT + case ARDOUR::LXVST: + h = vstfx_load (dllpath); + break; +#endif +#ifdef MACVST_SUPPORT + case ARDOUR::MacVST: + h = mac_vst_load (dllpath); + break; +#endif + default: + assert (0); + break; + } + return h; +} + +static VSTState* +vst2_instantiate (VSTHandle* h, ARDOUR::PluginType type) +{ + VSTState* s = NULL; + switch (type) { +#ifdef WINDOWS_VST_SUPPORT + case ARDOUR::Windows_VST: + s = fst_instantiate (h, simple_master_callback, 0); + break; +#endif +#ifdef LXVST_SUPPORT + case ARDOUR::LXVST: + s = vstfx_instantiate (h, simple_master_callback, 0); + break; +#endif +#ifdef MACVST_SUPPORT + case ARDOUR::MacVST: + s = mac_vst_instantiate (h, simple_master_callback, 0); + break; +#endif + default: + assert (0); + break; + } + return s; +} + +#ifdef PLATFORM_WINDOWS +static std::string +dll_info (std::string path) +{ + std::string rv; + uint8_t buf[68]; + uint16_t type = 0; + off_t pe_hdr_off = 0; + + int fd = g_open(path.c_str(), O_RDONLY, 0444); + + if (fd < 0) { + return string_compose (_("cannot open dll '%1' (%2)"), path, g_strerror (errno)); + } + + if (68 != read (fd, buf, 68)) { + rv = _("invalid dll, file too small"); + goto errorout; + } + if (buf[0] != 'M' && buf[1] != 'Z') { + rv = _("not a dll"); + goto errorout; + } + + pe_hdr_off = *((int32_t*) &buf[60]); + if (pe_hdr_off !=lseek (fd, pe_hdr_off, SEEK_SET)) { + rv = _("cannot determine dll type"); + goto errorout; + } + if (6 != read (fd, buf, 6)) { + rv = _("cannot read dll PE header"); + goto errorout; + } + + if (buf[0] != 'P' && buf[1] != 'E') { + rv = _("invalid dll PE header"); + goto errorout; + } + + type = *((uint16_t*) &buf[4]); + switch (type) { + case 0x014c: + rv = _("i386 (32-bit)"); + break; + case 0x0200: + rv = _("Itanium"); + break; + case 0x8664: + rv = _("x64 (64-bit)"); + break; + case 0: + rv = _("Native Architecture"); + break; + default: + rv = _("Unknown Architecture"); + break; + } +errorout: + assert (rv.length() > 0); + close (fd); + return rv; +} +#endif + +std::string +ARDOUR::vst2_id_to_str (int32_t id) +{ + std::string rv; + for (int i = 0; i < 4; ++i) { + char a = ((char*)&id)[i]; + if (isprint (a)) { + rv += a; + } else { + rv += '.'; + } + } + return rv; +} +std::string +ARDOUR::vst2_arch () +{ +#ifndef PLATFORM_WINDOWS + struct utsname utb; + if (uname (&utb) >= 0) { + return utb.machine; + } +#endif + +#if ( defined(__x86_64__) || defined(_M_X64) ) + return "x86_64"; +#elif defined __i386__ || defined _M_IX86 + return "i386"; +#elif defined __ppc__ && defined __LP64__ + return "ppc64"; +#elif defined __ppc__ + return "ppc"; +#elif defined __aarch64__ + return "aarch64"; +#elif defined __arm__ && defined __ARM_NEON + return "armhf"; +#elif defined __arm__ + return "arm"; +#elif defined __LP64__ + return "x64"; +#else + return "x32"; +#endif +} + +/** wrapper around \ref vstfx_parse_vst_state, + * iterate over plugins in shell, translate VST-info into ardour VSTState + */ +static void +vstfx_info_from_plugin (VSTHandle* h, VSTState* vstfx, vector& rv, enum ARDOUR::PluginType type, bool verbose) +{ + assert (vstfx); + + ARDOUR::VST2Info nfo; + vstfx_parse_vst_state (nfo, vstfx, verbose); + + if (strncmp (nfo.category.c_str(), "Shell", 5) /*|| vstfx->handle->plugincnt != 1 */) { + rv.push_back (nfo); + vst2_close (vstfx, type); + return; + } + + if (verbose) { + PBD::info << "VST Shell Plugin" << endmsg; + } + + /* shell plugin. + * read the info for all of the plugins contained in this shell. + */ + int id; + vector< pair > ids; + AEffect* plugin = vstfx->plugin; + + do { + char name[65] = "Unknown"; + id = plugin->dispatcher (plugin, effShellGetNextPlugin, 0, 0, name, 0); + ids.push_back (std::make_pair (id, name)); + } while (id != 0); + + vst2_close (vstfx, type); + + if (verbose) { + PBD::info << "Found " << ids.size() << " Plugin(s) in shell)" << endmsg; + } + + for (vector< pair >::iterator x = ids.begin (); x != ids.end (); ++x) { + id = (*x).first; + if (id == 0) { + if (verbose) { + PBD::info << string_compose (_("Skipping VST2 Shell ID: '%1' Name: '%2'"), id, (*x).second) << endmsg; + } + continue; + } + /* recurse vstfx_get_info() */ + vstfx_current_loading_id = id; + if (verbose) { + PBD::info << string_compose (_("Instantiating VST2 Shell ID: '%1' Name: '%2'"), ARDOUR::vst2_id_to_str(id), (*x).second) << endmsg; + } + vstfx = vst2_instantiate (h, type); + if (!vstfx) { + PBD::warning << string_compose (_("Error scanning VST2 Shell ID: '%1' Name: '%2'"), ARDOUR::vst2_id_to_str(id), (*x).second) << endmsg; + continue; + } + vstfx_info_from_plugin (h, vstfx, rv, type, verbose); + } +} + +static bool +discover_vst2 (std::string const& path, ARDOUR::PluginType type, std::vector& rv, bool verbose) +{ + VSTHandle* h = vstfx_load (path.c_str (), type); + + if (!h) { + PBD::warning << string_compose (_("Cannot get load VST plugin from '%1'"), path) << endmsg; + return false; + } + + vstfx_current_loading_id = 0; + vstfx_verbose_log = verbose; + VSTState* vstfx = vst2_instantiate (h, type); + + if (!vstfx) { + vst2_unload (h, type); + PBD::warning << string_compose (_("Cannot get VST information from '%1': instantiation failed."), path) << endmsg; + return false; + } + + vstfx_info_from_plugin (h, vstfx, rv, type, verbose); + vst2_unload (h, type); + return true; +} + +static std::string vst2_suffix () { +#ifdef __APPLE__ + return ""; +#elif defined PLATFORM_WINDOWS + return ".dll"; +#else // Linux + return ".so"; +#endif +} + +static string +vst2_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::vst2_cache_file (std::string const& path) +{ + char hash[41]; + Sha1Digest s; + sha1_init (&s); + sha1_write (&s, (const uint8_t *) path.c_str(), path.size()); + sha1_result_hash (&s, hash); + /* Arch specifix suffix or arch specific folder? + * start 32bit Ardour, scan 32bit plugin -> create cache file. + * start 64bit Ardour, scan 32bit plugin -> invalid -> cleanup -> cache file is removed + */ +# if ( defined(__x86_64__) || defined(_M_X64) ) + return Glib::build_filename (vst2_info_cache_dir (), std::string (hash) + std::string ("-x64.v2i")); +#else + return Glib::build_filename (vst2_info_cache_dir (), std::string (hash) + std::string ("-x86.v2i")); +#endif +} + +string +ARDOUR::vst2_valid_cache_file (std::string const& path, bool verbose, bool* is_new) +{ + string const cache_file = ARDOUR::vst2_cache_file (path); + if (!Glib::file_test (cache_file, Glib::FileTest (Glib::FILE_TEST_EXISTS | Glib::FILE_TEST_IS_REGULAR))) { + if (is_new) { + *is_new = true; + } + return ""; + } + + if (is_new) { + *is_new = false; + } + + if (verbose) { + PBD::info << "Found cache file: '" << cache_file <<"'" << endmsg; + } + + GStatBuf sb_vst; + GStatBuf sb_v2i; + + if (g_stat (path.c_str(), &sb_vst) == 0 && g_stat (cache_file.c_str (), &sb_v2i) == 0) { + if (sb_vst.st_mtime < sb_v2i.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& path, std::string const& cache_file) +{ + GStatBuf sb_vst; + GStatBuf sb_v2i; + if (g_stat (path.c_str(), &sb_vst) == 0 && g_stat (cache_file.c_str (), &sb_v2i) == 0) { + struct utimbuf utb; + utb.actime = sb_v2i.st_atime; + utb.modtime = std::max (sb_vst.st_mtime, sb_v2i.st_mtime); + g_utime (cache_file.c_str (), &utb); + } +} + +static bool +vst2_save_cache_file (std::string const& path, XMLNode* root, bool verbose) +{ + string const cache_file = ARDOUR::vst2_cache_file (path); + + XMLTree tree; + tree.set_root (root); + if (!tree.write (cache_file)) { + PBD::error << "Could not save VST2 plugin cache to: " << cache_file << endmsg; + return false; + } else { + touch_cachefile (path, cache_file); + } + if (verbose) { + root->dump (std::cout, "\t"); + } + return true; +} + +bool +ARDOUR::vst2_scan_and_cache (std::string const& path, ARDOUR::PluginType type, boost::function cb, bool verbose) +{ + XMLNode* root = new XMLNode ("VST2Cache"); + root->set_property ("version", 1); + root->set_property ("binary", path); + root->set_property ("arch", vst2_arch ()); + +#ifdef PLATFORM_WINDOWS + if (type == ARDOUR::Windows_VST) { + PBD::info << "File type: " << dll_info (path) << endmsg; + } +#endif + + try { + std::vector nfo; + if (!discover_vst2 (path, type, nfo, verbose)) { + delete root; + return false; + } + if (nfo.empty ()) { + cerr << "No plugins in VST2 plugin: '" << path << "'\n"; + delete root; + return false; + } + for (std::vector::const_iterator i = nfo.begin(); i != nfo.end(); ++i) { + cb (path, type, *i); + root->add_child_nocopy (i->state ()); + } + } catch (...) { + cerr << "Cannot load VST2 plugin: '" << path << "'\n"; + delete root; + return false; + } + + return vst2_save_cache_file (path, root, verbose); +} + + +using namespace ARDOUR; + +VST2Info::VST2Info (XMLNode const& node) + : id (0) + , n_inputs (0) + , n_outputs (0) + , n_midi_inputs (0) + , n_midi_outputs (0) + , is_instrument (false) + , can_process_replace (false) + , has_editor (false) +{ + bool err = false; + + if (node.name() != "VST2Info") { + throw failed_constructor (); + } + err |= !node.get_property ("id", id); + err |= !node.get_property ("name", name); + err |= !node.get_property ("creator", creator); + err |= !node.get_property ("category", category); + err |= !node.get_property ("version", version); + + err |= !node.get_property ("n_inputs", n_inputs); + err |= !node.get_property ("n_outputs", n_outputs); + err |= !node.get_property ("n_midi_inputs", n_midi_inputs); + err |= !node.get_property ("n_midi_outputs", n_midi_outputs); + + err |= !node.get_property ("is_instrument", is_instrument); + err |= !node.get_property ("can_process_replace", can_process_replace); + err |= !node.get_property ("has_editor", has_editor); + + if (err) { + throw failed_constructor (); + } +} + +XMLNode& +VST2Info::state () const +{ + XMLNode* node = new XMLNode("VST2Info"); + node->set_property ("id", id); + node->set_property ("name", name); + node->set_property ("creator", creator); + node->set_property ("category", category); + node->set_property ("version", version); + + node->set_property ("n_inputs", n_inputs); + node->set_property ("n_outputs", n_outputs); + node->set_property ("n_midi_inputs", n_midi_inputs); + node->set_property ("n_midi_outputs", n_midi_outputs); + + node->set_property ("is_instrument", is_instrument); + node->set_property ("can_process_replace", can_process_replace); + node->set_property ("has_editor", has_editor); + return *node; +} + diff --git a/libs/fst/scanner.cc b/libs/fst/scanner.cc deleted file mode 100644 index b3a84de889..0000000000 --- a/libs/fst/scanner.cc +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2014-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 -#include -#include - -#ifdef COMPILER_MSVC -#include -#else -#include -#include -#endif - -#include - -#include "pbd/pbd.h" -#include "pbd/transmitter.h" -#include "pbd/receiver.h" -#include "pbd/win_console.h" - -#ifdef __MINGW64__ -#define NO_OLDNAMES // no backwards compat _pid_t, conflict with w64 pthread/sched -#endif - -#include "ardour/filesystem_paths.h" -#ifdef LXVST_SUPPORT -#include "ardour/linux_vst_support.h" -#endif -#include "ardour/vst_info_file.h" - -/* make stupid waf happy. - * waf cannot build multiple variants of .o object files from the same - * source using different wscripts.. it mingles e.g. - * build/libs/ardour/vst_info_file.cc.1.o for - * both lib/ardour/wscript and lib/fst/wscript - * - * ...but waf does track include dependencies. - */ -#include "../ardour/vst_info_file.cc" -#ifdef LXVST_SUPPORT -#include "../ardour/linux_vst_support.cc" -#endif -#ifdef MACVST_SUPPORT -#include "../ardour/mac_vst_support.cc" -#endif -#include "../ardour/filesystem_paths.cc" -#include "../ardour/directory_names.cc" - -#include "../ardour/vst_state.cc" - -#ifdef LXVST_SUPPORT -void -vstfx_destroy_editor (VSTState* /*vstfx*/) { } -#endif - -using namespace PBD; - -class DummyReceiver : public Receiver { - protected: - void receive (Transmitter::Channel chn, const char * str) { - const char *prefix = ""; - switch (chn) { - case Transmitter::Debug: - /* ignore */ - return; - case Transmitter::Info: - /* ignore */ - return; - case Transmitter::Warning: - prefix = "[WARNING]: "; - break; - case Transmitter::Error: - prefix = "[ERROR]: "; - break; - case Transmitter::Fatal: - prefix = "[FATAL]: "; - break; - case Transmitter::Throw: - abort (); - } - - std::cerr << prefix << str << std::endl; - - if (chn == Transmitter::Fatal) { - console_madness_end (); - ::exit (EXIT_FAILURE); - } - } -}; - -DummyReceiver dummy_receiver; - -int main (int argc, char **argv) { - char *dllpath = NULL; - console_madness_begin (); - - if (argc == 3 && !strcmp("-f", argv[1])) { - dllpath = argv[2]; - const size_t slen = strlen (dllpath); - if ( - (slen > 3 && 0 == g_ascii_strcasecmp (&dllpath[slen-3], ".so")) - || - (slen > 4 && 0 == g_ascii_strcasecmp (&dllpath[slen-4], ".dll")) - ) { - vstfx_remove_infofile(dllpath); - vstfx_un_blacklist(dllpath); - } - - } - else if (argc != 2) { - fprintf(stderr, "usage: %s [-f] \n", argv[0]); - console_madness_end (); - return EXIT_FAILURE; - } else { - dllpath = argv[1]; - } - - PBD::init(); - - dummy_receiver.listen_to (warning); - dummy_receiver.listen_to (error); - dummy_receiver.listen_to (fatal); - - std::vector *infos = 0; - - const size_t slen = strlen (dllpath); - if (0) { } -#ifdef LXVST_SUPPORT - else if (slen > 3 && 0 == g_ascii_strcasecmp (&dllpath[slen-3], ".so")) { - infos = vstfx_get_info_lx(dllpath); - } -#endif - -#ifdef WINDOWS_VST_SUPPORT - else if (slen > 4 && 0 == g_ascii_strcasecmp (&dllpath[slen-4], ".dll")) { - infos = vstfx_get_info_fst(dllpath); - } -#endif - -#ifdef MACVST_SUPPORT - else if (slen > 4 && 0 == g_ascii_strcasecmp (&dllpath[slen-4], ".vst")) { - infos = vstfx_get_info_mac(dllpath); - } -#endif - else { - fprintf(stderr, "'%s' is not a supported VST plugin.\n", dllpath); - } - - PBD::cleanup(); - - console_madness_end (); - - if (!infos || infos->empty()) { - return EXIT_FAILURE; - } else { - return EXIT_SUCCESS; - } -} diff --git a/libs/fst/vst2-scanner.cc b/libs/fst/vst2-scanner.cc new file mode 100644 index 0000000000..ce0b59521e --- /dev/null +++ b/libs/fst/vst2-scanner.cc @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2021 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 +#include +#include +#include +#include + +#ifdef COMPILER_MSVC +#include +#else +#include +#include +#endif + +#ifdef __MINGW64__ +#define NO_OLDNAMES // no backwards compat _pid_t, conflict with w64 pthread/sched +#endif + +#include "pbd/error.h" +#include "pbd/transmitter.h" +#include "pbd/receiver.h" +#include "pbd/pbd.h" +#include "pbd/win_console.h" +#include "pbd/xml++.h" + +#include "ardour/types.h" +#include "ardour/vst_types.h" + +#ifdef __MINGW64__ +#undef NO_OLDNAMES +#endif + +#include "../ardour/filesystem_paths.cc" +#include "../ardour/vst_state.cc" +#include "../ardour/vst2_scan.cc" + +#ifdef LXVST_SUPPORT +#include "../ardour/linux_vst_support.cc" +void vstfx_destroy_editor (VSTState* /*vstfx*/) { } +#endif + +#ifdef MACVST_SUPPORT +#include "../ardour/mac_vst_support.cc" +#endif + +using namespace PBD; + +class LogReceiver : public Receiver +{ +protected: + void receive (Transmitter::Channel chn, const char * str) { + const char *prefix = ""; + switch (chn) { + case Transmitter::Debug: + /* ignore */ + break; + case Transmitter::Info: + prefix = "[Info]: "; + break; + case Transmitter::Warning: + prefix = "[WARNING]: "; + break; + case Transmitter::Error: + prefix = "[ERROR]: "; + break; + case Transmitter::Fatal: + prefix = "[FATAL]: "; + break; + case Transmitter::Throw: + abort (); + } + + std::cout << prefix << str << std::endl; + + if (chn == Transmitter::Fatal) { + console_madness_end (); + ::exit (EXIT_FAILURE); + } + } +}; + +LogReceiver log_receiver; + +static void vst2_plugin (std::string const& module_path, PluginType, VST2Info const& i) +{ + info << "Found Plugin: '" << ARDOUR::vst2_id_to_str (i.id) << "' " << i.name << endmsg; +} + +static bool +scan_vst2 (std::string const& path, ARDOUR::PluginType type, bool force, bool verbose) +{ + info << "Scanning: " << path << endmsg; + + if (!vst2_valid_cache_file (path, verbose).empty()) { + if (!force) { + info << "Skipping scan." << endmsg; + return true; + } + } + + if (vst2_scan_and_cache (path, type, sigc::ptr_fun (&vst2_plugin), verbose)) { + info << string_compose (_("Saved VST2 plugin cache to %1"), vst2_cache_file (path)) << endmsg; + } + + return true; +} + +static void +usage () +{ + // help2man compatible format (standard GNU help-text) + printf ("ardour-vst2-scanner - load and index VST2 plugins.\n\n"); + printf ("Usage: ardour-vst2-scanner [ OPTIONS ] [VST2-file]*\n\n"); + printf ("Options:\n\ + -f, --force Force update of cache file\n\ + -h, --help Display this help and exit\n\ + -q, --quiet Hide usual output, only print errors\n\ + -v, --verbose Give verbose output (unless quiet)\n\ + -V, --version Print version information and exit\n\ +\n"); + + printf ("\n\ +This tool ...\n\ +\n"); + + printf ("Report bugs to \n" + "Website: \n"); + + console_madness_end (); + ::exit (EXIT_SUCCESS); +} + +int +main (int argc, char **argv) +{ + bool print_log = true; + bool stop_on_error = false; + bool force = false; + bool verbose = false; + + const char* optstring = "fhqvV"; + + /* clang-format off */ + const struct option longopts[] = { + { "force", no_argument, 0, 'f' }, + { "help", no_argument, 0, 'h' }, + { "quiet", no_argument, 0, 'q' }, + { "verbose", no_argument, 0, 'v' }, + { "version", no_argument, 0, 'V' }, + }; + /* clang-format on */ + + console_madness_begin (); + + int c = 0; + while (EOF != (c = getopt_long (argc, argv, optstring, longopts, (int*)0))) { + switch (c) { + case 'V': + printf ("ardour-vst2-scanner version %s\n\n", VERSIONSTRING); + printf ("Copyright (C) GPL 2021 Robin Gareus \n"); + console_madness_end (); + exit (EXIT_SUCCESS); + break; + + case 'f': + force = true; + break; + + case 'h': + usage (); + break; + + case 'q': + print_log = false; + break; + + case 'v': + verbose = true; + break; + + default: + std::cerr << "Error: unrecognized option. See --help for usage information.\n"; + console_madness_end (); + ::exit (EXIT_FAILURE); + break; + } + } + + if (optind >= argc) { + std::cerr << "Error: Missing parameter. See --help for usage information.\n"; + console_madness_end (); + ::exit (EXIT_FAILURE); + } + + + PBD::init(); + + if (print_log) { + log_receiver.listen_to (info); + log_receiver.listen_to (warning); + log_receiver.listen_to (error); + log_receiver.listen_to (fatal); + } else { + verbose = false; + } + + bool err = false; + + while (optind < argc) { + const char* dllpath = argv[optind++]; + const size_t slen = strlen (dllpath); + ARDOUR::PluginType type; + if (false) { } +#ifdef LXVST_SUPPORT + else if (slen > 3 && 0 == g_ascii_strcasecmp (&dllpath[slen-3], ".so")) { + type = ARDOUR::LXVST; + } +#endif +#ifdef WINDOWS_VST_SUPPORT + else if (slen > 4 && 0 == g_ascii_strcasecmp (&dllpath[slen-4], ".dll")) { + type = ARDOUR::Windows_VST; + } +#endif +#ifdef MACVST_SUPPORT + else if (slen > 4 && 0 == g_ascii_strcasecmp (&dllpath[slen-4], ".vst")) { + type = ARDOUR::MacVST; + } +#endif + else { + error << "'" << dllpath << "' is not a supported VST plugin." << endmsg; + //fprintf(stderr, "'%s' is not a supported VST plugin.\n", dllpath); + continue; + } + + if (!scan_vst2 (dllpath, type, force, verbose)) { + err = true; + } + if (stop_on_error) { + break; + } + } + + PBD::cleanup(); + + console_madness_end (); + + if (err) { + return EXIT_FAILURE; + } else { + return EXIT_SUCCESS; + } +} diff --git a/libs/fst/wscript b/libs/fst/wscript index 33cb39e3fe..4ba1dccf31 100644 --- a/libs/fst/wscript +++ b/libs/fst/wscript @@ -59,16 +59,16 @@ def build(bld): if bld.is_defined('WINDOWS_VST_SUPPORT') or bld.is_defined('LXVST_SUPPORT') or bld.is_defined ('MACVST_SUPPORT'): obj = bld (features = 'cxx c cxxprogram') - obj.source = ( 'scanner.cc' ) + obj.source = ( 'vst2-scanner.cc' ) obj.target = 'ardour-vst-scanner' if bld.is_defined('WINDOWS_VST_SUPPORT'): bld (features = 'c', name='vstwin', source='vstwin.c', uselib = 'GIOMM', includes = [ '../pbd/', '../ardour/', '.' ]) - obj.uselib = ['GIOMM', 'DL', 'GDI32'] + obj.uselib = ['GIOMM', 'DL', 'GDI32', 'XML'] obj.linkflags = ['-mwindows'] - obj.use = [ 'libpbd', 'vstwin' ] + obj.use = [ 'libpbd', 'libtemporal', 'libevoral', 'vstwin' ] else: obj.uselib = ['GIOMM', 'DL', 'OSX', 'XML' ] - obj.use = [ 'libpbd' ] + obj.use = [ 'libpbd', 'libtemporal', 'libevoral' ] obj.includes = [ '../pbd/', '../ardour/', '.' ] obj.defines = [ @@ -77,6 +77,7 @@ def build(bld): 'VST_SCANNER_APP', 'PACKAGE="' + I18N_PACKAGE + str(bld.env['MAJOR']) + '"', 'LIBARDOUR="' + bld.env['lwrcase_dirname'] + '"', + 'VERSIONSTRING="' + str(bld.env['VERSION']) + '"', ] if re.search ("bsd", sys.platform) != None: obj.defines.append('_POSIX_C_SOURCE=200809')