summaryrefslogtreecommitdiffstats
path: root/src/server/LV2Node.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/LV2Node.cpp')
-rw-r--r--src/server/LV2Node.cpp410
1 files changed, 410 insertions, 0 deletions
diff --git a/src/server/LV2Node.cpp b/src/server/LV2Node.cpp
new file mode 100644
index 00000000..26f1e918
--- /dev/null
+++ b/src/server/LV2Node.cpp
@@ -0,0 +1,410 @@
+/* This file is part of Ingen.
+ * Copyright 2007-2011 David Robillard <http://drobilla.net>
+ *
+ * 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 <float.h>
+#include <stdint.h>
+
+#include <cassert>
+#include <cmath>
+#include <string>
+
+#include "raul/log.hpp"
+#include "raul/Maid.hpp"
+#include "raul/Array.hpp"
+
+#include "AudioBuffer.hpp"
+#include "EventBuffer.hpp"
+#include "InputPort.hpp"
+#include "LV2Node.hpp"
+#include "LV2Plugin.hpp"
+#include "LV2URIMap.hpp"
+#include "MessageContext.hpp"
+#include "OutputPort.hpp"
+#include "ProcessContext.hpp"
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+namespace Server {
+
+/** Partially construct a LV2Node.
+ *
+ * Object is not usable until instantiate() is called with success.
+ * (It _will_ crash!)
+ */
+LV2Node::LV2Node(LV2Plugin* plugin,
+ const string& name,
+ bool polyphonic,
+ PatchImpl* parent,
+ SampleRate srate)
+ : NodeImpl(plugin, name, polyphonic, parent, srate)
+ , _lv2_plugin(plugin)
+ , _instances(NULL)
+ , _prepared_instances(NULL)
+ , _message_funcs(NULL)
+{
+ assert(_lv2_plugin);
+}
+
+LV2Node::~LV2Node()
+{
+ delete _instances;
+}
+
+bool
+LV2Node::prepare_poly(BufferFactory& bufs, uint32_t poly)
+{
+ if (!_polyphonic)
+ poly = 1;
+
+ NodeImpl::prepare_poly(bufs, poly);
+
+ if (_polyphony == poly)
+ return true;
+
+ SharedPtr<LV2Info> info = _lv2_plugin->lv2_info();
+ _prepared_instances = new Instances(poly, *_instances, SharedPtr<void>());
+ for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) {
+ _prepared_instances->at(i) = SharedPtr<void>(
+ slv2_plugin_instantiate(
+ _lv2_plugin->slv2_plugin(), _srate, _features->array()),
+ slv2_instance_free);
+
+ if (!_prepared_instances->at(i)) {
+ error << "Failed to instantiate plugin" << endl;
+ return false;
+ }
+
+ // Initialize the values of new ports
+ for (uint32_t j = 0; j < num_ports(); ++j) {
+ PortImpl* const port = _ports->at(j);
+ Buffer* const buffer = port->prepared_buffer(i).get();
+ if (buffer) {
+ if (port->is_a(PortType::CONTROL)) {
+ ((AudioBuffer*)buffer)->set_value(port->value().get_float(), 0, 0);
+ } else {
+ buffer->clear();
+ }
+ }
+ }
+
+ if (_activated)
+ slv2_instance_activate((SLV2Instance)(*_prepared_instances)[i].get());
+ }
+
+ return true;
+}
+
+bool
+LV2Node::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ if (!_polyphonic)
+ poly = 1;
+
+ if (_prepared_instances) {
+ maid.push(_instances);
+ _instances = _prepared_instances;
+ _prepared_instances = NULL;
+ }
+ assert(poly <= _instances->size());
+
+ return NodeImpl::apply_poly(maid, poly);
+}
+
+/** Instantiate self from LV2 plugin descriptor.
+ *
+ * Implemented as a seperate function (rather than in the constructor) to
+ * allow graceful error-catching of broken plugins.
+ *
+ * Returns whether or not plugin was successfully instantiated. If return
+ * value is false, this object may not be used.
+ */
+bool
+LV2Node::instantiate(BufferFactory& bufs)
+{
+ const Ingen::Shared::LV2URIMap& uris = bufs.uris();
+ SharedPtr<LV2Info> info = _lv2_plugin->lv2_info();
+ SLV2Plugin plug = _lv2_plugin->slv2_plugin();
+
+ uint32_t num_ports = slv2_plugin_get_num_ports(plug);
+ assert(num_ports > 0);
+
+ _ports = new Raul::Array<PortImpl*>(num_ports, NULL);
+ _instances = new Instances(_polyphony, SharedPtr<void>());
+
+ _features = info->world().lv2_features()->lv2_features(&info->world(), this);
+
+ uint32_t port_buffer_size = 0;
+ SLV2Value ctx_ext_uri = slv2_value_new_uri(info->lv2_world(),
+ LV2_CONTEXTS_URI "#MessageContext");
+
+ for (uint32_t i = 0; i < _polyphony; ++i) {
+ (*_instances)[i] = SharedPtr<void>(
+ slv2_plugin_instantiate(plug, _srate, _features->array()),
+ slv2_instance_free);
+
+ if (!instance(i)) {
+ error << "Failed to instantiate plugin " << _lv2_plugin->uri()
+ << " voice " << i << endl;
+ return false;
+ }
+
+ if (!slv2_plugin_has_feature(plug, ctx_ext_uri))
+ continue;
+
+ const void* ctx_ext = slv2_instance_get_extension_data(
+ instance(i), LV2_CONTEXTS_URI "#MessageContext");
+
+ if (i == 0 && ctx_ext) {
+ assert(!_message_funcs);
+ _message_funcs = (LV2_Contexts_MessageContext*)ctx_ext;
+ }
+ }
+
+ slv2_value_free(ctx_ext_uri);
+
+ string port_name;
+ Path port_path;
+
+ PortImpl* port = NULL;
+ bool ret = true;
+
+ float* min_values = new float[num_ports];
+ float* max_values = new float[num_ports];
+ float* def_values = new float[num_ports];
+ slv2_plugin_get_port_ranges_float(plug, min_values, max_values, def_values);
+
+ SLV2Value context_pred = slv2_value_new_uri(info->lv2_world(),
+ "http://lv2plug.in/ns/ext/contexts#context");
+
+ SLV2Value default_pred = slv2_value_new_uri(info->lv2_world(),
+ "http://lv2plug.in/ns/lv2core#default");
+
+ SLV2Value min_size_pred = slv2_value_new_uri(info->lv2_world(),
+ "http://lv2plug.in/ns/ext/resize-port#minimumSize");
+
+ SLV2Value port_property_pred = slv2_value_new_uri(info->lv2_world(),
+ "http://lv2plug.in/ns/lv2core#portProperty");
+
+ SLV2Value supports_pred = slv2_value_new_uri(info->lv2_world(),
+ "http://lv2plug.in/ns/ext/atom#supports");
+
+ //SLV2Value as_large_as_pred = slv2_value_new_uri(info->lv2_world(),
+ // "http://lv2plug.in/ns/ext/resize-port#asLargeAs");
+
+ for (uint32_t j = 0; j < num_ports; ++j) {
+ SLV2Port id = slv2_plugin_get_port_by_index(plug, j);
+
+ // LV2 port symbols are guaranteed to be unique, valid C identifiers
+ port_name = slv2_value_as_string(slv2_port_get_symbol(plug, id));
+
+ if (!Symbol::is_valid(port_name)) {
+ error << "Plugin " << _lv2_plugin->uri() << " port " << j
+ << " has illegal symbol `" << port_name << "'" << endl;
+ ret = false;
+ break;
+ }
+
+ assert(port_name.find('/') == string::npos);
+
+ port_path = path().child(port_name);
+
+ Raul::Atom val;
+ PortType data_type = PortType::UNKNOWN;
+ if (slv2_port_is_a(plug, id, info->control_class)) {
+ data_type = PortType::CONTROL;
+ } else if (slv2_port_is_a(plug, id, info->audio_class)) {
+ data_type = PortType::AUDIO;
+ } else if (slv2_port_is_a(plug, id, info->event_class)) {
+ data_type = PortType::EVENTS;
+ } else if (slv2_port_is_a(plug, id, info->value_port_class)) {
+ data_type = PortType::VALUE;
+ } else if (slv2_port_is_a(plug, id, info->message_port_class)) {
+ data_type = PortType::MESSAGE;
+ }
+
+ port_buffer_size = bufs.default_buffer_size(data_type);
+
+ if (data_type == PortType::VALUE || data_type == PortType::MESSAGE) {
+ // Get default value, and its length
+ SLV2Values defaults = slv2_port_get_value(plug, id, default_pred);
+ SLV2_FOREACH(i, defaults) {
+ SLV2Value d = slv2_values_get(defaults, i);
+ if (slv2_value_is_string(d)) {
+ const char* str_val = slv2_value_as_string(d);
+ const size_t str_val_len = strlen(str_val);
+ val = str_val;
+ port_buffer_size = str_val_len;
+ }
+ }
+
+ // Get minimum size, if set in data
+ SLV2Values sizes = slv2_port_get_value(plug, id, min_size_pred);
+ SLV2_FOREACH(i, sizes) {
+ SLV2Value d = slv2_values_get(sizes, i);
+ if (slv2_value_is_int(d)) {
+ size_t size_val = slv2_value_as_int(d);
+ port_buffer_size = size_val;
+ }
+ }
+ }
+
+ enum { UNKNOWN, INPUT, OUTPUT } direction = UNKNOWN;
+ if (slv2_port_is_a(plug, id, info->input_class)) {
+ direction = INPUT;
+ } else if (slv2_port_is_a(plug, id, info->output_class)) {
+ direction = OUTPUT;
+ }
+
+ if (data_type == PortType::UNKNOWN || direction == UNKNOWN) {
+ warn << "Unknown type or direction for port `" << port_name << "'" << endl;
+ ret = false;
+ break;
+ }
+
+ if (val.type() == Atom::NIL)
+ val = isnan(def_values[j]) ? 0.0f : def_values[j];
+
+ if (direction == INPUT)
+ port = new InputPort(bufs, this, port_name, j, _polyphony, data_type, val);
+ else
+ port = new OutputPort(bufs, this, port_name, j, _polyphony, data_type, val);
+
+ if (direction == INPUT && data_type == PortType::CONTROL) {
+ port->set_value(val);
+ if (!isnan(min_values[j])) {
+ port->set_property(uris.lv2_minimum, min_values[j]);
+ }
+ if (!isnan(max_values[j])) {
+ port->set_property(uris.lv2_maximum, max_values[j]);
+ }
+ }
+
+ // Set lv2:portProperty properties
+ SLV2Values properties = slv2_port_get_value(plug, id, port_property_pred);
+ SLV2_FOREACH(i, properties) {
+ SLV2Value p = slv2_values_get(properties, i);
+ if (slv2_value_is_uri(p)) {
+ port->set_property(uris.lv2_portProperty, Raul::URI(slv2_value_as_uri(p)));
+ }
+ }
+
+ // Set atom:supports properties
+ SLV2Values types = slv2_port_get_value(plug, id, supports_pred);
+ SLV2_FOREACH(i, types) {
+ SLV2Value type = slv2_values_get(types, i);
+ if (slv2_value_is_uri(type)) {
+ port->add_property(uris.atom_supports, Raul::URI(slv2_value_as_uri(type)));
+ }
+ }
+
+ SLV2Values contexts = slv2_port_get_value(plug, id, context_pred);
+ SLV2_FOREACH(i, contexts) {
+ SLV2Value c = slv2_values_get(contexts, i);
+ const char* context = slv2_value_as_string(c);
+ if (!strcmp(LV2_CONTEXTS_URI "#MessageContext", context)) {
+ if (!_message_funcs) {
+ warn << _lv2_plugin->uri()
+ << " has a message port, but no context extension data." << endl;
+ }
+ port->set_context(Context::MESSAGE);
+ } else {
+ warn << _lv2_plugin->uri() << " port " << i << " has unknown context "
+ << slv2_value_as_string(c)
+ << endl;
+ }
+ }
+
+ _ports->at(j) = port;
+ }
+
+ if (!ret) {
+ delete _ports;
+ _ports = NULL;
+ delete _instances;
+ _instances = NULL;
+ }
+
+ delete[] min_values;
+ delete[] max_values;
+ delete[] def_values;
+ slv2_value_free(context_pred);
+ slv2_value_free(default_pred);
+ slv2_value_free(min_size_pred);
+ slv2_value_free(port_property_pred);
+
+ return ret;
+}
+
+void
+LV2Node::activate(BufferFactory& bufs)
+{
+ NodeImpl::activate(bufs);
+
+ for (uint32_t i = 0; i < _polyphony; ++i)
+ slv2_instance_activate(instance(i));
+}
+
+void
+LV2Node::deactivate()
+{
+ NodeImpl::deactivate();
+
+ for (uint32_t i = 0; i < _polyphony; ++i)
+ slv2_instance_deactivate(instance(i));
+}
+
+void
+LV2Node::message_run(MessageContext& context)
+{
+ for (size_t i = 0; i < num_ports(); ++i) {
+ PortImpl* const port = _ports->at(i);
+ if (port->context() == Context::MESSAGE)
+ port->pre_process(context);
+ }
+
+ if (!_valid_ports)
+ _valid_ports = calloc(num_ports() / 8, 1);
+
+ if (_message_funcs)
+ (*_message_funcs->run)(instance(0)->lv2_handle, _valid_ports, _valid_ports);
+}
+
+void
+LV2Node::process(ProcessContext& context)
+{
+ NodeImpl::pre_process(context);
+
+ for (uint32_t i = 0; i < _polyphony; ++i)
+ slv2_instance_run(instance(i), context.nframes());
+
+ NodeImpl::post_process(context);
+}
+
+void
+LV2Node::set_port_buffer(uint32_t voice, uint32_t port_num,
+ IntrusivePtr<Buffer> buf, SampleCount offset)
+{
+ NodeImpl::set_port_buffer(voice, port_num, buf, offset);
+ slv2_instance_connect_port(instance(voice), port_num,
+ buf ? buf->port_data(_ports->at(port_num)->buffer_type(), offset) : NULL);
+}
+
+} // namespace Server
+} // namespace Ingen
+