prototype online self-automating LV2 plugin interface
authorRobin Gareus <robin@gareus.org>
Sun, 13 Mar 2016 22:20:45 +0000 (23:20 +0100)
committerRobin Gareus <robin@gareus.org>
Sun, 13 Mar 2016 23:17:14 +0000 (00:17 +0100)
goes along with https://github.com/x42/automate.lv2

libs/ardour/ardour/lv2_plugin.h
libs/ardour/ardour/plugin.h
libs/ardour/ardour/uri_map.h
libs/ardour/lv2_plugin.cc
libs/ardour/plugin_insert.cc
libs/ardour/uri_map.cc

index a4cdfcd036f2bf69354953baeebf46903ad7cbd8..aacef82e5b6b6ac4488f4fc457a36347f075a85e 100644 (file)
 #include "ardour/worker.h"
 #include "pbd/ringbuffer.h"
 
 #include "ardour/worker.h"
 #include "pbd/ringbuffer.h"
 
+#ifdef LV2_EXTENDED // -> needs to eventually go upstream to lv2plug.in
+#include "ardour/lv2_extensions.h"
+#endif
+
 #ifndef PATH_MAX
 #define PATH_MAX 1024
 #endif
 #ifndef PATH_MAX
 #define PATH_MAX 1024
 #endif
@@ -91,6 +95,7 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
        const LV2_Feature* const* features () { return _features; }
 
        std::set<Evoral::Parameter> automatable () const;
        const LV2_Feature* const* features () { return _features; }
 
        std::set<Evoral::Parameter> automatable () const;
+       virtual void set_automation_control (uint32_t, boost::shared_ptr<AutomationControl>);
 
        void activate ();
        void deactivate ();
 
        void activate ();
        void deactivate ();
@@ -182,6 +187,7 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
        uint32_t      _patch_port_out_index;
        URIMap&       _uri_map;
        bool          _no_sample_accurate_ctrl;
        uint32_t      _patch_port_out_index;
        URIMap&       _uri_map;
        bool          _no_sample_accurate_ctrl;
+       bool          _can_write_automation;
 
        friend const void* lv2plugin_get_port_value(const char* port_symbol,
                                                    void*       user_data,
 
        friend const void* lv2plugin_get_port_value(const char* port_symbol,
                                                    void*       user_data,
@@ -197,7 +203,9 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
                PORT_SEQUENCE = 1 << 5,  ///< New atom API event port
                PORT_MIDI     = 1 << 6,  ///< Event port understands MIDI
                PORT_POSITION = 1 << 7,  ///< Event port understands position
                PORT_SEQUENCE = 1 << 5,  ///< New atom API event port
                PORT_MIDI     = 1 << 6,  ///< Event port understands MIDI
                PORT_POSITION = 1 << 7,  ///< Event port understands position
-               PORT_PATCHMSG = 1 << 8   ///< Event port supports patch:Message
+               PORT_PATCHMSG = 1 << 8,  ///< Event port supports patch:Message
+               PORT_AUTOCTRL = 1 << 9,  ///< Event port supports auto:AutomationControl
+               PORT_CTRLED   = 1 << 10  ///< Port prop auto:AutomationControlled (can be self controlled)
        } PortFlag;
 
        typedef unsigned PortFlags;
        } PortFlag;
 
        typedef unsigned PortFlags;
@@ -208,6 +216,25 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
 
        PropertyDescriptors _property_descriptors;
 
 
        PropertyDescriptors _property_descriptors;
 
+       struct AutomationCtrl {
+               AutomationCtrl (const AutomationCtrl &other)
+                       : ac (other.ac)
+                       , guard (other.guard)
+               { }
+
+               AutomationCtrl (boost::shared_ptr<ARDOUR::AutomationControl> c)
+                       : ac (c)
+                       , guard (false)
+               { }
+               boost::shared_ptr<ARDOUR::AutomationControl> ac;
+               bool guard;
+       };
+
+       typedef boost::shared_ptr<AutomationCtrl> AutomationCtrlPtr;
+       typedef std::map<uint32_t, AutomationCtrlPtr> AutomationCtrlMap;
+       AutomationCtrlMap _ctrl_map;
+       AutomationCtrlPtr get_automation_control (uint32_t);
+
        /// Message send to/from UI via ports
        struct UIMessage {
                uint32_t index;
        /// Message send to/from UI via ports
        struct UIMessage {
                uint32_t index;
index 0ce5522c7c7eb5f6c910f41a2391b91589342b25..0bc766c4622f447d76bf933ea4185aa9313faf17 100644 (file)
@@ -49,6 +49,7 @@ class BufferSet;
 class PluginInsert;
 class Plugin;
 class PluginInfo;
 class PluginInsert;
 class Plugin;
 class PluginInfo;
+class AutomationControl;
 
 typedef boost::shared_ptr<Plugin> PluginPtr;
 typedef boost::shared_ptr<PluginInfo> PluginInfoPtr;
 
 typedef boost::shared_ptr<Plugin> PluginPtr;
 typedef boost::shared_ptr<PluginInfo> PluginInfoPtr;
@@ -100,6 +101,8 @@ class LIBARDOUR_API Plugin : public PBD::StatefulDestructible, public Latent
        virtual bool parameter_is_input(uint32_t) const = 0;
        virtual bool parameter_is_output(uint32_t) const = 0;
 
        virtual bool parameter_is_input(uint32_t) const = 0;
        virtual bool parameter_is_output(uint32_t) const = 0;
 
+       virtual void set_automation_control (uint32_t /*port_index*/, boost::shared_ptr<ARDOUR::AutomationControl>) { }
+
        virtual boost::shared_ptr<ScalePoints> get_scale_points(uint32_t /*port_index*/) const {
                return boost::shared_ptr<ScalePoints>();
        }
        virtual boost::shared_ptr<ScalePoints> get_scale_points(uint32_t /*port_index*/) const {
                return boost::shared_ptr<ScalePoints>();
        }
index d745ad58e775434a9cec2b661cf5a9286c3d4a58..523eb18e917e1dd6bdf5b2fcb7bf3d969ffe81f6 100644 (file)
@@ -83,6 +83,15 @@ public:
                uint32_t patch_Set;
                uint32_t patch_property;
                uint32_t patch_value;
                uint32_t patch_Set;
                uint32_t patch_property;
                uint32_t patch_value;
+#ifdef LV2_EXTENDED
+               uint32_t auto_event;
+               uint32_t auto_setup;
+               uint32_t auto_finalize;
+               uint32_t auto_start;
+               uint32_t auto_end;
+               uint32_t auto_parameter;
+               uint32_t auto_value;
+#endif
        };
 
        URIDs urids;
        };
 
        URIDs urids;
index 8dde93f3b3b1d2dcc84ff929ae4643bf19ecc6b5..4af6748c41ded1195ca0dfd6ad2c6c974faf701f 100644 (file)
@@ -155,13 +155,25 @@ public:
        LilvNode* units_midiNote;
        LilvNode* patch_writable;
        LilvNode* patch_Message;
        LilvNode* units_midiNote;
        LilvNode* patch_writable;
        LilvNode* patch_Message;
-       LilvNode* lv2_noSampleAccurateCtrl;
 #ifdef HAVE_LV2_1_2_0
        LilvNode* bufz_powerOf2BlockLength;
        LilvNode* bufz_fixedBlockLength;
        LilvNode* bufz_nominalBlockLength;
 #endif
 
 #ifdef HAVE_LV2_1_2_0
        LilvNode* bufz_powerOf2BlockLength;
        LilvNode* bufz_fixedBlockLength;
        LilvNode* bufz_nominalBlockLength;
 #endif
 
+#ifdef HAVE_LV2_1_10_0
+       LilvNode* atom_int;
+       LilvNode* atom_float;
+       LilvNode* atom_object; // new in 1.8
+       LilvNode* atom_vector;
+#endif
+#ifdef LV2_EXTENDED
+       LilvNode* lv2_noSampleAccurateCtrl;
+       LilvNode* auto_can_write_automatation; // lv2:optionalFeature
+       LilvNode* auto_automation_control; // atom:supports
+       LilvNode* auto_automation_controlled; // lv2:portProperty
+#endif
+
 private:
        bool _bundle_checked;
 };
 private:
        bool _bundle_checked;
 };
@@ -335,6 +347,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
        _state_version          = 0;
        _was_activated          = false;
        _has_state_interface    = false;
        _state_version          = 0;
        _was_activated          = false;
        _has_state_interface    = false;
+       _can_write_automation   = false;
        _impl->block_length     = _session.get_block_size();
 
        _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
        _impl->block_length     = _session.get_block_size();
 
        _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
@@ -484,11 +497,16 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
                throw failed_constructor();
        }
        lilv_nodes_free(required_features);
                throw failed_constructor();
        }
        lilv_nodes_free(required_features);
+#endif
 
 
+#ifdef LV2_EXTENDED
        LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin);
        if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) {
                _no_sample_accurate_ctrl = true;
        }
        LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin);
        if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) {
                _no_sample_accurate_ctrl = true;
        }
+       if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) {
+               _can_write_automation = true;
+       }
        lilv_nodes_free(optional_features);
 #endif
 
        lilv_nodes_free(optional_features);
 #endif
 
@@ -542,6 +560,11 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
                                if (lilv_nodes_contains(atom_supports, _world.time_Position)) {
                                        flags |= PORT_POSITION;
                                }
                                if (lilv_nodes_contains(atom_supports, _world.time_Position)) {
                                        flags |= PORT_POSITION;
                                }
+#ifdef LV2_EXTENDED
+                               if (lilv_nodes_contains(atom_supports, _world.auto_automation_control)) {
+                                       flags |= PORT_AUTOCTRL;
+                               }
+#endif
                                if (lilv_nodes_contains(atom_supports, _world.patch_Message)) {
                                        flags |= PORT_PATCHMSG;
                                        if (flags & PORT_INPUT) {
                                if (lilv_nodes_contains(atom_supports, _world.patch_Message)) {
                                        flags |= PORT_PATCHMSG;
                                        if (flags & PORT_INPUT) {
@@ -566,6 +589,14 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
                        throw failed_constructor();
                }
 
                        throw failed_constructor();
                }
 
+#ifdef LV2_EXTENDED
+               if (lilv_port_has_property(_impl->plugin, port, _world.auto_automation_controlled)) {
+                       if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) {
+                               flags |= PORT_CTRLED;
+                       }
+               }
+#endif
+
                _port_flags.push_back(flags);
                _port_minimumSize.push_back(minimumSize);
        }
                _port_flags.push_back(flags);
                _port_minimumSize.push_back(minimumSize);
        }
@@ -1903,6 +1934,23 @@ LV2Plugin::automatable() const
        return ret;
 }
 
        return ret;
 }
 
+void
+LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr<AutomationControl> c)
+{
+       if ((_port_flags[i] & PORT_CTRLED)) {
+               _ctrl_map [i] = AutomationCtrlPtr (new AutomationCtrl(c));
+       }
+}
+
+LV2Plugin::AutomationCtrlPtr
+LV2Plugin::get_automation_control (uint32_t i)
+{
+       if (_ctrl_map.find (i) == _ctrl_map.end()) {
+               return AutomationCtrlPtr ();
+       }
+       return _ctrl_map[i];
+}
+
 void
 LV2Plugin::activate()
 {
 void
 LV2Plugin::activate()
 {
@@ -2074,6 +2122,15 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                *_bpm_control_port = tmetric.tempo().beats_per_minute();
        }
 
                *_bpm_control_port = tmetric.tempo().beats_per_minute();
        }
 
+#ifdef LV2_EXTENDED
+       if (_can_write_automation && _session.transport_frame() != _next_cycle_start) {
+               // add guard-points after locating
+               for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
+                       i->second->guard = true;
+               }
+       }
+#endif
+
        ChanCount bufs_count;
        bufs_count.set(DataType::AUDIO, 1);
        bufs_count.set(DataType::MIDI, 1);
        ChanCount bufs_count;
        bufs_count.set(DataType::AUDIO, 1);
        bufs_count.set(DataType::MIDI, 1);
@@ -2258,9 +2315,8 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                        }
                }
 
                        }
                }
 
-
                // Write messages to UI
                // Write messages to UI
-               if ((_to_ui || _patch_port_out_index != (uint32_t)-1) &&
+               if ((_to_ui || _can_write_automation || _patch_port_out_index != (uint32_t)-1) &&
                    (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
                        LV2_Evbuf* buf = _ev_buffers[port_index];
                        for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf);
                    (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
                        LV2_Evbuf* buf = _ev_buffers[port_index];
                        for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf);
@@ -2270,6 +2326,78 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
                                uint8_t* data;
                                lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
 
                                uint8_t* data;
                                lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
 
+#ifdef LV2_EXTENDED
+                               // Intercept Automation Write Events
+                               if ((flags & PORT_AUTOCTRL)) {
+                                       LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
+                                       if (atom->type == _uri_map.urids.atom_Blank ||
+                                                       atom->type == _uri_map.urids.atom_Object) {
+                                               LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
+                                               if (obj->body.otype == _uri_map.urids.auto_event) {
+                                                       // only if transport_rolling ??
+                                                       const LV2_Atom* parameter = NULL;
+                                                       const LV2_Atom* value    = NULL;
+                                                       lv2_atom_object_get(obj,
+                                                                           _uri_map.urids.auto_parameter, &parameter,
+                                                                           _uri_map.urids.auto_value,     &value,
+                                                                           0);
+                                                       if (parameter && value) {
+                                                               const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
+                                                               const float v = ((const LV2_Atom_Float*)value)->body;
+                                                               // -> add automation event..
+                                                               AutomationCtrlPtr c = get_automation_control (p);
+                                                               if (c && c->ac->automation_state() == Touch) {
+                                                                       if (c->guard) {
+                                                                               c->guard = false;
+                                                                               c->ac->list()->add (_session.transport_frame() + frames, v, true, true);
+                                                                       } else {
+                                                                               c->ac->set_double (v, _session.transport_frame() + frames, true);
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                               else if (obj->body.otype == _uri_map.urids.auto_setup) {
+                                                       // TODO optional arguments, for now we assume the plugin
+                                                       // writes automation for its own inputs
+                                                       // -> put them in "touch" mode (preferably "exclusive plugin touch(TM)"
+                                                       for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
+                                                               i->second->ac->set_automation_state (Touch);
+                                                       }
+                                               }
+                                               else if (obj->body.otype == _uri_map.urids.auto_finalize) {
+                                                       // set [touched] parameters to "play" ??
+                                               }
+                                               else if (obj->body.otype == _uri_map.urids.auto_start) {
+                                                       const LV2_Atom* parameter = NULL;
+                                                       lv2_atom_object_get(obj,
+                                                                           _uri_map.urids.auto_parameter, &parameter,
+                                                                           0);
+                                                       if (parameter) {
+                                                               const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
+                                                               AutomationCtrlPtr c = get_automation_control (p);
+                                                               if (c) {
+                                                                       c->ac->start_touch (_session.transport_frame());
+                                                                       c->guard = true;
+                                                               }
+                                                       }
+                                               }
+                                               else if (obj->body.otype == _uri_map.urids.auto_end) {
+                                                       const LV2_Atom* parameter = NULL;
+                                                       lv2_atom_object_get(obj,
+                                                                           _uri_map.urids.auto_parameter, &parameter,
+                                                                           0);
+                                                       if (parameter) {
+                                                               const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
+                                                               AutomationCtrlPtr c = get_automation_control (p);
+                                                               if (c) {
+                                                                       c->ac->stop_touch (true, _session.transport_frame());
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+#endif
+
                                // Intercept patch change messages to emit PropertyChanged signal
                                if ((flags & PORT_PATCHMSG)) {
                                        LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
                                // Intercept patch change messages to emit PropertyChanged signal
                                if ((flags & PORT_PATCHMSG)) {
                                        LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
@@ -2525,7 +2653,12 @@ LV2World::LV2World()
        units_db           = lilv_new_uri(world, LV2_UNITS__db);
        patch_writable     = lilv_new_uri(world, LV2_PATCH__writable);
        patch_Message      = lilv_new_uri(world, LV2_PATCH__Message);
        units_db           = lilv_new_uri(world, LV2_UNITS__db);
        patch_writable     = lilv_new_uri(world, LV2_PATCH__writable);
        patch_Message      = lilv_new_uri(world, LV2_PATCH__Message);
-       lv2_noSampleAccurateCtrl = lilv_new_uri(world, LV2_CORE_PREFIX "noSampleAccurateControls");
+#ifdef LV2_EXTENDED
+       lv2_noSampleAccurateCtrl    = lilv_new_uri(world, LV2_CORE_PREFIX "noSampleAccurateControls");
+       auto_can_write_automatation = lilv_new_uri(world, LV2_AUTOMATE_URI__can_write);
+       auto_automation_control     = lilv_new_uri(world, LV2_AUTOMATE_URI__control);
+       auto_automation_controlled  = lilv_new_uri(world, LV2_AUTOMATE_URI__controlled);
+#endif
 #ifdef HAVE_LV2_1_2_0
        bufz_powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength);
        bufz_fixedBlockLength    = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength);
 #ifdef HAVE_LV2_1_2_0
        bufz_powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength);
        bufz_fixedBlockLength    = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength);
@@ -2544,7 +2677,12 @@ LV2World::~LV2World()
        lilv_node_free(bufz_fixedBlockLength);
        lilv_node_free(bufz_powerOf2BlockLength);
 #endif
        lilv_node_free(bufz_fixedBlockLength);
        lilv_node_free(bufz_powerOf2BlockLength);
 #endif
+#ifdef LV2_EXTENDED
        lilv_node_free(lv2_noSampleAccurateCtrl);
        lilv_node_free(lv2_noSampleAccurateCtrl);
+       lilv_node_free(auto_can_write_automatation);
+       lilv_node_free(auto_automation_control);
+       lilv_node_free(auto_automation_controlled);
+#endif
        lilv_node_free(patch_Message);
        lilv_node_free(patch_writable);
        lilv_node_free(units_hz);
        lilv_node_free(patch_Message);
        lilv_node_free(patch_writable);
        lilv_node_free(units_hz);
index 30fe1c005d2b1b00cd567db6db6a28a0ac7c09cf..c085f1f9cbb5255e0f00845cf98c65ac5db5ab4c 100644 (file)
@@ -251,7 +251,9 @@ PluginInsert::create_automatable_parameters ()
 
                        can_automate (param);
                        boost::shared_ptr<AutomationList> list(new AutomationList(param, desc));
 
                        can_automate (param);
                        boost::shared_ptr<AutomationList> list(new AutomationList(param, desc));
-                       add_control (boost::shared_ptr<AutomationControl> (new PluginControl(this, param, desc, list)));
+                       boost::shared_ptr<AutomationControl> c (new PluginControl(this, param, desc, list));
+                       add_control (c);
+                       _plugins.front()->set_automation_control (i->id(), c);
                } else if (i->type() == PluginPropertyAutomation) {
                        Evoral::Parameter param(*i);
                        const ParameterDescriptor& desc = _plugins.front()->get_property_descriptor(param.id());
                } else if (i->type() == PluginPropertyAutomation) {
                        Evoral::Parameter param(*i);
                        const ParameterDescriptor& desc = _plugins.front()->get_property_descriptor(param.id());
index 6fac103715441d26be9e17bcdb99f5383d929c70..0bf67965476ce951821af5d9853487653c2fbf94 100644 (file)
@@ -27,6 +27,7 @@
 #include "pbd/error.h"
 
 #include "ardour/uri_map.h"
 #include "pbd/error.h"
 
 #include "ardour/uri_map.h"
+#include "ardour/lv2_extensions.h"
 
 namespace ARDOUR {
 
 
 namespace ARDOUR {
 
@@ -60,6 +61,15 @@ URIMap::URIDs::init(URIMap& uri_map)
        patch_Set           = uri_map.uri_to_id("http://lv2plug.in/ns/ext/patch#Set");
        patch_property      = uri_map.uri_to_id("http://lv2plug.in/ns/ext/patch#property");
        patch_value         = uri_map.uri_to_id("http://lv2plug.in/ns/ext/patch#value");
        patch_Set           = uri_map.uri_to_id("http://lv2plug.in/ns/ext/patch#Set");
        patch_property      = uri_map.uri_to_id("http://lv2plug.in/ns/ext/patch#property");
        patch_value         = uri_map.uri_to_id("http://lv2plug.in/ns/ext/patch#value");
+#ifdef LV2_EXTENDED
+       auto_event         = uri_map.uri_to_id(LV2_AUTOMATE_URI__event);
+       auto_setup         = uri_map.uri_to_id(LV2_AUTOMATE_URI__setup);
+       auto_finalize      = uri_map.uri_to_id(LV2_AUTOMATE_URI__finalize);
+       auto_start         = uri_map.uri_to_id(LV2_AUTOMATE_URI__start);
+       auto_end           = uri_map.uri_to_id(LV2_AUTOMATE_URI__end);
+       auto_parameter     = uri_map.uri_to_id(LV2_AUTOMATE_URI__parameter);
+       auto_value         = uri_map.uri_to_id(LV2_AUTOMATE_URI__value);
+#endif
 }
 
 URIMap&
 }
 
 URIMap&