/* This file is part of Ingen. * Copyright (C) 2007 Dave Robillard * * Ingen is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "NodeFactory.h" #include "config.h" #include #include #include #include #include #include #include "ThreadManager.h" #include "MidiNoteNode.h" #include "MidiTriggerNode.h" #include "MidiControlNode.h" #include "TransportNode.h" #include "PluginLibrary.h" #include "Plugin.h" #include "Patch.h" #ifdef HAVE_SLV2 #include "LV2Node.h" #include #endif #ifdef HAVE_LADSPA #include "LADSPANode.h" #endif #ifdef HAVE_DSSI #include "DSSINode.h" #endif using std::string; using std::cerr; using std::cout; using std::endl; namespace Ingen { /* I am perfectly aware that the vast majority of this class is a * vomit inducing nightmare at the moment ;) */ NodeFactory::NodeFactory() : _has_loaded(false) { #ifdef HAVE_SLV2 _world = slv2_world_new(); slv2_world_load_all(_world); #endif // Add builtin plugin types to _internal_plugins list // FIXME: ewwww, definitely a better way to do this! Patch* parent = new Patch("dummy", 1, NULL, 1, 1, 1); Node* n = NULL; n = new MidiNoteNode("foo", 1, parent, 1, 1); _internal_plugins.push_back(new Plugin(n->plugin())); delete n; n = new MidiTriggerNode("foo", 1, parent, 1, 1); _internal_plugins.push_back(new Plugin(n->plugin())); delete n; n = new MidiControlNode("foo", 1, parent, 1, 1); _internal_plugins.push_back(new Plugin(n->plugin())); delete n; n = new TransportNode("foo", 1, parent, 1, 1); _internal_plugins.push_back(new Plugin(n->plugin())); delete n; delete parent; } NodeFactory::~NodeFactory() { for (list::iterator i = _plugins.begin(); i != _plugins.end(); ++i) delete (*i); for (list::iterator i = _libraries.begin(); i != _libraries.end(); ++i) { (*i)->close(); delete (*i); } #ifdef HAVE_SLV2 slv2_world_free(_world); #endif } const Plugin* NodeFactory::plugin(const string& uri) { for (list::iterator i = _plugins.begin(); i != _plugins.end(); ++i) if ((*i)->uri() == uri) return (*i); return NULL; } /** DEPRECATED: Find a plugin by type, lib, label. * * Do not use. */ const Plugin* NodeFactory::plugin(const string& type, const string& lib, const string& label) { if (type == "" || lib == "" || label == "") return NULL; for (list::iterator i = _plugins.begin(); i != _plugins.end(); ++i) if ((*i)->type_string() == type && (*i)->lib_name() == lib && (*i)->plug_label() == label) return (*i); return NULL; } void NodeFactory::load_plugins() { assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); // Only load if we havn't already, so every client connecting doesn't cause // this (expensive!) stuff to happen. Not the best solution - would be nice // if clients could refresh plugins list for whatever reason :/ if (!_has_loaded) { _plugins.clear(); _plugins = _internal_plugins; #ifdef HAVE_SLV2 load_lv2_plugins(); #endif #ifdef HAVE_DSSI load_dssi_plugins(); #endif #ifdef HAVE_LADSPA load_ladspa_plugins(); #endif _has_loaded = true; } //cerr << "[NodeFactory] # Plugins: " << _plugins.size() << endl; } /** Loads a plugin. * * Calls the load_*_plugin functions to actually do things, just a wrapper. */ Node* NodeFactory::load_plugin(const Plugin* a_plugin, const string& name, size_t poly, Patch* parent) { assert(parent != NULL); assert(poly == 1 || poly == parent->internal_poly()); assert(a_plugin); Node* r = NULL; Plugin* plugin = NULL; const SampleRate srate = parent->sample_rate(); const size_t buffer_size = parent->buffer_size(); // FIXME FIXME FIXME: double lookup // Attempt to find the plugin in loaded DB if (a_plugin->type() != Plugin::Internal) { // DEPRECATED: Search by lib name / plug label if (a_plugin->uri().length() == 0) { assert(a_plugin->lib_name().length() > 0 && a_plugin->plug_label().length() > 0); //cerr << "Searching for: " << a_plugin->lib_name() << " : " << a_plugin->plug_label() << endl; for (list::iterator i = _plugins.begin(); i != _plugins.end(); ++i) { //cerr << (*i)->lib_name() << " : " << (*i)->plug_label() << endl; if (a_plugin->lib_name() == (*i)->lib_name() && a_plugin->plug_label() == (*i)->plug_label()) { plugin = *i; break; } } } else { // Search by URI for (list::iterator i = _plugins.begin(); i != _plugins.end(); ++i) { if (a_plugin->uri() == (*i)->uri()) { plugin = *i; break; } } } if (plugin == NULL) { cerr << "DID NOT FIND PLUGIN " << name << endl; return NULL; } } switch (a_plugin->type()) { #ifdef HAVE_SLV2 case Plugin::LV2: r = load_lv2_plugin(plugin->uri(), name, poly, parent, srate, buffer_size); break; #endif #ifdef HAVE_DSSI case Plugin::DSSI: r = load_dssi_plugin(plugin->uri(), name, poly, parent, srate, buffer_size); break; #endif #ifdef HAVE_LADSPA case Plugin::LADSPA: r = load_ladspa_plugin(plugin->uri(), name, poly, parent, srate, buffer_size); break; #endif case Plugin::Internal: r = load_internal_plugin(a_plugin->uri(), name, poly, parent, srate, buffer_size); break; default: cerr << "[NodeFactory] WARNING: Unknown plugin type." << endl; } return r; } /** Loads an internal plugin. */ Node* NodeFactory::load_internal_plugin(const string& uri, const string& name, size_t poly, Patch* parent, SampleRate srate, size_t buffer_size) { assert(parent != NULL); assert(poly == 1 || poly == parent->internal_poly()); assert(uri.length() > 6); assert(uri.substr(0, 6) == "ingen:"); for (list::iterator i = _internal_plugins.begin(); i != _internal_plugins.end(); ++i) if ((*i)->uri() == uri) return (*i)->instantiate(name, poly, parent, srate, buffer_size); return NULL; } #ifdef HAVE_SLV2 /** Loads information about all LV2 plugins into internal plugin database. */ void NodeFactory::load_lv2_plugins() { SLV2Plugins plugins = slv2_world_get_all_plugins(_world); //cerr << "[NodeFactory] Found " << slv2_plugins_get_length(plugins) << " LV2 plugins." << endl; for (unsigned i=0; i < slv2_plugins_size(plugins); ++i) { SLV2Plugin lv2_plug = slv2_plugins_get_at(plugins, i); //om_plug->library(plugin_library); const char* uri = (const char*)slv2_plugin_get_uri(lv2_plug); //cerr << "LV2 plugin: " << uri << endl; bool found = false; for (list::const_iterator i = _plugins.begin(); i != _plugins.end(); ++i) { if (!strcmp((*i)->uri().c_str(), uri)) { cerr << "Warning: Duplicate LV2 plugin (" << uri << ").\nUsing " << (*i)->lib_path() << " version." << endl; found = true; break; } } if (!found) { //printf("[NodeFactory] Found LV2 plugin %s\n", uri); Plugin* om_plug = new Plugin(Plugin::LV2, uri); om_plug->slv2_plugin(lv2_plug); // FIXME FIXME FIXME temporary hack om_plug->library(NULL); om_plug->lib_path("FIXME/Some/path"); om_plug->plug_label("FIXMElabel"); char* name = slv2_plugin_get_name(lv2_plug); om_plug->name(name); free(name); om_plug->type(Plugin::LV2); _plugins.push_back(om_plug); } } slv2_plugins_free(_world, plugins); } /** Loads a LV2 plugin. * Returns 'poly' independant plugins as a Node* */ Node* NodeFactory::load_lv2_plugin(const string& plug_uri, const string& node_name, size_t poly, Patch* parent, SampleRate srate, size_t buffer_size) { // Find (internal) Plugin Plugin* plugin = NULL; list::iterator i; for (i = _plugins.begin(); i != _plugins.end(); ++i) { plugin = (*i); if ((*i)->uri() == plug_uri) break; } Node* n = NULL; if (plugin) { n = new LV2Node(plugin, node_name, poly, parent, srate, buffer_size); bool success = ((LV2Node*)n)->instantiate(); if (!success) { delete n; n = NULL; } } return n; } #endif // HAVE_SLV2 #ifdef HAVE_DSSI /** Loads information about all DSSI plugins into internal plugin database. */ void NodeFactory::load_dssi_plugins() { // FIXME: too much code duplication with load_ladspa_plugin char* env_dssi_path = getenv("DSSI_PATH"); string dssi_path; if (!env_dssi_path) { cerr << "[NodeFactory] DSSI_PATH is empty. Assuming /usr/lib/dssi:/usr/local/lib/dssi:~/.dssi" << endl; dssi_path = string("/usr/lib/dssi:/usr/local/lib/dssi:").append( getenv("HOME")).append("/.dssi"); } else { dssi_path = env_dssi_path; } DSSI_Descriptor_Function df = NULL; DSSI_Descriptor* descriptor = NULL; string dir; string full_lib_name; // Yep, this should use an sstream alright.. while (dssi_path != "") { dir = dssi_path.substr(0, dssi_path.find(':')); if (dssi_path.find(':') != string::npos) dssi_path = dssi_path.substr(dssi_path.find(':')+1); else dssi_path = ""; DIR* pdir = opendir(dir.c_str()); if (pdir == NULL) { //cerr << "[NodeFactory] Unreadable directory in DSSI_PATH: " << dir.c_str() << endl; continue; } struct dirent* pfile; while ((pfile = readdir(pdir))) { if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, "..")) continue; full_lib_name = dir +"/"+ pfile->d_name; // Load descriptor function // Loaded with LAZY here, will be loaded with NOW on node loading void* handle = dlopen(full_lib_name.c_str(), RTLD_LAZY); if (handle == NULL) continue; df = (DSSI_Descriptor_Function)dlsym(handle, "dssi_descriptor"); if (df == NULL) { // Not a DSSI plugin library dlclose(handle); continue; } PluginLibrary* plugin_library = new PluginLibrary(full_lib_name); _libraries.push_back(plugin_library); const LADSPA_Descriptor* ld = NULL; for (unsigned long i=0; (descriptor = (DSSI_Descriptor*)df(i)) != NULL; ++i) { ld = descriptor->LADSPA_Plugin; assert(ld != NULL); string uri = string("dssi:") + pfile->d_name +":"+ ld->Label; Plugin* plugin = new Plugin(Plugin::DSSI, uri); assert(plugin_library != NULL); plugin->library(plugin_library); plugin->lib_path(dir + "/" + pfile->d_name); plugin->plug_label(ld->Label); plugin->name(ld->Name); plugin->type(Plugin::DSSI); plugin->id(ld->UniqueID); bool found = false; for (list::const_iterator i = _plugins.begin(); i != _plugins.end(); ++i) { if ((*i)->uri() == plugin->uri()) { cerr << "Warning: Duplicate DSSI plugin (" << plugin->lib_name() << ":" << plugin->plug_label() << ")" << " found.\nUsing " << (*i)->lib_path() << " version." << endl; found = true; break; } } if (!found) _plugins.push_back(plugin); else delete plugin; } df = NULL; descriptor = NULL; dlclose(handle); } closedir(pdir); } } /** Creates a Node by instancing a DSSI plugin. */ Node* NodeFactory::load_dssi_plugin(const string& uri, const string& name, size_t poly, Patch* parent, SampleRate srate, size_t buffer_size) { // FIXME: awful code duplication here assert(uri != ""); assert(name != ""); assert(poly > 0); DSSI_Descriptor_Function df = NULL; const Plugin* plugin = NULL; Node* n = NULL; void* handle = NULL; // Attempt to find the lib list::iterator i; for (i = _plugins.begin(); i != _plugins.end(); ++i) { plugin = (*i); if (plugin->uri() == uri) break; } assert(plugin->id() != 0); if (i == _plugins.end()) { cerr << "Did not find DSSI plugin " << uri << " in database." << endl; return NULL; } else { assert(plugin != NULL); plugin->library()->open(); handle = plugin->library()->handle(); assert(handle != NULL); // Load descriptor function dlerror(); df = (DSSI_Descriptor_Function)dlsym(handle, "dssi_descriptor"); if (df == NULL || dlerror() != NULL) { cerr << "Looks like this isn't a DSSI plugin." << endl; return NULL; } } // Attempt to find the plugin in lib DSSI_Descriptor* descriptor = NULL; for (unsigned long i=0; (descriptor = (DSSI_Descriptor*)df(i)) != NULL; ++i) { if (descriptor->LADSPA_Plugin != NULL && descriptor->LADSPA_Plugin->UniqueID == plugin->id()) { break; } } if (descriptor == NULL) { cerr << "Could not find plugin \"" << plugin->id() << "\" in " << plugin->lib_name() << endl; return NULL; } n = new DSSINode(plugin, name, poly, parent, descriptor, srate, buffer_size); bool success = ((DSSINode*)n)->instantiate(); if (!success) { delete n; n = NULL; } return n; } #endif // HAVE_DSSI #ifdef HAVE_LADSPA /** Loads information about all LADSPA plugins into internal plugin database. */ void NodeFactory::load_ladspa_plugins() { char* env_ladspa_path = getenv("LADSPA_PATH"); string ladspa_path; if (!env_ladspa_path) { cerr << "[NodeFactory] LADSPA_PATH is empty. Assuming /usr/lib/ladspa:/usr/local/lib/ladspa:~/.ladspa" << endl; ladspa_path = string("/usr/lib/ladspa:/usr/local/lib/ladspa:").append( getenv("HOME")).append("/.ladspa"); } else { ladspa_path = env_ladspa_path; } LADSPA_Descriptor_Function df = NULL; LADSPA_Descriptor* descriptor = NULL; string dir; string full_lib_name; // Yep, this should use an sstream alright.. while (ladspa_path != "") { dir = ladspa_path.substr(0, ladspa_path.find(':')); if (ladspa_path.find(':') != string::npos) ladspa_path = ladspa_path.substr(ladspa_path.find(':')+1); else ladspa_path = ""; DIR* pdir = opendir(dir.c_str()); if (pdir == NULL) { //cerr << "[NodeFactory] Unreadable directory in LADSPA_PATH: " << dir.c_str() << endl; continue; } struct dirent* pfile; while ((pfile = readdir(pdir))) { if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, "..")) continue; full_lib_name = dir +"/"+ pfile->d_name; // Load descriptor function // Loaded with LAZY here, will be loaded with NOW on node loading void* handle = dlopen(full_lib_name.c_str(), RTLD_LAZY); if (handle == NULL) continue; df = (LADSPA_Descriptor_Function)dlsym(handle, "ladspa_descriptor"); if (df == NULL) { dlclose(handle); continue; } PluginLibrary* plugin_library = new PluginLibrary(full_lib_name); _libraries.push_back(plugin_library); for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) { char id_str[11]; snprintf(id_str, 11, "%lu", descriptor->UniqueID); string uri = string("ladspa:").append(id_str); Plugin* plugin = new Plugin(Plugin::LADSPA, uri); assert(plugin_library != NULL); plugin->library(plugin_library); plugin->lib_path(dir + "/" + pfile->d_name); plugin->plug_label(descriptor->Label); plugin->name(descriptor->Name); plugin->type(Plugin::LADSPA); plugin->id(descriptor->UniqueID); bool found = false; for (list::const_iterator i = _plugins.begin(); i != _plugins.end(); ++i) { if ((*i)->uri() == plugin->uri()) { cerr << "Warning: Duplicate LADSPA plugin " << plugin->uri() << " found.\nChoosing " << (*i)->lib_path() << " over " << plugin->lib_path() << endl; found = true; break; } } if (!found) _plugins.push_back(plugin); else delete plugin; } df = NULL; descriptor = NULL; dlclose(handle); } closedir(pdir); } } /** Loads a LADSPA plugin. * Returns 'poly' independant plugins as a Node* */ Node* NodeFactory::load_ladspa_plugin(const string& uri, const string& name, size_t poly, Patch* parent, SampleRate srate, size_t buffer_size) { assert(uri != ""); assert(name != ""); assert(poly > 0); LADSPA_Descriptor_Function df = NULL; Plugin* plugin = NULL; Node* n = NULL; void* plugin_lib = NULL; // Attempt to find the lib list::iterator i; for (i = _plugins.begin(); i != _plugins.end(); ++i) { plugin = (*i); if (plugin->uri() == uri) break; } assert(plugin->id() != 0); if (i == _plugins.end()) { cerr << "Did not find LADSPA plugin " << uri << " in database." << endl; return NULL; } else { assert(plugin != NULL); plugin->library()->open(); plugin_lib = plugin->library()->handle(); assert(plugin_lib != NULL); // Load descriptor function dlerror(); df = (LADSPA_Descriptor_Function)dlsym(plugin_lib, "ladspa_descriptor"); if (df == NULL || dlerror() != NULL) { cerr << "Looks like this isn't a LADSPA plugin." << endl; return NULL; } } // Attempt to find the plugin in lib LADSPA_Descriptor* descriptor = NULL; for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) { if (descriptor->UniqueID == plugin->id()) { break; } } if (descriptor == NULL) { cerr << "Could not find plugin \"" << plugin->id() << "\" in " << plugin->lib_path() << endl; return NULL; } n = new LADSPANode(plugin, name, poly, parent, descriptor, srate, buffer_size); bool success = ((LADSPANode*)n)->instantiate(); if (!success) { delete n; n = NULL; } return n; } #endif // HAVE_LADSPA } // namespace Ingen