Automation of LV2 plugin properties.
[ardour.git] / libs / ardour / lv2_plugin.cc
index 7a8054ff5c126720fc84d8748355836748eb1b64..cf33c2242492ce975e37a6492fd6bfd59ac50f83 100644 (file)
 #include <suil/suil.h>
 #endif
 
+// Compatibility for lv2-1.0.0
+#ifndef LV2_ATOM_CONTENTS_CONST
+#define LV2_ATOM_CONTENTS_CONST(type, atom) \
+       ((const void*)((const uint8_t*)(atom) + sizeof(type)))
+#endif
+#ifndef LV2_ATOM_BODY_CONST
+#define LV2_ATOM_BODY_CONST(atom) LV2_ATOM_CONTENTS_CONST(LV2_Atom, atom)
+#endif
+
 /** The number of MIDI buffers that will fit in a UI/worker comm buffer.
     This needs to be roughly the number of cycles the UI will get around to
     actually processing the traffic.  Lower values are flakier but save memory.
@@ -88,32 +97,6 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
-URIMap LV2Plugin::_uri_map;
-
-LV2Plugin::URIDs LV2Plugin::urids = {
-       _uri_map.uri_to_id(LV2_ATOM__Chunk),
-       _uri_map.uri_to_id(LV2_ATOM__Path),
-       _uri_map.uri_to_id(LV2_ATOM__Sequence),
-       _uri_map.uri_to_id(LV2_ATOM__eventTransfer),
-       _uri_map.uri_to_id(LV2_ATOM__URID),
-       _uri_map.uri_to_id(LV2_ATOM__Blank),
-       _uri_map.uri_to_id(LV2_LOG__Error),
-       _uri_map.uri_to_id(LV2_LOG__Note),
-       _uri_map.uri_to_id(LV2_LOG__Warning),
-       _uri_map.uri_to_id(LV2_MIDI__MidiEvent),
-       _uri_map.uri_to_id(LV2_TIME__Position),
-       _uri_map.uri_to_id(LV2_TIME__bar),
-       _uri_map.uri_to_id(LV2_TIME__barBeat),
-       _uri_map.uri_to_id(LV2_TIME__beatUnit),
-       _uri_map.uri_to_id(LV2_TIME__beatsPerBar),
-       _uri_map.uri_to_id(LV2_TIME__beatsPerMinute),
-       _uri_map.uri_to_id(LV2_TIME__frame),
-       _uri_map.uri_to_id(LV2_TIME__speed),
-       _uri_map.uri_to_id(LV2_PATCH__Set),
-       _uri_map.uri_to_id(LV2_PATCH__property),
-       _uri_map.uri_to_id(LV2_PATCH__value)
-};
-
 class LV2World : boost::noncopyable {
 public:
        LV2World ();
@@ -140,11 +123,16 @@ public:
        LilvNode* lv2_freewheeling;
        LilvNode* lv2_inPlaceBroken;
        LilvNode* lv2_integer;
+       LilvNode* lv2_default;
+       LilvNode* lv2_minimum;
+       LilvNode* lv2_maximum;
        LilvNode* lv2_reportsLatency;
        LilvNode* lv2_sampleRate;
        LilvNode* lv2_toggled;
        LilvNode* midi_MidiEvent;
        LilvNode* rdfs_comment;
+       LilvNode* rdfs_label;
+       LilvNode* rdfs_range;
        LilvNode* rsz_minimumSize;
        LilvNode* time_Position;
        LilvNode* ui_GtkUI;
@@ -207,11 +195,11 @@ log_vprintf(LV2_Log_Handle /*handle*/,
 {
        char* str = NULL;
        const int ret = g_vasprintf(&str, fmt, args);
-       if (type == LV2Plugin::urids.log_Error) {
+       if (type == URIMap::instance().urids.log_Error) {
                error << str << endmsg;
-       } else if (type == LV2Plugin::urids.log_Warning) {
+       } else if (type == URIMap::instance().urids.log_Warning) {
                warning << str << endmsg;
-       } else if (type == LV2Plugin::urids.log_Note) {
+       } else if (type == URIMap::instance().urids.log_Note) {
                info << str << endmsg;
        }
        // TODO: Toggleable log:Trace message support
@@ -250,6 +238,7 @@ struct LV2Plugin::Impl {
        const LV2_Worker_Interface* work_iface;
        LilvState*                  state;
        LV2_Atom_Forge              forge;
+       LV2_Atom_Forge              ui_forge;
 };
 
 LV2Plugin::LV2Plugin (AudioEngine& engine,
@@ -262,11 +251,9 @@ LV2Plugin::LV2Plugin (AudioEngine& engine,
        , _features(NULL)
        , _worker(NULL)
        , _insert_id("0")
-       , _patch_count(0)
-       , _patch_value_uri(NULL)
-       , _patch_value_key(NULL)
-       , _patch_value_cur(NULL)
-       , _patch_value_set(NULL)
+       , _patch_port_in_index((uint32_t)-1)
+       , _patch_port_out_index((uint32_t)-1)
+       , _uri_map(URIMap::instance())
 {
        init(c_plugin, rate);
 }
@@ -278,11 +265,9 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other)
        , _features(NULL)
        , _worker(NULL)
        , _insert_id(other._insert_id)
-       , _patch_count(0)
-       , _patch_value_uri(NULL)
-       , _patch_value_key(NULL)
-       , _patch_value_cur(NULL)
-       , _patch_value_set(NULL)
+       , _patch_port_in_index((uint32_t)-1)
+       , _patch_port_out_index((uint32_t)-1)
+       , _uri_map(URIMap::instance())
 {
        init(other._impl->plugin, other._sample_rate);
 
@@ -353,6 +338,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
 #endif
 
        lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map());
+       lv2_atom_forge_init(&_impl->ui_forge, _uri_map.urid_map());
 
 #ifdef HAVE_LV2_1_2_0
        LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
@@ -476,6 +462,11 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
                                }
                                if (lilv_nodes_contains(atom_supports, _world.patch_Message)) {
                                        flags |= PORT_PATCHMSG;
+                                       if (flags & PORT_INPUT) {
+                                               _patch_port_in_index = i;
+                                       } else {
+                                               _patch_port_out_index = i;
+                                       }
                                }
                        }
                        LilvNodes* min_size_v = lilv_port_get_value(_impl->plugin, port, _world.rsz_minimumSize);
@@ -553,32 +544,6 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
 
        delete[] params;
 
-       /* scan supported patch:writable for this plugin.
-        * Note: the first Atom-port (in every direction) that supports patch:Message will be used
-        */
-       LilvNode* rdfs_label  = lilv_new_uri(_world.world, LILV_NS_RDFS "label");
-       LilvNode* rdfs_range  = lilv_new_uri(_world.world, LILV_NS_RDFS "range");
-       LilvNodes* properties = lilv_world_find_nodes (_world.world, lilv_plugin_get_uri(plugin), _world.patch_writable, NULL);
-       LILV_FOREACH(nodes, p, properties) {
-               const LilvNode* property = lilv_nodes_get(properties, p);
-               LilvNode*       label    = lilv_nodes_get_first (lilv_world_find_nodes (_world.world, property, rdfs_label, NULL));
-               LilvNode*       range    = lilv_nodes_get_first (lilv_world_find_nodes (_world.world, property, rdfs_range, NULL));
-               if (!range || _uri_map.uri_to_id(lilv_node_as_uri(range)) != LV2Plugin::urids.atom_Path) {
-                       continue;
-               }
-
-               _patch_value_uri = (char**) realloc (_patch_value_uri, (_patch_count + 1) * sizeof(char**));
-               _patch_value_key = (char**) realloc (_patch_value_key, (_patch_count + 1) * sizeof(char**));
-               _patch_value_uri[_patch_count] = strdup(lilv_node_as_uri(property));
-               _patch_value_key[_patch_count] = strdup(lilv_node_as_string(label ? label : property));
-               ++_patch_count;
-       }
-       lilv_node_free(rdfs_label);
-       lilv_node_free(rdfs_range);
-       lilv_nodes_free(properties);
-       _patch_value_cur = (char(*)[PATH_MAX]) calloc(_patch_count, sizeof(char[PATH_MAX]));
-       _patch_value_set = (char(*)[PATH_MAX]) calloc(_patch_count, sizeof(char[PATH_MAX]));
-
        LilvUIs* uis = lilv_plugin_get_uis(plugin);
        if (lilv_uis_size(uis) > 0) {
 #ifdef HAVE_SUIL
@@ -625,6 +590,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
                }
        }
 
+       load_supported_properties(_property_descriptors);
        allocate_atom_event_buffers();
        latency_compute_run();
 }
@@ -644,13 +610,6 @@ LV2Plugin::~LV2Plugin ()
        free(_make_path_feature.data);
        free(_work_schedule_feature.data);
 
-       for (uint32_t pidx = 0; pidx < _patch_count; ++pidx) {
-               free(_patch_value_uri[pidx]);
-               free(_patch_value_key[pidx]);
-       }
-       free(_patch_value_cur);
-       free(_patch_value_set);
-
        delete _to_ui;
        delete _from_ui;
        delete _worker;
@@ -1040,7 +999,7 @@ set_port_value(const char* port_symbol,
                uint32_t    type)
 {
        LV2Plugin* self = (LV2Plugin*)user_data;
-       if (type != 0 && type != self->_uri_map.uri_to_id(LV2_ATOM__Float)) {
+       if (type != 0 && type != URIMap::instance().urids.atom_Float) {
                return;  // TODO: Support non-float ports
        }
 
@@ -1239,6 +1198,213 @@ LV2Plugin::write_to_ui(uint32_t       index,
        return true;
 }
 
+static void
+forge_variant(LV2_Atom_Forge* forge, const Variant& value)
+{
+       switch (value.type()) {
+       case Variant::VOID:
+               break;
+       case Variant::BOOL:
+               lv2_atom_forge_bool(forge, value.get_bool());
+               break;
+       case Variant::DOUBLE:
+               lv2_atom_forge_double(forge, value.get_double());
+               break;
+       case Variant::FLOAT:
+               lv2_atom_forge_float(forge, value.get_float());
+               break;
+       case Variant::INT:
+               lv2_atom_forge_int(forge, value.get_int());
+               break;
+       case Variant::LONG:
+               lv2_atom_forge_long(forge, value.get_long());
+               break;
+       case Variant::PATH:
+               lv2_atom_forge_path(
+                       forge, value.get_path().c_str(), value.get_path().size());
+               break;
+       case Variant::STRING:
+               lv2_atom_forge_string(
+                       forge, value.get_string().c_str(), value.get_string().size());
+               break;
+       case Variant::URI:
+               lv2_atom_forge_uri(
+                       forge, value.get_uri().c_str(), value.get_uri().size());
+               break;
+       }
+}
+
+/** Get a variant type from a URI, return false iff no match found. */
+static bool
+uri_to_variant_type(const std::string& uri, Variant::Type& type)
+{
+       if (uri == LV2_ATOM__Bool) {
+               type = Variant::BOOL;
+       } else if (uri == LV2_ATOM__Double) {
+               type = Variant::DOUBLE;
+       } else if (uri == LV2_ATOM__Float) {
+               type = Variant::FLOAT;
+       } else if (uri == LV2_ATOM__Int) {
+               type = Variant::INT;
+       } else if (uri == LV2_ATOM__Long) {
+               type = Variant::LONG;
+       } else if (uri == LV2_ATOM__Path) {
+               type = Variant::PATH;
+       } else if (uri == LV2_ATOM__String) {
+               type = Variant::STRING;
+       } else if (uri == LV2_ATOM__URI) {
+               type = Variant::URI;
+       } else {
+               return false;
+       }
+       return true;
+}
+
+void
+LV2Plugin::set_property(uint32_t key, const Variant& value)
+{
+       if (_patch_port_in_index == (uint32_t)-1) {
+               error << "LV2: set_property called with unset patch_port_in_index" << endmsg;
+               return;
+       } else if (value.type() == Variant::VOID) {
+               error << "LV2: set_property called with void value" << endmsg;
+               return;
+       }
+
+       // Set up forge to write to temporary buffer on the stack
+       LV2_Atom_Forge*      forge = &_impl->ui_forge;
+       LV2_Atom_Forge_Frame frame;
+       uint8_t              buf[PATH_MAX];  // Ought to be enough for anyone...
+
+       lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
+
+       // Serialize patch:Set message to set property
+#ifdef HAVE_LV2_1_10_0
+       lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Set);
+       lv2_atom_forge_key(forge, _uri_map.urids.patch_property);
+       lv2_atom_forge_urid(forge, key);
+       lv2_atom_forge_key(forge, _uri_map.urids.patch_value);
+#else
+       lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Set);
+       lv2_atom_forge_property_head(forge, _uri_map.urids.patch_property, 0);
+       lv2_atom_forge_urid(forge, key);
+       lv2_atom_forge_property_head(forge, _uri_map.urids.patch_value, 0);
+#endif
+
+       forge_variant(forge, value);
+
+       // Write message to UI=>Plugin ring
+       const LV2_Atom* const atom = (const LV2_Atom*)buf;
+       write_from_ui(_patch_port_in_index,
+                     _uri_map.urids.atom_eventTransfer,
+                     lv2_atom_total_size(atom),
+                     (const uint8_t*)atom);
+}
+
+const ParameterDescriptor&
+LV2Plugin::get_property_descriptor(uint32_t id) const
+{
+       PropertyDescriptors::const_iterator p = _property_descriptors.find(id);
+       if (p != _property_descriptors.end()) {
+               return p->second;
+       }
+       return Plugin::get_property_descriptor(id);
+}
+
+static void
+set_parameter_descriptor(LV2World&            world,
+                         ParameterDescriptor& desc,
+                         Variant::Type        datatype,
+                         const LilvNode*      subject)
+{
+       LilvWorld* lworld  = _world.world;
+       LilvNode*  label   = lilv_world_get(lworld, subject, _world.rdfs_label, NULL);
+       LilvNode*  def     = lilv_world_get(lworld, subject, _world.lv2_default, NULL);
+       LilvNode*  minimum = lilv_world_get(lworld, subject, _world.lv2_minimum, NULL);
+       LilvNode*  maximum = lilv_world_get(lworld, subject, _world.lv2_maximum, NULL);
+       if (label) {
+               desc.label = lilv_node_as_string(label);
+       }
+       if (def && lilv_node_is_float(def)) {
+               desc.normal = lilv_node_as_float(def);
+       }
+       if (minimum && lilv_node_is_float(minimum)) {
+               desc.lower = lilv_node_as_float(minimum);
+       }
+       if (maximum && lilv_node_is_float(maximum)) {
+               desc.upper = lilv_node_as_float(maximum);
+       }
+       desc.datatype      = datatype;
+       desc.toggled      |= datatype == Variant::BOOL;
+       desc.integer_step |= datatype == Variant::INT || datatype == Variant::LONG;
+}
+
+void
+LV2Plugin::load_supported_properties(PropertyDescriptors& descs)
+{
+       LilvWorld*       lworld     = _world.world;
+       const LilvNode*  subject    = lilv_plugin_get_uri(_impl->plugin);
+       LilvNodes*       properties = lilv_world_find_nodes(
+               lworld, subject, _world.patch_writable, NULL);
+       LILV_FOREACH(nodes, p, properties) {
+               // Get label and range
+               const LilvNode* prop  = lilv_nodes_get(properties, p);
+               LilvNode*       range = lilv_world_get(lworld, prop, _world.rdfs_range, NULL);
+               if (!range) {
+                       warning << string_compose(_("LV2: property <%1> has no range datatype, ignoring"),
+                                                 lilv_node_as_uri(prop)) << endmsg;
+                       continue;
+               }
+
+               // Convert range to variant type (TODO: support for multiple range types)
+               Variant::Type datatype;
+               if (!uri_to_variant_type(lilv_node_as_uri(range), datatype)) {
+                       error << string_compose(_("LV2: property <%1> has unsupported datatype <%1>"),
+                                               lilv_node_as_uri(prop), lilv_node_as_uri(range)) << endmsg;
+                       continue;
+               }
+
+               // Add description to result
+               ParameterDescriptor desc;
+               desc.key      = _uri_map.uri_to_id(lilv_node_as_uri(prop));
+               desc.datatype = datatype;
+               set_parameter_descriptor(_world, desc, datatype, prop);
+               descs.insert(std::make_pair(desc.key, desc));
+
+               lilv_node_free(range);
+       }
+       lilv_nodes_free(properties);
+}
+
+void
+LV2Plugin::announce_property_values()
+{
+       if (_patch_port_in_index == (uint32_t)-1) {
+               return;
+       }
+
+       // Set up forge to write to temporary buffer on the stack
+       LV2_Atom_Forge*      forge = &_impl->ui_forge;
+       LV2_Atom_Forge_Frame frame;
+       uint8_t              buf[PATH_MAX];  // Ought to be enough for anyone...
+
+       lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
+
+       // Serialize patch:Get message with no subject (implicitly plugin instance)
+#ifdef HAVE_LV2_1_10_0
+       lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Get);
+#else
+       lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Get);
+#endif
+
+       // Write message to UI=>Plugin ring
+       const LV2_Atom* const atom = (const LV2_Atom*)buf;
+       write_from_ui(_patch_port_in_index,
+                     _uri_map.urids.atom_eventTransfer,
+                     lv2_atom_total_size(atom),
+                     (const uint8_t*)atom);
+}
+
 void
 LV2Plugin::enable_ui_emission()
 {
@@ -1383,6 +1549,11 @@ int
 LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) const
 {
        const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which);
+       if (!port) {
+               error << string_compose("LV2: get descriptor of non-existent port %1", which)
+                     << endmsg;
+               return 1;
+       }
 
        LilvNodes* portunits;
        LilvNode *def, *min, *max;
@@ -1418,6 +1589,7 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c
        }
 
        desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration);
+       desc.scale_points = get_scale_points(which);
 
        lilv_node_free(def);
        lilv_node_free(min);
@@ -1478,6 +1650,11 @@ LV2Plugin::automatable() const
                }
        }
 
+       for (PropertyDescriptors::const_iterator p = _property_descriptors.begin();
+            p != _property_descriptors.end();
+            ++p) {
+               ret.insert(ret.end(), Evoral::Parameter(PluginPropertyAutomation, 0, p->first));
+       }
        return ret;
 }
 
@@ -1566,7 +1743,7 @@ LV2Plugin::allocate_atom_event_buffers()
        _atom_ev_buffers = (LV2_Evbuf**) malloc((total_atom_buffers + 1) * sizeof(LV2_Evbuf*));
        for (int i = 0; i < total_atom_buffers; ++i ) {
                _atom_ev_buffers[i] = lv2_evbuf_new(minimumSize, LV2_EVBUF_ATOM,
-                               LV2Plugin::urids.atom_Chunk, LV2Plugin::urids.atom_Sequence);
+                               _uri_map.urids.atom_Chunk, _uri_map.urids.atom_Sequence);
        }
        _atom_ev_buffers[total_atom_buffers] = 0;
        return;
@@ -1584,25 +1761,46 @@ write_position(LV2_Atom_Forge*     forge,
                framepos_t          position,
                framecnt_t          offset)
 {
+       const URIMap::URIDs& urids = URIMap::instance().urids;
+
        uint8_t pos_buf[256];
        lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf));
        LV2_Atom_Forge_Frame frame;
-       lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.time_Position);
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_frame, 0);
+#ifdef HAVE_LV2_1_10_0
+       lv2_atom_forge_object(forge, &frame, 1, urids.time_Position);
+       lv2_atom_forge_key(forge, urids.time_frame);
        lv2_atom_forge_long(forge, position);
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_speed, 0);
+       lv2_atom_forge_key(forge, urids.time_speed);
        lv2_atom_forge_float(forge, speed);
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_barBeat, 0);
+       lv2_atom_forge_key(forge, urids.time_barBeat);
        lv2_atom_forge_float(forge, bbt.beats - 1 +
                             (bbt.ticks / Timecode::BBT_Time::ticks_per_beat));
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_bar, 0);
+       lv2_atom_forge_key(forge, urids.time_bar);
        lv2_atom_forge_long(forge, bbt.bars - 1);
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatUnit, 0);
+       lv2_atom_forge_key(forge, urids.time_beatUnit);
        lv2_atom_forge_int(forge, t.meter().note_divisor());
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerBar, 0);
+       lv2_atom_forge_key(forge, urids.time_beatsPerBar);
        lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerMinute, 0);
+       lv2_atom_forge_key(forge, urids.time_beatsPerMinute);
        lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
+#else
+       lv2_atom_forge_blank(forge, &frame, 1, urids.time_Position);
+       lv2_atom_forge_property_head(forge, urids.time_frame, 0);
+       lv2_atom_forge_long(forge, position);
+       lv2_atom_forge_property_head(forge, urids.time_speed, 0);
+       lv2_atom_forge_float(forge, speed);
+       lv2_atom_forge_property_head(forge, urids.time_barBeat, 0);
+       lv2_atom_forge_float(forge, bbt.beats - 1 +
+                            (bbt.ticks / Timecode::BBT_Time::ticks_per_beat));
+       lv2_atom_forge_property_head(forge, urids.time_bar, 0);
+       lv2_atom_forge_long(forge, bbt.bars - 1);
+       lv2_atom_forge_property_head(forge, urids.time_beatUnit, 0);
+       lv2_atom_forge_int(forge, t.meter().note_divisor());
+       lv2_atom_forge_property_head(forge, urids.time_beatsPerBar, 0);
+       lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
+       lv2_atom_forge_property_head(forge, urids.time_beatsPerMinute, 0);
+       lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
+#endif
 
        LV2_Evbuf_Iterator    end  = lv2_evbuf_end(buf);
        const LV2_Atom* const atom = (const LV2_Atom*)pos_buf;
@@ -1610,48 +1808,6 @@ write_position(LV2_Atom_Forge*     forge,
                               (const uint8_t*)(atom + 1));
 }
 
-static bool
-write_patch_change(
-               LV2_Atom_Forge* forge,
-               LV2_Evbuf*      buf,
-               const char*     uri,
-               const char*     filename
-               )
-{
-       LV2_Atom_Forge_Frame frame;
-       uint8_t patch_buf[PATH_MAX];
-       lv2_atom_forge_set_buffer(forge, patch_buf, sizeof(patch_buf));
-
-#if 0 // new LV2
-       lv2_atom_forge_object(forge, &frame, 0, LV2Plugin::urids.patch_Set);
-       lv2_atom_forge_key(forge, LV2Plugin::urids.patch_property);
-       lv2_atom_forge_urid(forge, uri_map.uri_to_id(uri));
-       lv2_atom_forge_key(forge, LV2Plugin::urids.patch_value);
-       lv2_atom_forge_path(forge, filename, strlen(filename));
-#else
-       lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.patch_Set);
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.patch_property, 0);
-       lv2_atom_forge_urid(forge, LV2Plugin::_uri_map.uri_to_id(uri));
-       lv2_atom_forge_property_head(forge, LV2Plugin::urids.patch_value, 0);
-       lv2_atom_forge_path(forge, filename, strlen(filename));
-#endif
-
-       LV2_Evbuf_Iterator end  = lv2_evbuf_end(buf);
-       const LV2_Atom* const atom = (const LV2_Atom*)patch_buf;
-       return lv2_evbuf_write(&end, 0, 0, atom->type, atom->size,
-                              (const uint8_t*)(atom + 1));
-}
-
-bool
-LV2Plugin::patch_set (const uint32_t p, const char * val) {
-       if (p >= _patch_count) return false;
-       _patch_set_lock.lock();
-       strncpy(_patch_value_set[p], val, PATH_MAX);
-       _patch_value_set[p][PATH_MAX - 1] = 0;
-       _patch_set_lock.unlock();
-       return true;
-}
-
 int
 LV2Plugin::connect_and_run(BufferSet& bufs,
        ChanMapping in_map, ChanMapping out_map,
@@ -1753,7 +1909,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                        : m;
 
                                // Now merge MIDI and any transport events into the buffer
-                               const uint32_t     type = LV2Plugin::urids.midi_MidiEvent;
+                               const uint32_t     type = _uri_map.urids.midi_MidiEvent;
                                const framepos_t   tend = _session.transport_frame() + nframes;
                                ++metric_i;
                                while (m != m_end || (metric_i != tmap.metrics_end() &&
@@ -1784,23 +1940,6 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                        (flags & PORT_INPUT), 0, (flags & PORT_EVENT));
                        }
 
-                       /* queue patch messages */
-                       if (flags & PORT_PATCHMSG) {
-                               if (_patch_set_lock.trylock()) {
-                                       for (uint32_t pidx = 0; pidx < _patch_count; ++ pidx) {
-                                               if (strlen(_patch_value_set[pidx]) > 0
-                                                               && 0 != strcmp(_patch_value_cur[pidx], _patch_value_set[pidx])
-                                                        )
-                                               {
-                                                       write_patch_change(&_impl->forge, _ev_buffers[port_index],
-                                                                       _patch_value_uri[pidx], _patch_value_set[pidx]);
-                                                       strncpy(_patch_value_cur[pidx], _patch_value_set[pidx], PATH_MAX);
-                                               }
-                                       }
-                                       _patch_set_lock.unlock();
-                               }
-                       }
-
                        buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]);
                } else {
                        continue;  // Control port, leave buffer alone
@@ -1822,7 +1961,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                error << "Error reading from UI=>Plugin RingBuffer" << endmsg;
                                break;
                        }
-                       if (msg.protocol == urids.atom_eventTransfer) {
+                       if (msg.protocol == URIMap::instance().urids.atom_eventTransfer) {
                                LV2_Evbuf*            buf  = _ev_buffers[msg.index];
                                LV2_Evbuf_Iterator    i    = lv2_evbuf_end(buf);
                                const LV2_Atom* const atom = (const LV2_Atom*)&body[0];
@@ -1875,7 +2014,8 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
 
 
                // Write messages to UI
-               if ((_to_ui || _patch_count > 0) && (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
+               if ((_to_ui || _patch_port_out_index != (uint32_t)-1) &&
+                   (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
                        LV2_Evbuf* buf = _ev_buffers[port_index];
                        for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf);
                             lv2_evbuf_is_valid(i);
@@ -1884,48 +2024,39 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                uint8_t* data;
                                lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
 
-                               // intercept patch change messages
-                               /* TODO this should eventually be done in the UI-thread
-                                * using a ringbuffer and no locks.
-                                * The current UI ringbuffer is unsuitable. It only exists
-                                * if a UI enabled and misses initial patch-set or changes.
-                                */
-                               if (_patch_count > 0 && (flags & PORT_OUTPUT) && (flags & PORT_PATCHMSG)) {
-                                       // TODO reduce if() nesting below:
+                               // Intercept patch change messages to emit PropertyChanged signal
+                               if ((flags & PORT_PATCHMSG)) {
                                        LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
-                                       if (atom->type == LV2Plugin::urids.atom_Blank) {
+                                       if (atom->type == _uri_map.urids.atom_Blank ||
+                                           atom->type == _uri_map.urids.atom_Object) {
                                                LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
-                                               if (obj->body.otype == LV2Plugin::urids.patch_Set) {
+                                               if (obj->body.otype == _uri_map.urids.patch_Set) {
                                                        const LV2_Atom* property = NULL;
-                                                       lv2_atom_object_get (obj, LV2Plugin::urids.patch_property, &property, 0);
-                                                       if (property->type == LV2Plugin::urids.atom_URID) {
-                                                               for (uint32_t pidx = 0; pidx < _patch_count; ++ pidx) {
-                                                                if (((const LV2_Atom_URID*)property)->body != _uri_map.uri_to_id(_patch_value_uri[pidx])) {
-                                                                        continue;
-                                                                }
-                                                                /* Get value. */
-                                                                const LV2_Atom* file_path = NULL;
-                                                                lv2_atom_object_get(obj, LV2Plugin::urids.patch_value, &file_path, 0);
-                                                                if (!file_path || file_path->type != LV2Plugin::urids.atom_Path) {
-                                                                        continue;
-                                                                }
-                                                                // LV2_ATOM_BODY() casts away qualifiers, so do it explicitly:
-                                                                const char* uri = (const char*)((uint8_t const*)(file_path) + sizeof(LV2_Atom));
-                                                                _patch_set_lock.lock();
-                                                                strncpy(_patch_value_cur[pidx], uri, PATH_MAX);
-                                                                strncpy(_patch_value_set[pidx], uri, PATH_MAX);
-                                                                _patch_value_cur[pidx][PATH_MAX - 1] = 0;
-                                                                _patch_value_set[pidx][PATH_MAX - 1] = 0;
-                                                                _patch_set_lock.unlock();
-                                                                PatchChanged(pidx); // emit signal
-                                                               }
+                                                       const LV2_Atom* value    = NULL;
+                                                       lv2_atom_object_get(obj,
+                                                                           _uri_map.urids.patch_property, &property,
+                                                                           _uri_map.urids.patch_value,    &value,
+                                                                           0);
+
+                                                       if (!property || !value ||
+                                                           property->type != _uri_map.urids.atom_URID ||
+                                                           value->type    != _uri_map.urids.atom_Path) {
+                                                               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));
                                                }
                                        }
                                }
 
                                if (!_to_ui) continue;
-                               write_to_ui(port_index, urids.atom_eventTransfer,
+                               write_to_ui(port_index, URIMap::instance().urids.atom_eventTransfer,
                                            size + sizeof(LV2_Atom),
                                            data - sizeof(LV2_Atom));
                        }
@@ -1989,18 +2120,18 @@ LV2Plugin::print_parameter(uint32_t param, char* buf, uint32_t len) const
        }
 }
 
-boost::shared_ptr<Plugin::ScalePoints>
+boost::shared_ptr<ScalePoints>
 LV2Plugin::get_scale_points(uint32_t port_index) const
 {
        const LilvPort*  port   = lilv_plugin_get_port_by_index(_impl->plugin, port_index);
        LilvScalePoints* points = lilv_port_get_scale_points(_impl->plugin, port);
 
-       boost::shared_ptr<Plugin::ScalePoints> ret;
+       boost::shared_ptr<ScalePoints> ret;
        if (!points) {
                return ret;
        }
 
-       ret = boost::shared_ptr<Plugin::ScalePoints>(new ScalePoints());
+       ret = boost::shared_ptr<ScalePoints>(new ScalePoints());
 
        LILV_FOREACH(scale_points, i, points) {
                const LilvScalePoint* p     = lilv_scale_points_get(points, i);
@@ -2126,6 +2257,9 @@ LV2World::LV2World()
        lv2_OutputPort     = lilv_new_uri(world, LILV_URI_OUTPUT_PORT);
        lv2_inPlaceBroken  = lilv_new_uri(world, LV2_CORE__inPlaceBroken);
        lv2_integer        = lilv_new_uri(world, LV2_CORE__integer);
+       lv2_default        = lilv_new_uri(world, LV2_CORE__default);
+       lv2_minimum        = lilv_new_uri(world, LV2_CORE__minimum);
+       lv2_maximum        = lilv_new_uri(world, LV2_CORE__maximum);
        lv2_reportsLatency = lilv_new_uri(world, LV2_CORE__reportsLatency);
        lv2_sampleRate     = lilv_new_uri(world, LV2_CORE__sampleRate);
        lv2_toggled        = lilv_new_uri(world, LV2_CORE__toggled);
@@ -2133,6 +2267,8 @@ LV2World::LV2World()
        lv2_freewheeling   = lilv_new_uri(world, LV2_CORE__freeWheeling);
        midi_MidiEvent     = lilv_new_uri(world, LILV_URI_MIDI_EVENT);
        rdfs_comment       = lilv_new_uri(world, LILV_NS_RDFS "comment");
+       rdfs_label         = lilv_new_uri(world, LILV_NS_RDFS "label");
+       rdfs_range         = lilv_new_uri(world, LILV_NS_RDFS "range");
        rsz_minimumSize    = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize);
        time_Position      = lilv_new_uri(world, LV2_TIME__Position);
        ui_GtkUI           = lilv_new_uri(world, LV2_UI__GtkUI);
@@ -2156,6 +2292,8 @@ LV2World::~LV2World()
        lilv_node_free(time_Position);
        lilv_node_free(rsz_minimumSize);
        lilv_node_free(rdfs_comment);
+       lilv_node_free(rdfs_label);
+       lilv_node_free(rdfs_range);
        lilv_node_free(midi_MidiEvent);
        lilv_node_free(lv2_enumeration);
        lilv_node_free(lv2_freewheeling);