Add automatable click-free bypass/enable feature to a-eq
[ardour.git] / libs / ardour / lv2_plugin.cc
index 940ec8ae0389ac49a47057a89da98f5550fb2f0a..e45270ebcb6326e6baabb87883c494b35b151893 100644 (file)
@@ -17,6 +17,7 @@
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
+#include <cctype>
 #include <string>
 #include <vector>
 #include <limits>
@@ -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"
@@ -50,7 +52,7 @@
 #include "ardour/worker.h"
 #include "ardour/search_paths.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 #include <lilv/lilv.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;
 };
@@ -176,15 +196,9 @@ work_schedule(LV2_Worker_Schedule_Handle handle,
               uint32_t                   size,
               const void*                data)
 {
-       LV2Plugin* plugin = (LV2Plugin*)handle;
-       if (plugin->session().engine().freewheeling()) {
-               // Freewheeling, do the work immediately in this (audio) thread
-               return (LV2_Worker_Status)plugin->work(size, data);
-       } else {
-               // Enqueue message for the worker thread
-               return plugin->worker()->schedule(size, data) ?
-                       LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN;
-       }
+       return (((Worker*)handle)->schedule(size, data)
+               ? LV2_WORKER_SUCCESS
+               : LV2_WORKER_ERR_UNKNOWN);
 }
 
 /** Called by the plugin to respond to non-RT work. */
@@ -192,17 +206,21 @@ static LV2_Worker_Status
 work_respond(LV2_Worker_Respond_Handle handle,
              uint32_t                  size,
              const void*               data)
+{
+       return (((Worker*)handle)->respond(size, data)
+               ? LV2_WORKER_SUCCESS
+               : LV2_WORKER_ERR_UNKNOWN);
+}
+
+#ifdef LV2_EXTENDED
+/* inline display extension */
+static void
+queue_draw (LV2_Inline_Display_Handle handle)
 {
        LV2Plugin* plugin = (LV2Plugin*)handle;
-       if (plugin->session().engine().freewheeling()) {
-               // Freewheeling, respond immediately in this (audio) thread
-               return (LV2_Worker_Status)plugin->work_response(size, data);
-       } else {
-               // Enqueue response for the worker
-               return plugin->worker()->respond(size, data) ?
-                       LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN;
-       }
+       plugin->QueueDraw(); /* EMIT SIGNAL */
 }
+#endif
 
 /* log extension */
 
@@ -214,14 +232,23 @@ 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) {
                warning << str << endmsg;
        } else if (type == URIMap::instance().urids.log_Note) {
                info << str << endmsg;
+       } else if (type == URIMap::instance().urids.log_Trace) {
+               DEBUG_TRACE(DEBUG::LV2, str);
        }
-       // TODO: Toggleable log:Trace message support
        return ret;
 }
 
@@ -247,6 +274,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 +302,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,
@@ -283,6 +316,7 @@ LV2Plugin::LV2Plugin (AudioEngine& engine,
        , _impl(new Impl())
        , _features(NULL)
        , _worker(NULL)
+       , _state_worker(NULL)
        , _insert_id("0")
        , _patch_port_in_index((uint32_t)-1)
        , _patch_port_out_index((uint32_t)-1)
@@ -298,6 +332,7 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other)
        , _impl(new Impl())
        , _features(NULL)
        , _worker(NULL)
+       , _state_worker(NULL)
        , _insert_id(other._insert_id)
        , _patch_port_in_index((uint32_t)-1)
        , _patch_port_out_index((uint32_t)-1)
@@ -335,6 +370,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 +396,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 +413,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
@@ -418,19 +467,24 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        log->vprintf = &log_vprintf;
        _log_feature.data = log;
 
+       const size_t ring_size = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS;
        LilvNode* worker_schedule = lilv_new_uri(_world.world, LV2_WORKER__schedule);
        if (lilv_plugin_has_feature(plugin, worker_schedule)) {
                LV2_Worker_Schedule* schedule = (LV2_Worker_Schedule*)malloc(
                        sizeof(LV2_Worker_Schedule));
-               size_t buf_size = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS;
-               _worker                     = new Worker(this, buf_size);
-               schedule->handle            = this;
+               _worker                     = new Worker(this, ring_size);
+               schedule->handle            = _worker;
                schedule->schedule_work     = work_schedule;
                _work_schedule_feature.data = schedule;
                _features[n_features++]     = &_work_schedule_feature;
        }
        lilv_node_free(worker_schedule);
 
+       if (_has_state_interface) {
+               // Create a non-threaded worker for use by state restore
+               _state_worker = new Worker(this, ring_size, false);
+       }
+
        _impl->instance = lilv_plugin_instantiate(plugin, rate, _features);
        _impl->name     = lilv_plugin_get_name(plugin);
        _impl->author   = lilv_plugin_get_author_name(plugin);
@@ -461,6 +515,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,16 +543,28 @@ 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
 
 #ifdef HAVE_LILV_0_16_0
        // Load default state
+       if (_worker) {
+               /* immediately schedule any work,
+                * so that state restore later will not find a busy
+                * worker.  latency_compute_run() flushes any replies
+                */
+               _worker->set_synchronous(true);
+       }
        LilvState* state = lilv_state_new_from_world(
                _world.world, _uri_map.urid_map(), lilv_plugin_get_uri(_impl->plugin));
        if (state && _has_state_interface) {
@@ -542,6 +613,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 +642,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 +710,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 +814,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,14 +838,19 @@ 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);
 
        delete _to_ui;
        delete _from_ui;
        delete _worker;
+       delete _state_worker;
 
        if (_atom_ev_buffers) {
                LV2_Evbuf**  b = _atom_ev_buffers;
@@ -749,6 +865,7 @@ LV2Plugin::~LV2Plugin ()
        delete [] _shadow_data;
        delete [] _defaults;
        delete [] _ev_buffers;
+       delete _impl;
 }
 
 bool
@@ -789,6 +906,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 +1058,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 = 1; h.y1 = 3; break; // Master Gain
+               case 23: h.x0 = 14; h.x1 = 15; h.y0 = 5; h.y1 = 6; break; // Master Enable
+               default:
+                       return false;
+       }
+       return true;
+}
+
 uint32_t
 LV2Plugin::nth_parameter(uint32_t n, bool& ok) const
 {
@@ -968,7 +1147,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 +1207,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 +1219,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 +1241,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 +1252,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 +1296,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(
@@ -1146,8 +1343,15 @@ LV2Plugin::load_preset(PresetRecord r)
        LilvNode*  pset  = lilv_new_uri(world, r.uri.c_str());
        LilvState* state = lilv_state_new_from_world(world, _uri_map.urid_map(), pset);
 
+       const LV2_Feature*  state_features[2]   = { NULL, NULL };
+       LV2_Worker_Schedule schedule            = { _state_worker, work_schedule };
+       const LV2_Feature   state_sched_feature = { LV2_WORKER__schedule, &schedule };
+       if (_state_worker) {
+               state_features[0] = &state_sched_feature;
+       }
+
        if (state) {
-               lilv_state_restore(state, _impl->instance, set_port_value, this, 0, NULL);
+               lilv_state_restore(state, _impl->instance, set_port_value, this, 0, state_features);
                lilv_state_free(state);
                Plugin::load_preset(r);
        }
@@ -1498,6 +1702,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);
                }
        }
@@ -1648,10 +1853,11 @@ LV2Plugin::emit_to_ui(void* controller, UIMessageSink sink)
 }
 
 int
-LV2Plugin::work(uint32_t size, const void* data)
+LV2Plugin::work(Worker& worker, uint32_t size, const void* data)
 {
+       Glib::Threads::Mutex::Lock lm(_work_mutex);
        return _impl->work_iface->work(
-               _impl->instance->lv2_handle, work_respond, this, size, data);
+               _impl->instance->lv2_handle, work_respond, &worker, size, data);
 }
 
 int
@@ -1664,20 +1870,32 @@ LV2Plugin::work_response(uint32_t size, const void* data)
 void
 LV2Plugin::set_insert_id(PBD::ID id)
 {
-       _insert_id = 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
 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;
@@ -1722,6 +1940,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) {
@@ -1742,6 +1964,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
 
@@ -1763,6 +1992,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);
@@ -1788,14 +2019,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)
 {
@@ -1826,6 +2132,12 @@ LV2Plugin::describe_parameter(Evoral::Parameter which)
        }
 }
 
+framecnt_t
+LV2Plugin::max_latency () const
+{
+       return _max_latency;
+}
+
 framecnt_t
 LV2Plugin::signal_latency() const
 {
@@ -1842,7 +2154,7 @@ LV2Plugin::automatable() const
        set<Evoral::Parameter> 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));
                }
        }
@@ -1855,6 +2167,24 @@ LV2Plugin::automatable() const
        return ret;
 }
 
+void
+LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr<AutomationControl> 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()
 {
@@ -1882,7 +2212,6 @@ LV2Plugin::cleanup()
 {
        DEBUG_TRACE(DEBUG::LV2, string_compose("%1 cleanup\n", name()));
 
-       activate();
        deactivate();
        lilv_instance_free(_impl->instance);
        _impl->instance = NULL;
@@ -1908,8 +2237,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++;
                                }
@@ -1936,7 +2264,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,
@@ -1955,6 +2283,7 @@ write_position(LV2_Atom_Forge*     forge,
                const TempoMetric&  t,
                Timecode::BBT_Time& bbt,
                double              speed,
+               double              bpm,
                framepos_t          position,
                framecnt_t          offset)
 {
@@ -1979,7 +2308,7 @@ write_position(LV2_Atom_Forge*     forge,
        lv2_atom_forge_key(forge, urids.time_beatsPerBar);
        lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
        lv2_atom_forge_key(forge, urids.time_beatsPerMinute);
-       lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
+       lv2_atom_forge_float(forge, bpm);
 #else
        lv2_atom_forge_blank(forge, &frame, 1, urids.time_Position);
        lv2_atom_forge_property_head(forge, urids.time_frame, 0);
@@ -1996,7 +2325,7 @@ write_position(LV2_Atom_Forge*     forge,
        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());
+       lv2_atom_forge_float(forge, bpm);
 #endif
 
        LV2_Evbuf_Iterator    end  = lv2_evbuf_end(buf);
@@ -2007,26 +2336,36 @@ 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;
        }
 
        if (_bpm_control_port) {
-               *_bpm_control_port = tmetric.tempo().beats_per_minute();
+               *_bpm_control_port = tmap.tempo_at_frame (start).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);
@@ -2085,15 +2424,20 @@ 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) {
-                                               // Transport has changed, write position at cycle start
-                                               tmap.bbt_time(_session.transport_frame(), bbt);
+                                       Timecode::BBT_Time bbt (tmap.bbt_at_frame (start));
+                                       double bpm = tmap.tempo_at_frame (start).beats_per_minute();
+                                       double beatpos = (bbt.bars - 1) * tmetric.meter().divisions_per_bar()
+                                                      + (bbt.beats - 1)
+                                                      + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat);
+                                       beatpos *= tmetric.meter().note_divisor() / 4.0;
+                                       if (start != _next_cycle_start ||
+                                                       speed != _next_cycle_speed ||
+                                                       rint (1000 * beatpos) != rint(1000 * _next_cycle_beat) ||
+                                                       bpm != _current_bpm) {
+                                               // Transport or Tempo has changed, write position at cycle start
                                                write_position(&_impl->forge, _ev_buffers[port_index],
-                                                              tmetric, bbt, _session.transport_speed(),
-                                                              _session.transport_frame(), 0);
+                                                               tmetric, bbt, speed, bpm, start, 0);
                                        }
                                }
 
@@ -2107,7 +2451,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)) {
@@ -2115,16 +2459,20 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                                ? *metric_i : NULL;
                                        if (m != m_end && (!metric || metric->frame() > (*m).time())) {
                                                const Evoral::MIDIEvent<framepos_t> 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();
+                                               Timecode::BBT_Time bbt;
+                                               bbt = tmap.bbt_at_pulse (metric->pulse());
+                                               double bpm = tmap.tempo_at_frame (start/*XXX*/).beats_per_minute();
                                                write_position(&_impl->forge, _ev_buffers[port_index],
-                                                              tmetric, bbt, _session.transport_speed(),
+                                                              tmetric, bbt, speed, bpm,
                                                               metric->frame(),
-                                                              metric->frame() - _session.transport_frame());
+                                                              metric->frame() - start);
                                                ++metric_i;
                                        }
                                }
@@ -2209,9 +2557,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);
@@ -2221,6 +2568,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, &parameter,
+                                                                           _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, &parameter,
+                                                                           _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, &parameter,
+                                                                           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, &parameter,
+                                                                           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));
@@ -2235,19 +2693,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));
                                                }
                                        }
                                }
@@ -2264,9 +2721,28 @@ 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;
+
+       {
+               /* keep track of lv2:timePosition like plugins can do.
+                * Note: for no-midi plugins, we only ever send information at cycle-start,
+                * so it needs to be realative to that.
+                */
+               TempoMetric t = tmap.metric_at(start);
+               _current_bpm = tmap.tempo_at_frame (start).beats_per_minute();
+               Timecode::BBT_Time bbt (tmap.bbt_at_frame (start));
+               double beatpos = (bbt.bars - 1) * t.meter().divisions_per_bar()
+                              + (bbt.beats - 1)
+                              + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat);
+               beatpos *= tmetric.meter().note_divisor() / 4.0;
+               _next_cycle_beat = beatpos + nframes * speed * _current_bpm / (60.f * _session.frame_rate());
+       }
 
+       if (_latency_control_port) {
+               framecnt_t new_latency = signal_latency ();
+               _current_latency = new_latency;
+       }
        return 0;
 }
 
@@ -2305,6 +2781,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
 {
@@ -2345,7 +2837,7 @@ LV2Plugin::get_scale_points(uint32_t port_index) const
 }
 
 void
-LV2Plugin::run(pframes_t nframes)
+LV2Plugin::run(pframes_t nframes, bool sync_work)
 {
        uint32_t const N = parameter_count();
        for (uint32_t i = 0; i < N; ++i) {
@@ -2354,10 +2846,24 @@ LV2Plugin::run(pframes_t nframes)
                }
        }
 
+       if (_worker) {
+               // Execute work synchronously if we're freewheeling (export)
+               _worker->set_synchronous(sync_work || session().engine().freewheeling());
+       }
+
+       // Run the plugin for this cycle
        lilv_instance_run(_impl->instance, nframes);
 
-       if (_impl->work_iface) {
+       // Emit any queued worker responses (calls a plugin callback)
+       if (_state_worker) {
+               _state_worker->emit_responses();
+       }
+       if (_worker) {
                _worker->emit_responses();
+       }
+
+       // Notify the plugin that a work run cycle is complete
+       if (_impl->work_iface) {
                if (_impl->work_iface->end_run) {
                        _impl->work_iface->end_run(_impl->instance->lv2_handle);
                }
@@ -2403,7 +2909,7 @@ LV2Plugin::latency_compute_run()
                port_index++;
        }
 
-       run(bufsize);
+       run(bufsize, true);
        deactivate();
        if (was_activated) {
                activate();
@@ -2446,11 +2952,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);
@@ -2476,7 +2987,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 "NoSampleAccurateCtrl"); // XXX
+#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);
@@ -2487,12 +3004,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);
@@ -2515,11 +3041,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);
@@ -2530,6 +3061,7 @@ LV2World::~LV2World()
        lilv_node_free(atom_Chunk);
        lilv_node_free(atom_AtomPort);
        lilv_world_free(world);
+       world = NULL;
 }
 
 void
@@ -2591,6 +3123,48 @@ LV2PluginInfo::load(Session& session)
        return PluginPtr();
 }
 
+std::vector<Plugin::PresetRecord>
+LV2PluginInfo::get_presets (bool /*user_only*/) const
+{
+       std::vector<Plugin::PresetRecord> 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
 {
@@ -2602,6 +3176,23 @@ LV2PluginInfo::in_category (const std::string &c) const
        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()
 {