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-sdk/PluginHostAdapter.h"
38 #include "PluginLoader.h"
39 #include "PluginInputDomainAdapter.h"
40 #include "PluginChannelAdapter.h"
43 #include <cctype> // tolower
49 #define PLUGIN_SUFFIX "dll"
57 #define PLUGIN_SUFFIX "dylib"
58 #else /* ! __APPLE__ */
59 #define PLUGIN_SUFFIX "so"
60 #endif /* ! __APPLE__ */
70 class PluginLoader::Impl
76 PluginKeyList listPlugins();
78 Plugin *loadPlugin(PluginKey key,
79 float inputSampleRate,
82 PluginKey composePluginKey(string libraryName, string identifier);
84 PluginCategoryHierarchy getPluginCategory(PluginKey key);
86 string getLibraryPathForPlugin(PluginKey key);
89 class PluginDeletionNotifyAdapter : public PluginWrapper {
91 PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader);
92 virtual ~PluginDeletionNotifyAdapter();
97 virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter);
99 map<PluginKey, string> m_pluginLibraryNameMap;
100 bool m_allPluginsEnumerated;
101 void enumeratePlugins(PluginKey forPlugin = "");
103 map<PluginKey, PluginCategoryHierarchy> m_taxonomy;
104 void generateTaxonomy();
106 map<Plugin *, void *> m_pluginLibraryHandleMap;
108 bool decomposePluginKey(PluginKey key,
109 string &libraryName, string &identifier);
111 void *loadLibrary(string path);
112 void unloadLibrary(void *handle);
113 void *lookupInLibrary(void *handle, const char *symbol);
115 string splicePath(string a, string b);
116 vector<string> listFiles(string dir, string ext);
120 PluginLoader::m_instance = 0;
122 PluginLoader::PluginLoader()
127 PluginLoader::~PluginLoader()
133 PluginLoader::getInstance()
135 if (!m_instance) m_instance = new PluginLoader();
139 vector<PluginLoader::PluginKey>
140 PluginLoader::listPlugins()
142 return m_impl->listPlugins();
146 PluginLoader::loadPlugin(PluginKey key,
147 float inputSampleRate,
150 return m_impl->loadPlugin(key, inputSampleRate, adapterFlags);
153 PluginLoader::PluginKey
154 PluginLoader::composePluginKey(string libraryName, string identifier)
156 return m_impl->composePluginKey(libraryName, identifier);
159 PluginLoader::PluginCategoryHierarchy
160 PluginLoader::getPluginCategory(PluginKey key)
162 return m_impl->getPluginCategory(key);
166 PluginLoader::getLibraryPathForPlugin(PluginKey key)
168 return m_impl->getLibraryPathForPlugin(key);
171 PluginLoader::Impl::Impl() :
172 m_allPluginsEnumerated(false)
176 PluginLoader::Impl::~Impl()
180 vector<PluginLoader::PluginKey>
181 PluginLoader::Impl::listPlugins()
183 if (!m_allPluginsEnumerated) enumeratePlugins();
185 vector<PluginKey> plugins;
186 for (map<PluginKey, string>::iterator mi = m_pluginLibraryNameMap.begin();
187 mi != m_pluginLibraryNameMap.end(); ++mi) {
188 plugins.push_back(mi->first);
195 PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin)
197 vector<string> path = PluginHostAdapter::getPluginPath();
199 string libraryName, identifier;
200 if (forPlugin != "") {
201 if (!decomposePluginKey(forPlugin, libraryName, identifier)) {
202 std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \""
203 << forPlugin << "\" in enumerate" << std::endl;
208 for (size_t i = 0; i < path.size(); ++i) {
210 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
212 for (vector<string>::iterator fi = files.begin();
213 fi != files.end(); ++fi) {
215 if (libraryName != "") {
216 // libraryName is lowercased and lacking an extension,
217 // as it came from the plugin key
219 for (size_t i = 0; i < temp.length(); ++i) {
220 temp[i] = tolower(temp[i]);
222 string::size_type pi = temp.find('.');
223 if (pi == string::npos) {
224 if (libraryName != temp) continue;
226 if (libraryName != temp.substr(0, pi)) continue;
230 string fullPath = path[i];
231 fullPath = splicePath(fullPath, *fi);
232 void *handle = loadLibrary(fullPath);
233 if (!handle) continue;
235 VampGetPluginDescriptorFunction fn =
236 (VampGetPluginDescriptorFunction)lookupInLibrary
237 (handle, "vampGetPluginDescriptor");
240 unloadLibrary(handle);
245 const VampPluginDescriptor *descriptor = 0;
247 while ((descriptor = fn(VAMP_API_VERSION, index))) {
249 if (identifier != "") {
250 if (descriptor->identifier != identifier) continue;
252 PluginKey key = composePluginKey(*fi, descriptor->identifier);
253 // std::cerr << "enumerate: " << key << " (path: " << fullPath << ")" << std::endl;
254 if (m_pluginLibraryNameMap.find(key) ==
255 m_pluginLibraryNameMap.end()) {
256 m_pluginLibraryNameMap[key] = fullPath;
260 unloadLibrary(handle);
264 if (forPlugin == "") m_allPluginsEnumerated = true;
267 PluginLoader::PluginKey
268 PluginLoader::Impl::composePluginKey(string libraryName, string identifier)
270 string basename = libraryName;
272 string::size_type li = basename.rfind('/');
273 if (li != string::npos) basename = basename.substr(li + 1);
275 li = basename.find('.');
276 if (li != string::npos) basename = basename.substr(0, li);
278 for (size_t i = 0; i < basename.length(); ++i) {
279 basename[i] = tolower(basename[i]);
282 return basename + ":" + identifier;
286 PluginLoader::Impl::decomposePluginKey(PluginKey key,
290 string::size_type ki = key.find(':');
291 if (ki == string::npos) {
295 libraryName = key.substr(0, ki);
296 identifier = key.substr(ki + 1);
300 PluginLoader::PluginCategoryHierarchy
301 PluginLoader::Impl::getPluginCategory(PluginKey plugin)
303 if (m_taxonomy.empty()) generateTaxonomy();
304 if (m_taxonomy.find(plugin) == m_taxonomy.end()) {
305 return PluginCategoryHierarchy();
307 return m_taxonomy[plugin];
311 PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin)
313 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
314 if (m_allPluginsEnumerated) return "";
315 enumeratePlugins(plugin);
317 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
320 return m_pluginLibraryNameMap[plugin];
324 PluginLoader::Impl::loadPlugin(PluginKey key,
325 float inputSampleRate, int adapterFlags)
327 string libname, identifier;
328 if (!decomposePluginKey(key, libname, identifier)) {
329 std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \""
330 << key << "\" in loadPlugin" << std::endl;
334 string fullPath = getLibraryPathForPlugin(key);
335 if (fullPath == "") return 0;
337 void *handle = loadLibrary(fullPath);
338 if (!handle) return 0;
340 VampGetPluginDescriptorFunction fn =
341 (VampGetPluginDescriptorFunction)lookupInLibrary
342 (handle, "vampGetPluginDescriptor");
345 unloadLibrary(handle);
350 const VampPluginDescriptor *descriptor = 0;
352 while ((descriptor = fn(VAMP_API_VERSION, index))) {
354 if (string(descriptor->identifier) == identifier) {
356 Vamp::PluginHostAdapter *plugin =
357 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
359 Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
361 m_pluginLibraryHandleMap[adapter] = handle;
363 if (adapterFlags & ADAPT_INPUT_DOMAIN) {
364 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
365 adapter = new PluginInputDomainAdapter(adapter);
369 if (adapterFlags & ADAPT_CHANNEL_COUNT) {
370 adapter = new PluginChannelAdapter(adapter);
379 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
380 << identifier << "\" not found in library \""
381 << fullPath << "\"" << endl;
387 PluginLoader::Impl::generateTaxonomy()
389 // cerr << "PluginLoader::Impl::generateTaxonomy" << endl;
391 vector<string> path = PluginHostAdapter::getPluginPath();
392 string libfragment = "/lib/";
393 vector<string> catpath;
395 string suffix = "cat";
397 for (vector<string>::iterator i = path.begin();
398 i != path.end(); ++i) {
400 // It doesn't matter that we're using literal forward-slash in
401 // this bit, as it's only relevant if the path contains
402 // "/lib/", which is only meaningful and only plausible on
403 // systems with forward-slash delimiters
406 string::size_type li = dir.find(libfragment);
408 if (li != string::npos) {
412 + dir.substr(li + libfragment.length()));
415 catpath.push_back(dir);
420 for (vector<string>::iterator i = catpath.begin();
421 i != catpath.end(); ++i) {
423 vector<string> files = listFiles(*i, suffix);
425 for (vector<string>::iterator fi = files.begin();
426 fi != files.end(); ++fi) {
428 string filepath = splicePath(*i, *fi);
429 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
432 // cerr << "failed to open: " << filepath << endl;
436 // cerr << "opened: " << filepath << endl;
438 while (!!is.getline(buffer, 1024)) {
442 // cerr << "line = " << line << endl;
444 string::size_type di = line.find("::");
445 if (di == string::npos) continue;
447 string id = line.substr(0, di);
448 string encodedCat = line.substr(di + 2);
450 if (id.substr(0, 5) != "vamp:") continue;
453 while (encodedCat.length() >= 1 &&
454 encodedCat[encodedCat.length()-1] == '\r') {
455 encodedCat = encodedCat.substr(0, encodedCat.length()-1);
458 // cerr << "id = " << id << ", cat = " << encodedCat << endl;
460 PluginCategoryHierarchy category;
461 string::size_type ai;
462 while ((ai = encodedCat.find(" > ")) != string::npos) {
463 category.push_back(encodedCat.substr(0, ai));
464 encodedCat = encodedCat.substr(ai + 3);
466 if (encodedCat != "") category.push_back(encodedCat);
468 m_taxonomy[id] = category;
475 PluginLoader::Impl::loadLibrary(string path)
479 handle = LoadLibrary(path.c_str());
481 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
482 << path << "\"" << endl;
485 handle = dlopen(path.c_str(), RTLD_LAZY);
487 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
488 << path << "\": " << dlerror() << endl;
495 PluginLoader::Impl::unloadLibrary(void *handle)
498 FreeLibrary((HINSTANCE)handle);
505 PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol)
508 return (void *)GetProcAddress((HINSTANCE)handle, symbol);
510 return (void *)dlsym(handle, symbol);
515 PluginLoader::Impl::splicePath(string a, string b)
525 PluginLoader::Impl::listFiles(string dir, string extension)
527 vector<string> files;
531 string expression = dir + "\\*." + extension;
532 WIN32_FIND_DATA data;
533 HANDLE fh = FindFirstFile(expression.c_str(), &data);
534 if (fh == INVALID_HANDLE_VALUE) return files;
538 files.push_back(data.cFileName);
539 ok = FindNextFile(fh, &data);
546 size_t extlen = extension.length();
547 DIR *d = opendir(dir.c_str());
548 if (!d) return files;
550 struct dirent *e = 0;
551 while ((e = readdir(d))) {
553 if (!(e->d_type & DT_REG) || !e->d_name) continue;
555 size_t len = strlen(e->d_name);
556 if (len < extlen + 2 ||
557 e->d_name + len - extlen - 1 != "." + extension) {
561 files.push_back(e->d_name);
571 PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
573 void *handle = m_pluginLibraryHandleMap[adapter];
574 if (handle) unloadLibrary(handle);
575 m_pluginLibraryHandleMap.erase(adapter);
578 PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
580 PluginWrapper(plugin),
585 PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
587 // We need to delete the plugin before calling pluginDeleted, as
588 // the delete call may require calling through to the descriptor
589 // (for e.g. cleanup) but pluginDeleted may unload the required
590 // library for the call. To prevent a double deletion when our
591 // parent's destructor runs (after this one), be sure to set
592 // m_plugin to 0 after deletion.
596 if (m_loader) m_loader->pluginDeleted(this);