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"
41 #include "PluginBufferingAdapter.h"
46 #include <cctype> // tolower
53 #define PLUGIN_SUFFIX "dll"
61 #define PLUGIN_SUFFIX "dylib"
62 #else /* ! __APPLE__ */
63 #define PLUGIN_SUFFIX "so"
64 #endif /* ! __APPLE__ */
74 class PluginLoader::Impl
80 PluginKeyList listPlugins();
82 Plugin *loadPlugin(PluginKey key,
83 float inputSampleRate,
86 PluginKey composePluginKey(string libraryName, string identifier);
88 PluginCategoryHierarchy getPluginCategory(PluginKey key);
90 string getLibraryPathForPlugin(PluginKey key);
92 static void setInstanceToClean(PluginLoader *instance);
95 class PluginDeletionNotifyAdapter : public PluginWrapper {
97 PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader);
98 virtual ~PluginDeletionNotifyAdapter();
103 class InstanceCleaner {
105 InstanceCleaner() : m_instance(0) { }
106 ~InstanceCleaner() { delete m_instance; }
107 void setInstance(PluginLoader *instance) { m_instance = instance; }
109 PluginLoader *m_instance;
112 virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter);
114 map<PluginKey, string> m_pluginLibraryNameMap;
115 bool m_allPluginsEnumerated;
116 void enumeratePlugins(PluginKey forPlugin = "");
118 map<PluginKey, PluginCategoryHierarchy> m_taxonomy;
119 void generateTaxonomy();
121 map<Plugin *, void *> m_pluginLibraryHandleMap;
123 bool decomposePluginKey(PluginKey key,
124 string &libraryName, string &identifier);
126 void *loadLibrary(string path);
127 void unloadLibrary(void *handle);
128 void *lookupInLibrary(void *handle, const char *symbol);
130 string splicePath(string a, string b);
131 vector<string> listFiles(string dir, string ext);
133 static InstanceCleaner m_cleaner;
137 PluginLoader::m_instance = 0;
139 PluginLoader::Impl::InstanceCleaner
140 PluginLoader::Impl::m_cleaner;
142 PluginLoader::PluginLoader()
147 PluginLoader::~PluginLoader()
153 PluginLoader::getInstance()
156 // The cleaner doesn't own the instance, because we leave the
157 // instance pointer in the base class for binary backwards
158 // compatibility reasons and to avoid waste
159 m_instance = new PluginLoader();
160 Impl::setInstanceToClean(m_instance);
165 vector<PluginLoader::PluginKey>
166 PluginLoader::listPlugins()
168 return m_impl->listPlugins();
172 PluginLoader::loadPlugin(PluginKey key,
173 float inputSampleRate,
176 return m_impl->loadPlugin(key, inputSampleRate, adapterFlags);
179 PluginLoader::PluginKey
180 PluginLoader::composePluginKey(string libraryName, string identifier)
182 return m_impl->composePluginKey(libraryName, identifier);
185 PluginLoader::PluginCategoryHierarchy
186 PluginLoader::getPluginCategory(PluginKey key)
188 return m_impl->getPluginCategory(key);
192 PluginLoader::getLibraryPathForPlugin(PluginKey key)
194 return m_impl->getLibraryPathForPlugin(key);
197 PluginLoader::Impl::Impl() :
198 m_allPluginsEnumerated(false)
202 PluginLoader::Impl::~Impl()
207 PluginLoader::Impl::setInstanceToClean(PluginLoader *instance)
209 m_cleaner.setInstance(instance);
212 vector<PluginLoader::PluginKey>
213 PluginLoader::Impl::listPlugins()
215 if (!m_allPluginsEnumerated) enumeratePlugins();
217 vector<PluginKey> plugins;
218 for (map<PluginKey, string>::iterator mi = m_pluginLibraryNameMap.begin();
219 mi != m_pluginLibraryNameMap.end(); ++mi) {
220 plugins.push_back(mi->first);
227 PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin)
229 vector<string> path = PluginHostAdapter::getPluginPath();
231 string libraryName, identifier;
232 if (forPlugin != "") {
233 if (!decomposePluginKey(forPlugin, libraryName, identifier)) {
234 std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \""
235 << forPlugin << "\" in enumerate" << std::endl;
240 for (size_t i = 0; i < path.size(); ++i) {
242 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
244 for (vector<string>::iterator fi = files.begin();
245 fi != files.end(); ++fi) {
247 if (libraryName != "") {
248 // libraryName is lowercased and lacking an extension,
249 // as it came from the plugin key
251 for (size_t i = 0; i < temp.length(); ++i) {
252 temp[i] = tolower(temp[i]);
254 string::size_type pi = temp.find('.');
255 if (pi == string::npos) {
256 if (libraryName != temp) continue;
258 if (libraryName != temp.substr(0, pi)) continue;
262 string fullPath = path[i];
263 fullPath = splicePath(fullPath, *fi);
264 void *handle = loadLibrary(fullPath);
265 if (!handle) continue;
267 VampGetPluginDescriptorFunction fn =
268 (VampGetPluginDescriptorFunction)lookupInLibrary
269 (handle, "vampGetPluginDescriptor");
272 unloadLibrary(handle);
277 const VampPluginDescriptor *descriptor = 0;
279 while ((descriptor = fn(VAMP_API_VERSION, index))) {
281 if (identifier != "") {
282 if (descriptor->identifier != identifier) continue;
284 PluginKey key = composePluginKey(*fi, descriptor->identifier);
285 // std::cerr << "enumerate: " << key << " (path: " << fullPath << ")" << std::endl;
286 if (m_pluginLibraryNameMap.find(key) ==
287 m_pluginLibraryNameMap.end()) {
288 m_pluginLibraryNameMap[key] = fullPath;
292 unloadLibrary(handle);
296 if (forPlugin == "") m_allPluginsEnumerated = true;
299 PluginLoader::PluginKey
300 PluginLoader::Impl::composePluginKey(string libraryName, string identifier)
302 string basename = libraryName;
304 string::size_type li = basename.rfind('/');
305 if (li != string::npos) basename = basename.substr(li + 1);
307 li = basename.find('.');
308 if (li != string::npos) basename = basename.substr(0, li);
310 for (size_t i = 0; i < basename.length(); ++i) {
311 basename[i] = tolower(basename[i]);
314 return basename + ":" + identifier;
318 PluginLoader::Impl::decomposePluginKey(PluginKey key,
322 string::size_type ki = key.find(':');
323 if (ki == string::npos) {
327 libraryName = key.substr(0, ki);
328 identifier = key.substr(ki + 1);
332 PluginLoader::PluginCategoryHierarchy
333 PluginLoader::Impl::getPluginCategory(PluginKey plugin)
335 if (m_taxonomy.empty()) generateTaxonomy();
336 if (m_taxonomy.find(plugin) == m_taxonomy.end()) {
337 return PluginCategoryHierarchy();
339 return m_taxonomy[plugin];
343 PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin)
345 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
346 if (m_allPluginsEnumerated) return "";
347 enumeratePlugins(plugin);
349 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
352 return m_pluginLibraryNameMap[plugin];
356 PluginLoader::Impl::loadPlugin(PluginKey key,
357 float inputSampleRate, int adapterFlags)
359 string libname, identifier;
360 if (!decomposePluginKey(key, libname, identifier)) {
361 std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \""
362 << key << "\" in loadPlugin" << std::endl;
366 string fullPath = getLibraryPathForPlugin(key);
367 if (fullPath == "") return 0;
369 void *handle = loadLibrary(fullPath);
370 if (!handle) return 0;
372 VampGetPluginDescriptorFunction fn =
373 (VampGetPluginDescriptorFunction)lookupInLibrary
374 (handle, "vampGetPluginDescriptor");
377 unloadLibrary(handle);
382 const VampPluginDescriptor *descriptor = 0;
384 while ((descriptor = fn(VAMP_API_VERSION, index))) {
386 if (string(descriptor->identifier) == identifier) {
388 Vamp::PluginHostAdapter *plugin =
389 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
391 Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
393 m_pluginLibraryHandleMap[adapter] = handle;
395 if (adapterFlags & ADAPT_INPUT_DOMAIN) {
396 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
397 adapter = new PluginInputDomainAdapter(adapter);
401 if (adapterFlags & ADAPT_BUFFER_SIZE) {
402 adapter = new PluginBufferingAdapter(adapter);
405 if (adapterFlags & ADAPT_CHANNEL_COUNT) {
406 adapter = new PluginChannelAdapter(adapter);
415 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
416 << identifier << "\" not found in library \""
417 << fullPath << "\"" << endl;
423 PluginLoader::Impl::generateTaxonomy()
425 // cerr << "PluginLoader::Impl::generateTaxonomy" << endl;
427 vector<string> path = PluginHostAdapter::getPluginPath();
428 string libfragment = "/lib/";
429 vector<string> catpath;
431 string suffix = "cat";
433 for (vector<string>::iterator i = path.begin();
434 i != path.end(); ++i) {
436 // It doesn't matter that we're using literal forward-slash in
437 // this bit, as it's only relevant if the path contains
438 // "/lib/", which is only meaningful and only plausible on
439 // systems with forward-slash delimiters
442 string::size_type li = dir.find(libfragment);
444 if (li != string::npos) {
448 + dir.substr(li + libfragment.length()));
451 catpath.push_back(dir);
456 for (vector<string>::iterator i = catpath.begin();
457 i != catpath.end(); ++i) {
459 vector<string> files = listFiles(*i, suffix);
461 for (vector<string>::iterator fi = files.begin();
462 fi != files.end(); ++fi) {
464 string filepath = splicePath(*i, *fi);
465 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
468 // cerr << "failed to open: " << filepath << endl;
472 // cerr << "opened: " << filepath << endl;
474 while (!!is.getline(buffer, 1024)) {
478 // cerr << "line = " << line << endl;
480 string::size_type di = line.find("::");
481 if (di == string::npos) continue;
483 string id = line.substr(0, di);
484 string encodedCat = line.substr(di + 2);
486 if (id.substr(0, 5) != "vamp:") continue;
489 while (encodedCat.length() >= 1 &&
490 encodedCat[encodedCat.length()-1] == '\r') {
491 encodedCat = encodedCat.substr(0, encodedCat.length()-1);
494 // cerr << "id = " << id << ", cat = " << encodedCat << endl;
496 PluginCategoryHierarchy category;
497 string::size_type ai;
498 while ((ai = encodedCat.find(" > ")) != string::npos) {
499 category.push_back(encodedCat.substr(0, ai));
500 encodedCat = encodedCat.substr(ai + 3);
502 if (encodedCat != "") category.push_back(encodedCat);
504 m_taxonomy[id] = category;
511 PluginLoader::Impl::loadLibrary(string path)
515 handle = LoadLibrary(path.c_str());
517 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
518 << path << "\"" << endl;
521 handle = dlopen(path.c_str(), RTLD_LAZY);
523 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
524 << path << "\": " << dlerror() << endl;
531 PluginLoader::Impl::unloadLibrary(void *handle)
534 FreeLibrary((HINSTANCE)handle);
541 PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol)
544 return (void *)GetProcAddress((HINSTANCE)handle, symbol);
546 return (void *)dlsym(handle, symbol);
551 PluginLoader::Impl::splicePath(string a, string b)
561 PluginLoader::Impl::listFiles(string dir, string extension)
563 vector<string> files;
567 string expression = dir + "\\*." + extension;
568 WIN32_FIND_DATA data;
569 HANDLE fh = FindFirstFile(expression.c_str(), &data);
570 if (fh == INVALID_HANDLE_VALUE) return files;
574 files.push_back(data.cFileName);
575 ok = FindNextFile(fh, &data);
582 size_t extlen = extension.length();
583 DIR *d = opendir(dir.c_str());
584 if (!d) return files;
586 struct dirent *e = 0;
587 while ((e = readdir(d))) {
589 if (!(e->d_type & DT_REG) && (e->d_type != DT_UNKNOWN)) continue;
591 if (!e->d_name) continue;
593 size_t len = strlen(e->d_name);
594 if (len < extlen + 2 ||
595 e->d_name + len - extlen - 1 != "." + extension) {
599 files.push_back(e->d_name);
609 PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
611 void *handle = m_pluginLibraryHandleMap[adapter];
612 if (handle) unloadLibrary(handle);
613 m_pluginLibraryHandleMap.erase(adapter);
616 PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
618 PluginWrapper(plugin),
623 PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
625 // We need to delete the plugin before calling pluginDeleted, as
626 // the delete call may require calling through to the descriptor
627 // (for e.g. cleanup) but pluginDeleted may unload the required
628 // library for the call. To prevent a double deletion when our
629 // parent's destructor runs (after this one), be sure to set
630 // m_plugin to 0 after deletion.
634 if (m_loader) m_loader->pluginDeleted(this);