NO-OP; whitespace
[ardour.git] / libs / ardour / lv2_plugin.cc
index d27c0457506bde3f147e99e629bdb279be16492e..7923bb66a1577d18c1a6ee179d5bc3f49ed64069 100644 (file)
@@ -45,6 +45,7 @@
 #include "ardour/audioengine.h"
 #include "ardour/debug.h"
 #include "ardour/lv2_plugin.h"
+#include "ardour/midi_patch_manager.h"
 #include "ardour/session.h"
 #include "ardour/tempo.h"
 #include "ardour/types.h"
@@ -52,7 +53,7 @@
 #include "ardour/worker.h"
 #include "ardour/search_paths.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 #include <lilv/lilv.h>
@@ -70,6 +71,7 @@
 #include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
 #include "lv2/lv2plug.in/ns/extensions/units/units.h"
 #include "lv2/lv2plug.in/ns/ext/patch/patch.h"
+#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h"
 #ifdef HAVE_LV2_1_2_0
 #include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
 #include "lv2/lv2plug.in/ns/ext/options/options.h"
@@ -109,6 +111,8 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
+bool LV2Plugin::force_state_save = false;
+
 class LV2World : boost::noncopyable {
 public:
        LV2World ();
@@ -131,14 +135,18 @@ public:
        LilvNode* ext_causesArtifacts;
        LilvNode* ext_notAutomatic;
        LilvNode* ext_rangeSteps;
+       LilvNode* groups_group;
+       LilvNode* groups_element;
        LilvNode* lv2_AudioPort;
        LilvNode* lv2_ControlPort;
        LilvNode* lv2_InputPort;
        LilvNode* lv2_OutputPort;
+       LilvNode* lv2_designation;
        LilvNode* lv2_enumeration;
        LilvNode* lv2_freewheeling;
        LilvNode* lv2_inPlaceBroken;
        LilvNode* lv2_isSideChain;
+       LilvNode* lv2_index;
        LilvNode* lv2_integer;
        LilvNode* lv2_default;
        LilvNode* lv2_minimum;
@@ -166,6 +174,7 @@ public:
        LilvNode* bufz_powerOf2BlockLength;
        LilvNode* bufz_fixedBlockLength;
        LilvNode* bufz_nominalBlockLength;
+       LilvNode* bufz_coarseBlockLength;
 #endif
 
 #ifdef HAVE_LV2_1_10_0
@@ -196,15 +205,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. */
@@ -213,15 +216,9 @@ work_respond(LV2_Worker_Respond_Handle handle,
              uint32_t                  size,
              const void*               data)
 {
-       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;
-       }
+       return (((Worker*)handle)->respond(size, data)
+               ? LV2_WORKER_SUCCESS
+               : LV2_WORKER_ERR_UNKNOWN);
 }
 
 #ifdef LV2_EXTENDED
@@ -232,6 +229,13 @@ queue_draw (LV2_Inline_Display_Handle handle)
        LV2Plugin* plugin = (LV2Plugin*)handle;
        plugin->QueueDraw(); /* EMIT SIGNAL */
 }
+
+static void
+midnam_update (LV2_Midnam_Handle handle)
+{
+       LV2Plugin* plugin = (LV2Plugin*)handle;
+       plugin->UpdateMidnam (); /* EMIT SIGNAL */
+}
 #endif
 
 /* log extension */
@@ -258,8 +262,9 @@ log_vprintf(LV2_Log_Handle /*handle*/,
                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;
 }
 
@@ -288,6 +293,7 @@ struct LV2Plugin::Impl {
 #endif
 #ifdef LV2_EXTENDED
               , queue_draw(0)
+              , midnam(0)
 #endif
        {}
 
@@ -315,6 +321,7 @@ struct LV2Plugin::Impl {
 #endif
 #ifdef LV2_EXTENDED
        LV2_Inline_Display*          queue_draw;
+       LV2_Midnam*                  midnam;
 #endif
 };
 
@@ -327,6 +334,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)
@@ -342,6 +350,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)
@@ -405,7 +414,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(12, sizeof(LV2_Feature*));
+       _features    = (LV2_Feature**)calloc(13, sizeof(LV2_Feature*));
        _features[0] = &_instance_access_feature;
        _features[1] = &_data_access_feature;
        _features[2] = &_make_path_feature;
@@ -431,6 +440,15 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        _queue_draw_feature.URI  = LV2_INLINEDISPLAY__queue_draw;
        _queue_draw_feature.data = _impl->queue_draw;
        _features[n_features++]  = &_queue_draw_feature;
+
+       _impl->midnam = (LV2_Midnam*)
+               malloc (sizeof(LV2_Midnam));
+       _impl->midnam->handle = this;
+       _impl->midnam->update = midnam_update;
+
+       _midnam_feature.URI  = LV2_MIDNAM__update;
+       _midnam_feature.data = _impl->midnam;
+       _features[n_features++]  = &_midnam_feature;
 #endif
 
 #ifdef HAVE_LV2_1_2_0
@@ -476,19 +494,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);
@@ -522,6 +545,12 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
 #ifdef LV2_EXTENDED
        _display_interface = (const LV2_Inline_Display_Interface*)
                extension_data (LV2_INLINEDISPLAY__interface);
+
+       _midname_interface = (const LV2_Midnam_Interface*)
+               extension_data (LV2_MIDNAM__interface);
+       if (_midname_interface) {
+               read_midnam ();
+       }
 #endif
 
        if (lilv_plugin_has_feature(plugin, _world.lv2_inPlaceBroken)) {
@@ -549,9 +578,15 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        lilv_nodes_free(required_features);
 #endif
 
-#ifdef LV2_EXTENDED
        LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin);
+#ifdef HAVE_LV2_1_2_0
+       if (lilv_nodes_contains (optional_features, _world.bufz_coarseBlockLength)) {
+               _no_sample_accurate_ctrl = true;
+       }
+#endif
+#ifdef LV2_EXTENDED
        if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) {
+               /* deprecated 2016-Sep-18 in favor of bufz_coarseBlockLength */
                _no_sample_accurate_ctrl = true;
        }
        if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) {
@@ -562,6 +597,13 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
 
 #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) {
@@ -828,6 +870,15 @@ LV2Plugin::~LV2Plugin ()
        deactivate();
        cleanup();
 
+#ifdef LV2_EXTENDED
+       if (has_midnam ()) {
+               std::stringstream ss;
+               ss << (void*)this;
+               ss << unique_id();
+               MIDI::Name::MidiPatchManager::instance().remove_custom_midnam (ss.str());
+       }
+#endif
+
        lilv_instance_free(_impl->instance);
        lilv_state_free(_impl->state);
        lilv_node_free(_impl->name);
@@ -837,6 +888,7 @@ LV2Plugin::~LV2Plugin ()
 #endif
 #ifdef LV2_EXTENDED
        free(_impl->queue_draw);
+       free(_impl->midnam);
 #endif
 
        free(_features);
@@ -847,6 +899,7 @@ LV2Plugin::~LV2Plugin ()
        delete _to_ui;
        delete _from_ui;
        delete _worker;
+       delete _state_worker;
 
        if (_atom_ev_buffers) {
                LV2_Evbuf**  b = _atom_ev_buffers;
@@ -917,6 +970,49 @@ LV2Plugin::render_inline_display (uint32_t w, uint32_t h) {
        }
        return NULL;
 }
+
+bool
+LV2Plugin::has_midnam () {
+       return _midname_interface ? true : false;
+}
+
+bool
+LV2Plugin::read_midnam () {
+       bool rv = false;
+       if (!_midname_interface) {
+               return rv;
+       }
+       char* midnam = _midname_interface->midnam ((void*)_impl->instance->lv2_handle);
+       if (midnam) {
+               std::stringstream ss;
+               ss << (void*)this;
+               ss << unique_id();
+               rv = MIDI::Name::MidiPatchManager::instance().update_custom_midnam (ss.str(), midnam);
+       }
+#ifndef NDEBUG
+       if (rv) {
+               info << string_compose(_("LV2: update midnam for plugin '%1'"), name ()) << endmsg;
+       } else {
+               warning << string_compose(_("LV2: Failed to parse midnam of plugin '%1'"), name ()) << endmsg;
+       }
+#endif
+       _midname_interface->free (midnam);
+       return rv;
+}
+
+std::string
+LV2Plugin::midnam_model () {
+       std::string rv;
+       if (!_midname_interface) {
+               return rv;
+       }
+       char* model = _midname_interface->model ((void*)_impl->instance->lv2_handle);
+       if (model) {
+               rv = model;
+       }
+       _midname_interface->free (model);
+       return rv;
+}
 #endif
 
 string
@@ -1063,38 +1159,36 @@ LV2Plugin::get_layout (uint32_t which, UILayoutHint& h) const
        }
        h.knob = true;
        switch (which) {
-               case  0: h.x0 = 0; h.x1 = 1; h.y0 = 4; h.y1 = 5; break; // Shelf toggle L
-               case  1: h.x0 = 0; h.x1 = 1; h.y0 = 2; h.y1 = 3; break; // Frequency L
-               case  2: h.x0 = 0; h.x1 = 1; h.y0 = 0; h.y1 = 1; break; // Gain L
-               case 19: h.x0 = 0; h.x1 = 1; h.y0 = 5; h.y1 = 6; break; // enable L
-
-               case  3: h.x0 = 1; h.x1 = 3; h.y0 = 2; h.y1 = 3; break; // Frequency 1
-               case  4: h.x0 = 1; h.x1 = 3; h.y0 = 0; h.y1 = 1; break; // Gain 1
-               case  5: h.x0 = 2; h.x1 = 4; h.y0 = 1; h.y1 = 2; break; // Bandwidth 1
-               case 20: h.x0 = 1; h.x1 = 4; h.y0 = 5; h.y1 = 6; break; // enable 1
-
-               case  6: h.x0 = 4; h.x1 = 6; h.y0 = 2; h.y1 = 3; break; // Frequency 2
-               case  7: h.x0 = 4; h.x1 = 6; h.y0 = 0; h.y1 = 1; break; // Gain 2
-               case  8: h.x0 = 5; h.x1 = 7; h.y0 = 1; h.y1 = 2; break; // Bandwidth 2
-               case 21: h.x0 = 4; h.x1 = 7; h.y0 = 5; h.y1 = 6; break; // enable 2
-
-               case  9: h.x0 = 7; h.x1 =  9; h.y0 = 2; h.y1 = 3; break; // Frequency 3
-               case 10: h.x0 = 7; h.x1 =  9; h.y0 = 0; h.y1 = 1; break; // Gain 3
-               case 11: h.x0 = 8; h.x1 = 10; h.y0 = 1; h.y1 = 2; break; // Bandwidth 3
-               case 22: h.x0 = 7; h.x1 = 10; h.y0 = 5; h.y1 = 6; break; // enable 3
-
-               case 12: h.x0 = 10; h.x1 = 12; h.y0 = 2; h.y1 = 3; break; // Frequency 4
-               case 13: h.x0 = 10; h.x1 = 12; h.y0 = 0; h.y1 = 1; break; // Gain 4
-               case 14: h.x0 = 11; h.x1 = 13; h.y0 = 1; h.y1 = 2; break; // Bandwidth 4
-               case 23: h.x0 = 10; h.x1 = 13; h.y0 = 5; h.y1 = 6; break; // enable 4
-
-               case 15: h.x0 = 13; h.x1 = 14; h.y0 = 4; h.y1 = 5; break; // Shelf toggle H
-               case 16: h.x0 = 13; h.x1 = 14; h.y0 = 2; h.y1 = 3; break; // Frequency H
-               case 17: h.x0 = 13; h.x1 = 14; h.y0 = 0; h.y1 = 1; break; // Gain H
-               case 24: h.x0 = 13; h.x1 = 14; h.y0 = 5; h.y1 = 6; break; // enable H
-
-
-               case 18: h.x0 = 14; h.x1 = 15; h.y0 = 4; h.y1 = 6; break; // Master Gain
+               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;
        }
@@ -1224,6 +1318,8 @@ LV2Plugin::add_state(XMLNode* root) const
        if (_has_state_interface) {
                // Provisionally increment state version and create directory
                const std::string new_dir = state_dir(++_state_version);
+               // and keep track of it (for templates & archive)
+               unsigned int saved_state = _state_version;;
                g_mkdir_with_parents(new_dir.c_str(), 0744);
 
                LilvState* state = lilv_state_new_from_instance(
@@ -1239,7 +1335,7 @@ LV2Plugin::add_state(XMLNode* root) const
                        0,
                        NULL);
 
-               if (!_plugin_state_dir.empty()
+               if (!_plugin_state_dir.empty() || force_state_save
                    || !_impl->state
                    || !lilv_state_equals(state, _impl->state)) {
                        lilv_state_save(_world.world,
@@ -1250,22 +1346,29 @@ LV2Plugin::add_state(XMLNode* root) const
                                        new_dir.c_str(),
                                        "state.ttl");
 
-                       if (_plugin_state_dir.empty()) {
+                       if (force_state_save) {
+                               // archive or save-as
+                               lilv_state_free(state);
+                               --_state_version;
+                       }
+                       else 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);
+                               --_state_version;
                        }
                } else {
                        // State is identical, decrement version and nuke directory
                        lilv_state_free(state);
                        PBD::remove_directory(new_dir);
                        --_state_version;
+                       saved_state = _state_version;
                }
 
-               root->add_property("state-dir", string_compose("state%1", _state_version));
+               root->add_property("state-dir", string_compose("state%1", saved_state));
        }
 }
 
@@ -1331,6 +1434,7 @@ set_port_value(const char* port_symbol,
        const uint32_t port_index = self->port_index(port_symbol);
        if (port_index != (uint32_t)-1) {
                self->set_parameter(port_index, *(const float*)value);
+               self->PresetPortSetValue (port_index, *(const float*)value); /* EMIT SIGNAL */
        }
 }
 
@@ -1341,8 +1445,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);
        }
@@ -1537,7 +1648,8 @@ LV2Plugin::write_from_ui(uint32_t       index,
                if (_atom_ev_buffers && _atom_ev_buffers[0]) {
                        bufsiz =  lv2_evbuf_get_capacity(_atom_ev_buffers[0]);
                }
-               rbs = max((size_t) bufsiz * 8, rbs);
+               int fact = ceilf(_session.frame_rate () / 3000.f);
+               rbs = max((size_t) bufsiz * std::max (8, fact), rbs);
                _from_ui = new RingBuffer<uint8_t>(rbs);
        }
 
@@ -1647,12 +1759,12 @@ LV2Plugin::set_property(uint32_t key, const Variant& value)
 
        // Serialize patch:Set message to set property
 #ifdef HAVE_LV2_1_10_0
-       lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Set);
+       lv2_atom_forge_object(forge, &frame, 0, _uri_map.urids.patch_Set);
        lv2_atom_forge_key(forge, _uri_map.urids.patch_property);
        lv2_atom_forge_urid(forge, key);
        lv2_atom_forge_key(forge, _uri_map.urids.patch_value);
 #else
-       lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Set);
+       lv2_atom_forge_blank(forge, &frame, 0, _uri_map.urids.patch_Set);
        lv2_atom_forge_property_head(forge, _uri_map.urids.patch_property, 0);
        lv2_atom_forge_urid(forge, key);
        lv2_atom_forge_property_head(forge, _uri_map.urids.patch_value, 0);
@@ -1714,14 +1826,26 @@ load_parameter_descriptor(LV2World&            world,
        if (label) {
                desc.label = lilv_node_as_string(label);
        }
-       if (def && lilv_node_is_float(def)) {
-               desc.normal = lilv_node_as_float(def);
+       if (def) {
+               if (lilv_node_is_float(def)) {
+                       desc.normal = lilv_node_as_float(def);
+               } else if (lilv_node_is_int(def)) {
+                       desc.normal = lilv_node_as_int(def);
+               }
        }
-       if (minimum && lilv_node_is_float(minimum)) {
-               desc.lower = lilv_node_as_float(minimum);
+       if (minimum) {
+               if (lilv_node_is_float(minimum)) {
+                       desc.lower = lilv_node_as_float(minimum);
+               } else if (lilv_node_is_int(minimum)) {
+                       desc.lower = lilv_node_as_int(minimum);
+               }
        }
-       if (maximum && lilv_node_is_float(maximum)) {
-               desc.upper = lilv_node_as_float(maximum);
+       if (maximum) {
+               if (lilv_node_is_float(maximum)) {
+                       desc.upper = lilv_node_as_float(maximum);
+               } else if (lilv_node_is_int(maximum)) {
+                       desc.upper = lilv_node_as_int(maximum);
+               }
        }
        load_parameter_descriptor_units(lworld, desc, units);
        desc.datatype      = datatype;
@@ -1789,9 +1913,9 @@ LV2Plugin::announce_property_values()
 
        // Serialize patch:Get message with no subject (implicitly plugin instance)
 #ifdef HAVE_LV2_1_10_0
-       lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Get);
+       lv2_atom_forge_object(forge, &frame, 0, _uri_map.urids.patch_Get);
 #else
-       lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Get);
+       lv2_atom_forge_blank(forge, &frame, 0, _uri_map.urids.patch_Get);
 #endif
 
        // Write message to UI=>Plugin ring
@@ -1844,10 +1968,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
@@ -2080,13 +2205,55 @@ LV2Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
                return Plugin::IOPortDescription ("?");
        }
 
-       LilvNode* name = lilv_port_get_name(_impl->plugin,
-                       lilv_plugin_get_port_by_index(_impl->plugin, idx));
+       const LilvPort* pport = lilv_plugin_get_port_by_index (_impl->plugin, idx);
+
+       LilvNode* name = lilv_port_get_name(_impl->plugin, pport);
        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)) {
+       /* get the port's pg:group */
+       LilvNodes* groups = lilv_port_get_value (_impl->plugin, pport, _world.groups_group);
+       if (lilv_nodes_size (groups) > 0) {
+               const LilvNode* group = lilv_nodes_get_first (groups);
+               LilvNodes* grouplabel = lilv_world_find_nodes (_world.world, group, _world.rdfs_label, NULL);
+
+               /* get the name of the port-group */
+               if (lilv_nodes_size (grouplabel) > 0) {
+                       const LilvNode* grpname = lilv_nodes_get_first (grouplabel);
+                       iod.group_name = lilv_node_as_string (grpname);
+               }
+               lilv_nodes_free (grouplabel);
+
+               /* get all port designations.
+                * we're interested in e.g. lv2:designation pg:right */
+               LilvNodes* designations = lilv_port_get_value (_impl->plugin, pport, _world.lv2_designation);
+               if (lilv_nodes_size (designations) > 0) {
+                       /* get all pg:elements of the pg:group */
+                       LilvNodes* group_childs = lilv_world_find_nodes (_world.world, group, _world.groups_element, NULL);
+                       if (lilv_nodes_size (group_childs) > 0) {
+                               /* iterate over all port designations .. */
+                               LILV_FOREACH (nodes, i, designations) {
+                                       const LilvNode* designation = lilv_nodes_get (designations, i);
+                                       /* match the lv2:designation's element against the port-group's element */
+                                       LILV_FOREACH (nodes, j, group_childs) {
+                                               const LilvNode* group_element = lilv_nodes_get (group_childs, j);
+                                               LilvNodes* elem = lilv_world_find_nodes (_world.world, group_element, _world.lv2_designation, designation);
+                                               /* found it. Now look up the index (channel-number) of the pg:Element */
+                                               if (lilv_nodes_size (elem) > 0) {
+                                                       LilvNodes* idx = lilv_world_find_nodes (_world.world, lilv_nodes_get_first (elem), _world.lv2_index, NULL);
+                                                       if (lilv_node_is_int (lilv_nodes_get_first (idx))) {
+                                                               iod.group_channel = lilv_node_as_int(lilv_nodes_get_first (idx));
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               lilv_nodes_free (groups);
+               lilv_nodes_free (designations);
+       }
+
+       if (lilv_port_has_property(_impl->plugin, pport, _world.lv2_isSideChain)) {
                iod.is_sidechain = true;
        }
        return iod;
@@ -2273,6 +2440,7 @@ write_position(LV2_Atom_Forge*     forge,
                const TempoMetric&  t,
                Timecode::BBT_Time& bbt,
                double              speed,
+               double              bpm,
                framepos_t          position,
                framecnt_t          offset)
 {
@@ -2282,7 +2450,7 @@ write_position(LV2_Atom_Forge*     forge,
        lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf));
        LV2_Atom_Forge_Frame frame;
 #ifdef HAVE_LV2_1_10_0
-       lv2_atom_forge_object(forge, &frame, 1, urids.time_Position);
+       lv2_atom_forge_object(forge, &frame, 0, urids.time_Position);
        lv2_atom_forge_key(forge, urids.time_frame);
        lv2_atom_forge_long(forge, position);
        lv2_atom_forge_key(forge, urids.time_speed);
@@ -2297,7 +2465,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);
@@ -2314,7 +2482,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);
@@ -2343,7 +2511,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
        }
 
        if (_bpm_control_port) {
-               *_bpm_control_port = tmetric.tempo().beats_per_minute();
+               *_bpm_control_port = tmap.tempo_at_frame (start).note_types_per_minute();
        }
 
 #ifdef LV2_EXTENDED
@@ -2413,14 +2581,20 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                        }
 
                        if (valid && (flags & PORT_INPUT)) {
-                               Timecode::BBT_Time bbt;
                                if ((flags & PORT_POSITION)) {
+                                       Timecode::BBT_Time bbt (tmap.bbt_at_frame (start));
+                                       double bpm = tmap.tempo_at_frame (start).note_types_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) {
-                                               // Transport has changed, write position at cycle start
-                                               bbt = tmap.bbt_at_frame (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, speed, start, 0);
+                                                               tmetric, bbt, speed, bpm, start, 0);
                                        }
                                }
 
@@ -2441,7 +2615,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                        MetricSection* metric = (metric_i != tmap.metrics_end())
                                                ? *metric_i : NULL;
                                        if (m != m_end && (!metric || metric->frame() > (*m).time())) {
-                                               const Evoral::MIDIEvent<framepos_t> ev(*m, false);
+                                               const Evoral::Event<framepos_t> ev(*m, false);
                                                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());
@@ -2449,9 +2623,11 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                                ++m;
                                        } else {
                                                tmetric.set_metric(metric);
-                                               bbt = tmap.bbt_at_pulse (metric->pulse());
+                                               Timecode::BBT_Time bbt;
+                                               bbt = tmap.bbt_at_frame (metric->frame());
+                                               double bpm = tmap.tempo_at_frame (start/*XXX*/).note_types_per_minute();
                                                write_position(&_impl->forge, _ev_buffers[port_index],
-                                                              tmetric, bbt, speed,
+                                                              tmetric, bbt, speed, bpm,
                                                               metric->frame(),
                                                               metric->frame() - start);
                                                ++metric_i;
@@ -2659,6 +2835,17 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                        }
                                }
 #endif
+                               // Intercept state dirty message
+                               if (_has_state_interface /* && (flags & PORT_DIRTYMSG)*/) {
+                                       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.state_StateChanged) {
+                                                       _session.set_dirty ();
+                                               }
+                                       }
+                               }
 
                                // Intercept patch change messages to emit PropertyChanged signal
                                if ((flags & PORT_PATCHMSG)) {
@@ -2705,11 +2892,23 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
        _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).note_types_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 ();
-               if (_current_latency != new_latency) {
-                       LatencyChanged (_current_latency, new_latency); /* EMIT SIGNAL */
-               }
                _current_latency = new_latency;
        }
        return 0;
@@ -2753,9 +2952,17 @@ LV2Plugin::parameter_is_input(uint32_t param) const
 uint32_t
 LV2Plugin::designated_bypass_port ()
 {
-#ifdef LV2_EXTENDED
        const LilvPort* port = NULL;
-       LilvNode* designation = lilv_new_uri (_world.world, LV2_PROCESSING_URI__enable);
+       LilvNode* designation = lilv_new_uri (_world.world, LV2_CORE_PREFIX "enabled");
+       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);
+       }
+#ifdef LV2_EXTENDED
+       /* deprecated on 2016-Sep-18 in favor of lv2:enabled */
+       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);
@@ -2806,7 +3013,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) {
@@ -2815,10 +3022,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);
                }
@@ -2864,7 +3085,7 @@ LV2Plugin::latency_compute_run()
                port_index++;
        }
 
-       run(bufsize);
+       run(bufsize, true);
        deactivate();
        if (was_activated) {
                activate();
@@ -2911,12 +3132,15 @@ LV2World::LV2World()
        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);
+       groups_group       = lilv_new_uri(world, LV2_PORT_GROUPS__group);
+       groups_element     = lilv_new_uri(world, LV2_PORT_GROUPS__element);
        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_index          = lilv_new_uri(world, LV2_CORE__index);
        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);
@@ -2924,6 +3148,7 @@ LV2World::LV2World()
        lv2_reportsLatency = lilv_new_uri(world, LV2_CORE__reportsLatency);
        lv2_sampleRate     = lilv_new_uri(world, LV2_CORE__sampleRate);
        lv2_toggled        = lilv_new_uri(world, LV2_CORE__toggled);
+       lv2_designation    = lilv_new_uri(world, LV2_CORE__designation);
        lv2_enumeration    = lilv_new_uri(world, LV2_CORE__enumeration);
        lv2_freewheeling   = lilv_new_uri(world, LV2_CORE__freeWheeling);
        midi_MidiEvent     = lilv_new_uri(world, LILV_URI_MIDI_EVENT);
@@ -2943,7 +3168,7 @@ LV2World::LV2World()
        patch_writable     = lilv_new_uri(world, LV2_PATCH__writable);
        patch_Message      = lilv_new_uri(world, LV2_PATCH__Message);
 #ifdef LV2_EXTENDED
-       lv2_noSampleAccurateCtrl    = lilv_new_uri(world, "http://ardour.org/lv2/ext#noSampleAccurateControls");
+       lv2_noSampleAccurateCtrl    = lilv_new_uri(world, "http://ardour.org/lv2/ext#noSampleAccurateControls"); // deprecated 2016-09-18
        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);
@@ -2953,6 +3178,7 @@ LV2World::LV2World()
        bufz_powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength);
        bufz_fixedBlockLength    = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength);
        bufz_nominalBlockLength  = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength");
+       bufz_coarseBlockLength   = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#coarseBlockLength");
 #endif
 
 }
@@ -2963,6 +3189,7 @@ LV2World::~LV2World()
                return;
        }
 #ifdef HAVE_LV2_1_2_0
+       lilv_node_free(bufz_coarseBlockLength);
        lilv_node_free(bufz_nominalBlockLength);
        lilv_node_free(bufz_fixedBlockLength);
        lilv_node_free(bufz_powerOf2BlockLength);
@@ -2990,11 +3217,13 @@ LV2World::~LV2World()
        lilv_node_free(rdfs_label);
        lilv_node_free(rdfs_range);
        lilv_node_free(midi_MidiEvent);
+       lilv_node_free(lv2_designation);
        lilv_node_free(lv2_enumeration);
        lilv_node_free(lv2_freewheeling);
        lilv_node_free(lv2_toggled);
        lilv_node_free(lv2_sampleRate);
        lilv_node_free(lv2_reportsLatency);
+       lilv_node_free(lv2_index);
        lilv_node_free(lv2_integer);
        lilv_node_free(lv2_isSideChain);
        lilv_node_free(lv2_inPlaceBroken);
@@ -3002,6 +3231,8 @@ LV2World::~LV2World()
        lilv_node_free(lv2_InputPort);
        lilv_node_free(lv2_ControlPort);
        lilv_node_free(lv2_AudioPort);
+       lilv_node_free(groups_group);
+       lilv_node_free(groups_element);
        lilv_node_free(ext_rangeSteps);
        lilv_node_free(ext_notAutomatic);
        lilv_node_free(ext_causesArtifacts);