Wouldn't it be nice if plugin presets had a description/comment?
[ardour.git] / libs / ardour / lv2_plugin.cc
index 1b6775ab499427c91e0066c397c7d7f2b296af16..21a47fb3a2418e47a8ea0974715a57b8ae300f03 100644 (file)
 #include "pbd/stl_delete.h"
 #include "pbd/compose.h"
 #include "pbd/error.h"
+#include "pbd/locale_guard.h"
+#include "pbd/pthread_utils.h"
 #include "pbd/replace_all.h"
 #include "pbd/xml++.h"
 
+#ifdef PLATFORM_WINDOWS
+#include <shlobj.h> // CSIDL_*
+#include "pbd/windows_special_dirs.h"
+#endif
+
+#ifdef WAF_BUILD
 #include "libardour-config.h"
+#endif
 
 #include "ardour/audio_buffer.h"
 #include "ardour/audioengine.h"
+#include "ardour/directory_names.h"
 #include "ardour/debug.h"
 #include "ardour/lv2_plugin.h"
 #include "ardour/midi_patch_manager.h"
@@ -189,6 +199,7 @@ public:
        LilvNode* auto_automation_control; // atom:supports
        LilvNode* auto_automation_controlled; // lv2:portProperty
        LilvNode* auto_automation_controller; // lv2:portProperty
+       LilvNode* inline_display_in_gui; // lv2:optionalFeature
 #endif
 
 private:
@@ -223,19 +234,36 @@ work_respond(LV2_Worker_Respond_Handle handle,
 
 #ifdef LV2_EXTENDED
 /* inline display extension */
-static void
-queue_draw (LV2_Inline_Display_Handle handle)
+void
+LV2Plugin::queue_draw (LV2_Inline_Display_Handle handle)
 {
        LV2Plugin* plugin = (LV2Plugin*)handle;
        plugin->QueueDraw(); /* EMIT SIGNAL */
 }
 
-static void
-midnam_update (LV2_Midnam_Handle handle)
+void
+LV2Plugin::midnam_update (LV2_Midnam_Handle handle)
 {
        LV2Plugin* plugin = (LV2Plugin*)handle;
+       plugin->_midnam_dirty = true;
        plugin->UpdateMidnam (); /* EMIT SIGNAL */
 }
+
+void
+LV2Plugin::bankpatch_notify (LV2_BankPatch_Handle handle, uint8_t chn, uint32_t bank, uint8_t pgm)
+{
+       LV2Plugin* plugin = (LV2Plugin*)handle;
+       if (chn > 15) {
+               return;
+       }
+       plugin->seen_bankpatch = true;
+       if (pgm > 127 || bank > 16383) {
+               plugin->_bankpatch[chn] = UINT32_MAX;
+       } else {
+               plugin->_bankpatch[chn] = (bank << 7) | pgm;
+       }
+       plugin->BankPatchChange (chn); /* EMIT SIGNAL */
+}
 #endif
 
 /* log extension */
@@ -322,13 +350,14 @@ struct LV2Plugin::Impl {
 #ifdef LV2_EXTENDED
        LV2_Inline_Display*          queue_draw;
        LV2_Midnam*                  midnam;
+       LV2_BankPatch*               bankpatch;
 #endif
 };
 
 LV2Plugin::LV2Plugin (AudioEngine& engine,
                       Session&     session,
                       const void*  c_plugin,
-                      framecnt_t   rate)
+                      samplecnt_t   rate)
        : Plugin (engine, session)
        , Workee ()
        , _impl(new Impl())
@@ -366,7 +395,7 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other)
 }
 
 void
-LV2Plugin::init(const void* c_plugin, framecnt_t rate)
+LV2Plugin::init(const void* c_plugin, samplecnt_t rate)
 {
        DEBUG_TRACE(DEBUG::LV2, "init\n");
 
@@ -382,13 +411,16 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        _bpm_control_port       = 0;
        _freewheel_control_port = 0;
        _latency_control_port   = 0;
-       _next_cycle_start       = std::numeric_limits<framepos_t>::max();
+       _next_cycle_start       = std::numeric_limits<samplepos_t>::max();
        _next_cycle_speed       = 1.0;
        _seq_size               = _engine.raw_buffer_size(DataType::MIDI);
        _state_version          = 0;
        _was_activated          = false;
        _has_state_interface    = false;
        _can_write_automation   = false;
+#ifdef LV2_EXTENDED
+       _inline_display_in_gui  = false;
+#endif
        _max_latency            = 0;
        _current_latency        = 0;
        _impl->block_length     = _session.get_block_size();
@@ -414,7 +446,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(13, sizeof(LV2_Feature*));
+       _features    = (LV2_Feature**)calloc(14, sizeof(LV2_Feature*));
        _features[0] = &_instance_access_feature;
        _features[1] = &_data_access_feature;
        _features[2] = &_make_path_feature;
@@ -449,12 +481,23 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        _midnam_feature.URI  = LV2_MIDNAM__update;
        _midnam_feature.data = _impl->midnam;
        _features[n_features++]  = &_midnam_feature;
+
+       _impl->bankpatch = (LV2_BankPatch*)
+               malloc (sizeof(LV2_BankPatch));
+       _impl->bankpatch->handle = this;
+       _impl->bankpatch->notify = bankpatch_notify;
+
+       _bankpatch_feature.URI  = LV2_BANKPATCH__notify;
+       _bankpatch_feature.data = _impl->bankpatch;
+       _features[n_features++]  = &_bankpatch_feature;
 #endif
 
 #ifdef HAVE_LV2_1_2_0
        LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
        static const int32_t _min_block_length = 1;   // may happen during split-cycles
        static const int32_t _max_block_length = 8192; // max possible (with all engines and during export)
+       static const int32_t rt_policy = PBD_SCHED_FIFO;
+       static const int32_t rt_priority = pbd_absolute_rt_priority (PBD_SCHED_FIFO, AudioEngine::instance()->client_real_time_priority () - 2);
        /* Consider updating max-block-size whenever the buffersize changes.
         * It requires re-instantiating the plugin (which is a non-realtime operation),
         * so it should be done lightly and only for plugins that require it.
@@ -471,6 +514,10 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
                  sizeof(int32_t), atom_Int, &_seq_size },
                { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"),
                  sizeof(int32_t), atom_Int, &_impl->block_length },
+               { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/threads/#schedPolicy"),
+                 sizeof(int32_t), atom_Int, &rt_policy },
+               { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/threads/#schedPriority"),
+                 sizeof(int32_t), atom_Int, &rt_priority },
                { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }
        };
 
@@ -482,6 +529,13 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        _features[n_features++] = &_options_feature;
 #endif
 
+#ifdef LV2_EXTENDED
+       seen_bankpatch = false;
+       for (uint32_t chn = 0; chn < 16; ++chn) {
+               _bankpatch[chn] = UINT32_MAX;
+       }
+#endif
+
        LV2_State_Make_Path* make_path = (LV2_State_Make_Path*)malloc(
                sizeof(LV2_State_Make_Path));
        make_path->handle = this;
@@ -549,6 +603,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        _midname_interface = (const LV2_Midnam_Interface*)
                extension_data (LV2_MIDNAM__interface);
        if (_midname_interface) {
+               _midnam_dirty = true;
                read_midnam ();
        }
 #endif
@@ -592,6 +647,9 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) {
                _can_write_automation = true;
        }
+       if (lilv_nodes_contains (optional_features, _world.inline_display_in_gui)) {
+               _inline_display_in_gui = true;
+       }
        lilv_nodes_free(optional_features);
 #endif
 
@@ -742,7 +800,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
                        lilv_port_get_range(plugin, port, &def, NULL, NULL);
                        _defaults[i] = def ? lilv_node_as_float(def) : 0.0f;
                        if (lilv_port_has_property (plugin, port, _world.lv2_sampleRate)) {
-                               _defaults[i] *= _session.frame_rate ();
+                               _defaults[i] *= _session.sample_rate ();
                        }
                        lilv_node_free(def);
 
@@ -855,7 +913,7 @@ LV2Plugin::requires_fixed_sized_buffers () const
         */
        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
+                * Also connect_and_run() also uses _session.transport_sample() directly
                 * (for BBT) which is not offset for plugin cycle split.
                 */
                return true;
@@ -889,6 +947,7 @@ LV2Plugin::~LV2Plugin ()
 #ifdef LV2_EXTENDED
        free(_impl->queue_draw);
        free(_impl->midnam);
+       free(_impl->bankpatch);
 #endif
 
        free(_features);
@@ -961,6 +1020,11 @@ LV2Plugin::has_inline_display () {
        return _display_interface ? true : false;
 }
 
+bool
+LV2Plugin::inline_display_in_gui () {
+       return _inline_display_in_gui;
+}
+
 Plugin::Display_Image_Surface*
 LV2Plugin::render_inline_display (uint32_t w, uint32_t h) {
        if (_display_interface) {
@@ -979,7 +1043,7 @@ LV2Plugin::has_midnam () {
 bool
 LV2Plugin::read_midnam () {
        bool rv = false;
-       if (!_midname_interface) {
+       if (!_midname_interface || !_midnam_dirty) {
                return rv;
        }
        char* midnam = _midname_interface->midnam ((void*)_impl->instance->lv2_handle);
@@ -997,6 +1061,10 @@ LV2Plugin::read_midnam () {
        }
 #endif
        _midname_interface->free (midnam);
+       if (rv) {
+               UpdatedMidnam ();
+               _midnam_dirty = false;
+       }
        return rv;
 }
 
@@ -1165,22 +1233,22 @@ LV2Plugin::get_layout (uint32_t which, UILayoutHint& h) const
 
                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  4: h.x0 = 1; h.x1 = 3; 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  7: h.x0 = 4; h.x1 = 6; 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 10: h.x0 = 7; h.x1 =  9; 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 13: h.x0 = 10; h.x1 = 12; 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
@@ -1264,7 +1332,7 @@ LV2Plugin::file_dir() const
 const std::string
 LV2Plugin::state_dir(unsigned num) const
 {
-       return Glib::build_filename(plugin_dir(), string_compose("state%1", num));
+       return Glib::build_filename(plugin_dir(), string("state") + PBD::to_string (num));
 }
 
 /** Implementation of state:makePath for files created at instantiation time.
@@ -1298,35 +1366,42 @@ LV2Plugin::add_state(XMLNode* root) const
        assert(_insert_id != PBD::ID("0"));
 
        XMLNode*    child;
-       char        buf[32];
        LocaleGuard lg;
 
        for (uint32_t i = 0; i < parameter_count(); ++i) {
                if (parameter_is_input(i) && parameter_is_control(i)) {
                        child = new XMLNode("Port");
-                       child->add_property("symbol", port_symbol(i));
-                       snprintf(buf, sizeof(buf), "%+f", _shadow_data[i]);
-                       child->add_property("value", string(buf));
+                       child->set_property("symbol", port_symbol(i));
+                       child->set_property("value", _shadow_data[i]);
                        root->add_child_nocopy(*child);
                }
        }
 
        if (!_plugin_state_dir.empty()) {
-               root->add_property("template-dir", _plugin_state_dir);
+               root->set_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);
+               // and keep track of it (for templates & archive)
+               unsigned int saved_state = _state_version;;
                g_mkdir_with_parents(new_dir.c_str(), 0744);
 
+               std::string xternal_dir = _session.externals_dir ();
+
+               if (!_plugin_state_dir.empty()) {
+                       xternal_dir = Glib::build_filename (_plugin_state_dir, externals_dir_name);
+                       g_mkdir_with_parents(xternal_dir.c_str(), 0744);
+               }
+
                LilvState* state = lilv_state_new_from_instance(
                        _impl->plugin,
                        _impl->instance,
                        _uri_map.urid_map(),
                        scratch_dir().c_str(),
                        file_dir().c_str(),
-                       _session.externals_dir().c_str(),
+                       xternal_dir.c_str(),
                        new_dir.c_str(),
                        NULL,
                        const_cast<LV2Plugin*>(this),
@@ -1356,6 +1431,7 @@ LV2Plugin::add_state(XMLNode* root) const
                        } else {
                                // template save (dedicated state-dir)
                                lilv_state_free(state);
+                               g_rmdir (xternal_dir.c_str()); // try remove unused dir
                                --_state_version;
                        }
                } else {
@@ -1363,9 +1439,10 @@ LV2Plugin::add_state(XMLNode* root) const
                        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->set_property("state-dir", string("state") + PBD::to_string (saved_state));
        }
 }
 
@@ -1385,22 +1462,30 @@ get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate)
 void
 LV2Plugin::find_presets()
 {
+       /* see also LV2PluginInfo::get_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");
+       LilvNode* rdfs_comment  = lilv_new_uri(_world.world, LILV_NS_RDFS "comment");
 
        LilvNodes* presets = lilv_plugin_get_related(_impl->plugin, 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
+               LilvNode* comment = get_value(_world.world, preset, rdfs_comment);
+               /* TODO properly identify user vs factory presets.
+                * here's an indirect condition: only factory presets can have comments
+                */
+               bool userpreset = comment ? false : true;
                if (name) {
                        _presets.insert(std::make_pair(lilv_node_as_string(preset),
                                                       Plugin::PresetRecord(
                                                               lilv_node_as_string(preset),
                                                               lilv_node_as_string(name),
-                                                              userpreset)));
+                                                              userpreset,
+                                                              comment ? lilv_node_as_string (comment) : ""
+                                                      )));
                        lilv_node_free(name);
                } else {
                        warning << string_compose(
@@ -1408,9 +1493,13 @@ LV2Plugin::find_presets()
                            lilv_node_as_string(lilv_plugin_get_uri(_impl->plugin)),
                            lilv_node_as_string(preset)) << endmsg;
                }
+               if (comment) {
+                       lilv_node_free(comment);
+               }
        }
        lilv_nodes_free(presets);
 
+       lilv_node_free(rdfs_comment);
        lilv_node_free(rdfs_label);
        lilv_node_free(pset_Preset);
        lilv_node_free(lv2_appliesTo);
@@ -1431,6 +1520,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 */
        }
 }
 
@@ -1490,9 +1580,28 @@ LV2Plugin::do_save_preset(string name)
        const string prefix    = legalize_for_uri(lilv_node_as_string(plug_name));
        const string base_name = legalize_for_uri(name);
        const string file_name = base_name + ".ttl";
+#ifdef PLATFORM_WINDOWS
+       /* http://lv2plug.in/pages/filesystem-hierarchy-standard.html */
+       std::string appdata = PBD::get_win_special_folder_path (CSIDL_APPDATA);
+       if (appdata.empty ()) {
+               // TODO consider a fallback location
+               return "";
+       }
+       const string bundle = Glib::build_filename (
+                       appdata, "LV2", prefix + "_" + base_name + ".lv2");
+#else
+       /* while macOS/OSX user-specific path is
+        *
+        *   $HOME/Library/Audio/Plug-Ins/LV2/
+        *
+        * liblilv's LV2 search path on all unices does include ~/.lv2/
+        * Ardour has been saving lv2 presets to ~/.lv2 for along time,
+        * so just keep them there.
+        */
        const string bundle    = Glib::build_filename(
                Glib::get_home_dir(),
                Glib::build_filename(".lv2", prefix + "_" + base_name + ".lv2"));
+#endif
 
 #ifdef HAVE_LILV_0_21_3
        /* delete reference to old preset (if any) */
@@ -1644,7 +1753,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.sample_rate () / 3000.f);
+               rbs = max((size_t) bufsiz * std::max (8, fact), rbs);
                _from_ui = new RingBuffer<uint8_t>(rbs);
        }
 
@@ -1747,19 +1857,19 @@ LV2Plugin::set_property(uint32_t key, const Variant& value)
 
        // Set up forge to write to temporary buffer on the stack
        LV2_Atom_Forge*      forge = &_impl->ui_forge;
-       LV2_Atom_Forge_Frame frame;
+       LV2_Atom_Forge_Frame sample;
        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_object(forge, &sample, 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, &sample, 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);
@@ -1800,7 +1910,16 @@ 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");
+                       /* override lilv's default "%f" format */
+                       if (desc.integer_step) {
+                               replace_all (desc.print_fmt, "%f", "%.0f");
+                       } else if (desc.upper - desc.lower >= 1000) {
+                               replace_all (desc.print_fmt, "%f", "%.1f");
+                       } else if (desc.upper - desc.lower >= 100) {
+                               replace_all (desc.print_fmt, "%f", "%.2f");
+                       } else {
+                               replace_all (desc.print_fmt, "%f", "%.3f");
+                       }
                        lilv_node_free(render);
                }
        }
@@ -1821,14 +1940,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;
@@ -1880,6 +2011,16 @@ LV2Plugin::load_supported_properties(PropertyDescriptors& descs)
        lilv_nodes_free(properties);
 }
 
+Variant
+LV2Plugin::get_property_value (uint32_t prop_id) const
+{
+       std::map<uint32_t, Variant>::const_iterator it;
+       if ((it = _property_values.find (prop_id)) == _property_values.end()) {
+               return Variant();
+       }
+       return it->second;
+}
+
 void
 LV2Plugin::announce_property_values()
 {
@@ -1889,16 +2030,16 @@ LV2Plugin::announce_property_values()
 
        // Set up forge to write to temporary buffer on the stack
        LV2_Atom_Forge*      forge = &_impl->ui_forge;
-       LV2_Atom_Forge_Frame frame;
+       LV2_Atom_Forge_Frame sample;
        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);
+       lv2_atom_forge_object(forge, &sample, 0, _uri_map.urids.patch_Get);
 #else
-       lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Get);
+       lv2_atom_forge_blank(forge, &sample, 0, _uri_map.urids.patch_Get);
 #endif
 
        // Write message to UI=>Plugin ring
@@ -1987,12 +2128,8 @@ int
 LV2Plugin::set_state(const XMLNode& node, int version)
 {
        XMLNodeList          nodes;
-       XMLProperty const * prop;
        XMLNodeConstIterator iter;
        XMLNode*             child;
-       const char*          sym;
-       const char*          value;
-       uint32_t             port_id;
        LocaleGuard          lg;
 
        if (node.name() != state_node_name()) {
@@ -2012,15 +2149,16 @@ LV2Plugin::set_state(const XMLNode& node, int version)
 
                child = *iter;
 
-               if ((prop = child->property("symbol")) != 0) {
-                       sym = prop->value().c_str();
-               } else {
+               std::string sym;
+               if (!child->get_property("symbol", sym)) {
                        warning << _("LV2: port has no symbol, ignored") << endmsg;
                        continue;
                }
 
                map<string, uint32_t>::iterator i = _port_indices.find(sym);
 
+               uint32_t port_id;
+
                if (i != _port_indices.end()) {
                        port_id = i->second;
                } else {
@@ -2028,31 +2166,32 @@ LV2Plugin::set_state(const XMLNode& node, int version)
                        continue;
                }
 
-               if ((prop = child->property("value")) != 0) {
-                       value = prop->value().c_str();
-               } else {
+               float val;
+               if (!child->get_property("value", val)) {
                        warning << _("LV2: port has no value, ignored") << endmsg;
                        continue;
                }
 
-               set_parameter(port_id, atof(value));
+               set_parameter(port_id, val);
        }
 
-       if ((prop = node.property("template-dir")) != 0) {
-               set_state_dir (prop->value ());
+       std::string template_dir;
+       if (node.get_property("template-dir", template_dir)) {
+               set_state_dir (template_dir);
        }
 
        _state_version = 0;
-       if ((prop = node.property("state-dir")) != 0) {
-               if (sscanf(prop->value().c_str(), "state%u", &_state_version) != 1) {
+       std::string state_dir;
+       if (node.get_property("state-dir", state_dir) != 0) {
+               if (sscanf(state_dir.c_str(), "state%u", &_state_version) != 1) {
                        error << string_compose(
                                "LV2: failed to parse state version from \"%1\"",
-                               prop->value()) << endmsg;
+                               state_dir) << endmsg;
                }
 
                std::string state_file = Glib::build_filename(
                        plugin_dir(),
-                       Glib::build_filename(prop->value(), "state.ttl"));
+                       Glib::build_filename(state_dir, "state.ttl"));
 
                LilvState* state = lilv_state_new_from_file(
                        _world.world, _uri_map.urid_map(), NULL, state_file.c_str());
@@ -2105,41 +2244,18 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c
        load_parameter_descriptor_units(_world.world, desc, portunits);
 
        if (desc.sr_dependent) {
-               desc.lower *= _session.frame_rate ();
-               desc.upper *= _session.frame_rate ();
+               desc.lower *= _session.sample_rate ();
+               desc.upper *= _session.sample_rate ();
        }
 
-       desc.min_unbound  = false; // TODO: LV2 extension required
-       desc.max_unbound  = false; // TODO: LV2 extension required
-
        desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration);
        desc.scale_points = get_scale_points(which);
 
-       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));
+               desc.rangesteps = lilv_node_as_float (steps);
        }
 
+       desc.update_steps();
 
        lilv_node_free(def);
        lilv_node_free(min);
@@ -2247,18 +2363,22 @@ LV2Plugin::describe_parameter(Evoral::Parameter which)
 {
        if (( which.type() == PluginAutomation) && ( which.id() < parameter_count()) ) {
 
-               if (lilv_port_has_property(_impl->plugin,
-                                       lilv_plugin_get_port_by_index(_impl->plugin, which.id()), _world.ext_notOnGUI)) {
+               const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which.id());
+
+               if (lilv_port_has_property(_impl->plugin, port, _world.ext_notOnGUI)) {
+                       return X_("hidden");
+               }
+
+               const LilvPort* fwport = lilv_plugin_get_port_by_designation(_impl->plugin, _world.lv2_InputPort, _world.lv2_freewheeling);
+               if (fwport && fwport == port) {
                        return X_("hidden");
                }
 
-               if (lilv_port_has_property(_impl->plugin,
-                                       lilv_plugin_get_port_by_index(_impl->plugin, which.id()), _world.lv2_freewheeling)) {
+               if (lilv_port_has_property(_impl->plugin, port, _world.lv2_freewheeling)) {
                        return X_("hidden");
                }
 
-               if (lilv_port_has_property(_impl->plugin,
-                                       lilv_plugin_get_port_by_index(_impl->plugin, which.id()), _world.lv2_reportsLatency)) {
+               if (lilv_port_has_property(_impl->plugin, port, _world.lv2_reportsLatency)) {
                        return X_("latency");
                }
 
@@ -2272,17 +2392,17 @@ LV2Plugin::describe_parameter(Evoral::Parameter which)
        }
 }
 
-framecnt_t
+samplecnt_t
 LV2Plugin::max_latency () const
 {
        return _max_latency;
 }
 
-framecnt_t
+samplecnt_t
 LV2Plugin::signal_latency() const
 {
        if (_latency_control_port) {
-               return (framecnt_t)floor(*_latency_control_port);
+               return (samplecnt_t)floor(*_latency_control_port);
        } else {
                return 0;
        }
@@ -2424,17 +2544,17 @@ write_position(LV2_Atom_Forge*     forge,
                Timecode::BBT_Time& bbt,
                double              speed,
                double              bpm,
-               framepos_t          position,
-               framecnt_t          offset)
+               samplepos_t          position,
+               samplecnt_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_Frame sample;
 #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_object(forge, &sample, 0, urids.time_Position);
+       lv2_atom_forge_key(forge, urids.time_sample);
        lv2_atom_forge_long(forge, position);
        lv2_atom_forge_key(forge, urids.time_speed);
        lv2_atom_forge_float(forge, speed);
@@ -2450,8 +2570,8 @@ write_position(LV2_Atom_Forge*     forge,
        lv2_atom_forge_key(forge, urids.time_beatsPerMinute);
        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);
+       lv2_atom_forge_blank(forge, &sample, 1, urids.time_Position);
+       lv2_atom_forge_property_head(forge, urids.time_sample, 0);
        lv2_atom_forge_long(forge, position);
        lv2_atom_forge_property_head(forge, urids.time_speed, 0);
        lv2_atom_forge_float(forge, speed);
@@ -2476,9 +2596,9 @@ write_position(LV2_Atom_Forge*     forge,
 
 int
 LV2Plugin::connect_and_run(BufferSet& bufs,
-               framepos_t start, framepos_t end, double speed,
-               ChanMapping in_map, ChanMapping out_map,
-               pframes_t nframes, framecnt_t offset)
+               samplepos_t start, samplepos_t end, double speed,
+               ChanMapping const& in_map, ChanMapping const& out_map,
+               pframes_t nframes, samplecnt_t offset)
 {
        DEBUG_TRACE(DEBUG::LV2, string_compose("%1 run %2 offset %3\n", name(), nframes, offset));
        Plugin::connect_and_run(bufs, start, end, speed, in_map, out_map, nframes, offset);
@@ -2494,7 +2614,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
        }
 
        if (_bpm_control_port) {
-               *_bpm_control_port = tmap.tempo_at_frame (start).beats_per_minute();
+               *_bpm_control_port = tmap.tempo_at_sample (start).note_types_per_minute();
        }
 
 #ifdef LV2_EXTENDED
@@ -2565,8 +2685,8 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
 
                        if (valid && (flags & PORT_INPUT)) {
                                if ((flags & PORT_POSITION)) {
-                                       Timecode::BBT_Time bbt (tmap.bbt_at_frame (start));
-                                       double bpm = tmap.tempo_at_frame (start).beats_per_minute();
+                                       Timecode::BBT_Time bbt (tmap.bbt_at_sample (start));
+                                       double bpm = tmap.tempo_at_sample (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);
@@ -2591,14 +2711,14 @@ 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 = end;
+                               const samplepos_t   tend = end;
                                ++metric_i;
                                while (m != m_end || (metric_i != tmap.metrics_end() &&
-                                                     (*metric_i)->frame() < tend)) {
+                                                     (*metric_i)->sample() < tend)) {
                                        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);
+                                       if (m != m_end && (!metric || metric->sample() > (*m).time())) {
+                                               const Evoral::Event<samplepos_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());
@@ -2607,12 +2727,12 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                        } else {
                                                tmetric.set_metric(metric);
                                                Timecode::BBT_Time bbt;
-                                               bbt = tmap.bbt_at_frame (metric->frame());
-                                               double bpm = tmap.tempo_at_frame (start/*XXX*/).beats_per_minute();
+                                               bbt = tmap.bbt_at_sample (metric->sample());
+                                               double bpm = tmap.tempo_at_sample (start/*XXX*/).note_types_per_minute();
                                                write_position(&_impl->forge, _ev_buffers[port_index],
                                                               tmetric, bbt, speed, bpm,
-                                                              metric->frame(),
-                                                              metric->frame() - start);
+                                                              metric->sample(),
+                                                              metric->sample() - start);
                                                ++metric_i;
                                        }
                                }
@@ -2704,9 +2824,9 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                        for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf);
                             lv2_evbuf_is_valid(i);
                             i = lv2_evbuf_next(i)) {
-                               uint32_t frames, subframes, type, size;
+                               uint32_t samples, subframes, type, size;
                                uint8_t* data;
-                               lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
+                               lv2_evbuf_get(i, &samples, &subframes, &type, &size, &data);
 
 #ifdef LV2_EXTENDED
                                // Intercept Automation Write Events
@@ -2728,13 +2848,13 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                                                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));
+                                                                               string_compose ("Event p: %1 t: %2 v: %3\n", p, samples, 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);
+                                                                       samplepos_t when = std::max ((samplepos_t) 0, start + samples - _current_latency);
+                                                                       assert (start + samples - _current_latency >= 0);
                                                                        if (c->guard) {
                                                                                c->guard = false;
                                                                                c->ac->list()->add (when, v, true, true);
@@ -2796,7 +2916,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                                                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->ac->start_touch (std::max ((samplepos_t)0, start - _current_latency));
                                                                        c->guard = true;
                                                                }
                                                        }
@@ -2811,7 +2931,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                                                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));
+                                                                       c->ac->stop_touch (std::max ((samplepos_t)0, start - _current_latency));
                                                                }
                                                        }
                                                }
@@ -2853,6 +2973,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                                                // Emit PropertyChanged signal for UI
                                                                // TODO: This should emit the control's Changed signal
                                                                PropertyChanged(prop_id, Variant(Variant::PATH, path));
+                                                               _property_values[prop_id] = Variant(Variant::PATH, path);
                                                        } else {
                                                                std::cerr << "warning: patch:Set for unknown property" << std::endl;
                                                        }
@@ -2881,17 +3002,17 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                 * 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));
+               _current_bpm = tmap.tempo_at_sample (start).note_types_per_minute();
+               Timecode::BBT_Time bbt (tmap.bbt_at_sample (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());
+               _next_cycle_beat = beatpos + nframes * speed * _current_bpm / (60.f * _session.sample_rate());
        }
 
        if (_latency_control_port) {
-               framecnt_t new_latency = signal_latency ();
+               samplecnt_t new_latency = signal_latency ();
                _current_latency = new_latency;
        }
        return 0;
@@ -3046,7 +3167,7 @@ LV2Plugin::latency_compute_run()
        uint32_t out_index  = 0;
 
        // this is done in the main thread. non realtime.
-       const framecnt_t bufsize = _engine.samples_per_cycle();
+       const samplecnt_t bufsize = _engine.samples_per_cycle();
        float            *buffer = (float*) malloc(_engine.samples_per_cycle() * sizeof(float));
 
        memset(buffer, 0, sizeof(float) * bufsize);
@@ -3156,6 +3277,7 @@ LV2World::LV2World()
        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);
+       inline_display_in_gui       = lilv_new_uri(world, LV2_INLINEDISPLAY__in_gui);
 #endif
 #ifdef HAVE_LV2_1_2_0
        bufz_powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength);
@@ -3281,7 +3403,7 @@ LV2PluginInfo::load(Session& session)
                if (!uri) { throw failed_constructor(); }
                const LilvPlugin* lp = lilv_plugins_get_by_uri(plugins, uri);
                if (!lp) { throw failed_constructor(); }
-               plugin.reset(new LV2Plugin(session.engine(), session, lp, session.frame_rate()));
+               plugin.reset(new LV2Plugin(session.engine(), session, lp, session.sample_rate()));
                lilv_node_free(uri);
                plugin->set_info(PluginInfoPtr(shared_from_this ()));
                return plugin;
@@ -3314,19 +3436,28 @@ LV2PluginInfo::get_presets (bool /*user_only*/) const
        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");
+       LilvNode* rdfs_comment  = lilv_new_uri(_world.world, LILV_NS_RDFS "comment");
 
        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
+               LilvNode* comment = get_value(_world.world, preset, rdfs_comment);
+               /* TODO properly identify user vs factory presets.
+                * here's an indirect condition: only factory presets can have comments
+                */
+               bool userpreset = comment ? false : true;
                if (name) {
-                       p.push_back (Plugin::PresetRecord (lilv_node_as_string(preset), lilv_node_as_string(name), userpreset));
+                       p.push_back (Plugin::PresetRecord (lilv_node_as_string(preset), lilv_node_as_string(name), userpreset, comment ? lilv_node_as_string (comment) : ""));
                        lilv_node_free(name);
                }
+               if (comment) {
+                       lilv_node_free(comment);
+               }
        }
        lilv_nodes_free(presets);
+       lilv_node_free(rdfs_comment);
        lilv_node_free(rdfs_label);
        lilv_node_free(pset_Preset);
        lilv_node_free(lv2_appliesTo);
@@ -3334,34 +3465,6 @@ LV2PluginInfo::get_presets (bool /*user_only*/) const
        return p;
 }
 
-bool
-LV2PluginInfo::in_category (const std::string &c) const
-{
-       // TODO use untranslated lilv_plugin_get_class()
-       // match gtk2_ardour/plugin_selector.cc
-       if (category == c) {
-               return true;
-       }
-       return false;
-}
-
-bool
-LV2PluginInfo::is_instrument () const
-{
-       if (category == "Instrument") {
-               return true;
-       }
-#if 1
-       /* until we make sure that category remains untranslated in the lv2.ttl spec
-        * and until most instruments also classify themselves as such, there's a 2nd check:
-        */
-       if (n_inputs.n_midi() > 0 && n_inputs.n_audio() == 0 && n_outputs.n_audio() > 0) {
-               return true;
-       }
-#endif
-       return false;
-}
-
 PluginInfoList*
 LV2PluginInfo::discover()
 {