From 58469214befaa714c856790b78da58c4593b2b54 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sun, 13 Mar 2016 23:20:45 +0100 Subject: [PATCH] prototype online self-automating LV2 plugin interface goes along with https://github.com/x42/automate.lv2 --- libs/ardour/ardour/lv2_plugin.h | 29 ++++++- libs/ardour/ardour/plugin.h | 3 + libs/ardour/ardour/uri_map.h | 9 ++ libs/ardour/lv2_plugin.cc | 146 +++++++++++++++++++++++++++++++- libs/ardour/plugin_insert.cc | 4 +- libs/ardour/uri_map.cc | 10 +++ 6 files changed, 195 insertions(+), 6 deletions(-) diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h index a4cdfcd036..aacef82e5b 100644 --- a/libs/ardour/ardour/lv2_plugin.h +++ b/libs/ardour/ardour/lv2_plugin.h @@ -30,6 +30,10 @@ #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 @@ -91,6 +95,7 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee const LV2_Feature* const* features () { return _features; } std::set automatable () const; + virtual void set_automation_control (uint32_t, boost::shared_ptr); 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; + bool _can_write_automation; 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_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; @@ -208,6 +216,25 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee PropertyDescriptors _property_descriptors; + struct AutomationCtrl { + AutomationCtrl (const AutomationCtrl &other) + : ac (other.ac) + , guard (other.guard) + { } + + AutomationCtrl (boost::shared_ptr c) + : ac (c) + , guard (false) + { } + boost::shared_ptr ac; + bool guard; + }; + + typedef boost::shared_ptr AutomationCtrlPtr; + typedef std::map AutomationCtrlMap; + AutomationCtrlMap _ctrl_map; + AutomationCtrlPtr get_automation_control (uint32_t); + /// Message send to/from UI via ports struct UIMessage { uint32_t index; diff --git a/libs/ardour/ardour/plugin.h b/libs/ardour/ardour/plugin.h index 0ce5522c7c..0bc766c462 100644 --- a/libs/ardour/ardour/plugin.h +++ b/libs/ardour/ardour/plugin.h @@ -49,6 +49,7 @@ class BufferSet; class PluginInsert; class Plugin; class PluginInfo; +class AutomationControl; typedef boost::shared_ptr PluginPtr; typedef boost::shared_ptr 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 void set_automation_control (uint32_t /*port_index*/, boost::shared_ptr) { } + virtual boost::shared_ptr get_scale_points(uint32_t /*port_index*/) const { return boost::shared_ptr(); } diff --git a/libs/ardour/ardour/uri_map.h b/libs/ardour/ardour/uri_map.h index d745ad58e7..523eb18e91 100644 --- a/libs/ardour/ardour/uri_map.h +++ b/libs/ardour/ardour/uri_map.h @@ -83,6 +83,15 @@ public: 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; diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 8dde93f3b3..4af6748c41 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -155,13 +155,25 @@ public: LilvNode* units_midiNote; LilvNode* patch_writable; LilvNode* patch_Message; - LilvNode* lv2_noSampleAccurateCtrl; #ifdef HAVE_LV2_1_2_0 LilvNode* bufz_powerOf2BlockLength; LilvNode* bufz_fixedBlockLength; LilvNode* bufz_nominalBlockLength; #endif +#ifdef HAVE_LV2_1_10_0 + LilvNode* atom_int; + LilvNode* atom_float; + LilvNode* atom_object; // new in 1.8 + LilvNode* atom_vector; +#endif +#ifdef LV2_EXTENDED + LilvNode* lv2_noSampleAccurateCtrl; + LilvNode* auto_can_write_automatation; // lv2:optionalFeature + LilvNode* auto_automation_control; // atom:supports + LilvNode* auto_automation_controlled; // lv2:portProperty +#endif + 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; + _can_write_automation = false; _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); +#endif +#ifdef LV2_EXTENDED LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin); if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) { _no_sample_accurate_ctrl = true; } + if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) { + _can_write_automation = true; + } lilv_nodes_free(optional_features); #endif @@ -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; } +#ifdef LV2_EXTENDED + if (lilv_nodes_contains(atom_supports, _world.auto_automation_control)) { + flags |= PORT_AUTOCTRL; + } +#endif if (lilv_nodes_contains(atom_supports, _world.patch_Message)) { flags |= PORT_PATCHMSG; if (flags & PORT_INPUT) { @@ -566,6 +589,14 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) 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); } @@ -1903,6 +1934,23 @@ LV2Plugin::automatable() const return ret; } +void +LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr 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() { @@ -2074,6 +2122,15 @@ LV2Plugin::connect_and_run(BufferSet& bufs, *_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); @@ -2258,9 +2315,8 @@ LV2Plugin::connect_and_run(BufferSet& bufs, } } - // Write messages to UI - if ((_to_ui || _patch_port_out_index != (uint32_t)-1) && + if ((_to_ui || _can_write_automation || _patch_port_out_index != (uint32_t)-1) && (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) { LV2_Evbuf* buf = _ev_buffers[port_index]; for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf); @@ -2270,6 +2326,78 @@ LV2Plugin::connect_and_run(BufferSet& bufs, uint8_t* data; lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data); +#ifdef LV2_EXTENDED + // Intercept Automation Write Events + if ((flags & PORT_AUTOCTRL)) { + LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom)); + if (atom->type == _uri_map.urids.atom_Blank || + atom->type == _uri_map.urids.atom_Object) { + LV2_Atom_Object* obj = (LV2_Atom_Object*)atom; + if (obj->body.otype == _uri_map.urids.auto_event) { + // only if transport_rolling ?? + const LV2_Atom* parameter = NULL; + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, + _uri_map.urids.auto_parameter, ¶meter, + _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, ¶meter, + 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, ¶meter, + 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)); @@ -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); - 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); @@ -2544,7 +2677,12 @@ LV2World::~LV2World() lilv_node_free(bufz_fixedBlockLength); lilv_node_free(bufz_powerOf2BlockLength); #endif +#ifdef LV2_EXTENDED lilv_node_free(lv2_noSampleAccurateCtrl); + lilv_node_free(auto_can_write_automatation); + lilv_node_free(auto_automation_control); + lilv_node_free(auto_automation_controlled); +#endif lilv_node_free(patch_Message); lilv_node_free(patch_writable); lilv_node_free(units_hz); diff --git a/libs/ardour/plugin_insert.cc b/libs/ardour/plugin_insert.cc index 30fe1c005d..c085f1f9cb 100644 --- a/libs/ardour/plugin_insert.cc +++ b/libs/ardour/plugin_insert.cc @@ -251,7 +251,9 @@ PluginInsert::create_automatable_parameters () can_automate (param); boost::shared_ptr list(new AutomationList(param, desc)); - add_control (boost::shared_ptr (new PluginControl(this, param, desc, list))); + boost::shared_ptr 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()); diff --git a/libs/ardour/uri_map.cc b/libs/ardour/uri_map.cc index 6fac103715..0bf6796547 100644 --- a/libs/ardour/uri_map.cc +++ b/libs/ardour/uri_map.cc @@ -27,6 +27,7 @@ #include "pbd/error.h" #include "ardour/uri_map.h" +#include "ardour/lv2_extensions.h" 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"); +#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& -- 2.30.2