diff options
Diffstat (limited to 'src/server/LV2Block.cpp')
-rw-r--r-- | src/server/LV2Block.cpp | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/src/server/LV2Block.cpp b/src/server/LV2Block.cpp new file mode 100644 index 00000000..d26f7417 --- /dev/null +++ b/src/server/LV2Block.cpp @@ -0,0 +1,469 @@ +/* + This file is part of Ingen. + Copyright 2007-2012 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdint.h> + +#include <cassert> +#include <cmath> + +#include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h" +#include "lv2/lv2plug.in/ns/ext/morph/morph.h" + +#include "raul/Maid.hpp" +#include "raul/Array.hpp" + +#include "ingen/Log.hpp" +#include "ingen/URIMap.hpp" +#include "ingen/URIs.hpp" + +#include "Buffer.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "InputPort.hpp" +#include "LV2Block.hpp" +#include "LV2Plugin.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ProcessContext.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +/** Partially construct a LV2Block. + * + * Object is not usable until instantiate() is called with success. + * (It _will_ crash!) + */ +LV2Block::LV2Block(LV2Plugin* plugin, + const Raul::Symbol& symbol, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) +: BlockImpl(plugin, symbol, polyphonic, parent, srate) + , _lv2_plugin(plugin) + , _instances(NULL) + , _prepared_instances(NULL) + , _worker_iface(NULL) +{ + assert(_lv2_plugin); +} + +LV2Block::~LV2Block() +{ + delete _instances; +} + +SharedPtr<LilvInstance> +LV2Block::make_instance(URIs& uris, + SampleRate rate, + uint32_t voice, + bool preparing) +{ + LilvInstance* inst = lilv_plugin_instantiate( + _lv2_plugin->lilv_plugin(), rate, _features->array()); + + if (!inst) { + parent_patch()->engine().log().error( + Raul::fmt("Failed to instantiate <%1%>\n") + % _lv2_plugin->uri().c_str()); + return SharedPtr<LilvInstance>(); + } + + const LV2_Morph_Interface* morph_iface = (const LV2_Morph_Interface*) + lilv_instance_get_extension_data(inst, LV2_MORPH__interface); + + for (uint32_t p = 0; p < num_ports(); ++p) { + PortImpl* const port = _ports->at(p); + Buffer* const buffer = (preparing) + ? port->prepared_buffer(voice).get() + : port->buffer(voice).get(); + if (port->is_morph() && port->is_a(PortType::CV)) { + if (morph_iface) { + morph_iface->morph_port( + inst->lv2_handle, p, uris.lv2_CVPort, NULL); + } + } + + if (buffer) { + if (port->is_a(PortType::CV) || port->is_a(PortType::CONTROL)) { + buffer->set_block(port->value().get_float(), 0, buffer->nframes()); + } else { + buffer->clear(); + } + } + } + + if (morph_iface) { + for (uint32_t p = 0; p < num_ports(); ++p) { + PortImpl* const port = _ports->at(p); + if (port->is_auto_morph()) { + LV2_URID type = morph_iface->port_type( + inst->lv2_handle, p, NULL); + if (type == _uris.lv2_ControlPort) { + port->set_type(PortType::CONTROL, 0); + } else if (type == _uris.lv2_CVPort) { + port->set_type(PortType::CV, 0); + } else { + parent_patch()->engine().log().error( + Raul::fmt("%1% auto-morphed to unknown type %2%\n") + % port->path().c_str() % type); + return SharedPtr<LilvInstance>(); + } + } + } + } + + return SharedPtr<LilvInstance>(inst, lilv_instance_free); +} + +bool +LV2Block::prepare_poly(BufferFactory& bufs, uint32_t poly) +{ + if (!_polyphonic) + poly = 1; + + BlockImpl::prepare_poly(bufs, poly); + + if (_polyphony == poly) + return true; + + const SampleRate rate = bufs.engine().driver()->sample_rate(); + assert(!_prepared_instances); + _prepared_instances = new Instances(poly, *_instances, SharedPtr<void>()); + for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) { + SharedPtr<LilvInstance> inst = make_instance(bufs.uris(), rate, i, true); + if (!inst) { + return false; + } + + _prepared_instances->at(i) = inst; + + if (_activated) { + lilv_instance_activate(inst.get()); + } + } + + return true; +} + +bool +LV2Block::apply_poly(ProcessContext& context, Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic) + poly = 1; + + if (_prepared_instances) { + maid.dispose(_instances); + _instances = _prepared_instances; + _prepared_instances = NULL; + } + assert(poly <= _instances->size()); + + return BlockImpl::apply_poly(context, 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 +LV2Block::instantiate(BufferFactory& bufs) +{ + const Ingen::URIs& uris = bufs.uris(); + SharedPtr<LV2Info> info = _lv2_plugin->lv2_info(); + const LilvPlugin* plug = _lv2_plugin->lilv_plugin(); + Ingen::Forge& forge = bufs.forge(); + const uint32_t num_ports = lilv_plugin_get_num_ports(plug); + + _ports = new Raul::Array<PortImpl*>(num_ports, 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]; + lilv_plugin_get_port_ranges_float(plug, min_values, max_values, def_values); + + // Get all the necessary information about ports + for (uint32_t j = 0; j < num_ports; ++j) { + const LilvPort* id = lilv_plugin_get_port_by_index(plug, j); + + /* LV2 port symbols are guaranteed to be unique, valid C identifiers, + and Lilv guarantees that lilv_port_get_symbol() is valid. */ + const Raul::Symbol port_sym( + lilv_node_as_string(lilv_port_get_symbol(plug, id))); + + // Get port type + Raul::Atom val; + PortType port_type = PortType::UNKNOWN; + LV2_URID buffer_type = 0; + bool is_morph = false; + bool is_auto_morph = false; + if (lilv_port_is_a(plug, id, info->lv2_ControlPort)) { + if (lilv_port_is_a(plug, id, info->morph_MorphPort)) { + is_morph = true; + LilvNodes* types = lilv_port_get_value( + plug, id, info->morph_supportsType); + LILV_FOREACH(nodes, i, types) { + const LilvNode* type = lilv_nodes_get(types, i); + if (lilv_node_equals(type, info->lv2_CVPort)) { + port_type = PortType::CV; + buffer_type = uris.atom_Sound; + } + } + lilv_nodes_free(types); + } + if (port_type == PortType::UNKNOWN) { + port_type = PortType::CONTROL; + buffer_type = uris.atom_Float; + } + } else if (lilv_port_is_a(plug, id, info->lv2_CVPort)) { + port_type = PortType::CV; + buffer_type = uris.atom_Sound; + } else if (lilv_port_is_a(plug, id, info->lv2_AudioPort)) { + port_type = PortType::AUDIO; + buffer_type = uris.atom_Sound; + } else if (lilv_port_is_a(plug, id, info->atom_AtomPort)) { + port_type = PortType::ATOM; + } + + if (lilv_port_is_a(plug, id, info->morph_AutoMorphPort)) { + is_auto_morph = true; + } + + // Get buffer type if necessary (atom ports) + if (!buffer_type) { + LilvNodes* types = lilv_port_get_value( + plug, id, info->atom_bufferType); + LILV_FOREACH(nodes, i, types) { + const LilvNode* type = lilv_nodes_get(types, i); + if (lilv_node_is_uri(type)) { + buffer_type = bufs.engine().world()->uri_map().map_uri( + lilv_node_as_uri(type)); + } + } + lilv_nodes_free(types); + } + + uint32_t port_buffer_size = bufs.default_size(buffer_type); + + if (port_type == PortType::ATOM) { + // Get default value, and its length + LilvNodes* defaults = lilv_port_get_value(plug, id, info->lv2_default); + LILV_FOREACH(nodes, i, defaults) { + const LilvNode* d = lilv_nodes_get(defaults, i); + if (lilv_node_is_string(d)) { + const char* str_val = lilv_node_as_string(d); + const uint32_t str_val_len = strlen(str_val); + val = forge.alloc(str_val); + port_buffer_size = std::max(port_buffer_size, str_val_len); + } + } + lilv_nodes_free(defaults); + + // Get minimum size, if set in data + LilvNodes* sizes = lilv_port_get_value(plug, id, info->rsz_minimumSize); + LILV_FOREACH(nodes, i, sizes) { + const LilvNode* d = lilv_nodes_get(sizes, i); + if (lilv_node_is_int(d)) { + uint32_t size_val = lilv_node_as_int(d); + port_buffer_size = std::max(port_buffer_size, size_val); + } + } + lilv_nodes_free(sizes); + + bufs.engine().log().info( + Raul::fmt("Atom port %1% buffer size %2%\n") + % path().c_str() % port_buffer_size); + } + + enum { UNKNOWN, INPUT, OUTPUT } direction = UNKNOWN; + if (lilv_port_is_a(plug, id, info->lv2_InputPort)) { + direction = INPUT; + } else if (lilv_port_is_a(plug, id, info->lv2_OutputPort)) { + direction = OUTPUT; + } + + if (port_type == PortType::UNKNOWN || direction == UNKNOWN) { + parent_patch()->engine().log().error( + Raul::fmt("<%1%> port %2% has unknown type or direction\n") + % _lv2_plugin->uri().c_str() % port_sym.c_str()); + ret = false; + break; + } + + if (!val.type()) + val = forge.make(isnan(def_values[j]) ? 0.0f : def_values[j]); + + PortImpl* port = (direction == INPUT) + ? static_cast<PortImpl*>( + new InputPort(bufs, this, port_sym, j, _polyphony, + port_type, buffer_type, val)) + : static_cast<PortImpl*>( + new OutputPort(bufs, this, port_sym, j, _polyphony, + port_type, buffer_type, val)); + + port->set_morphable(is_morph, is_auto_morph); + if (direction == INPUT && (port_type == PortType::CONTROL + || port_type == PortType::CV)) { + port->set_value(val); + if (!isnan(min_values[j])) { + port->set_property(uris.lv2_minimum, forge.make(min_values[j])); + port->set_minimum(forge.make(min_values[j])); + } + if (!isnan(max_values[j])) { + port->set_property(uris.lv2_maximum, forge.make(max_values[j])); + port->set_maximum(forge.make(max_values[j])); + } + } + + // Inherit certain properties from plugin port + LilvNode* preds[] = { info->lv2_portProperty, info->atom_supports, 0 }; + for (int p = 0; preds[p]; ++p) { + LilvNodes* values = lilv_port_get_value(plug, id, preds[p]); + LILV_FOREACH(nodes, v, values) { + const LilvNode* val = lilv_nodes_get(values, v); + if (lilv_node_is_uri(val)) { + port->add_property(Raul::URI(lilv_node_as_uri(preds[p])), + forge.alloc_uri(lilv_node_as_uri(val))); + } + } + lilv_nodes_free(values); + } + + port->cache_properties(); + + _ports->at(j) = port; + } + + delete[] min_values; + delete[] max_values; + delete[] def_values; + + if (!ret) { + delete _ports; + _ports = NULL; + return ret; + } + + _features = info->world().lv2_features().lv2_features(&info->world(), this); + + // Actually create plugin instances and port buffers. + const SampleRate rate = bufs.engine().driver()->sample_rate(); + _instances = new Instances(_polyphony, SharedPtr<void>()); + for (uint32_t i = 0; i < _polyphony; ++i) { + _instances->at(i) = make_instance(bufs.uris(), rate, i, false); + if (!_instances->at(i)) { + return false; + } + } + + // FIXME: Polyphony + worker? + if (lilv_plugin_has_feature(plug, info->work_schedule)) { + _worker_iface = (const LV2_Worker_Interface*) + lilv_instance_get_extension_data(instance(0), + LV2_WORKER__interface); + } + + return ret; +} + +void +LV2Block::activate(BufferFactory& bufs) +{ + BlockImpl::activate(bufs); + + for (uint32_t i = 0; i < _polyphony; ++i) + lilv_instance_activate(instance(i)); +} + +void +LV2Block::deactivate() +{ + BlockImpl::deactivate(); + + for (uint32_t i = 0; i < _polyphony; ++i) + lilv_instance_deactivate(instance(i)); +} + +LV2_Worker_Status +LV2Block::work_respond(LV2_Worker_Respond_Handle handle, + uint32_t size, + const void* data) +{ + LV2Block* block = (LV2Block*)handle; + LV2Block::Response* r = new LV2Block::Response(size, data); + block->_responses.push_back(*r); + return LV2_WORKER_SUCCESS; +} + +void +LV2Block::work(uint32_t size, const void* data) +{ + if (_worker_iface) { + LV2_Handle inst = lilv_instance_get_handle(instance(0)); + if (_worker_iface->work(inst, work_respond, this, size, data)) { + parent_patch()->engine().log().error( + Raul::fmt("Error calling %1% work method\n") % _path); + } + } +} + +void +LV2Block::process(ProcessContext& context) +{ + BlockImpl::pre_process(context); + + for (uint32_t i = 0; i < _polyphony; ++i) + lilv_instance_run(instance(i), context.nframes()); + + if (_worker_iface) { + LV2_Handle inst = lilv_instance_get_handle(instance(0)); + while (!_responses.empty()) { + Response& r = _responses.front(); + _worker_iface->work_response(inst, r.size, r.data); + _responses.pop_front(); + context.engine().maid()->dispose(&r); + } + + if (_worker_iface->end_run) { + _worker_iface->end_run(inst); + } + } + + BlockImpl::post_process(context); +} + +void +LV2Block::set_port_buffer(uint32_t voice, + uint32_t port_num, + BufferRef buf) +{ + BlockImpl::set_port_buffer(voice, port_num, buf); + lilv_instance_connect_port( + instance(voice), port_num, + buf ? buf->port_data(_ports->at(port_num)->type()) : NULL); +} + +} // namespace Server +} // namespace Ingen + |