Text case change (_VSTInfo::Category needs to be capitalized)
[ardour.git] / libs / ardour / plugin_manager.cc
index cbf6994d6595f8486d79d36e9f60deb881d9e3b3..d48fe8cd15f8ae55022d3fef77f3fde844e508c4 100644 (file)
 #include <cstring>
 #endif //LXVST_SUPPORT
 
+#ifdef MACVST_SUPPORT
+#include "ardour/vst_info_file.h"
+#include "ardour/mac_vst_support.h"
+#include "ardour/mac_vst_plugin.h"
+#include "pbd/basename.h"
+#include "pbd/pathexpand.h"
+#include <cstring>
+#endif //MACVST_SUPPORT
+
 #include <glibmm/miscutils.h>
 #include <glibmm/pattern.h>
 #include <glibmm/fileutils.h>
 #include <glibmm/miscutils.h>
 
-#include "pbd/whitespace.h"
+#include "pbd/convert.h"
 #include "pbd/file_utils.h"
+#include "pbd/tokenizer.h"
+#include "pbd/whitespace.h"
 
+#include "ardour/directory_names.h"
 #include "ardour/debug.h"
 #include "ardour/filesystem_paths.h"
 #include "ardour/ladspa.h"
 #include "ardour/ladspa_plugin.h"
+#include "ardour/luascripting.h"
+#include "ardour/luaproc.h"
 #include "ardour/plugin.h"
 #include "ardour/plugin_manager.h"
 #include "ardour/rc_configuration.h"
 #include "pbd/error.h"
 #include "pbd/stl_delete.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 #include "ardour/debug.h"
 
@@ -116,16 +130,18 @@ PluginManager::instance()
 PluginManager::PluginManager ()
        : _windows_vst_plugin_info(0)
        , _lxvst_plugin_info(0)
+       , _mac_vst_plugin_info(0)
        , _ladspa_plugin_info(0)
        , _lv2_plugin_info(0)
        , _au_plugin_info(0)
+       , _lua_plugin_info(0)
        , _cancel_scan(false)
        , _cancel_timeout(false)
 {
        char* s;
        string lrdf_path;
 
-#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT
+#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT
        // source-tree (ardev, etc)
        PBD::Searchpath vstsp(Glib::build_filename(ARDOUR::ardour_dll_directory(), "fst"));
 
@@ -158,6 +174,8 @@ PluginManager::PluginManager ()
 
        load_statuses ();
 
+       load_tags ();
+
        if ((s = getenv ("LADSPA_RDF_PATH"))){
                lrdf_path = s;
        }
@@ -180,6 +198,12 @@ PluginManager::PluginManager ()
        }
 #endif /* Native LinuxVST support*/
 
+#ifdef MACVST_SUPPORT
+       if (Config->get_use_macvst ()) {
+               add_mac_vst_presets ();
+       }
+#endif
+
        if ((s = getenv ("VST_PATH"))) {
                windows_vst_path = s;
        } else if ((s = getenv ("VST_PLUGINS"))) {
@@ -215,6 +239,8 @@ PluginManager::PluginManager ()
        }
 
        BootMessage (_("Discovering Plugins"));
+
+       LuaScripting::instance().scripts_changed.connect_same_thread (lua_refresh_connection, boost::bind (&PluginManager::lua_refresh_cb, this));
 }
 
 
@@ -224,9 +250,11 @@ PluginManager::~PluginManager()
                // don't bother, just exit quickly.
                delete _windows_vst_plugin_info;
                delete _lxvst_plugin_info;
+               delete _mac_vst_plugin_info;
                delete _ladspa_plugin_info;
                delete _lv2_plugin_info;
                delete _au_plugin_info;
+               delete _lua_plugin_info;
        }
 }
 
@@ -244,6 +272,8 @@ PluginManager::refresh (bool cache_only)
 
        BootMessage (_("Scanning LADSPA Plugins"));
        ladspa_refresh ();
+       BootMessage (_("Scanning Lua DSP Processors"));
+       lua_refresh ();
 #ifdef LV2_SUPPORT
        BootMessage (_("Scanning LV2 Plugins"));
        lv2_refresh ();
@@ -270,13 +300,32 @@ PluginManager::refresh (bool cache_only)
        }
 #endif //Native linuxVST SUPPORT
 
-#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT)
+#ifdef MACVST_SUPPORT
+       if(Config->get_use_macvst ()) {
+               if (cache_only) {
+                       BootMessage (_("Scanning Mac VST Plugins"));
+               } else {
+                       BootMessage (_("Discovering Mac VST Plugins"));
+               }
+               mac_vst_refresh (cache_only);
+       } else if (_mac_vst_plugin_info) {
+               _mac_vst_plugin_info->clear ();
+       } else {
+               _mac_vst_plugin_info = new ARDOUR::PluginInfoList();
+       }
+#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)) {
-                                       PBD::info << _("VST Blacklist:") << "\n" << bl << "-----" << endmsg;
+                                       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);
                                }
                        }
@@ -371,7 +420,7 @@ PluginManager::clear_vst_cache ()
 #endif
 #endif // old cache cleanup
 
-#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT)
+#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT)
        {
                string dn = Glib::build_filename (ARDOUR::user_cache_directory(), "vst");
                vector<string> fsi_files;
@@ -418,7 +467,7 @@ PluginManager::clear_vst_blacklist ()
 
 #endif // old blacklist cleanup
 
-#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT)
+#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT)
        {
                string fn = Glib::build_filename (ARDOUR::user_cache_directory(), VST_BLACKLIST);
                if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
@@ -433,11 +482,7 @@ void
 PluginManager::clear_au_cache ()
 {
 #ifdef AUDIOUNIT_SUPPORT
-       // AUPluginInfo::au_cache_path ()
-       string fn = Glib::build_filename (ARDOUR::user_config_directory(), "au_cache");
-       if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
-               ::g_unlink(fn.c_str());
-       }
+       AUPluginInfo::clear_cache ();
 #endif
 }
 
@@ -452,6 +497,33 @@ PluginManager::clear_au_blacklist ()
 #endif
 }
 
+void
+PluginManager::lua_refresh ()
+{
+       if (_lua_plugin_info) {
+               _lua_plugin_info->clear ();
+       } else {
+               _lua_plugin_info = new ARDOUR::PluginInfoList ();
+       }
+       ARDOUR::LuaScriptList & _scripts (LuaScripting::instance ().scripts (LuaScriptInfo::DSP));
+       for (LuaScriptList::const_iterator s = _scripts.begin(); s != _scripts.end(); ++s) {
+               LuaPluginInfoPtr lpi (new LuaPluginInfo(*s));
+               _lua_plugin_info->push_back (lpi);
+               set_tags (lpi->type, lpi->unique_id, lpi->category, true);
+       }
+}
+
+void
+PluginManager::lua_refresh_cb ()
+{
+       Glib::Threads::Mutex::Lock lm (_lock, Glib::Threads::TRY_LOCK);
+       if (!lm.locked()) {
+               return;
+       }
+       lua_refresh ();
+       PluginListChanged (); /* EMIT SIGNAL */
+}
+
 void
 PluginManager::ladspa_refresh ()
 {
@@ -504,6 +576,12 @@ PluginManager::add_windows_vst_presets()
        add_presets ("windows-vst");
 }
 
+void
+PluginManager::add_mac_vst_presets()
+{
+       add_presets ("mac-vst");
+}
+
 void
 PluginManager::add_lxvst_presets()
 {
@@ -582,6 +660,11 @@ PluginManager::ladspa_discover (string path)
        DEBUG_TRACE (DEBUG::PluginManager, string_compose ("LADSPA plugin found at %1\n", path));
 
        for (uint32_t i = 0; ; ++i) {
+               /* if a ladspa plugin allocates memory here
+                * it is never free()ed (or plugin-dependent only when unloading).
+                * For some plugins memory allocated is incremental, we should
+                * avoid re-scanning plugins and file bug reports.
+                */
                if ((descriptor = dfunc (i)) == 0) {
                        break;
                }
@@ -595,23 +678,40 @@ PluginManager::ladspa_discover (string path)
                PluginInfoPtr info(new LadspaPluginInfo);
                info->name = descriptor->Name;
                info->category = get_ladspa_category(descriptor->UniqueID);
-               info->creator = descriptor->Maker;
                info->path = path;
                info->index = i;
                info->n_inputs = ChanCount();
                info->n_outputs = ChanCount();
                info->type = ARDOUR::LADSPA;
 
+               string::size_type pos = 0;
+               string creator = descriptor->Maker;
+               /* stupid LADSPA creator strings */
+#ifdef PLATFORM_WINDOWS
+               while (pos < creator.length() && creator[pos] > -2 && creator[pos] < 256 && (isalnum (creator[pos]) || isspace (creator[pos]) || creator[pos] == '.')) ++pos;
+#else
+               while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]) || creator[pos] == '.')) ++pos;
+#endif
+
+               /* If there were too few characters to create a
+                * meaningful name, mark this creator as 'Unknown'
+                */
+               if (creator.length() < 2 || pos < 3) {
+                       info->creator = "Unknown";
+               } else{
+                       info->creator = creator.substr (0, pos);
+               }
+
                char buf[32];
                snprintf (buf, sizeof (buf), "%lu", descriptor->UniqueID);
                info->unique_id = buf;
 
                for (uint32_t n=0; n < descriptor->PortCount; ++n) {
-                       if ( LADSPA_IS_PORT_AUDIO (descriptor->PortDescriptors[n]) ) {
-                               if ( LADSPA_IS_PORT_INPUT (descriptor->PortDescriptors[n]) ) {
+                       if (LADSPA_IS_PORT_AUDIO (descriptor->PortDescriptors[n])) {
+                               if (LADSPA_IS_PORT_INPUT (descriptor->PortDescriptors[n])) {
                                        info->n_inputs.set_audio(info->n_inputs.n_audio() + 1);
                                }
-                               else if ( LADSPA_IS_PORT_OUTPUT (descriptor->PortDescriptors[n]) ) {
+                               else if (LADSPA_IS_PORT_OUTPUT (descriptor->PortDescriptors[n])) {
                                        info->n_outputs.set_audio(info->n_outputs.n_audio() + 1);
                                }
                        }
@@ -717,6 +817,10 @@ PluginManager::lv2_refresh ()
        DEBUG_TRACE (DEBUG::PluginManager, "LV2: refresh\n");
        delete _lv2_plugin_info;
        _lv2_plugin_info = LV2PluginInfo::discover();
+
+       for (PluginInfoList::iterator i = _lv2_plugin_info->begin(); i != _lv2_plugin_info->end(); ++i) {
+               set_tags ((*i)->type, (*i)->unique_id, (*i)->category, true);
+       }
 }
 #endif
 
@@ -737,6 +841,10 @@ PluginManager::au_refresh (bool cache_only)
        // successful scan re-enabled automatic discovery if it was set
        Config->set_discover_audio_units (discover_at_start);
        Config->save_state();
+
+       for (PluginInfoList::iterator i = _au_plugin_info->begin(); i != _au_plugin_info->end(); ++i) {
+               set_tags ((*i)->type, (*i)->unique_id, (*i)->category, true);
+       }
 }
 
 #endif
@@ -770,11 +878,16 @@ PluginManager::windows_vst_discover_from_path (string path, bool cache_only)
 
        DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Discovering Windows VST plugins along %1\n", path));
 
+       if (Session::get_disable_all_loaded_plugins ()) {
+               info << _("Disabled WindowsVST scan (safe mode)") << endmsg;
+               return -1;
+       }
+
        if (Config->get_verbose_plugin_scan()) {
                info << string_compose (_("--- Windows VST plugins Scan: %1"), path) << endmsg;
        }
 
-       find_files_matching_filter (plugin_objects, Config->get_plugin_path_vst(), windows_vst_filter, 0, false, true, true);
+       find_files_matching_filter (plugin_objects, path, windows_vst_filter, 0, false, true, true);
 
        for (x = plugin_objects.begin(); x != plugin_objects.end (); ++x) {
                ARDOUR::PluginScanMessage(_("VST"), *x, !cache_only && !cancelled());
@@ -794,55 +907,56 @@ static std::string dll_info (std::string path) {
        uint16_t type = 0;
        off_t pe_hdr_off = 0;
 
-       int fd = open(path.c_str(), O_RDONLY, 0444);
+       int fd = g_open(path.c_str(), O_RDONLY, 0444);
 
        if (fd < 0) {
-               return _("cannot open dll"); // TODO strerror()
+               return _("cannot open dll"); // TODO strerror()
        }
 
        if (68 != read (fd, buf, 68)) {
-               rv = _("invalid dll, file too small");
+               rv = _("invalid dll, file too small");
                goto errorout;
        }
        if (buf[0] != 'M' && buf[1] != 'Z') {
-               rv = _("not a dll");
+               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");
+               rv = _("cannot determine dll type");
                goto errorout;
        }
        if (6 != read (fd, buf, 6)) {
-               rv = _("cannot read dll PE header");
+               rv = _("cannot read dll PE header");
                goto errorout;
        }
 
        if (buf[0] != 'P' && buf[1] != 'E') {
-               rv = _("invalid dll PE header");
+               rv = _("invalid dll PE header");
                goto errorout;
        }
 
        type = *((uint16_t*) &buf[4]);
        switch (type) {
                case 0x014c:
-                       rv = _("- i386 (32bit)");
+                       rv = _("i386 (32-bit)");
                        break;
                case  0x0200:
-                       rv = _("Itanium");
+                       rv = _("Itanium");
                        break;
                case 0x8664:
-                       rv = _("- x64 (64bit)");
+                       rv = _("x64 (64-bit)");
                        break;
                case 0:
-                       rv = _("Native Architecture");
+                       rv = _("Native Architecture");
                        break;
                default:
-                       rv = _("Unknown Architecture");
+                       rv = _("Unknown Architecture");
                        break;
        }
 errorout:
+       assert (rv.length() > 0);
        close (fd);
        return rv;
 }
@@ -853,7 +967,11 @@ PluginManager::windows_vst_discover (string path, bool cache_only)
        DEBUG_TRACE (DEBUG::PluginManager, string_compose ("windows_vst_discover '%1'\n", path));
 
        if (Config->get_verbose_plugin_scan()) {
-               info << string_compose (_(" *  %1 %2 %3"), path, (cache_only ? _(" (cache only)") : "", dll_info (path))) << endmsg;
+               if (cache_only) {
+                       info << string_compose (_(" *  %1 (cache only)"), path) << endmsg;
+               } else {
+                       info << string_compose (_(" *  %1 - %2"), path, dll_info (path)) << endmsg;
+               }
        }
 
        _cancel_timeout = false;
@@ -896,7 +1014,7 @@ PluginManager::windows_vst_discover (string path, bool cache_only)
 
                snprintf (buf, sizeof (buf), "%d", finfo->UniqueID);
                info->unique_id = buf;
-               info->category = "VST";
+               info->category = finfo->Category;
                info->path = path;
                info->creator = finfo->creator;
                info->index = 0;
@@ -906,6 +1024,9 @@ PluginManager::windows_vst_discover (string path, bool cache_only)
                info->n_outputs.set_midi ((finfo->wantMidi&2) ? 1 : 0);
                info->type = ARDOUR::Windows_VST;
 
+               /* if we don't have any tags for this plugin, make some up. */
+               set_tags (info->type, info->unique_id, info->category, true);
+
                // TODO: check dup-IDs (lxvst AND windows vst)
                bool duplicate = false;
 
@@ -935,6 +1056,140 @@ PluginManager::windows_vst_discover (string path, bool cache_only)
 
 #endif // WINDOWS_VST_SUPPORT
 
+#ifdef MACVST_SUPPORT
+void
+PluginManager::mac_vst_refresh (bool cache_only)
+{
+       if (_mac_vst_plugin_info) {
+               _mac_vst_plugin_info->clear ();
+       } else {
+               _mac_vst_plugin_info = new ARDOUR::PluginInfoList();
+       }
+
+       mac_vst_discover_from_path ("~/Library/Audio/Plug-Ins/VST:/Library/Audio/Plug-Ins/VST", cache_only);
+}
+
+static bool mac_vst_filter (const string& str)
+{
+       string plist = Glib::build_filename (str, "Contents", "Info.plist");
+       if (!Glib::file_test (plist, Glib::FILE_TEST_IS_REGULAR)) {
+               return false;
+       }
+       return str[0] != '.' && str.length() > 4 && strings_equal_ignore_case (".vst", str.substr(str.length() - 4));
+}
+
+int
+PluginManager::mac_vst_discover_from_path (string path, bool cache_only)
+{
+       if (Session::get_disable_all_loaded_plugins ()) {
+               info << _("Disabled MacVST scan (safe mode)") << endmsg;
+               return -1;
+       }
+
+       Searchpath paths (path);
+       /* customized version of run_functor_for_paths() */
+       for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
+               string expanded_path = path_expand (*i);
+               if (!Glib::file_test (expanded_path, Glib::FILE_TEST_IS_DIR)) continue;
+               try {
+                       Glib::Dir dir(expanded_path);
+                       for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
+                               string fullpath = Glib::build_filename (expanded_path, *di);
+
+                               /* we're only interested in bundles */
+                               if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) {
+                                       continue;
+                               }
+
+                               if (mac_vst_filter (fullpath)) {
+                                       ARDOUR::PluginScanMessage(_("MacVST"), fullpath, !cache_only && !cancelled());
+                                       mac_vst_discover (fullpath, cache_only || cancelled());
+                                       continue;
+                               }
+
+                               /* don't descend into AU bundles in the VST dir */
+                               if (fullpath[0] == '.' || (fullpath.length() > 10 && strings_equal_ignore_case (".component", fullpath.substr(fullpath.length() - 10)))) {
+                                       continue;
+                               }
+
+                               /* recurse */
+                               mac_vst_discover_from_path (fullpath, cache_only);
+                       }
+               } catch (Glib::FileError& err) { }
+       }
+
+       return 0;
+}
+
+int
+PluginManager::mac_vst_discover (string path, bool cache_only)
+{
+       DEBUG_TRACE (DEBUG::PluginManager, string_compose ("checking apparent MacVST plugin at %1\n", path));
+
+       _cancel_timeout = false;
+
+       vector<VSTInfo*>* finfos = vstfx_get_info_mac (const_cast<char *> (path.c_str()),
+                       cache_only ? VST_SCAN_CACHE_ONLY : VST_SCAN_USE_APP);
+
+       if (finfos->empty()) {
+               DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot get Mac VST information from '%1'\n", path));
+               return -1;
+       }
+
+       uint32_t discovered = 0;
+       for (vector<VSTInfo *>::iterator x = finfos->begin(); x != finfos->end(); ++x) {
+               VSTInfo* finfo = *x;
+               char buf[32];
+
+               if (!finfo->canProcessReplacing) {
+                       warning << string_compose (_("Mac VST plugin %1 does not support processReplacing, and so cannot be used in %2 at this time"),
+                                                        finfo->name, PROGRAM_NAME)
+                               << endl;
+                       continue;
+               }
+
+               PluginInfoPtr info (new MacVSTPluginInfo);
+
+               info->name = finfo->name;
+
+               snprintf (buf, sizeof (buf), "%d", finfo->UniqueID);
+               info->unique_id = buf;
+               info->category = finfo->Category;
+               info->path = path;
+               info->creator = finfo->creator;
+               info->index = 0;
+               info->n_inputs.set_audio (finfo->numInputs);
+               info->n_outputs.set_audio (finfo->numOutputs);
+               info->n_inputs.set_midi ((finfo->wantMidi&1) ? 1 : 0);
+               info->n_outputs.set_midi ((finfo->wantMidi&2) ? 1 : 0);
+               info->type = ARDOUR::MacVST;
+
+               /* if we don't have any tags for this plugin, make some up. */
+               set_tags (info->type, info->unique_id, info->category, true);
+
+               bool duplicate = false;
+               if (!_mac_vst_plugin_info->empty()) {
+                       for (PluginInfoList::iterator i =_mac_vst_plugin_info->begin(); i != _mac_vst_plugin_info->end(); ++i) {
+                               if ((info->type == (*i)->type)&&(info->unique_id == (*i)->unique_id)) {
+                                       warning << "Ignoring duplicate Mac VST plugin " << info->name << "\n";
+                                       duplicate = true;
+                                       break;
+                               }
+                       }
+               }
+
+               if (!duplicate) {
+                       _mac_vst_plugin_info->push_back (info);
+                       discovered++;
+               }
+       }
+
+       vstfx_free_info_list (finfos);
+       return discovered > 0 ? 0 : -1;
+}
+
+#endif // MAC_VST_SUPPORT
+
 #ifdef LXVST_SUPPORT
 
 void
@@ -963,6 +1218,11 @@ PluginManager::lxvst_discover_from_path (string path, bool cache_only)
        vector<string>::iterator x;
        int ret = 0;
 
+       if (Session::get_disable_all_loaded_plugins ()) {
+               info << _("Disabled LinuxVST scan (safe mode)") << endmsg;
+               return -1;
+       }
+
 #ifndef NDEBUG
        (void) path;
 #endif
@@ -1016,7 +1276,7 @@ PluginManager::lxvst_discover (string path, bool cache_only)
 
                snprintf (buf, sizeof (buf), "%d", finfo->UniqueID);
                info->unique_id = buf;
-               info->category = "linuxVSTs";
+               info->category = finfo->Category;
                info->path = path;
                info->creator = finfo->creator;
                info->index = 0;
@@ -1026,12 +1286,14 @@ PluginManager::lxvst_discover (string path, bool cache_only)
                info->n_outputs.set_midi ((finfo->wantMidi&2) ? 1 : 0);
                info->type = ARDOUR::LXVST;
 
-                                       /* Make sure we don't find the same plugin in more than one place along
-                        the LXVST_PATH We can't use a simple 'find' because the path is included
-                        in the PluginInfo, and that is the one thing we can be sure MUST be
-                        different if a duplicate instance is found.  So we just compare the type
-                        and unique ID (which for some VSTs isn't actually unique...)
-               */
+               set_tags (info->type, info->unique_id, info->category, true);
+
+               /* Make sure we don't find the same plugin in more than one place along
+                * the LXVST_PATH We can't use a simple 'find' because the path is included
+                * in the PluginInfo, and that is the one thing we can be sure MUST be
+                * different if a duplicate instance is found.  So we just compare the type
+                * and unique ID (which for some VSTs isn't actually unique...)
+                */
 
                // TODO: check dup-IDs with windowsVST, too
                bool duplicate = false;
@@ -1059,11 +1321,11 @@ PluginManager::lxvst_discover (string path, bool cache_only)
 
 
 PluginManager::PluginStatusType
-PluginManager::get_status (const PluginInfoPtr& pi)
+PluginManager::get_status (const PluginInfoPtr& pi) const
 {
        PluginStatus ps (pi->type, pi->unique_id);
        PluginStatusList::const_iterator i =  find (statuses.begin(), statuses.end(), ps);
-       if (i ==  statuses.end() ) {
+       if (i ==  statuses.end()) {
                return Normal;
        } else {
                return i->status;
@@ -1073,7 +1335,7 @@ PluginManager::get_status (const PluginInfoPtr& pi)
 void
 PluginManager::save_statuses ()
 {
-       std::string path = Glib::build_filename (user_config_directory(), "plugin_statuses");
+       std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_statuses");
        stringstream ofs;
 
        for (PluginStatusList::iterator i = statuses.begin(); i != statuses.end(); ++i) {
@@ -1093,6 +1355,12 @@ PluginManager::save_statuses ()
                case LXVST:
                        ofs << "LXVST";
                        break;
+               case MacVST:
+                       ofs << "MacVST";
+                       break;
+               case Lua:
+                       ofs << "Lua";
+                       break;
                }
 
                ofs << ' ';
@@ -1110,6 +1378,7 @@ PluginManager::save_statuses ()
                }
 
                ofs << ' ';
+
                ofs << (*i).unique_id;;
                ofs << endl;
        }
@@ -1119,7 +1388,8 @@ PluginManager::save_statuses ()
 void
 PluginManager::load_statuses ()
 {
-       std::string path = Glib::build_filename (user_config_directory(), "plugin_statuses");
+       std::string path;
+       find_file (plugin_metadata_search_path(), "plugin_statuses", path);  //note: if no user folder is found, this will find the resources path
        gchar *fbuf = NULL;
        if (!g_file_get_contents (path.c_str(), &fbuf, NULL, NULL))  {
                return;
@@ -1178,6 +1448,10 @@ PluginManager::load_statuses ()
                        type = Windows_VST;
                } else if (stype == "LXVST") {
                        type = LXVST;
+               } else if (stype == "MacVST") {
+                       type = MacVST;
+               } else if (stype == "Lua") {
+                       type = Lua;
                } else {
                        error << string_compose (_("unknown plugin type \"%1\" - ignored"), stype)
                              << endmsg;
@@ -1196,14 +1470,235 @@ PluginManager::set_status (PluginType t, string id, PluginStatusType status)
        PluginStatus ps (t, id, status);
        statuses.erase (ps);
 
-       if (status == Normal) {
-               return;
+       if (status != Normal) {
+               statuses.insert (ps);
+       }
+
+       PluginStatusesChanged (t, id, status); /* EMIT SIGNAL */
+}
+
+PluginType
+PluginManager::to_generic_vst (const PluginType t)
+{
+       switch (t) {
+               case Windows_VST:
+               case LXVST:
+               case MacVST:
+                       return LXVST;
+               default:
+                       break;
+       }
+       return t;
+}
+
+struct SortByTag {
+       bool operator() (std::string a, std::string b) {
+               return a.compare (b) < 0;
+       }
+};
+
+vector<std::string>
+PluginManager::get_tags (const PluginInfoPtr& pi) const
+{
+       vector<std::string> tags;
+
+       PluginTag ps (to_generic_vst(pi->type), pi->unique_id, "", false);
+       PluginTagList::const_iterator i = find (ptags.begin(), ptags.end(), ps);
+       if (i != ptags.end ()) {
+               PBD::tokenize (i->tags, string(" "), std::back_inserter (tags), true);
+               SortByTag sorter;
+               sort (tags.begin(), tags.end(), sorter);
+       }
+       return tags;
+}
+
+std::string
+PluginManager::get_tags_as_string (PluginInfoPtr const& pi) const
+{
+       std::string ret;
+
+       vector<std::string> tags = get_tags(pi);
+       for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
+               if (t != tags.begin ()) {
+                       ret.append(" ");
+               }
+               ret.append(*t);
+       }
+
+       return ret;
+}
+
+std::string
+PluginManager::user_plugin_metadata_dir () const
+{
+       std::string dir = Glib::build_filename (user_config_directory(), plugin_metadata_dir_name);
+       g_mkdir_with_parents (dir.c_str(), 0744);
+       return dir;
+}
+
+void
+PluginManager::save_tags ()
+{
+       std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_tags");
+       XMLNode* root = new XMLNode (X_("PluginTags"));
+
+       for (PluginTagList::iterator i = ptags.begin(); i != ptags.end(); ++i) {
+               if (!(*i).user_set) {
+                       continue;
+               }
+               XMLNode* node = new XMLNode (X_("Plugin"));
+               node->set_property (X_("type"), to_generic_vst ((*i).type));
+               node->set_property (X_("id"), (*i).unique_id);
+               node->set_property (X_("tags"), (*i).tags);
+               node->set_property (X_("user-set"), (*i).user_set);
+               root->add_child_nocopy (*node);
+       }
+
+       XMLTree tree;
+       tree.set_root (root);
+       if (!tree.write (path)) {
+               error << string_compose (_("Could not save Plugin Tags info to %1"), path) << endmsg;
+       }
+}
+
+void
+PluginManager::load_tags ()
+{
+       vector<std::string> tmp;
+       find_files_matching_pattern (tmp, plugin_metadata_search_path (), "plugin_tags");
+
+       for (vector<std::string>::const_reverse_iterator p = tmp.rbegin (); p != tmp.rend(); ++p) {
+               std::string path = *p;
+               info << string_compose (_("Loading plugin meta data file %1"), path) << endmsg;
+               if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
+                       return;
+               }
+
+               XMLTree tree;
+               if (!tree.read (path)) {
+                       error << string_compose (_("Cannot parse plugin tag info from %1"), path) << endmsg;
+                       return;
+               }
+
+               for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
+                       PluginType type;
+                       string id;
+                       string tags;
+                       bool user_set;
+                       if (!(*i)->get_property (X_("type"), type) ||
+                                       !(*i)->get_property (X_("id"), id) ||
+                                       !(*i)->get_property (X_("tags"), tags)) {
+                       }
+                       if (!(*i)->get_property (X_("user-set"), user_set)) {
+                               user_set = false;
+                       }
+                       strip_whitespace_edges (tags);
+                       set_tags (type, id, tags, !user_set);
+               }
+       }
+}
+
+void
+PluginManager::set_tags (PluginType t, string id, string tag, bool factory, bool force)
+{
+       string sanitized = sanitize_tag (tag);
+
+       PluginTag ps (to_generic_vst (t), id, sanitized, !factory);
+       PluginTagList::const_iterator i = find (ptags.begin(), ptags.end(), ps);
+       if (i == ptags.end()) {
+               ptags.insert (ps);
+       } else if (!factory || force || !(*i).user_set) {
+               ptags.erase (ps);
+               ptags.insert (ps);
+       }
+       if (!factory || force) {
+               PluginTagsChanged (t, id, sanitized); /* EMIT SIGNAL */
+       }
+}
+
+void
+PluginManager::reset_tags (PluginInfoPtr const& pi)
+{
+       set_tags (pi->type, pi->unique_id, pi->category, true, true);
+}
+
+std::string
+PluginManager::sanitize_tag (const std::string to_sanitize) const
+{
+       if (to_sanitize.empty ()) {
+               return "";
+       }
+       string sanitized = to_sanitize;
+       vector<string> tags;
+       if (!PBD::tokenize (sanitized, string(" ,\n"), std::back_inserter (tags), true)) {
+#ifndef NDEBUG
+               cerr << _("PluginManager::sanitize_tag could not tokenize string: ") << sanitized << endmsg;
+#endif
+               return "";
        }
 
-       statuses.insert (ps);
+       /* convert tokens to lower-case, space-separated list */
+       sanitized = "";
+       for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
+               if (t != tags.begin ()) {
+                       sanitized.append(" ");
+               }
+               sanitized.append (downcase (*t));
+       }
+
+       return sanitized;
 }
 
-ARDOUR::PluginInfoList&
+std::vector<std::string>
+PluginManager::get_all_tags (bool favorites_only) const
+{
+       std::vector<std::string> ret;
+
+       PluginTagList::const_iterator pt;
+       for (pt = ptags.begin(); pt != ptags.end(); ++pt) {
+               if ((*pt).tags.empty ()) {
+                       continue;
+               }
+
+               /* if favorites_only then we need to check the info ptr and maybe skip */
+               if (favorites_only) {
+                       PluginStatus stat ((*pt).type, (*pt).unique_id);
+                       PluginStatusList::const_iterator i =  find (statuses.begin(), statuses.end(), stat);
+                       if ((i != statuses.end()) && (i->status == Favorite)) {
+                               /* it's a favorite! */
+                       } else {
+                               continue;
+                       }
+               }
+
+               /* parse each plugin's tag string into separate tags */
+               vector<string> tags;
+               if (!PBD::tokenize ((*pt).tags, string(" "), std::back_inserter (tags), true)) {
+#ifndef NDEBUG
+                       cerr << _("PluginManager: Could not tokenize string: ") << (*pt).tags << endmsg;
+#endif
+                       continue;
+               }
+
+               /* maybe add the tags we've found */
+               for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
+                       /* if this tag isn't already in the list, add it */
+                       vector<string>::iterator i =  find (ret.begin(), ret.end(), *t);
+                       if (i == ret.end()) {
+                               ret.push_back (*t);
+                       }
+               }
+       }
+
+       /* sort in alphabetical order */
+       SortByTag sorter;
+       sort (ret.begin(), ret.end(), sorter);
+
+       return ret;
+}
+
+
+const ARDOUR::PluginInfoList&
 PluginManager::windows_vst_plugin_info ()
 {
 #ifdef WINDOWS_VST_SUPPORT
@@ -1216,7 +1711,18 @@ PluginManager::windows_vst_plugin_info ()
 #endif
 }
 
-ARDOUR::PluginInfoList&
+const ARDOUR::PluginInfoList&
+PluginManager::mac_vst_plugin_info ()
+{
+#ifdef MACVST_SUPPORT
+       assert(_mac_vst_plugin_info);
+       return *_mac_vst_plugin_info;
+#else
+       return _empty_plugin_info;
+#endif
+}
+
+const ARDOUR::PluginInfoList&
 PluginManager::lxvst_plugin_info ()
 {
 #ifdef LXVST_SUPPORT
@@ -1227,14 +1733,14 @@ PluginManager::lxvst_plugin_info ()
 #endif
 }
 
-ARDOUR::PluginInfoList&
+const ARDOUR::PluginInfoList&
 PluginManager::ladspa_plugin_info ()
 {
        assert(_ladspa_plugin_info);
        return *_ladspa_plugin_info;
 }
 
-ARDOUR::PluginInfoList&
+const ARDOUR::PluginInfoList&
 PluginManager::lv2_plugin_info ()
 {
 #ifdef LV2_SUPPORT
@@ -1245,7 +1751,7 @@ PluginManager::lv2_plugin_info ()
 #endif
 }
 
-ARDOUR::PluginInfoList&
+const ARDOUR::PluginInfoList&
 PluginManager::au_plugin_info ()
 {
 #ifdef AUDIOUNIT_SUPPORT
@@ -1255,3 +1761,10 @@ PluginManager::au_plugin_info ()
 #endif
        return _empty_plugin_info;
 }
+
+const ARDOUR::PluginInfoList&
+PluginManager::lua_plugin_info ()
+{
+       assert(_lua_plugin_info);
+       return *_lua_plugin_info;
+}