X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Flv2_plugin.cc;h=c850cb6fe7de6d64129437eab8c824c280ecf0f7;hb=fdfff818509cb8e7b7753cf40336cb3d112b4e7c;hp=9cac6492b4ed0ea2e732e3529c3955c5b62099fc;hpb=7b6ef41f0caca083441748a4ef5d836df2be243a;p=ardour.git diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 9cac6492b4..c850cb6fe7 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -17,6 +17,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -35,6 +36,7 @@ #include "pbd/stl_delete.h" #include "pbd/compose.h" #include "pbd/error.h" +#include "pbd/replace_all.h" #include "pbd/xml++.h" #include "libardour-config.h" @@ -125,6 +127,10 @@ public: LilvNode* ev_EventPort; LilvNode* ext_logarithmic; LilvNode* ext_notOnGUI; + LilvNode* ext_expensive; + LilvNode* ext_causesArtifacts; + LilvNode* ext_notAutomatic; + LilvNode* ext_rangeSteps; LilvNode* lv2_AudioPort; LilvNode* lv2_ControlPort; LilvNode* lv2_InputPort; @@ -132,6 +138,7 @@ public: LilvNode* lv2_enumeration; LilvNode* lv2_freewheeling; LilvNode* lv2_inPlaceBroken; + LilvNode* lv2_isSideChain; LilvNode* lv2_integer; LilvNode* lv2_default; LilvNode* lv2_minimum; @@ -155,13 +162,26 @@ public: LilvNode* units_midiNote; LilvNode* patch_writable; LilvNode* patch_Message; - LilvNode* lv2_noSampleAccurateCtrl; #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 + LilvNode* auto_automation_controller; // lv2:portProperty +#endif + private: bool _bundle_checked; }; @@ -204,6 +224,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 @@ -214,6 +244,14 @@ log_vprintf(LV2_Log_Handle /*handle*/, { char* str = NULL; const int ret = g_vasprintf(&str, fmt, args); + /* strip trailing whitespace */ + while (strlen (str) > 0 && isspace (str[strlen (str) - 1])) { + str[strlen (str) - 1] = '\0'; + } + if (strlen (str) == 0) { + return 0; + } + if (type == URIMap::instance().urids.log_Error) { error << str << endmsg; } else if (type == URIMap::instance().urids.log_Warning) { @@ -247,6 +285,9 @@ struct LV2Plugin::Impl { , block_length(0) #ifdef HAVE_LV2_1_2_0 , options(0) +#endif +#ifdef LV2_EXTENDED + , queue_draw(0) #endif {} @@ -272,6 +313,9 @@ struct LV2Plugin::Impl { #ifdef HAVE_LV2_1_2_0 LV2_Options_Option* options; #endif +#ifdef LV2_EXTENDED + LV2_Inline_Display* queue_draw; +#endif }; LV2Plugin::LV2Plugin (AudioEngine& engine, @@ -335,6 +379,9 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _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"; @@ -358,7 +405,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; @@ -375,6 +422,17 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) 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; + + _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 @@ -461,6 +519,11 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) 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."), @@ -484,11 +547,16 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) 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 @@ -542,6 +610,11 @@ 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) { @@ -566,8 +639,33 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) throw failed_constructor(); } + if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) { + if (lilv_port_has_property(_impl->plugin, port, _world.ext_causesArtifacts)) { + flags |= PORT_NOAUTO; + } + if (lilv_port_has_property(_impl->plugin, port, _world.ext_notAutomatic)) { + flags |= PORT_NOAUTO; + } + if (lilv_port_has_property(_impl->plugin, port, _world.ext_expensive)) { + flags |= PORT_NOAUTO; + } + } +#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; + } + } + if (lilv_port_has_property(_impl->plugin, port, _world.auto_automation_controller)) { + if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) { + flags |= PORT_CTRLER; + } + } +#endif + _port_flags.push_back(flags); _port_minimumSize.push_back(minimumSize); + DEBUG_TRACE(DEBUG::LV2, string_compose("port %1 buffer %2 bytes\n", i, minimumSize)); } _control_data = new float[num_ports]; @@ -609,6 +707,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; } @@ -710,6 +811,13 @@ LV2Plugin::requires_fixed_sized_buffers () const * 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; } @@ -727,8 +835,12 @@ LV2Plugin::~LV2Plugin () #ifdef HAVE_LV2_1_2_0 free(_impl->options); #endif +#ifdef LV2_EXTENDED + free(_impl->queue_draw); +#endif free(_features); + free(_log_feature.data); free(_make_path_feature.data); free(_work_schedule_feature.data); @@ -749,6 +861,7 @@ LV2Plugin::~LV2Plugin () delete [] _shadow_data; delete [] _defaults; delete [] _ev_buffers; + delete _impl; } bool @@ -789,6 +902,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 { @@ -924,6 +1054,51 @@ LV2Plugin::get_parameter_docs(uint32_t which) const return ""; } +bool +LV2Plugin::get_layout (uint32_t which, UILayoutHint& h) const +{ + /// TODO lookup port-properties + if (unique_id () != "urn:ardour:a-eq") { + return false; + } + h.knob = true; + switch (which) { + case 0: h.x0 = 0; h.x1 = 1; h.y0 = 2; h.y1 = 3; break; // Frequency L + case 1: h.x0 = 0; h.x1 = 1; h.y0 = 0; h.y1 = 1; break; // Gain L + case 17: h.x0 = 0; h.x1 = 1; h.y0 = 5; h.y1 = 6; break; // enable L + + case 2: h.x0 = 1; h.x1 = 3; h.y0 = 2; h.y1 = 3; break; // Frequency 1 + case 3: h.x0 = 1; h.x1 = 3; h.y0 = 0; h.y1 = 1; break; // Gain 1 + case 4: h.x0 = 2; h.x1 = 4; h.y0 = 1; h.y1 = 2; break; // Bandwidth 1 + case 18: h.x0 = 1; h.x1 = 4; h.y0 = 5; h.y1 = 6; break; // enable 1 + + case 5: h.x0 = 4; h.x1 = 6; h.y0 = 2; h.y1 = 3; break; // Frequency 2 + case 6: h.x0 = 4; h.x1 = 6; h.y0 = 0; h.y1 = 1; break; // Gain 2 + case 7: h.x0 = 5; h.x1 = 7; h.y0 = 1; h.y1 = 2; break; // Bandwidth 2 + case 19: h.x0 = 4; h.x1 = 7; h.y0 = 5; h.y1 = 6; break; // enable 2 + + case 8: h.x0 = 7; h.x1 = 9; h.y0 = 2; h.y1 = 3; break; // Frequency 3 + case 9: h.x0 = 7; h.x1 = 9; h.y0 = 0; h.y1 = 1; break; // Gain 3 + case 10: h.x0 = 8; h.x1 = 10; h.y0 = 1; h.y1 = 2; break; // Bandwidth 3 + case 20: h.x0 = 7; h.x1 = 10; h.y0 = 5; h.y1 = 6; break; // enable 3 + + case 11: h.x0 = 10; h.x1 = 12; h.y0 = 2; h.y1 = 3; break; // Frequency 4 + case 12: h.x0 = 10; h.x1 = 12; h.y0 = 0; h.y1 = 1; break; // Gain 4 + case 13: h.x0 = 11; h.x1 = 13; h.y0 = 1; h.y1 = 2; break; // Bandwidth 4 + case 21: h.x0 = 10; h.x1 = 13; h.y0 = 5; h.y1 = 6; break; // enable 4 + + case 14: h.x0 = 13; h.x1 = 14; h.y0 = 2; h.y1 = 3; break; // Frequency H + case 15: h.x0 = 13; h.x1 = 14; h.y0 = 0; h.y1 = 1; break; // Gain H + case 22: h.x0 = 13; h.x1 = 14; h.y0 = 5; h.y1 = 6; break; // enable H + + + case 16: h.x0 = 14; h.x1 = 15; h.y0 = 4; h.y1 = 6; break; // Master Gain + default: + return false; + } + return true; +} + uint32_t LV2Plugin::nth_parameter(uint32_t n, bool& ok) const { @@ -968,7 +1143,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). */ @@ -1024,7 +1203,7 @@ LV2Plugin::add_state(XMLNode* root) const XMLNode* child; char buf[32]; - LocaleGuard lg(X_("C")); + LocaleGuard lg; for (uint32_t i = 0; i < parameter_count(); ++i) { if (parameter_is_input(i) && parameter_is_control(i)) { @@ -1036,6 +1215,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); @@ -1054,7 +1237,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(), @@ -1063,8 +1248,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); @@ -1101,11 +1292,13 @@ LV2Plugin::find_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) { _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( @@ -1498,6 +1691,7 @@ load_parameter_descriptor_units(LilvWorld* lworld, ParameterDescriptor& desc, co LilvNode* render = get_value(lworld, unit, _world.units_render); if (render) { desc.print_fmt = lilv_node_as_string(render); + replace_all (desc.print_fmt, "%f", "%.2f"); lilv_node_free(render); } } @@ -1673,17 +1867,23 @@ LV2Plugin::set_insert_id(PBD::ID id) } } +void +LV2Plugin::set_state_dir (const std::string& d) +{ + _plugin_state_dir = d; +} + int LV2Plugin::set_state(const XMLNode& node, int version) { XMLNodeList nodes; - const XMLProperty* prop; + XMLProperty const * prop; XMLNodeConstIterator iter; XMLNode* child; const char* sym; const char* value; uint32_t port_id; - LocaleGuard lg(X_("C")); + LocaleGuard lg; if (node.name() != state_node_name()) { error << _("Bad node sent to LV2Plugin::set_state") << endmsg; @@ -1728,6 +1928,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) { @@ -1736,8 +1940,6 @@ LV2Plugin::set_state(const XMLNode& node, int version) prop->value()) << endmsg; } - // TODO: special case track-templates - // (state must be saved with the template) std::string state_file = Glib::build_filename( plugin_dir(), Glib::build_filename(prop->value(), "state.ttl")); @@ -1750,6 +1952,13 @@ LV2Plugin::set_state(const XMLNode& node, int version) _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(); #endif @@ -1771,6 +1980,8 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c lilv_port_get_range(_impl->plugin, port, &def, &min, &max); portunits = lilv_port_get_value(_impl->plugin, port, _world.units_unit); + LilvNode* steps = lilv_port_get(_impl->plugin, port, _world.ext_rangeSteps); + // 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); @@ -1796,14 +2007,89 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c desc.update_steps(); + if (steps) { + //override auto-calculated steps in update_steps() + float s = lilv_node_as_float (steps); + const float delta = desc.upper - desc.lower; + + desc.step = desc.smallstep = (delta / s); + desc.largestep = std::min ((delta / 5.0f), 10.f * desc.smallstep); + + if (desc.logarithmic) { + // TODO marry AutomationControl::internal_to_interface () with + // http://lv2plug.in/ns/ext/port-props/#rangeSteps + desc.smallstep = desc.smallstep / logf(s); + desc.step = desc.step / logf(s); + desc.largestep = desc.largestep / logf(s); + } else if (desc.integer_step) { + desc.smallstep = 1.0; + desc.step = std::max(1.f, rintf (desc.step)); + desc.largestep = std::max(1.f, rintf (desc.largestep)); + } + DEBUG_TRACE(DEBUG::LV2, string_compose("parameter %1 small: %2, step: %3 largestep: %4\n", + which, desc.smallstep, desc.step, desc.largestep)); + } + + lilv_node_free(def); lilv_node_free(min); lilv_node_free(max); + lilv_node_free(steps); lilv_nodes_free(portunits); 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) { @@ -1834,6 +2120,12 @@ LV2Plugin::describe_parameter(Evoral::Parameter which) } } +framecnt_t +LV2Plugin::max_latency () const +{ + return _max_latency; +} + framecnt_t LV2Plugin::signal_latency() const { @@ -1850,7 +2142,7 @@ LV2Plugin::automatable() const set ret; for (uint32_t i = 0; i < parameter_count(); ++i) { - if (parameter_is_input(i) && parameter_is_control(i)) { + if (parameter_is_input(i) && parameter_is_control(i) && !(_port_flags[i] & PORT_NOAUTO)) { ret.insert(ret.end(), Evoral::Parameter(PluginAutomation, 0, i)); } } @@ -1863,6 +2155,24 @@ LV2Plugin::automatable() const return ret; } +void +LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr c) +{ + if ((_port_flags[i] & (PORT_CTRLED | PORT_CTRLER))) { + DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Ctrl Port %1\n", i)); + _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() { @@ -1890,7 +2200,6 @@ LV2Plugin::cleanup() { DEBUG_TRACE(DEBUG::LV2, string_compose("%1 cleanup\n", name())); - activate(); deactivate(); lilv_instance_free(_impl->instance); _impl->instance = NULL; @@ -1916,8 +2225,7 @@ LV2Plugin::allocate_atom_event_buffers() LilvNodes* atom_supports = lilv_port_get_value( p, port, _world.atom_supports); - if (!lilv_nodes_contains(buffer_types, _world.atom_Sequence) - || !lilv_nodes_contains(atom_supports, _world.midi_MidiEvent)) { + if (lilv_nodes_contains(buffer_types, _world.atom_Sequence)) { if (lilv_port_is_a(p, port, _world.lv2_InputPort)) { count_atom_in++; } @@ -1944,7 +2252,7 @@ LV2Plugin::allocate_atom_event_buffers() return; } - DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers of %d bytes\n", total_atom_buffers, minimumSize)); + DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers of %2 bytes\n", total_atom_buffers, minimumSize)); _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, @@ -2015,17 +2323,18 @@ write_position(LV2_Atom_Forge* forge, int LV2Plugin::connect_and_run(BufferSet& bufs, - ChanMapping in_map, ChanMapping out_map, - pframes_t nframes, framecnt_t offset) + framepos_t start, framepos_t end, double speed, + ChanMapping in_map, ChanMapping out_map, + pframes_t nframes, framecnt_t offset) { DEBUG_TRACE(DEBUG::LV2, string_compose("%1 run %2 offset %3\n", name(), nframes, offset)); - Plugin::connect_and_run(bufs, in_map, out_map, nframes, offset); + Plugin::connect_and_run(bufs, start, end, speed, in_map, out_map, nframes, offset); cycles_t then = get_cycles(); TempoMap& tmap = _session.tempo_map(); Metrics::const_iterator metric_i = tmap.metrics_end(); - TempoMetric tmetric = tmap.metric_at(_session.transport_frame(), &metric_i); + TempoMetric tmetric = tmap.metric_at(start, &metric_i); if (_freewheel_control_port) { *_freewheel_control_port = _session.engine().freewheeling() ? 1.f : 0.f; @@ -2035,6 +2344,15 @@ LV2Plugin::connect_and_run(BufferSet& bufs, *_bpm_control_port = tmetric.tempo().beats_per_minute(); } +#ifdef LV2_EXTENDED + if (_can_write_automation && start != _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); @@ -2095,13 +2413,12 @@ LV2Plugin::connect_and_run(BufferSet& bufs, if (valid && (flags & PORT_INPUT)) { Timecode::BBT_Time bbt; if ((flags & PORT_POSITION)) { - if (_session.transport_frame() != _next_cycle_start || - _session.transport_speed() != _next_cycle_speed) { + if (start != _next_cycle_start || + speed != _next_cycle_speed) { // Transport has changed, write position at cycle start - tmap.bbt_time(_session.transport_frame(), bbt); + bbt = tmap.bbt_at_frame (start); write_position(&_impl->forge, _ev_buffers[port_index], - tmetric, bbt, _session.transport_speed(), - _session.transport_frame(), 0); + tmetric, bbt, speed, start, 0); } } @@ -2115,7 +2432,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs, // Now merge MIDI and any transport events into the buffer const uint32_t type = _uri_map.urids.midi_MidiEvent; - const framepos_t tend = _session.transport_frame() + nframes; + const framepos_t tend = end; ++metric_i; while (m != m_end || (metric_i != tmap.metrics_end() && (*metric_i)->frame() < tend)) { @@ -2123,16 +2440,18 @@ 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); - bbt = metric->start(); + bbt = tmap.bbt_at_pulse (metric->pulse()); write_position(&_impl->forge, _ev_buffers[port_index], - tmetric, bbt, _session.transport_speed(), + tmetric, bbt, speed, metric->frame(), - metric->frame() - _session.transport_frame()); + metric->frame() - start); ++metric_i; } } @@ -2217,9 +2536,8 @@ LV2Plugin::connect_and_run(BufferSet& bufs, } } - // Write messages to UI - if ((_to_ui || _patch_port_out_index != (uint32_t)-1) && + 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); @@ -2229,6 +2547,117 @@ LV2Plugin::connect_and_run(BufferSet& bufs, uint8_t* data; lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data); +#ifdef LV2_EXTENDED + // Intercept Automation Write Events + if ((flags & PORT_AUTOCTRL)) { + 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.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.. + DEBUG_TRACE(DEBUG::LV2Automate, + string_compose ("Event p: %1 t: %2 v: %3\n", p, frames, v)); + AutomationCtrlPtr c = get_automation_control (p); + if (c && + (c->ac->automation_state() == Touch || c->ac->automation_state() == Write) + ) { + framepos_t when = std::max ((framepos_t) 0, start + frames - _current_latency); + assert (start + frames - _current_latency >= 0); + if (c->guard) { + c->guard = false; + c->ac->list()->add (when, v, true, true); + } else { + c->ac->set_double (v, when, 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) { + if (_port_flags[i->first] & PORT_CTRLED) { + DEBUG_TRACE(DEBUG::LV2Automate, + string_compose ("Setup p: %1\n", i->first)); + i->second->ac->set_automation_state (Touch); + } + } + } + else if (obj->body.otype == _uri_map.urids.auto_finalize) { + // set [touched] parameters to "play" ?? + // allow plugin to change its mode (from analyze to apply) + 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; + AutomationCtrlPtr c = get_automation_control (p); + DEBUG_TRACE(DEBUG::LV2Automate, + string_compose ("Finalize p: %1 v: %2\n", p, v)); + if (c && _port_flags[p] & PORT_CTRLER) { + c->ac->set_value(v, Controllable::NoGroup); + } + } else { + DEBUG_TRACE(DEBUG::LV2Automate, "Finalize\n"); + } + for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) { + // guard will be false if an event was written + if ((_port_flags[i->first] & PORT_CTRLED) && !i->second->guard) { + DEBUG_TRACE(DEBUG::LV2Automate, + string_compose ("Thin p: %1\n", i->first)); + i->second->ac->alist ()->thin (20); + } + } + } + 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); + DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Start Touch p: %1\n", p)); + if (c) { + c->ac->start_touch (std::max ((framepos_t)0, start - _current_latency)); + 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); + DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("End Touch p: %1\n", p)); + if (c) { + c->ac->stop_touch (true, std::max ((framepos_t)0, start - _current_latency)); + } + } + } + } + } +#endif + // Intercept patch change messages to emit PropertyChanged signal if ((flags & PORT_PATCHMSG)) { LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom)); @@ -2243,19 +2672,18 @@ LV2Plugin::connect_and_run(BufferSet& bufs, _uri_map.urids.patch_value, &value, 0); - if (!property || !value || - property->type != _uri_map.urids.atom_URID || - value->type != _uri_map.urids.atom_Path) { + 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; - continue; } - - 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)); } } } @@ -2272,9 +2700,13 @@ LV2Plugin::connect_and_run(BufferSet& bufs, set_cycles((uint32_t)(now - then)); // Update expected transport information for next cycle so we can detect changes - _next_cycle_speed = _session.transport_speed(); - _next_cycle_start = _session.transport_frame() + (nframes * _next_cycle_speed); + _next_cycle_speed = speed; + _next_cycle_start = end; + if (_latency_control_port) { + framecnt_t new_latency = signal_latency (); + _current_latency = new_latency; + } return 0; } @@ -2313,6 +2745,22 @@ LV2Plugin::parameter_is_input(uint32_t param) const return _port_flags[param] & PORT_INPUT; } +uint32_t +LV2Plugin::designated_bypass_port () +{ +#ifdef LV2_EXTENDED + const LilvPort* port = NULL; + LilvNode* designation = lilv_new_uri (_world.world, LV2_PROCESSING_URI__enable); + port = lilv_plugin_get_port_by_designation ( + _impl->plugin, _world.lv2_InputPort, designation); + lilv_node_free(designation); + if (port) { + return lilv_port_get_index (_impl->plugin, port); + } +#endif + return UINT32_MAX; +} + void LV2Plugin::print_parameter(uint32_t param, char* buf, uint32_t len) const { @@ -2454,11 +2902,16 @@ LV2World::LV2World() ev_EventPort = lilv_new_uri(world, LILV_URI_EVENT_PORT); ext_logarithmic = lilv_new_uri(world, LV2_PORT_PROPS__logarithmic); ext_notOnGUI = lilv_new_uri(world, LV2_PORT_PROPS__notOnGUI); + ext_expensive = lilv_new_uri(world, LV2_PORT_PROPS__expensive); + ext_causesArtifacts= lilv_new_uri(world, LV2_PORT_PROPS__causesArtifacts); + ext_notAutomatic = lilv_new_uri(world, LV2_PORT_PROPS__notAutomatic); + ext_rangeSteps = lilv_new_uri(world, LV2_PORT_PROPS__rangeSteps); lv2_AudioPort = lilv_new_uri(world, LILV_URI_AUDIO_PORT); lv2_ControlPort = lilv_new_uri(world, LILV_URI_CONTROL_PORT); 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); @@ -2484,7 +2937,13 @@ LV2World::LV2World() 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); - lv2_noSampleAccurateCtrl = lilv_new_uri(world, LV2_CORE_PREFIX "noSampleAccurateControls"); +#ifdef LV2_EXTENDED + lv2_noSampleAccurateCtrl = lilv_new_uri(world, "http://ardour.org/lv2/ext#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); + auto_automation_controller = lilv_new_uri(world, LV2_AUTOMATE_URI__controller); +#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); @@ -2495,12 +2954,21 @@ LV2World::LV2World() 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); + lilv_node_free(auto_automation_controller); +#endif lilv_node_free(patch_Message); lilv_node_free(patch_writable); lilv_node_free(units_hz); @@ -2523,11 +2991,16 @@ 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); lilv_node_free(lv2_ControlPort); lilv_node_free(lv2_AudioPort); + lilv_node_free(ext_rangeSteps); + lilv_node_free(ext_notAutomatic); + lilv_node_free(ext_causesArtifacts); + lilv_node_free(ext_expensive); lilv_node_free(ext_notOnGUI); lilv_node_free(ext_logarithmic); lilv_node_free(ev_EventPort); @@ -2538,6 +3011,7 @@ LV2World::~LV2World() lilv_node_free(atom_Chunk); lilv_node_free(atom_AtomPort); lilv_world_free(world); + world = NULL; } void @@ -2599,6 +3073,48 @@ 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 {