cannam@64: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@64: cannam@64: /* cannam@64: Vamp cannam@64: cannam@64: An API for audio analysis and feature extraction plugins. cannam@64: cannam@64: Centre for Digital Music, Queen Mary, University of London. cannam@71: Copyright 2006-2007 Chris Cannam and QMUL. cannam@64: cannam@64: Permission is hereby granted, free of charge, to any person cannam@64: obtaining a copy of this software and associated documentation cannam@64: files (the "Software"), to deal in the Software without cannam@64: restriction, including without limitation the rights to use, copy, cannam@64: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@64: of the Software, and to permit persons to whom the Software is cannam@64: furnished to do so, subject to the following conditions: cannam@64: cannam@64: The above copyright notice and this permission notice shall be cannam@64: included in all copies or substantial portions of the Software. cannam@64: cannam@64: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@64: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@64: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@64: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@64: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@64: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@64: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@64: cannam@64: Except as contained in this notice, the names of the Centre for cannam@64: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@64: shall not be used in advertising or otherwise to promote the sale, cannam@64: use or other dealings in this Software without prior written cannam@64: authorization. cannam@64: */ cannam@64: cannam@64: #include "vamp-sdk/PluginHostAdapter.h" cannam@64: #include "PluginLoader.h" cannam@64: #include "PluginInputDomainAdapter.h" cannam@64: #include "PluginChannelAdapter.h" cannam@92: #include "PluginBufferingAdapter.h" cannam@64: cannam@64: #include cannam@75: #include // tolower cannam@64: cannam@64: #ifdef _WIN32 cannam@64: cannam@64: #include cannam@64: #include cannam@64: #define PLUGIN_SUFFIX "dll" cannam@64: cannam@64: #else /* ! _WIN32 */ cannam@64: cannam@64: #include cannam@64: #include cannam@64: cannam@64: #ifdef __APPLE__ cannam@64: #define PLUGIN_SUFFIX "dylib" cannam@64: #else /* ! __APPLE__ */ cannam@64: #define PLUGIN_SUFFIX "so" cannam@64: #endif /* ! __APPLE__ */ cannam@64: cannam@64: #endif /* ! _WIN32 */ cannam@64: cannam@64: using namespace std; cannam@64: cannam@64: namespace Vamp { cannam@64: cannam@64: namespace HostExt { cannam@64: cannam@69: class PluginLoader::Impl cannam@69: { cannam@69: public: cannam@72: Impl(); cannam@72: virtual ~Impl(); cannam@69: cannam@69: PluginKeyList listPlugins(); cannam@69: cannam@69: Plugin *loadPlugin(PluginKey key, cannam@69: float inputSampleRate, cannam@69: int adapterFlags); cannam@69: cannam@69: PluginKey composePluginKey(string libraryName, string identifier); cannam@69: cannam@69: PluginCategoryHierarchy getPluginCategory(PluginKey key); cannam@69: cannam@72: string getLibraryPathForPlugin(PluginKey key); cannam@69: cannam@98: static void setInstanceToClean(PluginLoader *instance); cannam@98: cannam@69: protected: cannam@69: class PluginDeletionNotifyAdapter : public PluginWrapper { cannam@69: public: cannam@69: PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader); cannam@69: virtual ~PluginDeletionNotifyAdapter(); cannam@69: protected: cannam@69: Impl *m_loader; cannam@69: }; cannam@69: cannam@98: class InstanceCleaner { cannam@98: public: cannam@98: InstanceCleaner() : m_instance(0) { } cannam@98: ~InstanceCleaner() { delete m_instance; } cannam@98: void setInstance(PluginLoader *instance) { m_instance = instance; } cannam@98: protected: cannam@98: PluginLoader *m_instance; cannam@98: }; cannam@98: cannam@69: virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter); cannam@69: cannam@72: map m_pluginLibraryNameMap; cannam@72: bool m_allPluginsEnumerated; cannam@72: void enumeratePlugins(PluginKey forPlugin = ""); cannam@69: cannam@72: map m_taxonomy; cannam@69: void generateTaxonomy(); cannam@69: cannam@72: map m_pluginLibraryHandleMap; cannam@69: cannam@72: bool decomposePluginKey(PluginKey key, cannam@72: string &libraryName, string &identifier); cannam@72: cannam@72: void *loadLibrary(string path); cannam@69: void unloadLibrary(void *handle); cannam@69: void *lookupInLibrary(void *handle, const char *symbol); cannam@69: cannam@72: string splicePath(string a, string b); cannam@72: vector listFiles(string dir, string ext); cannam@98: cannam@98: static InstanceCleaner m_cleaner; cannam@69: }; cannam@69: cannam@64: PluginLoader * cannam@64: PluginLoader::m_instance = 0; cannam@64: cannam@98: PluginLoader::Impl::InstanceCleaner cannam@98: PluginLoader::Impl::m_cleaner; cannam@98: cannam@64: PluginLoader::PluginLoader() cannam@64: { cannam@69: m_impl = new Impl(); cannam@64: } cannam@64: cannam@64: PluginLoader::~PluginLoader() cannam@64: { cannam@69: delete m_impl; cannam@64: } cannam@64: cannam@64: PluginLoader * cannam@64: PluginLoader::getInstance() cannam@64: { cannam@98: if (!m_instance) { cannam@98: // The cleaner doesn't own the instance, because we leave the cannam@98: // instance pointer in the base class for binary backwards cannam@98: // compatibility reasons and to avoid waste cannam@98: m_instance = new PluginLoader(); cannam@98: Impl::setInstanceToClean(m_instance); cannam@98: } cannam@64: return m_instance; cannam@64: } cannam@64: cannam@64: vector cannam@64: PluginLoader::listPlugins() cannam@64: { cannam@69: return m_impl->listPlugins(); cannam@69: } cannam@69: cannam@69: Plugin * cannam@69: PluginLoader::loadPlugin(PluginKey key, cannam@69: float inputSampleRate, cannam@69: int adapterFlags) cannam@69: { cannam@69: return m_impl->loadPlugin(key, inputSampleRate, adapterFlags); cannam@69: } cannam@69: cannam@69: PluginLoader::PluginKey cannam@69: PluginLoader::composePluginKey(string libraryName, string identifier) cannam@69: { cannam@69: return m_impl->composePluginKey(libraryName, identifier); cannam@69: } cannam@69: cannam@69: PluginLoader::PluginCategoryHierarchy cannam@69: PluginLoader::getPluginCategory(PluginKey key) cannam@69: { cannam@69: return m_impl->getPluginCategory(key); cannam@69: } cannam@69: cannam@69: string cannam@69: PluginLoader::getLibraryPathForPlugin(PluginKey key) cannam@69: { cannam@69: return m_impl->getLibraryPathForPlugin(key); cannam@69: } cannam@72: cannam@72: PluginLoader::Impl::Impl() : cannam@72: m_allPluginsEnumerated(false) cannam@72: { cannam@72: } cannam@72: cannam@72: PluginLoader::Impl::~Impl() cannam@72: { cannam@72: } cannam@69: cannam@98: void cannam@98: PluginLoader::Impl::setInstanceToClean(PluginLoader *instance) cannam@98: { cannam@98: m_cleaner.setInstance(instance); cannam@98: } cannam@98: cannam@69: vector cannam@69: PluginLoader::Impl::listPlugins() cannam@69: { cannam@72: if (!m_allPluginsEnumerated) enumeratePlugins(); cannam@64: cannam@64: vector plugins; cannam@72: for (map::iterator mi = m_pluginLibraryNameMap.begin(); cannam@64: mi != m_pluginLibraryNameMap.end(); ++mi) { cannam@64: plugins.push_back(mi->first); cannam@64: } cannam@64: cannam@64: return plugins; cannam@64: } cannam@64: cannam@64: void cannam@72: PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin) cannam@64: { cannam@64: vector path = PluginHostAdapter::getPluginPath(); cannam@64: cannam@72: string libraryName, identifier; cannam@72: if (forPlugin != "") { cannam@72: if (!decomposePluginKey(forPlugin, libraryName, identifier)) { cannam@72: std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \"" cannam@72: << forPlugin << "\" in enumerate" << std::endl; cannam@72: return; cannam@72: } cannam@72: } cannam@72: cannam@64: for (size_t i = 0; i < path.size(); ++i) { cannam@64: cannam@64: vector files = listFiles(path[i], PLUGIN_SUFFIX); cannam@64: cannam@64: for (vector::iterator fi = files.begin(); cannam@64: fi != files.end(); ++fi) { cannam@64: cannam@72: if (libraryName != "") { cannam@73: // libraryName is lowercased and lacking an extension, cannam@73: // as it came from the plugin key cannam@73: string temp = *fi; cannam@73: for (size_t i = 0; i < temp.length(); ++i) { cannam@75: temp[i] = tolower(temp[i]); cannam@73: } cannam@73: string::size_type pi = temp.find('.'); cannam@72: if (pi == string::npos) { cannam@73: if (libraryName != temp) continue; cannam@72: } else { cannam@73: if (libraryName != temp.substr(0, pi)) continue; cannam@72: } cannam@72: } cannam@72: cannam@64: string fullPath = path[i]; cannam@64: fullPath = splicePath(fullPath, *fi); cannam@64: void *handle = loadLibrary(fullPath); cannam@64: if (!handle) continue; cannam@64: cannam@64: VampGetPluginDescriptorFunction fn = cannam@64: (VampGetPluginDescriptorFunction)lookupInLibrary cannam@64: (handle, "vampGetPluginDescriptor"); cannam@64: cannam@64: if (!fn) { cannam@64: unloadLibrary(handle); cannam@64: continue; cannam@64: } cannam@64: cannam@64: int index = 0; cannam@64: const VampPluginDescriptor *descriptor = 0; cannam@64: cannam@64: while ((descriptor = fn(VAMP_API_VERSION, index))) { cannam@72: ++index; cannam@72: if (identifier != "") { cannam@72: if (descriptor->identifier != identifier) continue; cannam@72: } cannam@64: PluginKey key = composePluginKey(*fi, descriptor->identifier); cannam@72: // std::cerr << "enumerate: " << key << " (path: " << fullPath << ")" << std::endl; cannam@64: if (m_pluginLibraryNameMap.find(key) == cannam@64: m_pluginLibraryNameMap.end()) { cannam@64: m_pluginLibraryNameMap[key] = fullPath; cannam@64: } cannam@64: } cannam@64: cannam@64: unloadLibrary(handle); cannam@64: } cannam@64: } cannam@72: cannam@72: if (forPlugin == "") m_allPluginsEnumerated = true; cannam@64: } cannam@64: cannam@64: PluginLoader::PluginKey cannam@69: PluginLoader::Impl::composePluginKey(string libraryName, string identifier) cannam@64: { cannam@64: string basename = libraryName; cannam@64: cannam@64: string::size_type li = basename.rfind('/'); cannam@64: if (li != string::npos) basename = basename.substr(li + 1); cannam@64: cannam@64: li = basename.find('.'); cannam@64: if (li != string::npos) basename = basename.substr(0, li); cannam@64: cannam@73: for (size_t i = 0; i < basename.length(); ++i) { cannam@75: basename[i] = tolower(basename[i]); cannam@73: } cannam@73: cannam@64: return basename + ":" + identifier; cannam@64: } cannam@64: cannam@72: bool cannam@72: PluginLoader::Impl::decomposePluginKey(PluginKey key, cannam@72: string &libraryName, cannam@72: string &identifier) cannam@72: { cannam@72: string::size_type ki = key.find(':'); cannam@72: if (ki == string::npos) { cannam@72: return false; cannam@72: } cannam@72: cannam@72: libraryName = key.substr(0, ki); cannam@72: identifier = key.substr(ki + 1); cannam@72: return true; cannam@72: } cannam@72: cannam@64: PluginLoader::PluginCategoryHierarchy cannam@69: PluginLoader::Impl::getPluginCategory(PluginKey plugin) cannam@64: { cannam@64: if (m_taxonomy.empty()) generateTaxonomy(); cannam@69: if (m_taxonomy.find(plugin) == m_taxonomy.end()) { cannam@69: return PluginCategoryHierarchy(); cannam@69: } cannam@64: return m_taxonomy[plugin]; cannam@64: } cannam@64: cannam@64: string cannam@69: PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin) cannam@64: { cannam@72: if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { cannam@72: if (m_allPluginsEnumerated) return ""; cannam@72: enumeratePlugins(plugin); cannam@72: } cannam@72: if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { cannam@72: return ""; cannam@72: } cannam@64: return m_pluginLibraryNameMap[plugin]; cannam@64: } cannam@64: cannam@64: Plugin * cannam@69: PluginLoader::Impl::loadPlugin(PluginKey key, cannam@69: float inputSampleRate, int adapterFlags) cannam@64: { cannam@72: string libname, identifier; cannam@72: if (!decomposePluginKey(key, libname, identifier)) { cannam@72: std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \"" cannam@72: << key << "\" in loadPlugin" << std::endl; cannam@72: return 0; cannam@72: } cannam@72: cannam@64: string fullPath = getLibraryPathForPlugin(key); cannam@64: if (fullPath == "") return 0; cannam@64: cannam@64: void *handle = loadLibrary(fullPath); cannam@64: if (!handle) return 0; cannam@64: cannam@64: VampGetPluginDescriptorFunction fn = cannam@64: (VampGetPluginDescriptorFunction)lookupInLibrary cannam@64: (handle, "vampGetPluginDescriptor"); cannam@64: cannam@64: if (!fn) { cannam@64: unloadLibrary(handle); cannam@64: return 0; cannam@64: } cannam@64: cannam@64: int index = 0; cannam@64: const VampPluginDescriptor *descriptor = 0; cannam@64: cannam@64: while ((descriptor = fn(VAMP_API_VERSION, index))) { cannam@64: cannam@64: if (string(descriptor->identifier) == identifier) { cannam@64: cannam@64: Vamp::PluginHostAdapter *plugin = cannam@64: new Vamp::PluginHostAdapter(descriptor, inputSampleRate); cannam@64: cannam@64: Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this); cannam@64: cannam@64: m_pluginLibraryHandleMap[adapter] = handle; cannam@64: cannam@64: if (adapterFlags & ADAPT_INPUT_DOMAIN) { cannam@64: if (adapter->getInputDomain() == Plugin::FrequencyDomain) { cannam@64: adapter = new PluginInputDomainAdapter(adapter); cannam@64: } cannam@64: } cannam@64: cannam@92: if (adapterFlags & ADAPT_BUFFER_SIZE) { cannam@92: adapter = new PluginBufferingAdapter(adapter); cannam@92: } cannam@92: cannam@64: if (adapterFlags & ADAPT_CHANNEL_COUNT) { cannam@64: adapter = new PluginChannelAdapter(adapter); cannam@64: } cannam@64: cannam@64: return adapter; cannam@64: } cannam@64: cannam@64: ++index; cannam@64: } cannam@64: cannam@64: cerr << "Vamp::HostExt::PluginLoader: Plugin \"" cannam@64: << identifier << "\" not found in library \"" cannam@64: << fullPath << "\"" << endl; cannam@64: cannam@64: return 0; cannam@64: } cannam@64: cannam@64: void cannam@69: PluginLoader::Impl::generateTaxonomy() cannam@64: { cannam@69: // cerr << "PluginLoader::Impl::generateTaxonomy" << endl; cannam@64: cannam@64: vector path = PluginHostAdapter::getPluginPath(); cannam@64: string libfragment = "/lib/"; cannam@64: vector catpath; cannam@64: cannam@64: string suffix = "cat"; cannam@64: cannam@64: for (vector::iterator i = path.begin(); cannam@64: i != path.end(); ++i) { cannam@64: cannam@64: // It doesn't matter that we're using literal forward-slash in cannam@64: // this bit, as it's only relevant if the path contains cannam@64: // "/lib/", which is only meaningful and only plausible on cannam@64: // systems with forward-slash delimiters cannam@64: cannam@64: string dir = *i; cannam@64: string::size_type li = dir.find(libfragment); cannam@64: cannam@64: if (li != string::npos) { cannam@64: catpath.push_back cannam@64: (dir.substr(0, li) cannam@64: + "/share/" cannam@64: + dir.substr(li + libfragment.length())); cannam@64: } cannam@64: cannam@64: catpath.push_back(dir); cannam@64: } cannam@64: cannam@64: char buffer[1024]; cannam@64: cannam@64: for (vector::iterator i = catpath.begin(); cannam@64: i != catpath.end(); ++i) { cannam@64: cannam@64: vector files = listFiles(*i, suffix); cannam@64: cannam@64: for (vector::iterator fi = files.begin(); cannam@64: fi != files.end(); ++fi) { cannam@64: cannam@64: string filepath = splicePath(*i, *fi); cannam@64: ifstream is(filepath.c_str(), ifstream::in | ifstream::binary); cannam@64: cannam@64: if (is.fail()) { cannam@64: // cerr << "failed to open: " << filepath << endl; cannam@64: continue; cannam@64: } cannam@64: cannam@64: // cerr << "opened: " << filepath << endl; cannam@64: cannam@64: while (!!is.getline(buffer, 1024)) { cannam@64: cannam@64: string line(buffer); cannam@64: cannam@64: // cerr << "line = " << line << endl; cannam@64: cannam@64: string::size_type di = line.find("::"); cannam@64: if (di == string::npos) continue; cannam@64: cannam@64: string id = line.substr(0, di); cannam@64: string encodedCat = line.substr(di + 2); cannam@64: cannam@64: if (id.substr(0, 5) != "vamp:") continue; cannam@64: id = id.substr(5); cannam@64: cannam@64: while (encodedCat.length() >= 1 && cannam@64: encodedCat[encodedCat.length()-1] == '\r') { cannam@64: encodedCat = encodedCat.substr(0, encodedCat.length()-1); cannam@64: } cannam@64: cannam@64: // cerr << "id = " << id << ", cat = " << encodedCat << endl; cannam@64: cannam@64: PluginCategoryHierarchy category; cannam@64: string::size_type ai; cannam@64: while ((ai = encodedCat.find(" > ")) != string::npos) { cannam@64: category.push_back(encodedCat.substr(0, ai)); cannam@64: encodedCat = encodedCat.substr(ai + 3); cannam@64: } cannam@64: if (encodedCat != "") category.push_back(encodedCat); cannam@64: cannam@64: m_taxonomy[id] = category; cannam@64: } cannam@64: } cannam@64: } cannam@64: } cannam@64: cannam@64: void * cannam@69: PluginLoader::Impl::loadLibrary(string path) cannam@64: { cannam@64: void *handle = 0; cannam@64: #ifdef _WIN32 cannam@64: handle = LoadLibrary(path.c_str()); cannam@64: if (!handle) { cannam@64: cerr << "Vamp::HostExt::PluginLoader: Unable to load library \"" cannam@64: << path << "\"" << endl; cannam@64: } cannam@64: #else cannam@64: handle = dlopen(path.c_str(), RTLD_LAZY); cannam@64: if (!handle) { cannam@64: cerr << "Vamp::HostExt::PluginLoader: Unable to load library \"" cannam@64: << path << "\": " << dlerror() << endl; cannam@64: } cannam@64: #endif cannam@64: return handle; cannam@64: } cannam@64: cannam@64: void cannam@69: PluginLoader::Impl::unloadLibrary(void *handle) cannam@64: { cannam@64: #ifdef _WIN32 cannam@64: FreeLibrary((HINSTANCE)handle); cannam@64: #else cannam@64: dlclose(handle); cannam@64: #endif cannam@64: } cannam@64: cannam@64: void * cannam@69: PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol) cannam@64: { cannam@64: #ifdef _WIN32 cannam@64: return (void *)GetProcAddress((HINSTANCE)handle, symbol); cannam@64: #else cannam@64: return (void *)dlsym(handle, symbol); cannam@64: #endif cannam@64: } cannam@64: cannam@64: string cannam@69: PluginLoader::Impl::splicePath(string a, string b) cannam@64: { cannam@64: #ifdef _WIN32 cannam@64: return a + "\\" + b; cannam@64: #else cannam@64: return a + "/" + b; cannam@64: #endif cannam@64: } cannam@64: cannam@64: vector cannam@69: PluginLoader::Impl::listFiles(string dir, string extension) cannam@64: { cannam@64: vector files; cannam@64: cannam@64: #ifdef _WIN32 cannam@64: cannam@64: string expression = dir + "\\*." + extension; cannam@64: WIN32_FIND_DATA data; cannam@64: HANDLE fh = FindFirstFile(expression.c_str(), &data); cannam@64: if (fh == INVALID_HANDLE_VALUE) return files; cannam@64: cannam@64: bool ok = true; cannam@64: while (ok) { cannam@64: files.push_back(data.cFileName); cannam@64: ok = FindNextFile(fh, &data); cannam@64: } cannam@64: cannam@64: FindClose(fh); cannam@64: cannam@64: #else cannam@78: cannam@78: size_t extlen = extension.length(); cannam@64: DIR *d = opendir(dir.c_str()); cannam@64: if (!d) return files; cannam@64: cannam@64: struct dirent *e = 0; cannam@64: while ((e = readdir(d))) { cannam@97: cannam@97: if (!(e->d_type & DT_REG) && (e->d_type != DT_UNKNOWN)) continue; cannam@64: cannam@97: if (!e->d_name) continue; cannam@97: cannam@64: size_t len = strlen(e->d_name); cannam@64: if (len < extlen + 2 || cannam@64: e->d_name + len - extlen - 1 != "." + extension) { cannam@64: continue; cannam@64: } cannam@64: cannam@64: files.push_back(e->d_name); cannam@64: } cannam@64: cannam@64: closedir(d); cannam@64: #endif cannam@64: cannam@64: return files; cannam@64: } cannam@64: cannam@64: void cannam@69: PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter) cannam@64: { cannam@64: void *handle = m_pluginLibraryHandleMap[adapter]; cannam@64: if (handle) unloadLibrary(handle); cannam@64: m_pluginLibraryHandleMap.erase(adapter); cannam@64: } cannam@64: cannam@69: PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin, cannam@69: Impl *loader) : cannam@64: PluginWrapper(plugin), cannam@64: m_loader(loader) cannam@64: { cannam@64: } cannam@64: cannam@69: PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() cannam@64: { cannam@64: // We need to delete the plugin before calling pluginDeleted, as cannam@64: // the delete call may require calling through to the descriptor cannam@64: // (for e.g. cleanup) but pluginDeleted may unload the required cannam@64: // library for the call. To prevent a double deletion when our cannam@64: // parent's destructor runs (after this one), be sure to set cannam@64: // m_plugin to 0 after deletion. cannam@64: delete m_plugin; cannam@64: m_plugin = 0; cannam@64: cannam@64: if (m_loader) m_loader->pluginDeleted(this); cannam@64: } cannam@64: cannam@64: } cannam@64: cannam@64: }