1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
6 An API for audio analysis and feature extraction plugins.
8 Centre for Digital Music, Queen Mary, University of London.
9 Copyright 2006-2007 Chris Cannam and QMUL.
11 Permission is hereby granted, free of charge, to any person
12 obtaining a copy of this software and associated documentation
13 files (the "Software"), to deal in the Software without
14 restriction, including without limitation the rights to use, copy,
15 modify, merge, publish, distribute, sublicense, and/or sell copies
16 of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
19 The above copyright notice and this permission notice shall be
20 included in all copies or substantial portions of the Software.
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 Except as contained in this notice, the names of the Centre for
31 Digital Music; Queen Mary, University of London; and Chris Cannam
32 shall not be used in advertising or otherwise to promote the sale,
33 use or other dealings in this Software without prior written
37 #include <vamp-hostsdk/PluginHostAdapter.h>
38 #include <vamp-hostsdk/PluginLoader.h>
39 #include <vamp-hostsdk/PluginInputDomainAdapter.h>
40 #include <vamp-hostsdk/PluginChannelAdapter.h>
41 #include <vamp-hostsdk/PluginBufferingAdapter.h>
44 #include <cctype> // tolower
52 #define PLUGIN_SUFFIX "dll"
60 #define PLUGIN_SUFFIX "dylib"
61 #else /* ! __APPLE__ */
62 #define PLUGIN_SUFFIX "so"
63 #endif /* ! __APPLE__ */
69 _VAMP_SDK_HOSTSPACE_BEGIN(PluginLoader.cpp)
75 class PluginLoader::Impl
81 PluginKeyList listPlugins();
83 Plugin *loadPlugin(PluginKey key,
84 float inputSampleRate,
87 PluginKey composePluginKey(string libraryName, string identifier);
89 PluginCategoryHierarchy getPluginCategory(PluginKey key);
91 string getLibraryPathForPlugin(PluginKey key);
93 static void setInstanceToClean(PluginLoader *instance);
96 class PluginDeletionNotifyAdapter : public PluginWrapper {
98 PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader);
99 virtual ~PluginDeletionNotifyAdapter();
104 class InstanceCleaner {
106 InstanceCleaner() : m_instance(0) { }
107 ~InstanceCleaner() { delete m_instance; }
108 void setInstance(PluginLoader *instance) { m_instance = instance; }
110 PluginLoader *m_instance;
113 virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter);
115 map<PluginKey, string> m_pluginLibraryNameMap;
116 bool m_allPluginsEnumerated;
117 void enumeratePlugins(PluginKey forPlugin = "");
119 map<PluginKey, PluginCategoryHierarchy> m_taxonomy;
120 void generateTaxonomy();
122 map<Plugin *, void *> m_pluginLibraryHandleMap;
124 bool decomposePluginKey(PluginKey key,
125 string &libraryName, string &identifier);
127 void *loadLibrary(string path);
128 void unloadLibrary(void *handle);
129 void *lookupInLibrary(void *handle, const char *symbol);
131 string splicePath(string a, string b);
132 vector<string> listFiles(string dir, string ext);
134 static InstanceCleaner m_cleaner;
138 PluginLoader::m_instance = 0;
140 PluginLoader::Impl::InstanceCleaner
141 PluginLoader::Impl::m_cleaner;
143 PluginLoader::PluginLoader()
148 PluginLoader::~PluginLoader()
154 PluginLoader::getInstance()
157 // The cleaner doesn't own the instance, because we leave the
158 // instance pointer in the base class for binary backwards
159 // compatibility reasons and to avoid waste
160 m_instance = new PluginLoader();
161 Impl::setInstanceToClean(m_instance);
166 vector<PluginLoader::PluginKey>
167 PluginLoader::listPlugins()
169 return m_impl->listPlugins();
173 PluginLoader::loadPlugin(PluginKey key,
174 float inputSampleRate,
177 return m_impl->loadPlugin(key, inputSampleRate, adapterFlags);
180 PluginLoader::PluginKey
181 PluginLoader::composePluginKey(string libraryName, string identifier)
183 return m_impl->composePluginKey(libraryName, identifier);
186 PluginLoader::PluginCategoryHierarchy
187 PluginLoader::getPluginCategory(PluginKey key)
189 return m_impl->getPluginCategory(key);
193 PluginLoader::getLibraryPathForPlugin(PluginKey key)
195 return m_impl->getLibraryPathForPlugin(key);
198 PluginLoader::Impl::Impl() :
199 m_allPluginsEnumerated(false)
203 PluginLoader::Impl::~Impl()
208 PluginLoader::Impl::setInstanceToClean(PluginLoader *instance)
210 m_cleaner.setInstance(instance);
213 vector<PluginLoader::PluginKey>
214 PluginLoader::Impl::listPlugins()
216 if (!m_allPluginsEnumerated) enumeratePlugins();
218 vector<PluginKey> plugins;
219 for (map<PluginKey, string>::iterator mi = m_pluginLibraryNameMap.begin();
220 mi != m_pluginLibraryNameMap.end(); ++mi) {
221 plugins.push_back(mi->first);
228 PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin)
230 vector<string> path = PluginHostAdapter::getPluginPath();
232 string libraryName, identifier;
233 if (forPlugin != "") {
234 if (!decomposePluginKey(forPlugin, libraryName, identifier)) {
235 std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \""
236 << forPlugin << "\" in enumerate" << std::endl;
241 for (size_t i = 0; i < path.size(); ++i) {
243 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
245 for (vector<string>::iterator fi = files.begin();
246 fi != files.end(); ++fi) {
248 if (libraryName != "") {
249 // libraryName is lowercased and lacking an extension,
250 // as it came from the plugin key
252 for (size_t i = 0; i < temp.length(); ++i) {
253 temp[i] = tolower(temp[i]);
255 string::size_type pi = temp.find('.');
256 if (pi == string::npos) {
257 if (libraryName != temp) continue;
259 if (libraryName != temp.substr(0, pi)) continue;
263 string fullPath = path[i];
264 fullPath = splicePath(fullPath, *fi);
265 void *handle = loadLibrary(fullPath);
266 if (!handle) continue;
268 VampGetPluginDescriptorFunction fn =
269 (VampGetPluginDescriptorFunction)lookupInLibrary
270 (handle, "vampGetPluginDescriptor");
273 unloadLibrary(handle);
278 const VampPluginDescriptor *descriptor = 0;
280 while ((descriptor = fn(VAMP_API_VERSION, index))) {
282 if (identifier != "") {
283 if (descriptor->identifier != identifier) continue;
285 PluginKey key = composePluginKey(*fi, descriptor->identifier);
286 // std::cerr << "enumerate: " << key << " (path: " << fullPath << ")" << std::endl;
287 if (m_pluginLibraryNameMap.find(key) ==
288 m_pluginLibraryNameMap.end()) {
289 m_pluginLibraryNameMap[key] = fullPath;
293 unloadLibrary(handle);
297 if (forPlugin == "") m_allPluginsEnumerated = true;
300 PluginLoader::PluginKey
301 PluginLoader::Impl::composePluginKey(string libraryName, string identifier)
303 string basename = libraryName;
305 string::size_type li = basename.rfind('/');
306 if (li != string::npos) basename = basename.substr(li + 1);
308 li = basename.find('.');
309 if (li != string::npos) basename = basename.substr(0, li);
311 for (size_t i = 0; i < basename.length(); ++i) {
312 basename[i] = tolower(basename[i]);
315 return basename + ":" + identifier;
319 PluginLoader::Impl::decomposePluginKey(PluginKey key,
323 string::size_type ki = key.find(':');
324 if (ki == string::npos) {
328 libraryName = key.substr(0, ki);
329 identifier = key.substr(ki + 1);
333 PluginLoader::PluginCategoryHierarchy
334 PluginLoader::Impl::getPluginCategory(PluginKey plugin)
336 if (m_taxonomy.empty()) generateTaxonomy();
337 if (m_taxonomy.find(plugin) == m_taxonomy.end()) {
338 return PluginCategoryHierarchy();
340 return m_taxonomy[plugin];
344 PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin)
346 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
347 if (m_allPluginsEnumerated) return "";
348 enumeratePlugins(plugin);
350 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
353 return m_pluginLibraryNameMap[plugin];
357 PluginLoader::Impl::loadPlugin(PluginKey key,
358 float inputSampleRate, int adapterFlags)
360 string libname, identifier;
361 if (!decomposePluginKey(key, libname, identifier)) {
362 std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \""
363 << key << "\" in loadPlugin" << std::endl;
367 string fullPath = getLibraryPathForPlugin(key);
368 if (fullPath == "") return 0;
370 void *handle = loadLibrary(fullPath);
371 if (!handle) return 0;
373 VampGetPluginDescriptorFunction fn =
374 (VampGetPluginDescriptorFunction)lookupInLibrary
375 (handle, "vampGetPluginDescriptor");
378 unloadLibrary(handle);
383 const VampPluginDescriptor *descriptor = 0;
385 while ((descriptor = fn(VAMP_API_VERSION, index))) {
387 if (string(descriptor->identifier) == identifier) {
389 Vamp::PluginHostAdapter *plugin =
390 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
392 Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
394 m_pluginLibraryHandleMap[adapter] = handle;
396 if (adapterFlags & ADAPT_INPUT_DOMAIN) {
397 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
398 adapter = new PluginInputDomainAdapter(adapter);
402 if (adapterFlags & ADAPT_BUFFER_SIZE) {
403 adapter = new PluginBufferingAdapter(adapter);
406 if (adapterFlags & ADAPT_CHANNEL_COUNT) {
407 adapter = new PluginChannelAdapter(adapter);
416 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
417 << identifier << "\" not found in library \""
418 << fullPath << "\"" << endl;
424 PluginLoader::Impl::generateTaxonomy()
426 // cerr << "PluginLoader::Impl::generateTaxonomy" << endl;
428 vector<string> path = PluginHostAdapter::getPluginPath();
429 string libfragment = "/lib/";
430 vector<string> catpath;
432 string suffix = "cat";
434 for (vector<string>::iterator i = path.begin();
435 i != path.end(); ++i) {
437 // It doesn't matter that we're using literal forward-slash in
438 // this bit, as it's only relevant if the path contains
439 // "/lib/", which is only meaningful and only plausible on
440 // systems with forward-slash delimiters
443 string::size_type li = dir.find(libfragment);
445 if (li != string::npos) {
449 + dir.substr(li + libfragment.length()));
452 catpath.push_back(dir);
457 for (vector<string>::iterator i = catpath.begin();
458 i != catpath.end(); ++i) {
460 vector<string> files = listFiles(*i, suffix);
462 for (vector<string>::iterator fi = files.begin();
463 fi != files.end(); ++fi) {
465 string filepath = splicePath(*i, *fi);
466 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
469 // cerr << "failed to open: " << filepath << endl;
473 // cerr << "opened: " << filepath << endl;
475 while (!!is.getline(buffer, 1024)) {
479 // cerr << "line = " << line << endl;
481 string::size_type di = line.find("::");
482 if (di == string::npos) continue;
484 string id = line.substr(0, di);
485 string encodedCat = line.substr(di + 2);
487 if (id.substr(0, 5) != "vamp:") continue;
490 while (encodedCat.length() >= 1 &&
491 encodedCat[encodedCat.length()-1] == '\r') {
492 encodedCat = encodedCat.substr(0, encodedCat.length()-1);
495 // cerr << "id = " << id << ", cat = " << encodedCat << endl;
497 PluginCategoryHierarchy category;
498 string::size_type ai;
499 while ((ai = encodedCat.find(" > ")) != string::npos) {
500 category.push_back(encodedCat.substr(0, ai));
501 encodedCat = encodedCat.substr(ai + 3);
503 if (encodedCat != "") category.push_back(encodedCat);
505 m_taxonomy[id] = category;
512 PluginLoader::Impl::loadLibrary(string path)
516 handle = LoadLibrary(path.c_str());
518 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
519 << path << "\"" << endl;
522 handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
524 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
525 << path << "\": " << dlerror() << endl;
532 PluginLoader::Impl::unloadLibrary(void *handle)
535 FreeLibrary((HINSTANCE)handle);
542 PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol)
545 return (void *)GetProcAddress((HINSTANCE)handle, symbol);
547 return (void *)dlsym(handle, symbol);
552 PluginLoader::Impl::splicePath(string a, string b)
562 PluginLoader::Impl::listFiles(string dir, string extension)
564 vector<string> files;
568 string expression = dir + "\\*." + extension;
569 WIN32_FIND_DATA data;
570 HANDLE fh = FindFirstFile(expression.c_str(), &data);
571 if (fh == INVALID_HANDLE_VALUE) return files;
575 files.push_back(data.cFileName);
576 ok = FindNextFile(fh, &data);
583 size_t extlen = extension.length();
584 DIR *d = opendir(dir.c_str());
585 if (!d) return files;
587 struct dirent *e = 0;
588 while ((e = readdir(d))) {
590 if (!e->d_name) continue;
592 size_t len = strlen(e->d_name);
593 if (len < extlen + 2 ||
594 e->d_name + len - extlen - 1 != "." + extension) {
598 files.push_back(e->d_name);
608 PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
610 void *handle = m_pluginLibraryHandleMap[adapter];
611 if (handle) unloadLibrary(handle);
612 m_pluginLibraryHandleMap.erase(adapter);
615 PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
617 PluginWrapper(plugin),
622 PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
624 // We need to delete the plugin before calling pluginDeleted, as
625 // the delete call may require calling through to the descriptor
626 // (for e.g. cleanup) but pluginDeleted may unload the required
627 // library for the call. To prevent a double deletion when our
628 // parent's destructor runs (after this one), be sure to set
629 // m_plugin to 0 after deletion.
633 if (m_loader) m_loader->pluginDeleted(this);
640 _VAMP_SDK_HOSTSPACE_END(PluginLoader.cpp)