diff options
Diffstat (limited to 'src/libs/engine/NodeFactory.cpp')
-rw-r--r-- | src/libs/engine/NodeFactory.cpp | 707 |
1 files changed, 707 insertions, 0 deletions
diff --git a/src/libs/engine/NodeFactory.cpp b/src/libs/engine/NodeFactory.cpp new file mode 100644 index 00000000..176d3f47 --- /dev/null +++ b/src/libs/engine/NodeFactory.cpp @@ -0,0 +1,707 @@ +/* This file is part of Om. Copyright (C) 2006 Dave Robillard. + * + * Om 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. + * + * Om 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "NodeFactory.h" +#include "config.h" +#include <cstdlib> +#include <pthread.h> +#include <dirent.h> +#include <float.h> +#include <cmath> +#include <dlfcn.h> +#include "AudioDriver.h" +#include "MidiNoteNode.h" +#include "MidiTriggerNode.h" +#include "MidiControlNode.h" +#include "AudioInputNode.h" +#include "AudioOutputNode.h" +#include "ControlInputNode.h" +#include "ControlOutputNode.h" +#include "MidiInputNode.h" +#include "MidiOutputNode.h" +#include "TransportNode.h" +#include "PluginLibrary.h" +#include "Plugin.h" +#include "Patch.h" +#include "Om.h" +#include "OmApp.h" +#ifdef HAVE_SLV2 +#include "LV2Plugin.h" +#include <slv2/slv2.h> +#endif +#ifdef HAVE_LADSPA +#include "LADSPAPlugin.h" +#endif +#ifdef HAVE_DSSI +#include "DSSIPlugin.h" +#endif + +using std::string; +using std::cerr; using std::cout; using std::endl; + + +namespace Om { + + +/* I am perfectly aware that the vast majority of this class is a + * vomit inducing nightmare at the moment ;) + */ + + + +NodeFactory::NodeFactory() +: m_has_loaded(false) +{ + pthread_mutex_init(&m_plugin_list_mutex, NULL); + + // Add builtin plugin types to m_internal_plugins list + // FIXME: ewwww, definitely a better way to do this! + //Plugin* pi = NULL; + + Patch* parent = new Patch("dummy", 1, NULL, 1, 1, 1); + + Node* n = NULL; + n = new AudioInputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new AudioOutputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new ControlInputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new ControlOutputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiInputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiOutputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiNoteNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiTriggerNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiControlNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new TransportNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + + + delete parent; +} + + +NodeFactory::~NodeFactory() +{ + for (list<Plugin*>::iterator i = m_plugins.begin(); i != m_plugins.end(); ++i) + delete (*i); + + for (list<PluginLibrary*>::iterator i = m_libraries.begin(); i != m_libraries.end(); ++i) { + (*i)->close(); + delete (*i); + } +} + + +void +NodeFactory::load_plugins() +{ + // 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 (!m_has_loaded) { + pthread_mutex_lock(&m_plugin_list_mutex); + + m_plugins.clear(); + m_plugins = m_internal_plugins; + +#if HAVE_SLV2 + load_lv2_plugins(); +#endif +#if HAVE_DSSI + load_dssi_plugins(); +#endif +#if HAVE_LADSPA + load_ladspa_plugins(); +#endif + + m_has_loaded = true; + + pthread_mutex_unlock(&m_plugin_list_mutex); + } +} + + +/** 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); + + pthread_mutex_lock(&m_plugin_list_mutex); + + Node* r = NULL; + Plugin* plugin = NULL; + + // Attempt to find the plugin in loaded DB + if (a_plugin->type() != Plugin::Internal) { + list<Plugin*>::iterator i; + if (a_plugin->plug_label().length() == 0) { + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + if (a_plugin->uri() == (*i)->uri()) { + plugin = *i; + break; + } + } + } else { + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + if (a_plugin->uri() == (*i)->uri()) { + plugin = *i; + break; + } + } + } + + if (plugin == NULL) + return NULL; + } + + switch (a_plugin->type()) { +#if HAVE_SLV2 + case Plugin::LV2: + r = load_lv2_plugin(plugin->uri(), name, poly, parent); + break; +#endif +#if HAVE_DSSI + case Plugin::DSSI: + r = load_dssi_plugin(plugin->uri(), name, poly, parent); + break; +#endif +#if HAVE_LADSPA + case Plugin::LADSPA: + r = load_ladspa_plugin(plugin->uri(), name, poly, parent); + break; +#endif + case Plugin::Internal: + r = load_internal_plugin(a_plugin->uri(), name, poly, parent); + break; + default: + cerr << "[NodeFactory] WARNING: Unknown plugin type." << endl; + } + + pthread_mutex_unlock(&m_plugin_list_mutex); + + return r; +} + + +/** Loads an internal plugin. + */ +Node* +NodeFactory::load_internal_plugin(const string& uri, const string& name, size_t poly, Patch* parent) +{ + assert(parent != NULL); + assert(poly == 1 || poly == parent->internal_poly()); + assert(uri.length() > 3); + assert(uri.substr(0, 3) == "om:"); + + string plug_label = uri.substr(3); + + if (plug_label == "midi_input") { + MidiInputNode* tn = new MidiInputNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return tn; + } else if (plug_label == "midi_output") { + MidiOutputNode* tn = new MidiOutputNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return tn; + } else if (plug_label == "audio_input") { + AudioInputNode* in = new AudioInputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return in; + } else if (plug_label == "control_input") { + ControlInputNode* in = new ControlInputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return in; + } else if (plug_label == "audio_output") { + AudioOutputNode* on = new AudioOutputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return on; + } else if (plug_label == "control_output") { + ControlOutputNode* on = new ControlOutputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return on; + } else if (plug_label == "note_in" || plug_label == "midi_note_in") { + MidiNoteNode* mn = new MidiNoteNode(name, poly, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return mn; + } else if (plug_label == "trigger_in" || plug_label == "midi_trigger_in") { + MidiTriggerNode* mn = new MidiTriggerNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return mn; + } else if (plug_label == "midi_control_in") { + MidiControlNode* mn = new MidiControlNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return mn; + } else if (plug_label == "transport") { + TransportNode* tn = new TransportNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return tn; + } else { + cerr << "Unknown internal plugin type '" << plug_label << "'" << endl; + } + + return NULL; +} + + +#ifdef HAVE_SLV2 + +/** Loads information about all LV2 plugins into internal plugin database. + */ +void +NodeFactory::load_lv2_plugins() +{ + SLV2List plugins = slv2_list_new(); + slv2_list_load_all(plugins); + + //cerr << "[NodeFactory] Found " << slv2_list_get_length(plugins) << " LV2 plugins." << endl; + + for (unsigned long i=0; i < slv2_list_get_length(plugins); ++i) { + + SLV2Plugin* lv2_plug = slv2_list_get_plugin_by_index(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<Plugin*>::const_iterator i = m_plugins.begin(); i != m_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(); + om_plug->type(Plugin::LV2); + om_plug->slv2_plugin(lv2_plug); + om_plug->uri(uri); + // FIXME FIXME FIXME temporary hack + om_plug->library(NULL); + om_plug->lib_path("FIXMEpath"); + om_plug->plug_label("FIXMElabel"); + unsigned char* name = slv2_plugin_get_name(lv2_plug); + om_plug->name((char*)name); + free(name); + om_plug->type(Plugin::LV2); + m_plugins.push_back(om_plug); + } + } + + slv2_list_free(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) +{ + // Find (Om) Plugin + Plugin* plugin = NULL; + list<Plugin*>::iterator i; + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + plugin = (*i); + if ((*i)->uri() == plug_uri) break; + } + + Node* n = NULL; + + if (plugin) { + n = new Om::LV2Plugin(node_name, poly, parent, plugin->slv2_plugin(), + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + bool success = ((LV2Plugin*)n)->instantiate(); + if (!success) { + delete n; + n = NULL; + } + n->plugin(plugin); + } + + return n; +} + +#endif // HAVE_SLV2 + + +#if 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); + m_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); + Plugin* plugin = new Plugin(); + 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<Plugin*>::const_iterator i = m_plugins.begin(); i != m_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) + m_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) +{ + // 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<Plugin*>::iterator i; + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + plugin = (*i); + if (plugin->uri() == uri) break; + } + + assert(plugin->id() != 0); + + if (i == m_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 DSSIPlugin(name, poly, parent, descriptor, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + bool success = ((DSSIPlugin*)n)->instantiate(); + if (!success) { + delete n; + n = NULL; + } + + n->plugin(plugin); + + 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); + m_libraries.push_back(plugin_library); + + for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) { + Plugin* plugin = new Plugin(); + 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<Plugin*>::const_iterator i = m_plugins.begin(); i != m_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) + m_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) +{ + 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<Plugin*>::iterator i; + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + plugin = (*i); + if (plugin->uri() == uri) break; + } + + assert(plugin->id() != 0); + + if (i == m_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 LADSPAPlugin(name, poly, parent, descriptor, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + bool success = ((LADSPAPlugin*)n)->instantiate(); + if (!success) { + delete n; + n = NULL; + } + + n->plugin(plugin); + + return n; +} + + +#endif // HAVE_LADSPA + + +} // namespace Om |