X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Flv2_plugin.cc;h=43a4901ca7d5639a1619f54cda0270bad2f79577;hb=424cacfbc83516d9e935bd93135e58b34dcfc002;hp=bbe94e15c28c440776a58c27459eeb84348a6d15;hpb=0a60077a617a5bf7a600e34def4439283434dc69;p=ardour.git diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index bbe94e15c2..43a4901ca7 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -25,7 +25,7 @@ #include #include -#include +#include "pbd/gstdio_compat.h" #include #include @@ -66,8 +66,9 @@ #include "lv2/lv2plug.in/ns/ext/worker/worker.h" #include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h" #include "lv2/lv2plug.in/ns/extensions/ui/ui.h" +#include "lv2/lv2plug.in/ns/extensions/units/units.h" #include "lv2/lv2plug.in/ns/ext/patch/patch.h" -#ifdef HAVE_NEW_LV2 +#ifdef HAVE_LV2_1_2_0 #include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h" #include "lv2/lv2plug.in/ns/ext/options/options.h" #endif @@ -78,6 +79,24 @@ #include #endif +// Compatibility for old LV2 +#ifndef LV2_ATOM_CONTENTS_CONST +#define LV2_ATOM_CONTENTS_CONST(type, atom) \ + ((const void*)((const uint8_t*)(atom) + sizeof(type))) +#endif +#ifndef LV2_ATOM_BODY_CONST +#define LV2_ATOM_BODY_CONST(atom) LV2_ATOM_CONTENTS_CONST(LV2_Atom, atom) +#endif +#ifndef LV2_PATCH__property +#define LV2_PATCH__property LV2_PATCH_PREFIX "property" +#endif +#ifndef LV2_PATCH__value +#define LV2_PATCH__value LV2_PATCH_PREFIX "value" +#endif +#ifndef LV2_PATCH__writable +#define LV2_PATCH__writable LV2_PATCH_PREFIX "writable" +#endif + /** The number of MIDI buffers that will fit in a UI/worker comm buffer. This needs to be roughly the number of cycles the UI will get around to actually processing the traffic. Lower values are flakier but save memory. @@ -88,32 +107,6 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -URIMap LV2Plugin::_uri_map; - -LV2Plugin::URIDs LV2Plugin::urids = { - _uri_map.uri_to_id(LV2_ATOM__Chunk), - _uri_map.uri_to_id(LV2_ATOM__Path), - _uri_map.uri_to_id(LV2_ATOM__Sequence), - _uri_map.uri_to_id(LV2_ATOM__eventTransfer), - _uri_map.uri_to_id(LV2_ATOM__URID), - _uri_map.uri_to_id(LV2_ATOM__Blank), - _uri_map.uri_to_id(LV2_LOG__Error), - _uri_map.uri_to_id(LV2_LOG__Note), - _uri_map.uri_to_id(LV2_LOG__Warning), - _uri_map.uri_to_id(LV2_MIDI__MidiEvent), - _uri_map.uri_to_id(LV2_TIME__Position), - _uri_map.uri_to_id(LV2_TIME__bar), - _uri_map.uri_to_id(LV2_TIME__barBeat), - _uri_map.uri_to_id(LV2_TIME__beatUnit), - _uri_map.uri_to_id(LV2_TIME__beatsPerBar), - _uri_map.uri_to_id(LV2_TIME__beatsPerMinute), - _uri_map.uri_to_id(LV2_TIME__frame), - _uri_map.uri_to_id(LV2_TIME__speed), - _uri_map.uri_to_id(LV2_PATCH__Set), - _uri_map.uri_to_id(LV2_PATCH__property), - _uri_map.uri_to_id(LV2_PATCH__value) -}; - class LV2World : boost::noncopyable { public: LV2World (); @@ -139,21 +132,48 @@ public: LilvNode* lv2_enumeration; LilvNode* lv2_freewheeling; LilvNode* lv2_inPlaceBroken; + LilvNode* lv2_isSideChain; LilvNode* lv2_integer; + LilvNode* lv2_default; + LilvNode* lv2_minimum; + LilvNode* lv2_maximum; LilvNode* lv2_reportsLatency; LilvNode* lv2_sampleRate; LilvNode* lv2_toggled; LilvNode* midi_MidiEvent; LilvNode* rdfs_comment; + LilvNode* rdfs_label; + LilvNode* rdfs_range; LilvNode* rsz_minimumSize; LilvNode* time_Position; LilvNode* ui_GtkUI; LilvNode* ui_external; LilvNode* ui_externalkx; + LilvNode* units_hz; + LilvNode* units_db; LilvNode* units_unit; + LilvNode* units_render; LilvNode* units_midiNote; LilvNode* patch_writable; LilvNode* patch_Message; +#ifdef HAVE_LV2_1_2_0 + LilvNode* bufz_powerOf2BlockLength; + LilvNode* bufz_fixedBlockLength; + LilvNode* bufz_nominalBlockLength; +#endif + +#ifdef HAVE_LV2_1_10_0 + LilvNode* atom_int; + LilvNode* atom_float; + LilvNode* atom_object; // new in 1.8 + LilvNode* atom_vector; +#endif +#ifdef LV2_EXTENDED + LilvNode* lv2_noSampleAccurateCtrl; + LilvNode* auto_can_write_automatation; // lv2:optionalFeature + LilvNode* auto_automation_control; // atom:supports + LilvNode* auto_automation_controlled; // lv2:portProperty +#endif private: bool _bundle_checked; @@ -197,6 +217,16 @@ work_respond(LV2_Worker_Respond_Handle handle, } } +#ifdef LV2_EXTENDED +/* inline display extension */ +static void +queue_draw (LV2_Inline_Display_Handle handle) +{ + LV2Plugin* plugin = (LV2Plugin*)handle; + plugin->QueueDraw(); /* EMIT SIGNAL */ +} +#endif + /* log extension */ static int @@ -207,11 +237,11 @@ log_vprintf(LV2_Log_Handle /*handle*/, { char* str = NULL; const int ret = g_vasprintf(&str, fmt, args); - if (type == LV2Plugin::urids.log_Error) { + if (type == URIMap::instance().urids.log_Error) { error << str << endmsg; - } else if (type == LV2Plugin::urids.log_Warning) { + } else if (type == URIMap::instance().urids.log_Warning) { warning << str << endmsg; - } else if (type == LV2Plugin::urids.log_Note) { + } else if (type == URIMap::instance().urids.log_Note) { info << str << endmsg; } // TODO: Toggleable log:Trace message support @@ -233,7 +263,17 @@ log_printf(LV2_Log_Handle handle, struct LV2Plugin::Impl { Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0) , work_iface(0) +#ifdef HAVE_LV2_1_2_0 + , opts_iface(0) +#endif , state(0) + , block_length(0) +#ifdef HAVE_LV2_1_2_0 + , options(0) +#endif +#ifdef LV2_EXTENDED + , queue_draw(0) +#endif {} /** Find the LV2 input port with the given designation. @@ -241,15 +281,26 @@ struct LV2Plugin::Impl { */ const LilvPort* designated_input (const char* uri, void** bufptrs[], void** bufptr); - const LilvPlugin* plugin; - const LilvUI* ui; - const LilvNode* ui_type; - LilvNode* name; - LilvNode* author; - LilvInstance* instance; - const LV2_Worker_Interface* work_iface; - LilvState* state; - LV2_Atom_Forge forge; + const LilvPlugin* plugin; + const LilvUI* ui; + const LilvNode* ui_type; + LilvNode* name; + LilvNode* author; + LilvInstance* instance; + const LV2_Worker_Interface* work_iface; +#ifdef HAVE_LV2_1_2_0 + const LV2_Options_Interface* opts_iface; +#endif + LilvState* state; + LV2_Atom_Forge forge; + LV2_Atom_Forge ui_forge; + int32_t block_length; +#ifdef HAVE_LV2_1_2_0 + LV2_Options_Option* options; +#endif +#ifdef LV2_EXTENDED + LV2_Inline_Display* queue_draw; +#endif }; LV2Plugin::LV2Plugin (AudioEngine& engine, @@ -262,11 +313,10 @@ LV2Plugin::LV2Plugin (AudioEngine& engine, , _features(NULL) , _worker(NULL) , _insert_id("0") - , _patch_count(0) - , _patch_value_uri(NULL) - , _patch_value_key(NULL) - , _patch_value_cur(NULL) - , _patch_value_set(NULL) + , _patch_port_in_index((uint32_t)-1) + , _patch_port_out_index((uint32_t)-1) + , _uri_map(URIMap::instance()) + , _no_sample_accurate_ctrl (false) { init(c_plugin, rate); } @@ -278,11 +328,10 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other) , _features(NULL) , _worker(NULL) , _insert_id(other._insert_id) - , _patch_count(0) - , _patch_value_uri(NULL) - , _patch_value_key(NULL) - , _patch_value_cur(NULL) - , _patch_value_set(NULL) + , _patch_port_in_index((uint32_t)-1) + , _patch_port_out_index((uint32_t)-1) + , _uri_map(URIMap::instance()) + , _no_sample_accurate_ctrl (false) { init(other._impl->plugin, other._sample_rate); @@ -311,11 +360,14 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _latency_control_port = 0; _next_cycle_start = std::numeric_limits::max(); _next_cycle_speed = 1.0; - _block_length = _engine.samples_per_cycle(); _seq_size = _engine.raw_buffer_size(DataType::MIDI); _state_version = 0; _was_activated = false; _has_state_interface = false; + _can_write_automation = false; + _max_latency = 0; + _current_latency = 0; + _impl->block_length = _session.get_block_size(); _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access"; _data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access"; @@ -338,7 +390,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) lilv_node_free(state_uri); lilv_node_free(state_iface_uri); - _features = (LV2_Feature**)calloc(11, sizeof(LV2_Feature*)); + _features = (LV2_Feature**)calloc(12, sizeof(LV2_Feature*)); _features[0] = &_instance_access_feature; _features[1] = &_data_access_feature; _features[2] = &_make_path_feature; @@ -348,26 +400,52 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _features[6] = &_log_feature; unsigned n_features = 7; -#ifdef HAVE_NEW_LV2 +#ifdef HAVE_LV2_1_2_0 _features[n_features++] = &_def_state_feature; #endif lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map()); + lv2_atom_forge_init(&_impl->ui_forge, _uri_map.urid_map()); + +#ifdef LV2_EXTENDED + _impl->queue_draw = (LV2_Inline_Display*) + malloc (sizeof(LV2_Inline_Display)); + _impl->queue_draw->handle = this; + _impl->queue_draw->queue_draw = queue_draw; -#ifdef HAVE_NEW_LV2 + _queue_draw_feature.URI = LV2_INLINEDISPLAY__queue_draw; + _queue_draw_feature.data = _impl->queue_draw; + _features[n_features++] = &_queue_draw_feature; +#endif + +#ifdef HAVE_LV2_1_2_0 LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int); + static const int32_t _min_block_length = 1; // may happen during split-cycles + static const int32_t _max_block_length = 8192; // max possible (with all engines and during export) + /* Consider updating max-block-size whenever the buffersize changes. + * It requires re-instantiating the plugin (which is a non-realtime operation), + * so it should be done lightly and only for plugins that require it. + * + * given that the block-size can change at any time (split-cycles) ardour currently + * does not support plugins that require bufz_fixedBlockLength. + */ LV2_Options_Option options[] = { { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__minBlockLength), - sizeof(int32_t), atom_Int, &_block_length }, + sizeof(int32_t), atom_Int, &_min_block_length }, { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__maxBlockLength), - sizeof(int32_t), atom_Int, &_block_length }, + sizeof(int32_t), atom_Int, &_max_block_length }, { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__sequenceSize), sizeof(int32_t), atom_Int, &_seq_size }, + { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"), + sizeof(int32_t), atom_Int, &_impl->block_length }, { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL } }; + _impl->options = (LV2_Options_Option*) malloc (sizeof (options)); + memcpy ((void*) _impl->options, (void*) options, sizeof (options)); + _options_feature.URI = LV2_OPTIONS__options; - _options_feature.data = options; + _options_feature.data = _impl->options; _features[n_features++] = &_options_feature; #endif @@ -416,15 +494,57 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) } lilv_node_free(worker_iface_uri); + +#ifdef HAVE_LV2_1_2_0 + LilvNode* options_iface_uri = lilv_new_uri(_world.world, LV2_OPTIONS__interface); + if (lilv_plugin_has_extension_data(plugin, options_iface_uri)) { + _impl->opts_iface = (const LV2_Options_Interface*)extension_data( + LV2_OPTIONS__interface); + } + lilv_node_free(options_iface_uri); +#endif + +#ifdef LV2_EXTENDED + _display_interface = (const LV2_Inline_Display_Interface*) + extension_data (LV2_INLINEDISPLAY__interface); +#endif + if (lilv_plugin_has_feature(plugin, _world.lv2_inPlaceBroken)) { error << string_compose( - _("LV2: \"%1\" cannot be used, since it cannot do inplace processing"), + _("LV2: \"%1\" cannot be used, since it cannot do inplace processing."), lilv_node_as_string(_impl->name)) << endmsg; lilv_node_free(_impl->name); lilv_node_free(_impl->author); throw failed_constructor(); } +#ifdef HAVE_LV2_1_2_0 + LilvNodes *required_features = lilv_plugin_get_required_features (plugin); + if (lilv_nodes_contains (required_features, _world.bufz_powerOf2BlockLength) || + lilv_nodes_contains (required_features, _world.bufz_fixedBlockLength) + ) { + error << string_compose( + _("LV2: \"%1\" buffer-size requirements cannot be satisfied."), + lilv_node_as_string(_impl->name)) << endmsg; + lilv_node_free(_impl->name); + lilv_node_free(_impl->author); + lilv_nodes_free(required_features); + throw failed_constructor(); + } + lilv_nodes_free(required_features); +#endif + +#ifdef LV2_EXTENDED + LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin); + if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) { + _no_sample_accurate_ctrl = true; + } + if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) { + _can_write_automation = true; + } + lilv_nodes_free(optional_features); +#endif + #ifdef HAVE_LILV_0_16_0 // Load default state LilvState* state = lilv_state_new_from_world( @@ -432,6 +552,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) if (state && _has_state_interface) { lilv_state_restore(state, _impl->instance, NULL, NULL, 0, NULL); } + lilv_state_free(state); #endif _sample_rate = rate; @@ -474,8 +595,18 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) if (lilv_nodes_contains(atom_supports, _world.time_Position)) { flags |= PORT_POSITION; } +#ifdef LV2_EXTENDED + if (lilv_nodes_contains(atom_supports, _world.auto_automation_control)) { + flags |= PORT_AUTOCTRL; + } +#endif if (lilv_nodes_contains(atom_supports, _world.patch_Message)) { flags |= PORT_PATCHMSG; + if (flags & PORT_INPUT) { + _patch_port_in_index = i; + } else { + _patch_port_out_index = i; + } } } LilvNodes* min_size_v = lilv_port_get_value(_impl->plugin, port, _world.rsz_minimumSize); @@ -493,6 +624,14 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) throw failed_constructor(); } +#ifdef LV2_EXTENDED + if (lilv_port_has_property(_impl->plugin, port, _world.auto_automation_controlled)) { + if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) { + flags |= PORT_CTRLED; + } + } +#endif + _port_flags.push_back(flags); _port_minimumSize.push_back(minimumSize); } @@ -536,6 +675,9 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) lilv_instance_connect_port(_impl->instance, i, &_control_data[i]); if (latent && i == latency_index) { + LilvNode *max; + lilv_port_get_range(_impl->plugin, port, NULL, NULL, &max); + _max_latency = max ? lilv_node_as_float(max) : .02 * _sample_rate; _latency_control_port = &_control_data[i]; *_latency_control_port = 0; } @@ -553,32 +695,6 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) delete[] params; - /* scan supported patch:writable for this plugin. - * Note: the first Atom-port (in every direction) that supports patch:Message will be used - */ - LilvNode* rdfs_label = lilv_new_uri(_world.world, LILV_NS_RDFS "label"); - LilvNode* rdfs_range = lilv_new_uri(_world.world, LILV_NS_RDFS "range"); - LilvNodes* properties = lilv_world_find_nodes (_world.world, lilv_plugin_get_uri(plugin), _world.patch_writable, NULL); - LILV_FOREACH(nodes, p, properties) { - const LilvNode* property = lilv_nodes_get(properties, p); - LilvNode* label = lilv_nodes_get_first (lilv_world_find_nodes (_world.world, property, rdfs_label, NULL)); - LilvNode* range = lilv_nodes_get_first (lilv_world_find_nodes (_world.world, property, rdfs_range, NULL)); - if (!range || _uri_map.uri_to_id(lilv_node_as_uri(range)) != LV2Plugin::urids.atom_Path) { - continue; - } - - _patch_value_uri = (char**) realloc (_patch_value_uri, (_patch_count + 1) * sizeof(char**)); - _patch_value_key = (char**) realloc (_patch_value_key, (_patch_count + 1) * sizeof(char**)); - _patch_value_uri[_patch_count] = strdup(lilv_node_as_uri(property)); - _patch_value_key[_patch_count] = strdup(lilv_node_as_string(label ? label : property)); - ++_patch_count; - } - lilv_node_free(rdfs_label); - lilv_node_free(rdfs_range); - lilv_nodes_free(properties); - _patch_value_cur = (char(*)[PATH_MAX]) calloc(_patch_count, sizeof(char[PATH_MAX])); - _patch_value_set = (char(*)[PATH_MAX]) calloc(_patch_count, sizeof(char[PATH_MAX])); - LilvUIs* uis = lilv_plugin_get_uis(plugin); if (lilv_uis_size(uis) > 0) { #ifdef HAVE_SUIL @@ -625,10 +741,54 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) } } + load_supported_properties(_property_descriptors); allocate_atom_event_buffers(); latency_compute_run(); } +int +LV2Plugin::set_block_size (pframes_t nframes) +{ +#ifdef HAVE_LV2_1_2_0 + if (_impl->opts_iface) { + LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int); + _impl->block_length = nframes; + LV2_Options_Option block_size_option = { + LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id ("http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"), + sizeof(int32_t), atom_Int, (void*)&_impl->block_length + }; + _impl->opts_iface->set (_impl->instance->lv2_handle, &block_size_option); + } +#endif + return 0; +} + +bool +LV2Plugin::requires_fixed_sized_buffers () const +{ + /* This controls if Ardour will split the plugin's run() + * on automation events in order to pass sample-accurate automation + * via standard control-ports. + * + * When returning true Ardour will *not* sub-divide the process-cycle. + * Automation events that happen between cycle-start and cycle-end will be + * ignored (ctrl values are interpolated to cycle-start). + * NB. Atom Sequences are still sample accurate. + * + * Note: This does not guarantee a fixed block-size. + * e.g The process cycle may be split when looping, also + * the period-size may change any time: see set_block_size() + */ + if (get_info()->n_inputs.n_midi() > 0) { + /* we don't yet implement midi buffer offsets (for split cycles). + * Also connect_and_run() also uses _session.transport_frame() directly + * (for BBT) which is not offset for plugin cycle split. + */ + return true; + } + return _no_sample_accurate_ctrl; +} + LV2Plugin::~LV2Plugin () { DEBUG_TRACE(DEBUG::LV2, string_compose("%1 destroy\n", name())); @@ -637,20 +797,20 @@ LV2Plugin::~LV2Plugin () cleanup(); lilv_instance_free(_impl->instance); + lilv_state_free(_impl->state); lilv_node_free(_impl->name); lilv_node_free(_impl->author); +#ifdef HAVE_LV2_1_2_0 + free(_impl->options); +#endif +#ifdef LV2_EXTENDED + free(_impl->queue_draw); +#endif free(_features); free(_make_path_feature.data); free(_work_schedule_feature.data); - for (uint32_t pidx = 0; pidx < _patch_count; ++pidx) { - free(_patch_value_uri[pidx]); - free(_patch_value_key[pidx]); - } - free(_patch_value_cur); - free(_patch_value_set); - delete _to_ui; delete _from_ui; delete _worker; @@ -666,6 +826,7 @@ LV2Plugin::~LV2Plugin () delete [] _control_data; delete [] _shadow_data; + delete [] _defaults; delete [] _ev_buffers; } @@ -707,6 +868,23 @@ LV2Plugin::ui_is_resizable () const return !fs_matches && !nrs_matches; } +#ifdef LV2_EXTENDED +bool +LV2Plugin::has_inline_display () { + return _display_interface ? true : false; +} + +Plugin::Display_Image_Surface* +LV2Plugin::render_inline_display (uint32_t w, uint32_t h) { + if (_display_interface) { + /* Plugin::Display_Image_Surface is identical to + * LV2_Inline_Display_Image_Surface */ + return (Plugin::Display_Image_Surface*) _display_interface->render ((void*)_impl->instance->lv2_handle, w, h); + } + return NULL; +} +#endif + string LV2Plugin::unique_id() const { @@ -886,7 +1064,11 @@ LV2Plugin::c_ui_type() const std::string LV2Plugin::plugin_dir() const { - return Glib::build_filename(_session.plugins_dir(), _insert_id.to_s()); + if (!_plugin_state_dir.empty ()){ + return Glib::build_filename(_plugin_state_dir, _insert_id.to_s()); + } else { + return Glib::build_filename(_session.plugins_dir(), _insert_id.to_s()); + } } /** Directory for files created by the plugin (except during save). */ @@ -941,8 +1123,8 @@ LV2Plugin::add_state(XMLNode* root) const assert(_insert_id != PBD::ID("0")); XMLNode* child; - char buf[16]; - LocaleGuard lg(X_("POSIX")); + char buf[32]; + LocaleGuard lg(X_("C")); for (uint32_t i = 0; i < parameter_count(); ++i) { if (parameter_is_input(i) && parameter_is_control(i)) { @@ -954,6 +1136,10 @@ LV2Plugin::add_state(XMLNode* root) const } } + if (!_plugin_state_dir.empty()) { + root->add_property("template-dir", _plugin_state_dir); + } + if (_has_state_interface) { // Provisionally increment state version and create directory const std::string new_dir = state_dir(++_state_version); @@ -972,7 +1158,9 @@ LV2Plugin::add_state(XMLNode* root) const 0, NULL); - if (!_impl->state || !lilv_state_equals(state, _impl->state)) { + if (!_plugin_state_dir.empty() + || !_impl->state + || !lilv_state_equals(state, _impl->state)) { lilv_state_save(_world.world, _uri_map.urid_map(), _uri_map.urid_unmap(), @@ -981,8 +1169,14 @@ LV2Plugin::add_state(XMLNode* root) const new_dir.c_str(), "state.ttl"); - lilv_state_free(_impl->state); - _impl->state = state; + if (_plugin_state_dir.empty()) { + // normal session save + lilv_state_free(_impl->state); + _impl->state = state; + } else { + // template save (dedicated state-dir) + lilv_state_free(state); + } } else { // State is identical, decrement version and nuke directory lilv_state_free(state); @@ -994,11 +1188,17 @@ LV2Plugin::add_state(XMLNode* root) const } } -static inline const LilvNode* +// TODO: Once we can rely on lilv 0.16.0, lilv_world_get can replace this +static LilvNode* get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate) { LilvNodes* vs = lilv_world_find_nodes(world, subject, predicate, NULL); - return vs ? lilv_nodes_get_first(vs) : NULL; + if (vs) { + LilvNode* node = lilv_node_duplicate(lilv_nodes_get_first(vs)); + lilv_nodes_free(vs); + return node; + } + return NULL; } void @@ -1012,12 +1212,15 @@ LV2Plugin::find_presets() LILV_FOREACH(nodes, i, presets) { const LilvNode* preset = lilv_nodes_get(presets, i); lilv_world_load_resource(_world.world, preset); - const LilvNode* name = get_value(_world.world, preset, rdfs_label); + LilvNode* name = get_value(_world.world, preset, rdfs_label); + bool userpreset = true; // TODO if (name) { _presets.insert(std::make_pair(lilv_node_as_string(preset), Plugin::PresetRecord( lilv_node_as_string(preset), - lilv_node_as_string(name)))); + lilv_node_as_string(name), + userpreset))); + lilv_node_free(name); } else { warning << string_compose( _("Plugin \"%1\" preset \"%2\" is missing a label\n"), @@ -1040,7 +1243,7 @@ set_port_value(const char* port_symbol, uint32_t type) { LV2Plugin* self = (LV2Plugin*)user_data; - if (type != 0 && type != self->_uri_map.uri_to_id(LV2_ATOM__Float)) { + if (type != 0 && type != URIMap::instance().urids.atom_Float) { return; // TODO: Support non-float ports } @@ -1095,11 +1298,25 @@ ARDOUR::lv2plugin_get_port_value(const char* port_symbol, std::string LV2Plugin::do_save_preset(string name) { + LilvNode* plug_name = lilv_plugin_get_name(_impl->plugin); + const string prefix = legalize_for_uri(lilv_node_as_string(plug_name)); const string base_name = legalize_for_uri(name); const string file_name = base_name + ".ttl"; const string bundle = Glib::build_filename( Glib::get_home_dir(), - Glib::build_filename(".lv2", base_name + ".lv2")); + Glib::build_filename(".lv2", prefix + "_" + base_name + ".lv2")); + +#ifdef HAVE_LILV_0_21_3 + /* delete reference to old preset (if any) */ + const PresetRecord* r = preset_by_label(name); + if (r) { + LilvNode* pset = lilv_new_uri (_world.world, r->uri.c_str()); + if (pset) { + lilv_world_unload_resource (_world.world, pset); + lilv_node_free(pset); + } + } +#endif LilvState* state = lilv_state_new_from_instance( _impl->plugin, @@ -1131,7 +1348,7 @@ LV2Plugin::do_save_preset(string name) std::string uri = Glib::filename_to_uri(Glib::build_filename(bundle, file_name)); LilvNode *node_bundle = lilv_new_uri(_world.world, Glib::filename_to_uri(Glib::build_filename(bundle, "/")).c_str()); LilvNode *node_preset = lilv_new_uri(_world.world, uri.c_str()); -#ifdef HAVE_LILV_0_19_2 +#ifdef HAVE_LILV_0_21_3 lilv_world_unload_resource(_world.world, node_preset); lilv_world_unload_bundle(_world.world, node_bundle); #endif @@ -1139,20 +1356,44 @@ LV2Plugin::do_save_preset(string name) lilv_world_load_resource(_world.world, node_preset); lilv_node_free(node_bundle); lilv_node_free(node_preset); + lilv_node_free(plug_name); return uri; } void LV2Plugin::do_remove_preset(string name) { - string preset_file = Glib::build_filename( - Glib::get_home_dir(), - Glib::build_filename( - Glib::build_filename(".lv2", "presets"), - name + ".ttl" - ) - ); - ::g_unlink(preset_file.c_str()); +#ifdef HAVE_LILV_0_21_3 + /* Look up preset record by label (FIXME: ick, label as ID) */ + const PresetRecord* r = preset_by_label(name); + if (!r) { + return; + } + + /* Load a LilvState for the preset. */ + LilvWorld* world = _world.world; + LilvNode* pset = lilv_new_uri(world, r->uri.c_str()); + LilvState* state = lilv_state_new_from_world(world, _uri_map.urid_map(), pset); + if (!state) { + lilv_node_free(pset); + return; + } + + /* Unload preset from world. */ + lilv_world_unload_resource(world, pset); + + /* Delete it from the file system. This will remove the preset file and the entry + from the manifest. If this results in an empty manifest (i.e. the + preset is the only thing in the bundle), then the bundle is removed. */ + lilv_state_delete(world, state); + + lilv_state_free(state); + lilv_node_free(pset); +#endif + /* Without lilv_state_delete(), we could delete the preset file, but this + would leave a broken bundle/manifest around, so the preset would still + be visible, but broken. Naively deleting a bundle is too dangerous, so + we simply do not support preset deletion with older Lilv */ } bool @@ -1239,6 +1480,246 @@ LV2Plugin::write_to_ui(uint32_t index, return true; } +static void +forge_variant(LV2_Atom_Forge* forge, const Variant& value) +{ + switch (value.type()) { + case Variant::NOTHING: + break; + case Variant::BEATS: + // No atom type for this, just forge a double + lv2_atom_forge_double(forge, value.get_beats().to_double()); + break; + case Variant::BOOL: + lv2_atom_forge_bool(forge, value.get_bool()); + break; + case Variant::DOUBLE: + lv2_atom_forge_double(forge, value.get_double()); + break; + case Variant::FLOAT: + lv2_atom_forge_float(forge, value.get_float()); + break; + case Variant::INT: + lv2_atom_forge_int(forge, value.get_int()); + break; + case Variant::LONG: + lv2_atom_forge_long(forge, value.get_long()); + break; + case Variant::PATH: + lv2_atom_forge_path( + forge, value.get_path().c_str(), value.get_path().size()); + break; + case Variant::STRING: + lv2_atom_forge_string( + forge, value.get_string().c_str(), value.get_string().size()); + break; + case Variant::URI: + lv2_atom_forge_uri( + forge, value.get_uri().c_str(), value.get_uri().size()); + break; + } +} + +/** Get a variant type from a URI, return false iff no match found. */ +static bool +uri_to_variant_type(const std::string& uri, Variant::Type& type) +{ + if (uri == LV2_ATOM__Bool) { + type = Variant::BOOL; + } else if (uri == LV2_ATOM__Double) { + type = Variant::DOUBLE; + } else if (uri == LV2_ATOM__Float) { + type = Variant::FLOAT; + } else if (uri == LV2_ATOM__Int) { + type = Variant::INT; + } else if (uri == LV2_ATOM__Long) { + type = Variant::LONG; + } else if (uri == LV2_ATOM__Path) { + type = Variant::PATH; + } else if (uri == LV2_ATOM__String) { + type = Variant::STRING; + } else if (uri == LV2_ATOM__URI) { + type = Variant::URI; + } else { + return false; + } + return true; +} + +void +LV2Plugin::set_property(uint32_t key, const Variant& value) +{ + if (_patch_port_in_index == (uint32_t)-1) { + error << "LV2: set_property called with unset patch_port_in_index" << endmsg; + return; + } else if (value.type() == Variant::NOTHING) { + error << "LV2: set_property called with void value" << endmsg; + return; + } + + // Set up forge to write to temporary buffer on the stack + LV2_Atom_Forge* forge = &_impl->ui_forge; + LV2_Atom_Forge_Frame frame; + uint8_t buf[PATH_MAX]; // Ought to be enough for anyone... + + lv2_atom_forge_set_buffer(forge, buf, sizeof(buf)); + + // Serialize patch:Set message to set property +#ifdef HAVE_LV2_1_10_0 + lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Set); + lv2_atom_forge_key(forge, _uri_map.urids.patch_property); + lv2_atom_forge_urid(forge, key); + lv2_atom_forge_key(forge, _uri_map.urids.patch_value); +#else + lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Set); + lv2_atom_forge_property_head(forge, _uri_map.urids.patch_property, 0); + lv2_atom_forge_urid(forge, key); + lv2_atom_forge_property_head(forge, _uri_map.urids.patch_value, 0); +#endif + + forge_variant(forge, value); + + // Write message to UI=>Plugin ring + const LV2_Atom* const atom = (const LV2_Atom*)buf; + write_from_ui(_patch_port_in_index, + _uri_map.urids.atom_eventTransfer, + lv2_atom_total_size(atom), + (const uint8_t*)atom); +} + +const ParameterDescriptor& +LV2Plugin::get_property_descriptor(uint32_t id) const +{ + PropertyDescriptors::const_iterator p = _property_descriptors.find(id); + if (p != _property_descriptors.end()) { + return p->second; + } + return Plugin::get_property_descriptor(id); +} + +static void +load_parameter_descriptor_units(LilvWorld* lworld, ParameterDescriptor& desc, const LilvNodes* units) +{ + if (lilv_nodes_contains(units, _world.units_midiNote)) { + desc.unit = ParameterDescriptor::MIDI_NOTE; + } else if (lilv_nodes_contains(units, _world.units_db)) { + desc.unit = ParameterDescriptor::DB; + } else if (lilv_nodes_contains(units, _world.units_hz)) { + desc.unit = ParameterDescriptor::HZ; + } + if (lilv_nodes_size(units) > 0) { + const LilvNode* unit = lilv_nodes_get_first(units); + LilvNode* render = get_value(lworld, unit, _world.units_render); + if (render) { + desc.print_fmt = lilv_node_as_string(render); + lilv_node_free(render); + } + } +} + +static void +load_parameter_descriptor(LV2World& world, + ParameterDescriptor& desc, + Variant::Type datatype, + const LilvNode* subject) +{ + LilvWorld* lworld = _world.world; + LilvNode* label = get_value(lworld, subject, _world.rdfs_label); + LilvNode* def = get_value(lworld, subject, _world.lv2_default); + LilvNode* minimum = get_value(lworld, subject, _world.lv2_minimum); + LilvNode* maximum = get_value(lworld, subject, _world.lv2_maximum); + LilvNodes* units = lilv_world_find_nodes(lworld, subject, _world.units_unit, NULL); + if (label) { + desc.label = lilv_node_as_string(label); + } + if (def && lilv_node_is_float(def)) { + desc.normal = lilv_node_as_float(def); + } + if (minimum && lilv_node_is_float(minimum)) { + desc.lower = lilv_node_as_float(minimum); + } + if (maximum && lilv_node_is_float(maximum)) { + desc.upper = lilv_node_as_float(maximum); + } + load_parameter_descriptor_units(lworld, desc, units); + desc.datatype = datatype; + desc.toggled |= datatype == Variant::BOOL; + desc.integer_step |= datatype == Variant::INT || datatype == Variant::LONG; + desc.update_steps(); + + lilv_nodes_free(units); + lilv_node_free(label); + lilv_node_free(def); + lilv_node_free(minimum); + lilv_node_free(maximum); +} + +void +LV2Plugin::load_supported_properties(PropertyDescriptors& descs) +{ + LilvWorld* lworld = _world.world; + const LilvNode* subject = lilv_plugin_get_uri(_impl->plugin); + LilvNodes* properties = lilv_world_find_nodes( + lworld, subject, _world.patch_writable, NULL); + LILV_FOREACH(nodes, p, properties) { + // Get label and range + const LilvNode* prop = lilv_nodes_get(properties, p); + LilvNode* range = get_value(lworld, prop, _world.rdfs_range); + if (!range) { + warning << string_compose(_("LV2: property <%1> has no range datatype, ignoring"), + lilv_node_as_uri(prop)) << endmsg; + continue; + } + + // Convert range to variant type (TODO: support for multiple range types) + Variant::Type datatype; + if (!uri_to_variant_type(lilv_node_as_uri(range), datatype)) { + error << string_compose(_("LV2: property <%1> has unsupported datatype <%1>"), + lilv_node_as_uri(prop), lilv_node_as_uri(range)) << endmsg; + continue; + } + + // Add description to result + ParameterDescriptor desc; + desc.key = _uri_map.uri_to_id(lilv_node_as_uri(prop)); + desc.datatype = datatype; + load_parameter_descriptor(_world, desc, datatype, prop); + descs.insert(std::make_pair(desc.key, desc)); + + lilv_node_free(range); + } + lilv_nodes_free(properties); +} + +void +LV2Plugin::announce_property_values() +{ + if (_patch_port_in_index == (uint32_t)-1) { + return; + } + + // Set up forge to write to temporary buffer on the stack + LV2_Atom_Forge* forge = &_impl->ui_forge; + LV2_Atom_Forge_Frame frame; + uint8_t buf[PATH_MAX]; // Ought to be enough for anyone... + + lv2_atom_forge_set_buffer(forge, buf, sizeof(buf)); + + // Serialize patch:Get message with no subject (implicitly plugin instance) +#ifdef HAVE_LV2_1_10_0 + lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Get); +#else + lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Get); +#endif + + // Write message to UI=>Plugin ring + const LV2_Atom* const atom = (const LV2_Atom*)buf; + write_from_ui(_patch_port_in_index, + _uri_map.urids.atom_eventTransfer, + lv2_atom_total_size(atom), + (const uint8_t*)atom); +} + void LV2Plugin::enable_ui_emission() { @@ -1295,9 +1776,21 @@ LV2Plugin::work_response(uint32_t size, const void* data) } void -LV2Plugin::set_insert_info(const PluginInsert* insert) +LV2Plugin::set_insert_id(PBD::ID id) { - _insert_id = insert->id(); + if (_insert_id == "0") { + _insert_id = id; + } else if (_insert_id != id) { + lilv_state_free(_impl->state); + _impl->state = NULL; + _insert_id = id; + } +} + +void +LV2Plugin::set_state_dir (const std::string& d) +{ + _plugin_state_dir = d; } int @@ -1310,7 +1803,7 @@ LV2Plugin::set_state(const XMLNode& node, int version) const char* sym; const char* value; uint32_t port_id; - LocaleGuard lg(X_("POSIX")); + LocaleGuard lg(X_("C")); if (node.name() != state_node_name()) { error << _("Bad node sent to LV2Plugin::set_state") << endmsg; @@ -1355,6 +1848,10 @@ LV2Plugin::set_state(const XMLNode& node, int version) set_parameter(port_id, atof(value)); } + if ((prop = node.property("template-dir")) != 0) { + set_state_dir (prop->value ()); + } + _state_version = 0; if ((prop = node.property("state-dir")) != 0) { if (sscanf(prop->value().c_str(), "state%u", &_state_version) != 1) { @@ -1371,6 +1868,15 @@ LV2Plugin::set_state(const XMLNode& node, int version) _world.world, _uri_map.urid_map(), NULL, state_file.c_str()); lilv_state_restore(state, _impl->instance, NULL, NULL, 0, NULL); + lilv_state_free(_impl->state); + _impl->state = state; + } + + if (!_plugin_state_dir.empty ()) { + // force save with session, next time (increment counter) + lilv_state_free (_impl->state); + _impl->state = NULL; + set_state_dir (""); } latency_compute_run(); @@ -1383,20 +1889,28 @@ int LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) const { const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which); + if (!port) { + error << string_compose("LV2: get descriptor of non-existent port %1", which) + << endmsg; + return 1; + } LilvNodes* portunits; LilvNode *def, *min, *max; lilv_port_get_range(_impl->plugin, port, &def, &min, &max); portunits = lilv_port_get_value(_impl->plugin, port, _world.units_unit); + // TODO: Once we can rely on lilv 0.18.0 being present, + // load_parameter_descriptor() can be used for ports as well desc.integer_step = lilv_port_has_property(_impl->plugin, port, _world.lv2_integer); desc.toggled = lilv_port_has_property(_impl->plugin, port, _world.lv2_toggled); desc.logarithmic = lilv_port_has_property(_impl->plugin, port, _world.ext_logarithmic); desc.sr_dependent = lilv_port_has_property(_impl->plugin, port, _world.lv2_sampleRate); desc.label = lilv_node_as_string(lilv_port_get_name(_impl->plugin, port)); + desc.normal = def ? lilv_node_as_float(def) : 0.0f; desc.lower = min ? lilv_node_as_float(min) : 0.0f; desc.upper = max ? lilv_node_as_float(max) : 1.0f; - desc.midinote = lilv_nodes_contains(portunits, _world.units_midiNote); + load_parameter_descriptor_units(_world.world, desc, portunits); if (desc.sr_dependent) { desc.lower *= _session.frame_rate (); @@ -1406,18 +1920,10 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c desc.min_unbound = false; // TODO: LV2 extension required desc.max_unbound = false; // TODO: LV2 extension required - if (desc.integer_step) { - desc.step = 1.0; - desc.smallstep = 0.1; - desc.largestep = 10.0; - } else { - const float delta = desc.upper - desc.lower; - desc.step = delta / 1000.0f; - desc.smallstep = delta / 10000.0f; - desc.largestep = delta / 10.0f; - } - desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration); + desc.scale_points = get_scale_points(which); + + desc.update_steps(); lilv_node_free(def); lilv_node_free(min); @@ -1427,6 +1933,56 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c return 0; } +Plugin::IOPortDescription +LV2Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const +{ + PortFlags match = 0; + switch (dt) { + case DataType::AUDIO: + match = PORT_AUDIO; + break; + case DataType::MIDI: + match = PORT_SEQUENCE | PORT_MIDI; // ignore old PORT_EVENT + break; + default: + return Plugin::IOPortDescription ("?"); + break; + } + if (input) { + match |= PORT_INPUT; + } else { + match |= PORT_OUTPUT; + } + + uint32_t p = 0; + uint32_t idx = UINT32_MAX; + + uint32_t const num_ports = parameter_count(); + for (uint32_t port_index = 0; port_index < num_ports; ++port_index) { + PortFlags flags = _port_flags[port_index]; + if ((flags & match) == match) { + if (p == id) { + idx = port_index; + } + ++p; + } + } + if (idx == UINT32_MAX) { + return Plugin::IOPortDescription ("?"); + } + + LilvNode* name = lilv_port_get_name(_impl->plugin, + lilv_plugin_get_port_by_index(_impl->plugin, idx)); + Plugin::IOPortDescription iod (lilv_node_as_string (name)); + lilv_node_free(name); + + if (lilv_port_has_property(_impl->plugin, + lilv_plugin_get_port_by_index(_impl->plugin, idx), _world.lv2_isSideChain)) { + iod.is_sidechain = true; + } + return iod; +} + string LV2Plugin::describe_parameter(Evoral::Parameter which) { @@ -1457,6 +2013,12 @@ LV2Plugin::describe_parameter(Evoral::Parameter which) } } +framecnt_t +LV2Plugin::max_latency () const +{ + return _max_latency; +} + framecnt_t LV2Plugin::signal_latency() const { @@ -1478,9 +2040,31 @@ LV2Plugin::automatable() const } } + for (PropertyDescriptors::const_iterator p = _property_descriptors.begin(); + p != _property_descriptors.end(); + ++p) { + ret.insert(ret.end(), Evoral::Parameter(PluginPropertyAutomation, 0, p->first)); + } return ret; } +void +LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr c) +{ + if ((_port_flags[i] & PORT_CTRLED)) { + _ctrl_map [i] = AutomationCtrlPtr (new AutomationCtrl(c)); + } +} + +LV2Plugin::AutomationCtrlPtr +LV2Plugin::get_automation_control (uint32_t i) +{ + if (_ctrl_map.find (i) == _ctrl_map.end()) { + return AutomationCtrlPtr (); + } + return _ctrl_map[i]; +} + void LV2Plugin::activate() { @@ -1508,7 +2092,6 @@ LV2Plugin::cleanup() { DEBUG_TRACE(DEBUG::LV2, string_compose("%1 cleanup\n", name())); - activate(); deactivate(); lilv_instance_free(_impl->instance); _impl->instance = NULL; @@ -1566,7 +2149,7 @@ LV2Plugin::allocate_atom_event_buffers() _atom_ev_buffers = (LV2_Evbuf**) malloc((total_atom_buffers + 1) * sizeof(LV2_Evbuf*)); for (int i = 0; i < total_atom_buffers; ++i ) { _atom_ev_buffers[i] = lv2_evbuf_new(minimumSize, LV2_EVBUF_ATOM, - LV2Plugin::urids.atom_Chunk, LV2Plugin::urids.atom_Sequence); + _uri_map.urids.atom_Chunk, _uri_map.urids.atom_Sequence); } _atom_ev_buffers[total_atom_buffers] = 0; return; @@ -1584,25 +2167,46 @@ write_position(LV2_Atom_Forge* forge, framepos_t position, framecnt_t offset) { + const URIMap::URIDs& urids = URIMap::instance().urids; + uint8_t pos_buf[256]; lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf)); LV2_Atom_Forge_Frame frame; - lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.time_Position); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_frame, 0); +#ifdef HAVE_LV2_1_10_0 + lv2_atom_forge_object(forge, &frame, 1, urids.time_Position); + lv2_atom_forge_key(forge, urids.time_frame); lv2_atom_forge_long(forge, position); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_speed, 0); + lv2_atom_forge_key(forge, urids.time_speed); lv2_atom_forge_float(forge, speed); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_barBeat, 0); + lv2_atom_forge_key(forge, urids.time_barBeat); lv2_atom_forge_float(forge, bbt.beats - 1 + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat)); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_bar, 0); + lv2_atom_forge_key(forge, urids.time_bar); lv2_atom_forge_long(forge, bbt.bars - 1); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatUnit, 0); + lv2_atom_forge_key(forge, urids.time_beatUnit); lv2_atom_forge_int(forge, t.meter().note_divisor()); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerBar, 0); + lv2_atom_forge_key(forge, urids.time_beatsPerBar); lv2_atom_forge_float(forge, t.meter().divisions_per_bar()); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerMinute, 0); + lv2_atom_forge_key(forge, urids.time_beatsPerMinute); lv2_atom_forge_float(forge, t.tempo().beats_per_minute()); +#else + lv2_atom_forge_blank(forge, &frame, 1, urids.time_Position); + lv2_atom_forge_property_head(forge, urids.time_frame, 0); + lv2_atom_forge_long(forge, position); + lv2_atom_forge_property_head(forge, urids.time_speed, 0); + lv2_atom_forge_float(forge, speed); + lv2_atom_forge_property_head(forge, urids.time_barBeat, 0); + lv2_atom_forge_float(forge, bbt.beats - 1 + + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat)); + lv2_atom_forge_property_head(forge, urids.time_bar, 0); + lv2_atom_forge_long(forge, bbt.bars - 1); + lv2_atom_forge_property_head(forge, urids.time_beatUnit, 0); + lv2_atom_forge_int(forge, t.meter().note_divisor()); + lv2_atom_forge_property_head(forge, urids.time_beatsPerBar, 0); + lv2_atom_forge_float(forge, t.meter().divisions_per_bar()); + lv2_atom_forge_property_head(forge, urids.time_beatsPerMinute, 0); + lv2_atom_forge_float(forge, t.tempo().beats_per_minute()); +#endif LV2_Evbuf_Iterator end = lv2_evbuf_end(buf); const LV2_Atom* const atom = (const LV2_Atom*)pos_buf; @@ -1610,48 +2214,6 @@ write_position(LV2_Atom_Forge* forge, (const uint8_t*)(atom + 1)); } -static bool -write_patch_change( - LV2_Atom_Forge* forge, - LV2_Evbuf* buf, - const char* uri, - const char* filename - ) -{ - LV2_Atom_Forge_Frame frame; - uint8_t patch_buf[PATH_MAX]; - lv2_atom_forge_set_buffer(forge, patch_buf, sizeof(patch_buf)); - -#if 0 // new LV2 - lv2_atom_forge_object(forge, &frame, 0, LV2Plugin::urids.patch_Set); - lv2_atom_forge_key(forge, LV2Plugin::urids.patch_property); - lv2_atom_forge_urid(forge, uri_map.uri_to_id(uri)); - lv2_atom_forge_key(forge, LV2Plugin::urids.patch_value); - lv2_atom_forge_path(forge, filename, strlen(filename)); -#else - lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.patch_Set); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.patch_property, 0); - lv2_atom_forge_urid(forge, LV2Plugin::_uri_map.uri_to_id(uri)); - lv2_atom_forge_property_head(forge, LV2Plugin::urids.patch_value, 0); - lv2_atom_forge_path(forge, filename, strlen(filename)); -#endif - - LV2_Evbuf_Iterator end = lv2_evbuf_end(buf); - const LV2_Atom* const atom = (const LV2_Atom*)patch_buf; - return lv2_evbuf_write(&end, 0, 0, atom->type, atom->size, - (const uint8_t*)(atom + 1)); -} - -bool -LV2Plugin::patch_set (const uint32_t p, const char * val) { - if (p >= _patch_count) return false; - _patch_set_lock.lock(); - strncpy(_patch_value_set[p], val, PATH_MAX); - _patch_value_set[p][PATH_MAX - 1] = 0; - _patch_set_lock.unlock(); - return true; -} - int LV2Plugin::connect_and_run(BufferSet& bufs, ChanMapping in_map, ChanMapping out_map, @@ -1667,13 +2229,22 @@ LV2Plugin::connect_and_run(BufferSet& bufs, TempoMetric tmetric = tmap.metric_at(_session.transport_frame(), &metric_i); if (_freewheel_control_port) { - *_freewheel_control_port = _session.engine().freewheeling(); + *_freewheel_control_port = _session.engine().freewheeling() ? 1.f : 0.f; } if (_bpm_control_port) { *_bpm_control_port = tmetric.tempo().beats_per_minute(); } +#ifdef LV2_EXTENDED + if (_can_write_automation && _session.transport_frame() != _next_cycle_start) { + // add guard-points after locating + for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) { + i->second->guard = true; + } + } +#endif + ChanCount bufs_count; bufs_count.set(DataType::AUDIO, 1); bufs_count.set(DataType::MIDI, 1); @@ -1753,7 +2324,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs, : m; // Now merge MIDI and any transport events into the buffer - const uint32_t type = LV2Plugin::urids.midi_MidiEvent; + const uint32_t type = _uri_map.urids.midi_MidiEvent; const framepos_t tend = _session.transport_frame() + nframes; ++metric_i; while (m != m_end || (metric_i != tmap.metrics_end() && @@ -1762,8 +2333,10 @@ LV2Plugin::connect_and_run(BufferSet& bufs, ? *metric_i : NULL; if (m != m_end && (!metric || metric->frame() > (*m).time())) { const Evoral::MIDIEvent ev(*m, false); - LV2_Evbuf_Iterator eend = lv2_evbuf_end(_ev_buffers[port_index]); - lv2_evbuf_write(&eend, ev.time(), 0, type, ev.size(), ev.buffer()); + if (ev.time() < nframes) { + LV2_Evbuf_Iterator eend = lv2_evbuf_end(_ev_buffers[port_index]); + lv2_evbuf_write(&eend, ev.time(), 0, type, ev.size(), ev.buffer()); + } ++m; } else { tmetric.set_metric(metric); @@ -1784,23 +2357,6 @@ LV2Plugin::connect_and_run(BufferSet& bufs, (flags & PORT_INPUT), 0, (flags & PORT_EVENT)); } - /* queue patch messages */ - if (flags & PORT_PATCHMSG) { - if (_patch_set_lock.trylock()) { - for (uint32_t pidx = 0; pidx < _patch_count; ++ pidx) { - if (strlen(_patch_value_set[pidx]) > 0 - && 0 != strcmp(_patch_value_cur[pidx], _patch_value_set[pidx]) - ) - { - write_patch_change(&_impl->forge, _ev_buffers[port_index], - _patch_value_uri[pidx], _patch_value_set[pidx]); - strncpy(_patch_value_cur[pidx], _patch_value_set[pidx], PATH_MAX); - } - } - _patch_set_lock.unlock(); - } - } - buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]); } else { continue; // Control port, leave buffer alone @@ -1822,11 +2378,11 @@ LV2Plugin::connect_and_run(BufferSet& bufs, error << "Error reading from UI=>Plugin RingBuffer" << endmsg; break; } - if (msg.protocol == urids.atom_eventTransfer) { + if (msg.protocol == URIMap::instance().urids.atom_eventTransfer) { LV2_Evbuf* buf = _ev_buffers[msg.index]; LV2_Evbuf_Iterator i = lv2_evbuf_end(buf); const LV2_Atom* const atom = (const LV2_Atom*)&body[0]; - if (!lv2_evbuf_write(&i, nframes, 0, atom->type, atom->size, + if (!lv2_evbuf_write(&i, nframes - 1, 0, atom->type, atom->size, (const uint8_t*)(atom + 1))) { error << "Failed to write data to LV2 event buffer\n"; } @@ -1873,9 +2429,9 @@ LV2Plugin::connect_and_run(BufferSet& bufs, } } - // Write messages to UI - if ((_to_ui || _patch_count > 0) && (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) { + if ((_to_ui || _can_write_automation || _patch_port_out_index != (uint32_t)-1) && + (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) { LV2_Evbuf* buf = _ev_buffers[port_index]; for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf); lv2_evbuf_is_valid(i); @@ -1884,48 +2440,110 @@ LV2Plugin::connect_and_run(BufferSet& bufs, uint8_t* data; lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data); - // intercept patch change messages - /* TODO this should eventually be done in the UI-thread - * using a ringbuffer and no locks. - * The current UI ringbuffer is unsuitable. It only exists - * if a UI enabled and misses initial patch-set or changes. - */ - if (_patch_count > 0 && (flags & PORT_OUTPUT) && (flags & PORT_PATCHMSG)) { - // TODO reduce if() nesting below: +#ifdef LV2_EXTENDED + // Intercept Automation Write Events + if ((flags & PORT_AUTOCTRL)) { LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom)); - if (atom->type == LV2Plugin::urids.atom_Blank) { + if (atom->type == _uri_map.urids.atom_Blank || + atom->type == _uri_map.urids.atom_Object) { LV2_Atom_Object* obj = (LV2_Atom_Object*)atom; - if (obj->body.otype == LV2Plugin::urids.patch_Set) { - const LV2_Atom* property = NULL; - lv2_atom_object_get (obj, LV2Plugin::urids.patch_property, &property, 0); - if (property->type == LV2Plugin::urids.atom_URID) { - for (uint32_t pidx = 0; pidx < _patch_count; ++ pidx) { - if (((const LV2_Atom_URID*)property)->body != _uri_map.uri_to_id(_patch_value_uri[pidx])) { - continue; - } - /* Get value. */ - const LV2_Atom* file_path = NULL; - lv2_atom_object_get(obj, LV2Plugin::urids.patch_value, &file_path, 0); - if (!file_path || file_path->type != LV2Plugin::urids.atom_Path) { - continue; - } - // LV2_ATOM_BODY() casts away qualifiers, so do it explicitly: - const char* uri = (const char*)((uint8_t const*)(file_path) + sizeof(LV2_Atom)); - _patch_set_lock.lock(); - strncpy(_patch_value_cur[pidx], uri, PATH_MAX); - strncpy(_patch_value_set[pidx], uri, PATH_MAX); - _patch_value_cur[pidx][PATH_MAX - 1] = 0; - _patch_value_set[pidx][PATH_MAX - 1] = 0; - _patch_set_lock.unlock(); - PatchChanged(pidx); // emit signal + if (obj->body.otype == _uri_map.urids.auto_event) { + // only if transport_rolling ?? + const LV2_Atom* parameter = NULL; + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, + _uri_map.urids.auto_parameter, ¶meter, + _uri_map.urids.auto_value, &value, + 0); + if (parameter && value) { + const uint32_t p = ((const LV2_Atom_Int*)parameter)->body; + const float v = ((const LV2_Atom_Float*)value)->body; + // -> add automation event.. + AutomationCtrlPtr c = get_automation_control (p); + if (c && c->ac->automation_state() == Touch) { + if (c->guard) { + c->guard = false; + c->ac->list()->add (_session.transport_frame() + frames, v, true, true); + } else { + c->ac->set_double (v, _session.transport_frame() + frames, true); + } + } + } + } + else if (obj->body.otype == _uri_map.urids.auto_setup) { + // TODO optional arguments, for now we assume the plugin + // writes automation for its own inputs + // -> put them in "touch" mode (preferably "exclusive plugin touch(TM)" + for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) { + i->second->ac->set_automation_state (Touch); + } + } + else if (obj->body.otype == _uri_map.urids.auto_finalize) { + // set [touched] parameters to "play" ?? + } + else if (obj->body.otype == _uri_map.urids.auto_start) { + const LV2_Atom* parameter = NULL; + lv2_atom_object_get(obj, + _uri_map.urids.auto_parameter, ¶meter, + 0); + if (parameter) { + const uint32_t p = ((const LV2_Atom_Int*)parameter)->body; + AutomationCtrlPtr c = get_automation_control (p); + if (c) { + c->ac->start_touch (_session.transport_frame()); + c->guard = true; + } + } + } + else if (obj->body.otype == _uri_map.urids.auto_end) { + const LV2_Atom* parameter = NULL; + lv2_atom_object_get(obj, + _uri_map.urids.auto_parameter, ¶meter, + 0); + if (parameter) { + const uint32_t p = ((const LV2_Atom_Int*)parameter)->body; + AutomationCtrlPtr c = get_automation_control (p); + if (c) { + c->ac->stop_touch (true, _session.transport_frame()); } } } } } +#endif + + // Intercept patch change messages to emit PropertyChanged signal + if ((flags & PORT_PATCHMSG)) { + LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom)); + if (atom->type == _uri_map.urids.atom_Blank || + atom->type == _uri_map.urids.atom_Object) { + LV2_Atom_Object* obj = (LV2_Atom_Object*)atom; + if (obj->body.otype == _uri_map.urids.patch_Set) { + const LV2_Atom* property = NULL; + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, + _uri_map.urids.patch_property, &property, + _uri_map.urids.patch_value, &value, + 0); + + if (property && value && + property->type == _uri_map.urids.atom_URID && + value->type == _uri_map.urids.atom_Path) { + const uint32_t prop_id = ((const LV2_Atom_URID*)property)->body; + const char* path = (const char*)LV2_ATOM_BODY_CONST(value); + + // Emit PropertyChanged signal for UI + // TODO: This should emit the control's Changed signal + PropertyChanged(prop_id, Variant(Variant::PATH, path)); + } else { + std::cerr << "warning: patch:Set for unknown property" << std::endl; + } + } + } + } if (!_to_ui) continue; - write_to_ui(port_index, urids.atom_eventTransfer, + write_to_ui(port_index, URIMap::instance().urids.atom_eventTransfer, size + sizeof(LV2_Atom), data - sizeof(LV2_Atom)); } @@ -1939,6 +2557,13 @@ LV2Plugin::connect_and_run(BufferSet& bufs, _next_cycle_speed = _session.transport_speed(); _next_cycle_start = _session.transport_frame() + (nframes * _next_cycle_speed); + if (_latency_control_port) { + framecnt_t new_latency = signal_latency (); + if (_current_latency != new_latency) { + LatencyChanged (_current_latency, new_latency); /* EMIT SIGNAL */ + } + _current_latency = new_latency; + } return 0; } @@ -1989,18 +2614,18 @@ LV2Plugin::print_parameter(uint32_t param, char* buf, uint32_t len) const } } -boost::shared_ptr +boost::shared_ptr LV2Plugin::get_scale_points(uint32_t port_index) const { const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, port_index); LilvScalePoints* points = lilv_port_get_scale_points(_impl->plugin, port); - boost::shared_ptr ret; + boost::shared_ptr ret; if (!points) { return ret; } - ret = boost::shared_ptr(new ScalePoints()); + ret = boost::shared_ptr(new ScalePoints()); LILV_FOREACH(scale_points, i, points) { const LilvScalePoint* p = lilv_scale_points_get(points, i); @@ -2100,7 +2725,7 @@ LV2Plugin::Impl::designated_input (const char* uri, void** bufptrs[], void** buf static bool lv2_filter (const string& str, void* /*arg*/) { /* Not a dotfile, has a prefix before a period, suffix is "lv2" */ - + return str[0] != '.' && (str.length() > 3 && str.find (".lv2") == (str.length() - 4)); } @@ -2109,8 +2734,6 @@ LV2World::LV2World() : world(lilv_world_new()) , _bundle_checked(false) { - lilv_world_load_all(world); - atom_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort); atom_Chunk = lilv_new_uri(world, LV2_ATOM__Chunk); atom_Sequence = lilv_new_uri(world, LV2_ATOM__Sequence); @@ -2125,7 +2748,11 @@ LV2World::LV2World() lv2_InputPort = lilv_new_uri(world, LILV_URI_INPUT_PORT); lv2_OutputPort = lilv_new_uri(world, LILV_URI_OUTPUT_PORT); lv2_inPlaceBroken = lilv_new_uri(world, LV2_CORE__inPlaceBroken); + lv2_isSideChain = lilv_new_uri(world, LV2_CORE_PREFIX "isSideChain"); lv2_integer = lilv_new_uri(world, LV2_CORE__integer); + lv2_default = lilv_new_uri(world, LV2_CORE__default); + lv2_minimum = lilv_new_uri(world, LV2_CORE__minimum); + lv2_maximum = lilv_new_uri(world, LV2_CORE__maximum); lv2_reportsLatency = lilv_new_uri(world, LV2_CORE__reportsLatency); lv2_sampleRate = lilv_new_uri(world, LV2_CORE__sampleRate); lv2_toggled = lilv_new_uri(world, LV2_CORE__toggled); @@ -2133,29 +2760,65 @@ LV2World::LV2World() lv2_freewheeling = lilv_new_uri(world, LV2_CORE__freeWheeling); midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT); rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); + rdfs_label = lilv_new_uri(world, LILV_NS_RDFS "label"); + rdfs_range = lilv_new_uri(world, LILV_NS_RDFS "range"); rsz_minimumSize = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize); time_Position = lilv_new_uri(world, LV2_TIME__Position); ui_GtkUI = lilv_new_uri(world, LV2_UI__GtkUI); ui_external = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/ui#external"); ui_externalkx = lilv_new_uri(world, "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget"); - units_unit = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#unit"); - units_midiNote = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#midiNote"); + units_unit = lilv_new_uri(world, LV2_UNITS__unit); + units_render = lilv_new_uri(world, LV2_UNITS__render); + units_hz = lilv_new_uri(world, LV2_UNITS__hz); + units_midiNote = lilv_new_uri(world, LV2_UNITS__midiNote); + units_db = lilv_new_uri(world, LV2_UNITS__db); patch_writable = lilv_new_uri(world, LV2_PATCH__writable); patch_Message = lilv_new_uri(world, LV2_PATCH__Message); +#ifdef LV2_EXTENDED + lv2_noSampleAccurateCtrl = lilv_new_uri(world, LV2_CORE_PREFIX "noSampleAccurateControls"); + auto_can_write_automatation = lilv_new_uri(world, LV2_AUTOMATE_URI__can_write); + auto_automation_control = lilv_new_uri(world, LV2_AUTOMATE_URI__control); + auto_automation_controlled = lilv_new_uri(world, LV2_AUTOMATE_URI__controlled); +#endif +#ifdef HAVE_LV2_1_2_0 + bufz_powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength); + bufz_fixedBlockLength = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength); + bufz_nominalBlockLength = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"); +#endif + } LV2World::~LV2World() { + if (!world) { + return; + } +#ifdef HAVE_LV2_1_2_0 + lilv_node_free(bufz_nominalBlockLength); + lilv_node_free(bufz_fixedBlockLength); + lilv_node_free(bufz_powerOf2BlockLength); +#endif +#ifdef LV2_EXTENDED + lilv_node_free(lv2_noSampleAccurateCtrl); + lilv_node_free(auto_can_write_automatation); + lilv_node_free(auto_automation_control); + lilv_node_free(auto_automation_controlled); +#endif lilv_node_free(patch_Message); lilv_node_free(patch_writable); + lilv_node_free(units_hz); lilv_node_free(units_midiNote); + lilv_node_free(units_db); lilv_node_free(units_unit); + lilv_node_free(units_render); lilv_node_free(ui_externalkx); lilv_node_free(ui_external); lilv_node_free(ui_GtkUI); lilv_node_free(time_Position); lilv_node_free(rsz_minimumSize); lilv_node_free(rdfs_comment); + lilv_node_free(rdfs_label); + lilv_node_free(rdfs_range); lilv_node_free(midi_MidiEvent); lilv_node_free(lv2_enumeration); lilv_node_free(lv2_freewheeling); @@ -2163,6 +2826,7 @@ LV2World::~LV2World() lilv_node_free(lv2_sampleRate); lilv_node_free(lv2_reportsLatency); lilv_node_free(lv2_integer); + lilv_node_free(lv2_isSideChain); lilv_node_free(lv2_inPlaceBroken); lilv_node_free(lv2_OutputPort); lilv_node_free(lv2_InputPort); @@ -2178,6 +2842,7 @@ LV2World::~LV2World() lilv_node_free(atom_Chunk); lilv_node_free(atom_AtomPort); lilv_world_free(world); + world = NULL; } void @@ -2201,6 +2866,7 @@ LV2World::load_bundled_plugins(bool verbose) lilv_node_free(node); } + lilv_world_load_all(world); _bundle_checked = true; } } @@ -2238,6 +2904,76 @@ LV2PluginInfo::load(Session& session) return PluginPtr(); } +std::vector +LV2PluginInfo::get_presets (bool /*user_only*/) const +{ + std::vector p; +#ifndef NO_PLUGIN_STATE + const LilvPlugin* lp = NULL; + try { + PluginPtr plugin; + const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world); + LilvNode* uri = lilv_new_uri(_world.world, _plugin_uri); + if (!uri) { throw failed_constructor(); } + lp = lilv_plugins_get_by_uri(plugins, uri); + if (!lp) { throw failed_constructor(); } + lilv_node_free(uri); + } catch (failed_constructor& err) { + return p; + } + assert (lp); + // see LV2Plugin::find_presets + LilvNode* lv2_appliesTo = lilv_new_uri(_world.world, LV2_CORE__appliesTo); + LilvNode* pset_Preset = lilv_new_uri(_world.world, LV2_PRESETS__Preset); + LilvNode* rdfs_label = lilv_new_uri(_world.world, LILV_NS_RDFS "label"); + + LilvNodes* presets = lilv_plugin_get_related(lp, pset_Preset); + LILV_FOREACH(nodes, i, presets) { + const LilvNode* preset = lilv_nodes_get(presets, i); + lilv_world_load_resource(_world.world, preset); + LilvNode* name = get_value(_world.world, preset, rdfs_label); + bool userpreset = true; // TODO + if (name) { + p.push_back (Plugin::PresetRecord (lilv_node_as_string(preset), lilv_node_as_string(name), userpreset)); + lilv_node_free(name); + } + } + lilv_nodes_free(presets); + lilv_node_free(rdfs_label); + lilv_node_free(pset_Preset); + lilv_node_free(lv2_appliesTo); +#endif + return p; +} + +bool +LV2PluginInfo::in_category (const std::string &c) const +{ + // TODO use untranslated lilv_plugin_get_class() + // match gtk2_ardour/plugin_selector.cc + if (category == c) { + return true; + } + return false; +} + +bool +LV2PluginInfo::is_instrument () const +{ + if (category == "Instrument") { + return true; + } +#if 1 + /* until we make sure that category remains untranslated in the lv2.ttl spec + * and until most instruments also classify themselves as such, there's a 2nd check: + */ + if (n_inputs.n_midi() > 0 && n_inputs.n_audio() == 0 && n_outputs.n_audio() > 0) { + return true; + } +#endif + return false; +} + PluginInfoList* LV2PluginInfo::discover() { @@ -2248,10 +2984,6 @@ LV2PluginInfo::discover() PluginInfoList* plugs = new PluginInfoList; const LilvPlugins* plugins = lilv_world_get_all_plugins(world.world); - if (!Config->get_show_plugin_scan_window()) { - info << "LV2: Discovering " << lilv_plugins_size(plugins) << " plugins" << endmsg; - } - LILV_FOREACH(plugins, i, plugins) { const LilvPlugin* p = lilv_plugins_get(plugins, i); const LilvNode* pun = lilv_plugin_get_uri(p); @@ -2266,6 +2998,29 @@ LV2PluginInfo::discover() continue; } + if (lilv_plugin_has_feature(p, world.lv2_inPlaceBroken)) { + warning << string_compose( + _("Ignoring LV2 plugin \"%1\" since it cannot do inplace processing."), + lilv_node_as_string(name)) << endmsg; + lilv_node_free(name); + continue; + } + +#ifdef HAVE_LV2_1_2_0 + LilvNodes *required_features = lilv_plugin_get_required_features (p); + if (lilv_nodes_contains (required_features, world.bufz_powerOf2BlockLength) || + lilv_nodes_contains (required_features, world.bufz_fixedBlockLength) + ) { + warning << string_compose( + _("Ignoring LV2 plugin \"%1\" because its buffer-size requirements cannot be satisfied."), + lilv_node_as_string(name)) << endmsg; + lilv_nodes_free(required_features); + lilv_node_free(name); + continue; + } + lilv_nodes_free(required_features); +#endif + info->type = LV2; info->name = string(lilv_node_as_string(name));