X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Flv2_plugin.cc;h=b51db6548526efd1184ae173a602342d731c6c92;hb=7872e4033f236356f0b5a10d0a4eb889adca52d2;hp=46582a5fcfffde1d7cfe5e66b53bd5d6932504d4;hpb=650f2802a0ac634dbcfa33172bf3bf51aa571845;p=ardour.git diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 46582a5fcf..b51db65485 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -17,6 +17,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -35,14 +36,22 @@ #include "pbd/stl_delete.h" #include "pbd/compose.h" #include "pbd/error.h" +#include "pbd/locale_guard.h" +#include "pbd/replace_all.h" #include "pbd/xml++.h" +#ifdef PLATFORM_WINDOWS +#include // CSIDL_* +#include "pbd/windows_special_dirs.h" +#endif + #include "libardour-config.h" #include "ardour/audio_buffer.h" #include "ardour/audioengine.h" #include "ardour/debug.h" #include "ardour/lv2_plugin.h" +#include "ardour/midi_patch_manager.h" #include "ardour/session.h" #include "ardour/tempo.h" #include "ardour/types.h" @@ -50,7 +59,7 @@ #include "ardour/worker.h" #include "ardour/search_paths.h" -#include "i18n.h" +#include "pbd/i18n.h" #include #include @@ -68,6 +77,7 @@ #include "lv2/lv2plug.in/ns/extensions/ui/ui.h" #include "lv2/lv2plug.in/ns/extensions/units/units.h" #include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h" #ifdef HAVE_LV2_1_2_0 #include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h" #include "lv2/lv2plug.in/ns/ext/options/options.h" @@ -107,6 +117,8 @@ using namespace std; using namespace ARDOUR; using namespace PBD; +bool LV2Plugin::force_state_save = false; + class LV2World : boost::noncopyable { public: LV2World (); @@ -125,14 +137,22 @@ public: LilvNode* ev_EventPort; LilvNode* ext_logarithmic; LilvNode* ext_notOnGUI; + LilvNode* ext_expensive; + LilvNode* ext_causesArtifacts; + LilvNode* ext_notAutomatic; + LilvNode* ext_rangeSteps; + LilvNode* groups_group; + LilvNode* groups_element; LilvNode* lv2_AudioPort; LilvNode* lv2_ControlPort; LilvNode* lv2_InputPort; LilvNode* lv2_OutputPort; + LilvNode* lv2_designation; LilvNode* lv2_enumeration; LilvNode* lv2_freewheeling; LilvNode* lv2_inPlaceBroken; LilvNode* lv2_isSideChain; + LilvNode* lv2_index; LilvNode* lv2_integer; LilvNode* lv2_default; LilvNode* lv2_minimum; @@ -160,6 +180,7 @@ public: LilvNode* bufz_powerOf2BlockLength; LilvNode* bufz_fixedBlockLength; LilvNode* bufz_nominalBlockLength; + LilvNode* bufz_coarseBlockLength; #endif #ifdef HAVE_LV2_1_10_0 @@ -173,6 +194,8 @@ public: LilvNode* auto_can_write_automatation; // lv2:optionalFeature 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: @@ -189,15 +212,9 @@ work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void* data) { - LV2Plugin* plugin = (LV2Plugin*)handle; - if (plugin->session().engine().freewheeling()) { - // Freewheeling, do the work immediately in this (audio) thread - return (LV2_Worker_Status)plugin->work(size, data); - } else { - // Enqueue message for the worker thread - return plugin->worker()->schedule(size, data) ? - LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN; - } + return (((Worker*)handle)->schedule(size, data) + ? LV2_WORKER_SUCCESS + : LV2_WORKER_ERR_UNKNOWN); } /** Called by the plugin to respond to non-RT work. */ @@ -206,25 +223,43 @@ work_respond(LV2_Worker_Respond_Handle handle, uint32_t size, const void* data) { - LV2Plugin* plugin = (LV2Plugin*)handle; - if (plugin->session().engine().freewheeling()) { - // Freewheeling, respond immediately in this (audio) thread - return (LV2_Worker_Status)plugin->work_response(size, data); - } else { - // Enqueue response for the worker - return plugin->worker()->respond(size, data) ? - LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN; - } + return (((Worker*)handle)->respond(size, data) + ? LV2_WORKER_SUCCESS + : LV2_WORKER_ERR_UNKNOWN); } #ifdef LV2_EXTENDED /* 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 */ } + +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 */ @@ -237,14 +272,23 @@ log_vprintf(LV2_Log_Handle /*handle*/, { char* str = NULL; const int ret = g_vasprintf(&str, fmt, args); + /* strip trailing whitespace */ + while (strlen (str) > 0 && isspace (str[strlen (str) - 1])) { + str[strlen (str) - 1] = '\0'; + } + if (strlen (str) == 0) { + return 0; + } + if (type == URIMap::instance().urids.log_Error) { error << str << endmsg; } else if (type == URIMap::instance().urids.log_Warning) { warning << str << endmsg; } else if (type == URIMap::instance().urids.log_Note) { info << str << endmsg; + } else if (type == URIMap::instance().urids.log_Trace) { + DEBUG_TRACE(DEBUG::LV2, str); } - // TODO: Toggleable log:Trace message support return ret; } @@ -273,6 +317,7 @@ struct LV2Plugin::Impl { #endif #ifdef LV2_EXTENDED , queue_draw(0) + , midnam(0) #endif {} @@ -300,6 +345,8 @@ struct LV2Plugin::Impl { #endif #ifdef LV2_EXTENDED LV2_Inline_Display* queue_draw; + LV2_Midnam* midnam; + LV2_BankPatch* bankpatch; #endif }; @@ -312,6 +359,7 @@ LV2Plugin::LV2Plugin (AudioEngine& engine, , _impl(new Impl()) , _features(NULL) , _worker(NULL) + , _state_worker(NULL) , _insert_id("0") , _patch_port_in_index((uint32_t)-1) , _patch_port_out_index((uint32_t)-1) @@ -327,6 +375,7 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other) , _impl(new Impl()) , _features(NULL) , _worker(NULL) + , _state_worker(NULL) , _insert_id(other._insert_id) , _patch_port_in_index((uint32_t)-1) , _patch_port_out_index((uint32_t)-1) @@ -365,6 +414,9 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _was_activated = false; _has_state_interface = false; _can_write_automation = false; + _inline_display_in_gui = false; + _max_latency = 0; + _current_latency = 0; _impl->block_length = _session.get_block_size(); _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access"; @@ -388,7 +440,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) lilv_node_free(state_uri); lilv_node_free(state_iface_uri); - _features = (LV2_Feature**)calloc(12, sizeof(LV2_Feature*)); + _features = (LV2_Feature**)calloc(14, sizeof(LV2_Feature*)); _features[0] = &_instance_access_feature; _features[1] = &_data_access_feature; _features[2] = &_make_path_feature; @@ -414,6 +466,24 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _queue_draw_feature.URI = LV2_INLINEDISPLAY__queue_draw; _queue_draw_feature.data = _impl->queue_draw; _features[n_features++] = &_queue_draw_feature; + + _impl->midnam = (LV2_Midnam*) + malloc (sizeof(LV2_Midnam)); + _impl->midnam->handle = this; + _impl->midnam->update = midnam_update; + + _midnam_feature.URI = LV2_MIDNAM__update; + _midnam_feature.data = _impl->midnam; + _features[n_features++] = &_midnam_feature; + + _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 @@ -447,6 +517,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; @@ -459,19 +536,24 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) log->vprintf = &log_vprintf; _log_feature.data = log; + const size_t ring_size = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS; LilvNode* worker_schedule = lilv_new_uri(_world.world, LV2_WORKER__schedule); if (lilv_plugin_has_feature(plugin, worker_schedule)) { LV2_Worker_Schedule* schedule = (LV2_Worker_Schedule*)malloc( sizeof(LV2_Worker_Schedule)); - size_t buf_size = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS; - _worker = new Worker(this, buf_size); - schedule->handle = this; + _worker = new Worker(this, ring_size); + schedule->handle = _worker; schedule->schedule_work = work_schedule; _work_schedule_feature.data = schedule; _features[n_features++] = &_work_schedule_feature; } lilv_node_free(worker_schedule); + if (_has_state_interface) { + // Create a non-threaded worker for use by state restore + _state_worker = new Worker(this, ring_size, false); + } + _impl->instance = lilv_plugin_instantiate(plugin, rate, _features); _impl->name = lilv_plugin_get_name(plugin); _impl->author = lilv_plugin_get_author_name(plugin); @@ -505,6 +587,13 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) #ifdef LV2_EXTENDED _display_interface = (const LV2_Inline_Display_Interface*) extension_data (LV2_INLINEDISPLAY__interface); + + _midname_interface = (const LV2_Midnam_Interface*) + extension_data (LV2_MIDNAM__interface); + if (_midname_interface) { + _midnam_dirty = true; + read_midnam (); + } #endif if (lilv_plugin_has_feature(plugin, _world.lv2_inPlaceBroken)) { @@ -532,19 +621,35 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) lilv_nodes_free(required_features); #endif -#ifdef LV2_EXTENDED LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin); +#ifdef HAVE_LV2_1_2_0 + if (lilv_nodes_contains (optional_features, _world.bufz_coarseBlockLength)) { + _no_sample_accurate_ctrl = true; + } +#endif +#ifdef LV2_EXTENDED if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) { + /* deprecated 2016-Sep-18 in favor of bufz_coarseBlockLength */ _no_sample_accurate_ctrl = true; } if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) { _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 #ifdef HAVE_LILV_0_16_0 // Load default state + if (_worker) { + /* immediately schedule any work, + * so that state restore later will not find a busy + * worker. latency_compute_run() flushes any replies + */ + _worker->set_synchronous(true); + } LilvState* state = lilv_state_new_from_world( _world.world, _uri_map.urid_map(), lilv_plugin_get_uri(_impl->plugin)); if (state && _has_state_interface) { @@ -622,16 +727,33 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) throw failed_constructor(); } + if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) { + if (lilv_port_has_property(_impl->plugin, port, _world.ext_causesArtifacts)) { + flags |= PORT_NOAUTO; + } + if (lilv_port_has_property(_impl->plugin, port, _world.ext_notAutomatic)) { + flags |= PORT_NOAUTO; + } + if (lilv_port_has_property(_impl->plugin, port, _world.ext_expensive)) { + flags |= PORT_NOAUTO; + } + } #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; } } + if (lilv_port_has_property(_impl->plugin, port, _world.auto_automation_controller)) { + if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) { + flags |= PORT_CTRLER; + } + } #endif _port_flags.push_back(flags); _port_minimumSize.push_back(minimumSize); + DEBUG_TRACE(DEBUG::LV2, string_compose("port %1 buffer %2 bytes\n", i, minimumSize)); } _control_data = new float[num_ports]; @@ -673,6 +795,9 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) lilv_instance_connect_port(_impl->instance, i, &_control_data[i]); if (latent && i == latency_index) { + LilvNode *max; + lilv_port_get_range(_impl->plugin, port, NULL, NULL, &max); + _max_latency = max ? lilv_node_as_float(max) : .02 * _sample_rate; _latency_control_port = &_control_data[i]; *_latency_control_port = 0; } @@ -791,6 +916,15 @@ LV2Plugin::~LV2Plugin () deactivate(); cleanup(); +#ifdef LV2_EXTENDED + if (has_midnam ()) { + std::stringstream ss; + ss << (void*)this; + ss << unique_id(); + MIDI::Name::MidiPatchManager::instance().remove_custom_midnam (ss.str()); + } +#endif + lilv_instance_free(_impl->instance); lilv_state_free(_impl->state); lilv_node_free(_impl->name); @@ -800,15 +934,19 @@ LV2Plugin::~LV2Plugin () #endif #ifdef LV2_EXTENDED free(_impl->queue_draw); + free(_impl->midnam); + free(_impl->bankpatch); #endif free(_features); + free(_log_feature.data); free(_make_path_feature.data); free(_work_schedule_feature.data); delete _to_ui; delete _from_ui; delete _worker; + delete _state_worker; if (_atom_ev_buffers) { LV2_Evbuf** b = _atom_ev_buffers; @@ -823,6 +961,7 @@ LV2Plugin::~LV2Plugin () delete [] _shadow_data; delete [] _defaults; delete [] _ev_buffers; + delete _impl; } bool @@ -869,6 +1008,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) { @@ -878,6 +1022,53 @@ LV2Plugin::render_inline_display (uint32_t w, uint32_t h) { } return NULL; } + +bool +LV2Plugin::has_midnam () { + return _midname_interface ? true : false; +} + +bool +LV2Plugin::read_midnam () { + bool rv = false; + if (!_midname_interface || !_midnam_dirty) { + return rv; + } + char* midnam = _midname_interface->midnam ((void*)_impl->instance->lv2_handle); + if (midnam) { + std::stringstream ss; + ss << (void*)this; + ss << unique_id(); + rv = MIDI::Name::MidiPatchManager::instance().update_custom_midnam (ss.str(), midnam); + } +#ifndef NDEBUG + if (rv) { + info << string_compose(_("LV2: update midnam for plugin '%1'"), name ()) << endmsg; + } else { + warning << string_compose(_("LV2: Failed to parse midnam of plugin '%1'"), name ()) << endmsg; + } +#endif + _midname_interface->free (midnam); + if (rv) { + UpdatedMidnam (); + _midnam_dirty = false; + } + return rv; +} + +std::string +LV2Plugin::midnam_model () { + std::string rv; + if (!_midname_interface) { + return rv; + } + char* model = _midname_interface->model ((void*)_impl->instance->lv2_handle); + if (model) { + rv = model; + } + _midname_interface->free (model); + return rv; +} #endif string @@ -1015,6 +1206,51 @@ LV2Plugin::get_parameter_docs(uint32_t which) const return ""; } +bool +LV2Plugin::get_layout (uint32_t which, UILayoutHint& h) const +{ + /// TODO lookup port-properties + if (unique_id () != "urn:ardour:a-eq") { + return false; + } + h.knob = true; + switch (which) { + case 0: h.x0 = 0; h.x1 = 1; h.y0 = 2; h.y1 = 3; break; // Frequency L + case 1: h.x0 = 0; h.x1 = 1; h.y0 = 0; h.y1 = 1; break; // Gain L + case 17: h.x0 = 0; h.x1 = 1; h.y0 = 5; h.y1 = 6; break; // enable L + + case 2: h.x0 = 1; h.x1 = 3; h.y0 = 2; h.y1 = 3; break; // Frequency 1 + case 3: h.x0 = 1; h.x1 = 3; h.y0 = 0; h.y1 = 1; break; // Gain 1 + case 4: h.x0 = 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 = 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 = 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 = 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 + case 15: h.x0 = 13; h.x1 = 14; h.y0 = 0; h.y1 = 1; break; // Gain H + case 22: h.x0 = 13; h.x1 = 14; h.y0 = 5; h.y1 = 6; break; // enable H + + case 16: h.x0 = 14; h.x1 = 15; h.y0 = 1; h.y1 = 3; break; // Master Gain + case 23: h.x0 = 14; h.x1 = 15; h.y0 = 5; h.y1 = 6; break; // Master Enable + default: + return false; + } + return true; +} + uint32_t LV2Plugin::nth_parameter(uint32_t n, bool& ok) const { @@ -1084,7 +1320,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. @@ -1118,26 +1354,26 @@ LV2Plugin::add_state(XMLNode* root) const assert(_insert_id != PBD::ID("0")); XMLNode* child; - char buf[32]; - LocaleGuard lg(X_("C")); + 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); LilvState* state = lilv_state_new_from_instance( @@ -1153,7 +1389,7 @@ LV2Plugin::add_state(XMLNode* root) const 0, NULL); - if (!_plugin_state_dir.empty() + if (!_plugin_state_dir.empty() || force_state_save || !_impl->state || !lilv_state_equals(state, _impl->state)) { lilv_state_save(_world.world, @@ -1164,22 +1400,29 @@ LV2Plugin::add_state(XMLNode* root) const new_dir.c_str(), "state.ttl"); - if (_plugin_state_dir.empty()) { + if (force_state_save) { + // archive or save-as + lilv_state_free(state); + --_state_version; + } + else if (_plugin_state_dir.empty()) { // normal session save lilv_state_free(_impl->state); _impl->state = state; } else { // template save (dedicated state-dir) lilv_state_free(state); + --_state_version; } } else { // State is identical, decrement version and nuke directory lilv_state_free(state); PBD::remove_directory(new_dir); --_state_version; + saved_state = _state_version; } - root->add_property("state-dir", string_compose("state%1", _state_version)); + root->set_property("state-dir", string("state") + PBD::to_string (saved_state)); } } @@ -1245,6 +1488,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 */ } } @@ -1255,8 +1499,15 @@ LV2Plugin::load_preset(PresetRecord r) LilvNode* pset = lilv_new_uri(world, r.uri.c_str()); LilvState* state = lilv_state_new_from_world(world, _uri_map.urid_map(), pset); + const LV2_Feature* state_features[2] = { NULL, NULL }; + LV2_Worker_Schedule schedule = { _state_worker, work_schedule }; + const LV2_Feature state_sched_feature = { LV2_WORKER__schedule, &schedule }; + if (_state_worker) { + state_features[0] = &state_sched_feature; + } + if (state) { - lilv_state_restore(state, _impl->instance, set_port_value, this, 0, NULL); + lilv_state_restore(state, _impl->instance, set_port_value, this, 0, state_features); lilv_state_free(state); Plugin::load_preset(r); } @@ -1297,9 +1548,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) */ @@ -1451,7 +1721,8 @@ LV2Plugin::write_from_ui(uint32_t index, if (_atom_ev_buffers && _atom_ev_buffers[0]) { bufsiz = lv2_evbuf_get_capacity(_atom_ev_buffers[0]); } - rbs = max((size_t) bufsiz * 8, rbs); + int fact = ceilf(_session.frame_rate () / 3000.f); + rbs = max((size_t) bufsiz * std::max (8, fact), rbs); _from_ui = new RingBuffer(rbs); } @@ -1561,12 +1832,12 @@ LV2Plugin::set_property(uint32_t key, const Variant& value) // Serialize patch:Set message to set property #ifdef HAVE_LV2_1_10_0 - lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Set); + lv2_atom_forge_object(forge, &frame, 0, _uri_map.urids.patch_Set); lv2_atom_forge_key(forge, _uri_map.urids.patch_property); lv2_atom_forge_urid(forge, key); lv2_atom_forge_key(forge, _uri_map.urids.patch_value); #else - lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Set); + lv2_atom_forge_blank(forge, &frame, 0, _uri_map.urids.patch_Set); lv2_atom_forge_property_head(forge, _uri_map.urids.patch_property, 0); lv2_atom_forge_urid(forge, key); lv2_atom_forge_property_head(forge, _uri_map.urids.patch_value, 0); @@ -1607,6 +1878,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); + /* 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); } } @@ -1627,14 +1908,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; @@ -1702,9 +1995,9 @@ LV2Plugin::announce_property_values() // Serialize patch:Get message with no subject (implicitly plugin instance) #ifdef HAVE_LV2_1_10_0 - lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Get); + lv2_atom_forge_object(forge, &frame, 0, _uri_map.urids.patch_Get); #else - lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Get); + lv2_atom_forge_blank(forge, &frame, 0, _uri_map.urids.patch_Get); #endif // Write message to UI=>Plugin ring @@ -1757,10 +2050,11 @@ LV2Plugin::emit_to_ui(void* controller, UIMessageSink sink) } int -LV2Plugin::work(uint32_t size, const void* data) +LV2Plugin::work(Worker& worker, uint32_t size, const void* data) { + Glib::Threads::Mutex::Lock lm(_work_mutex); return _impl->work_iface->work( - _impl->instance->lv2_handle, work_respond, this, size, data); + _impl->instance->lv2_handle, work_respond, &worker, size, data); } int @@ -1792,13 +2086,9 @@ int LV2Plugin::set_state(const XMLNode& node, int version) { XMLNodeList nodes; - const XMLProperty* prop; XMLNodeConstIterator iter; XMLNode* child; - const char* sym; - const char* value; - uint32_t port_id; - LocaleGuard lg(X_("C")); + LocaleGuard lg; if (node.name() != state_node_name()) { error << _("Bad node sent to LV2Plugin::set_state") << endmsg; @@ -1817,15 +2107,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::iterator i = _port_indices.find(sym); + uint32_t port_id; + if (i != _port_indices.end()) { port_id = i->second; } else { @@ -1833,31 +2124,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()); @@ -1895,6 +2187,8 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c lilv_port_get_range(_impl->plugin, port, &def, &min, &max); portunits = lilv_port_get_value(_impl->plugin, port, _world.units_unit); + LilvNode* steps = lilv_port_get(_impl->plugin, port, _world.ext_rangeSteps); + // TODO: Once we can rely on lilv 0.18.0 being present, // load_parameter_descriptor() can be used for ports as well desc.integer_step = lilv_port_has_property(_impl->plugin, port, _world.lv2_integer); @@ -1912,24 +2206,26 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c desc.upper *= _session.frame_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); + if (steps) { + desc.rangesteps = lilv_node_as_float (steps); + } + desc.update_steps(); lilv_node_free(def); lilv_node_free(min); lilv_node_free(max); + lilv_node_free(steps); lilv_nodes_free(portunits); return 0; } Plugin::IOPortDescription -LV2Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) +LV2Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const { PortFlags match = 0; switch (dt) { @@ -1966,13 +2262,55 @@ LV2Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) return Plugin::IOPortDescription ("?"); } - LilvNode* name = lilv_port_get_name(_impl->plugin, - lilv_plugin_get_port_by_index(_impl->plugin, idx)); + const LilvPort* pport = lilv_plugin_get_port_by_index (_impl->plugin, idx); + + LilvNode* name = lilv_port_get_name(_impl->plugin, pport); Plugin::IOPortDescription iod (lilv_node_as_string (name)); lilv_node_free(name); - if (lilv_port_has_property(_impl->plugin, - lilv_plugin_get_port_by_index(_impl->plugin, idx), _world.lv2_isSideChain)) { + /* get the port's pg:group */ + LilvNodes* groups = lilv_port_get_value (_impl->plugin, pport, _world.groups_group); + if (lilv_nodes_size (groups) > 0) { + const LilvNode* group = lilv_nodes_get_first (groups); + LilvNodes* grouplabel = lilv_world_find_nodes (_world.world, group, _world.rdfs_label, NULL); + + /* get the name of the port-group */ + if (lilv_nodes_size (grouplabel) > 0) { + const LilvNode* grpname = lilv_nodes_get_first (grouplabel); + iod.group_name = lilv_node_as_string (grpname); + } + lilv_nodes_free (grouplabel); + + /* get all port designations. + * we're interested in e.g. lv2:designation pg:right */ + LilvNodes* designations = lilv_port_get_value (_impl->plugin, pport, _world.lv2_designation); + if (lilv_nodes_size (designations) > 0) { + /* get all pg:elements of the pg:group */ + LilvNodes* group_childs = lilv_world_find_nodes (_world.world, group, _world.groups_element, NULL); + if (lilv_nodes_size (group_childs) > 0) { + /* iterate over all port designations .. */ + LILV_FOREACH (nodes, i, designations) { + const LilvNode* designation = lilv_nodes_get (designations, i); + /* match the lv2:designation's element against the port-group's element */ + LILV_FOREACH (nodes, j, group_childs) { + const LilvNode* group_element = lilv_nodes_get (group_childs, j); + LilvNodes* elem = lilv_world_find_nodes (_world.world, group_element, _world.lv2_designation, designation); + /* found it. Now look up the index (channel-number) of the pg:Element */ + if (lilv_nodes_size (elem) > 0) { + LilvNodes* idx = lilv_world_find_nodes (_world.world, lilv_nodes_get_first (elem), _world.lv2_index, NULL); + if (lilv_node_is_int (lilv_nodes_get_first (idx))) { + iod.group_channel = lilv_node_as_int(lilv_nodes_get_first (idx)); + } + } + } + } + } + } + lilv_nodes_free (groups); + lilv_nodes_free (designations); + } + + if (lilv_port_has_property(_impl->plugin, pport, _world.lv2_isSideChain)) { iod.is_sidechain = true; } return iod; @@ -2008,6 +2346,12 @@ LV2Plugin::describe_parameter(Evoral::Parameter which) } } +framecnt_t +LV2Plugin::max_latency () const +{ + return _max_latency; +} + framecnt_t LV2Plugin::signal_latency() const { @@ -2024,7 +2368,7 @@ LV2Plugin::automatable() const set ret; for (uint32_t i = 0; i < parameter_count(); ++i) { - if (parameter_is_input(i) && parameter_is_control(i)) { + if (parameter_is_input(i) && parameter_is_control(i) && !(_port_flags[i] & PORT_NOAUTO)) { ret.insert(ret.end(), Evoral::Parameter(PluginAutomation, 0, i)); } } @@ -2040,7 +2384,8 @@ LV2Plugin::automatable() const void LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr c) { - if ((_port_flags[i] & PORT_CTRLED)) { + if ((_port_flags[i] & (PORT_CTRLED | PORT_CTRLER))) { + DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Ctrl Port %1\n", i)); _ctrl_map [i] = AutomationCtrlPtr (new AutomationCtrl(c)); } } @@ -2106,8 +2451,7 @@ LV2Plugin::allocate_atom_event_buffers() LilvNodes* atom_supports = lilv_port_get_value( p, port, _world.atom_supports); - if (!lilv_nodes_contains(buffer_types, _world.atom_Sequence) - || !lilv_nodes_contains(atom_supports, _world.midi_MidiEvent)) { + if (lilv_nodes_contains(buffer_types, _world.atom_Sequence)) { if (lilv_port_is_a(p, port, _world.lv2_InputPort)) { count_atom_in++; } @@ -2134,7 +2478,7 @@ LV2Plugin::allocate_atom_event_buffers() return; } - DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers of %d bytes\n", total_atom_buffers, minimumSize)); + DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers of %2 bytes\n", total_atom_buffers, minimumSize)); _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, @@ -2153,6 +2497,7 @@ write_position(LV2_Atom_Forge* forge, const TempoMetric& t, Timecode::BBT_Time& bbt, double speed, + double bpm, framepos_t position, framecnt_t offset) { @@ -2162,7 +2507,7 @@ write_position(LV2_Atom_Forge* forge, lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf)); LV2_Atom_Forge_Frame frame; #ifdef HAVE_LV2_1_10_0 - lv2_atom_forge_object(forge, &frame, 1, urids.time_Position); + lv2_atom_forge_object(forge, &frame, 0, urids.time_Position); lv2_atom_forge_key(forge, urids.time_frame); lv2_atom_forge_long(forge, position); lv2_atom_forge_key(forge, urids.time_speed); @@ -2177,7 +2522,7 @@ write_position(LV2_Atom_Forge* forge, lv2_atom_forge_key(forge, urids.time_beatsPerBar); lv2_atom_forge_float(forge, t.meter().divisions_per_bar()); lv2_atom_forge_key(forge, urids.time_beatsPerMinute); - lv2_atom_forge_float(forge, t.tempo().beats_per_minute()); + lv2_atom_forge_float(forge, bpm); #else lv2_atom_forge_blank(forge, &frame, 1, urids.time_Position); lv2_atom_forge_property_head(forge, urids.time_frame, 0); @@ -2194,7 +2539,7 @@ write_position(LV2_Atom_Forge* forge, lv2_atom_forge_property_head(forge, urids.time_beatsPerBar, 0); lv2_atom_forge_float(forge, t.meter().divisions_per_bar()); lv2_atom_forge_property_head(forge, urids.time_beatsPerMinute, 0); - lv2_atom_forge_float(forge, t.tempo().beats_per_minute()); + lv2_atom_forge_float(forge, bpm); #endif LV2_Evbuf_Iterator end = lv2_evbuf_end(buf); @@ -2205,28 +2550,29 @@ write_position(LV2_Atom_Forge* forge, int LV2Plugin::connect_and_run(BufferSet& bufs, - ChanMapping in_map, ChanMapping out_map, - pframes_t nframes, framecnt_t offset) + framepos_t start, framepos_t end, double speed, + ChanMapping in_map, ChanMapping out_map, + pframes_t nframes, framecnt_t offset) { DEBUG_TRACE(DEBUG::LV2, string_compose("%1 run %2 offset %3\n", name(), nframes, offset)); - Plugin::connect_and_run(bufs, in_map, out_map, nframes, offset); + Plugin::connect_and_run(bufs, start, end, speed, in_map, out_map, nframes, offset); cycles_t then = get_cycles(); TempoMap& tmap = _session.tempo_map(); Metrics::const_iterator metric_i = tmap.metrics_end(); - TempoMetric tmetric = tmap.metric_at(_session.transport_frame(), &metric_i); + TempoMetric tmetric = tmap.metric_at(start, &metric_i); if (_freewheel_control_port) { *_freewheel_control_port = _session.engine().freewheeling() ? 1.f : 0.f; } if (_bpm_control_port) { - *_bpm_control_port = tmetric.tempo().beats_per_minute(); + *_bpm_control_port = tmap.tempo_at_frame (start).note_types_per_minute(); } #ifdef LV2_EXTENDED - if (_can_write_automation && _session.transport_frame() != _next_cycle_start) { + if (_can_write_automation && start != _next_cycle_start) { // add guard-points after locating for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) { i->second->guard = true; @@ -2292,15 +2638,20 @@ LV2Plugin::connect_and_run(BufferSet& bufs, } if (valid && (flags & PORT_INPUT)) { - Timecode::BBT_Time bbt; if ((flags & PORT_POSITION)) { - if (_session.transport_frame() != _next_cycle_start || - _session.transport_speed() != _next_cycle_speed) { - // Transport has changed, write position at cycle start - tmap.bbt_time(_session.transport_frame(), bbt); + Timecode::BBT_Time bbt (tmap.bbt_at_frame (start)); + double bpm = tmap.tempo_at_frame (start).note_types_per_minute(); + double beatpos = (bbt.bars - 1) * tmetric.meter().divisions_per_bar() + + (bbt.beats - 1) + + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat); + beatpos *= tmetric.meter().note_divisor() / 4.0; + if (start != _next_cycle_start || + speed != _next_cycle_speed || + rint (1000 * beatpos) != rint(1000 * _next_cycle_beat) || + bpm != _current_bpm) { + // Transport or Tempo has changed, write position at cycle start write_position(&_impl->forge, _ev_buffers[port_index], - tmetric, bbt, _session.transport_speed(), - _session.transport_frame(), 0); + tmetric, bbt, speed, bpm, start, 0); } } @@ -2314,14 +2665,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 = _session.transport_frame() + nframes; + const framepos_t tend = end; ++metric_i; while (m != m_end || (metric_i != tmap.metrics_end() && (*metric_i)->frame() < tend)) { MetricSection* metric = (metric_i != tmap.metrics_end()) ? *metric_i : NULL; if (m != m_end && (!metric || metric->frame() > (*m).time())) { - const Evoral::MIDIEvent ev(*m, false); + const Evoral::Event 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()); @@ -2329,11 +2680,13 @@ LV2Plugin::connect_and_run(BufferSet& bufs, ++m; } else { tmetric.set_metric(metric); - bbt = metric->start(); + Timecode::BBT_Time bbt; + bbt = tmap.bbt_at_frame (metric->frame()); + double bpm = tmap.tempo_at_frame (start/*XXX*/).note_types_per_minute(); write_position(&_impl->forge, _ev_buffers[port_index], - tmetric, bbt, _session.transport_speed(), + tmetric, bbt, speed, bpm, metric->frame(), - metric->frame() - _session.transport_frame()); + metric->frame() - start); ++metric_i; } } @@ -2448,13 +2801,19 @@ LV2Plugin::connect_and_run(BufferSet& bufs, const uint32_t p = ((const LV2_Atom_Int*)parameter)->body; 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)); AutomationCtrlPtr c = get_automation_control (p); - if (c && c->ac->automation_state() == Touch) { + 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); if (c->guard) { c->guard = false; - c->ac->list()->add (_session.transport_frame() + frames, v, true, true); + c->ac->list()->add (when, v, true, true); } else { - c->ac->set_double (v, _session.transport_frame() + frames, true); + c->ac->set_double (v, when, true); } } } @@ -2464,11 +2823,42 @@ LV2Plugin::connect_and_run(BufferSet& bufs, // 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); + if (_port_flags[i->first] & PORT_CTRLED) { + DEBUG_TRACE(DEBUG::LV2Automate, + string_compose ("Setup p: %1\n", i->first)); + i->second->ac->set_automation_state (Touch); + } } } else if (obj->body.otype == _uri_map.urids.auto_finalize) { // set [touched] parameters to "play" ?? + // allow plugin to change its mode (from analyze to apply) + 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; + AutomationCtrlPtr c = get_automation_control (p); + DEBUG_TRACE(DEBUG::LV2Automate, + string_compose ("Finalize p: %1 v: %2\n", p, v)); + if (c && _port_flags[p] & PORT_CTRLER) { + c->ac->set_value(v, Controllable::NoGroup); + } + } else { + DEBUG_TRACE(DEBUG::LV2Automate, "Finalize\n"); + } + for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) { + // guard will be false if an event was written + if ((_port_flags[i->first] & PORT_CTRLED) && !i->second->guard) { + DEBUG_TRACE(DEBUG::LV2Automate, + string_compose ("Thin p: %1\n", i->first)); + i->second->ac->alist ()->thin (20); + } + } } else if (obj->body.otype == _uri_map.urids.auto_start) { const LV2_Atom* parameter = NULL; @@ -2478,8 +2868,9 @@ LV2Plugin::connect_and_run(BufferSet& bufs, if (parameter) { const uint32_t p = ((const LV2_Atom_Int*)parameter)->body; AutomationCtrlPtr c = get_automation_control (p); + DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Start Touch p: %1\n", p)); if (c) { - c->ac->start_touch (_session.transport_frame()); + c->ac->start_touch (std::max ((framepos_t)0, start - _current_latency)); c->guard = true; } } @@ -2492,14 +2883,26 @@ LV2Plugin::connect_and_run(BufferSet& bufs, if (parameter) { const uint32_t p = ((const LV2_Atom_Int*)parameter)->body; 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, _session.transport_frame()); + c->ac->stop_touch (std::max ((framepos_t)0, start - _current_latency)); } } } } } #endif + // Intercept state dirty message + if (_has_state_interface /* && (flags & PORT_DIRTYMSG)*/) { + LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom)); + if (atom->type == _uri_map.urids.atom_Blank || + atom->type == _uri_map.urids.atom_Object) { + LV2_Atom_Object* obj = (LV2_Atom_Object*)atom; + if (obj->body.otype == _uri_map.urids.state_StateChanged) { + _session.set_dirty (); + } + } + } // Intercept patch change messages to emit PropertyChanged signal if ((flags & PORT_PATCHMSG)) { @@ -2543,9 +2946,28 @@ LV2Plugin::connect_and_run(BufferSet& bufs, set_cycles((uint32_t)(now - then)); // Update expected transport information for next cycle so we can detect changes - _next_cycle_speed = _session.transport_speed(); - _next_cycle_start = _session.transport_frame() + (nframes * _next_cycle_speed); + _next_cycle_speed = speed; + _next_cycle_start = end; + { + /* keep track of lv2:timePosition like plugins can do. + * Note: for no-midi plugins, we only ever send information at cycle-start, + * so it needs to be realative to that. + */ + TempoMetric t = tmap.metric_at(start); + _current_bpm = tmap.tempo_at_frame (start).note_types_per_minute(); + Timecode::BBT_Time bbt (tmap.bbt_at_frame (start)); + double beatpos = (bbt.bars - 1) * t.meter().divisions_per_bar() + + (bbt.beats - 1) + + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat); + beatpos *= tmetric.meter().note_divisor() / 4.0; + _next_cycle_beat = beatpos + nframes * speed * _current_bpm / (60.f * _session.frame_rate()); + } + + if (_latency_control_port) { + framecnt_t new_latency = signal_latency (); + _current_latency = new_latency; + } return 0; } @@ -2584,6 +3006,30 @@ LV2Plugin::parameter_is_input(uint32_t param) const return _port_flags[param] & PORT_INPUT; } +uint32_t +LV2Plugin::designated_bypass_port () +{ + const LilvPort* port = NULL; + LilvNode* designation = lilv_new_uri (_world.world, LV2_CORE_PREFIX "enabled"); + port = lilv_plugin_get_port_by_designation ( + _impl->plugin, _world.lv2_InputPort, designation); + lilv_node_free(designation); + if (port) { + return lilv_port_get_index (_impl->plugin, port); + } +#ifdef LV2_EXTENDED + /* deprecated on 2016-Sep-18 in favor of lv2:enabled */ + designation = lilv_new_uri (_world.world, LV2_PROCESSING_URI__enable); + port = lilv_plugin_get_port_by_designation ( + _impl->plugin, _world.lv2_InputPort, designation); + lilv_node_free(designation); + if (port) { + return lilv_port_get_index (_impl->plugin, port); + } +#endif + return UINT32_MAX; +} + void LV2Plugin::print_parameter(uint32_t param, char* buf, uint32_t len) const { @@ -2624,7 +3070,7 @@ LV2Plugin::get_scale_points(uint32_t port_index) const } void -LV2Plugin::run(pframes_t nframes) +LV2Plugin::run(pframes_t nframes, bool sync_work) { uint32_t const N = parameter_count(); for (uint32_t i = 0; i < N; ++i) { @@ -2633,10 +3079,24 @@ LV2Plugin::run(pframes_t nframes) } } + if (_worker) { + // Execute work synchronously if we're freewheeling (export) + _worker->set_synchronous(sync_work || session().engine().freewheeling()); + } + + // Run the plugin for this cycle lilv_instance_run(_impl->instance, nframes); - if (_impl->work_iface) { + // Emit any queued worker responses (calls a plugin callback) + if (_state_worker) { + _state_worker->emit_responses(); + } + if (_worker) { _worker->emit_responses(); + } + + // Notify the plugin that a work run cycle is complete + if (_impl->work_iface) { if (_impl->work_iface->end_run) { _impl->work_iface->end_run(_impl->instance->lv2_handle); } @@ -2682,7 +3142,7 @@ LV2Plugin::latency_compute_run() port_index++; } - run(bufsize); + run(bufsize, true); deactivate(); if (was_activated) { activate(); @@ -2725,12 +3185,19 @@ LV2World::LV2World() ev_EventPort = lilv_new_uri(world, LILV_URI_EVENT_PORT); ext_logarithmic = lilv_new_uri(world, LV2_PORT_PROPS__logarithmic); ext_notOnGUI = lilv_new_uri(world, LV2_PORT_PROPS__notOnGUI); + ext_expensive = lilv_new_uri(world, LV2_PORT_PROPS__expensive); + ext_causesArtifacts= lilv_new_uri(world, LV2_PORT_PROPS__causesArtifacts); + ext_notAutomatic = lilv_new_uri(world, LV2_PORT_PROPS__notAutomatic); + ext_rangeSteps = lilv_new_uri(world, LV2_PORT_PROPS__rangeSteps); + groups_group = lilv_new_uri(world, LV2_PORT_GROUPS__group); + groups_element = lilv_new_uri(world, LV2_PORT_GROUPS__element); lv2_AudioPort = lilv_new_uri(world, LILV_URI_AUDIO_PORT); lv2_ControlPort = lilv_new_uri(world, LILV_URI_CONTROL_PORT); lv2_InputPort = lilv_new_uri(world, LILV_URI_INPUT_PORT); lv2_OutputPort = lilv_new_uri(world, LILV_URI_OUTPUT_PORT); lv2_inPlaceBroken = lilv_new_uri(world, LV2_CORE__inPlaceBroken); lv2_isSideChain = lilv_new_uri(world, LV2_CORE_PREFIX "isSideChain"); + lv2_index = lilv_new_uri(world, LV2_CORE__index); lv2_integer = lilv_new_uri(world, LV2_CORE__integer); lv2_default = lilv_new_uri(world, LV2_CORE__default); lv2_minimum = lilv_new_uri(world, LV2_CORE__minimum); @@ -2738,6 +3205,7 @@ LV2World::LV2World() lv2_reportsLatency = lilv_new_uri(world, LV2_CORE__reportsLatency); lv2_sampleRate = lilv_new_uri(world, LV2_CORE__sampleRate); lv2_toggled = lilv_new_uri(world, LV2_CORE__toggled); + lv2_designation = lilv_new_uri(world, LV2_CORE__designation); lv2_enumeration = lilv_new_uri(world, LV2_CORE__enumeration); lv2_freewheeling = lilv_new_uri(world, LV2_CORE__freeWheeling); midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT); @@ -2757,15 +3225,18 @@ LV2World::LV2World() patch_writable = lilv_new_uri(world, LV2_PATCH__writable); patch_Message = lilv_new_uri(world, LV2_PATCH__Message); #ifdef LV2_EXTENDED - lv2_noSampleAccurateCtrl = lilv_new_uri(world, LV2_CORE_PREFIX "noSampleAccurateControls"); + lv2_noSampleAccurateCtrl = lilv_new_uri(world, "http://ardour.org/lv2/ext#noSampleAccurateControls"); // deprecated 2016-09-18 auto_can_write_automatation = lilv_new_uri(world, LV2_AUTOMATE_URI__can_write); auto_automation_control = lilv_new_uri(world, LV2_AUTOMATE_URI__control); auto_automation_controlled = lilv_new_uri(world, LV2_AUTOMATE_URI__controlled); + 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); bufz_fixedBlockLength = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength); bufz_nominalBlockLength = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"); + bufz_coarseBlockLength = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#coarseBlockLength"); #endif } @@ -2776,6 +3247,7 @@ LV2World::~LV2World() return; } #ifdef HAVE_LV2_1_2_0 + lilv_node_free(bufz_coarseBlockLength); lilv_node_free(bufz_nominalBlockLength); lilv_node_free(bufz_fixedBlockLength); lilv_node_free(bufz_powerOf2BlockLength); @@ -2785,6 +3257,7 @@ LV2World::~LV2World() lilv_node_free(auto_can_write_automatation); lilv_node_free(auto_automation_control); lilv_node_free(auto_automation_controlled); + lilv_node_free(auto_automation_controller); #endif lilv_node_free(patch_Message); lilv_node_free(patch_writable); @@ -2802,11 +3275,13 @@ LV2World::~LV2World() lilv_node_free(rdfs_label); lilv_node_free(rdfs_range); lilv_node_free(midi_MidiEvent); + lilv_node_free(lv2_designation); lilv_node_free(lv2_enumeration); lilv_node_free(lv2_freewheeling); lilv_node_free(lv2_toggled); lilv_node_free(lv2_sampleRate); lilv_node_free(lv2_reportsLatency); + lilv_node_free(lv2_index); lilv_node_free(lv2_integer); lilv_node_free(lv2_isSideChain); lilv_node_free(lv2_inPlaceBroken); @@ -2814,6 +3289,12 @@ LV2World::~LV2World() lilv_node_free(lv2_InputPort); lilv_node_free(lv2_ControlPort); lilv_node_free(lv2_AudioPort); + lilv_node_free(groups_group); + lilv_node_free(groups_element); + lilv_node_free(ext_rangeSteps); + lilv_node_free(ext_notAutomatic); + lilv_node_free(ext_causesArtifacts); + lilv_node_free(ext_expensive); lilv_node_free(ext_notOnGUI); lilv_node_free(ext_logarithmic); lilv_node_free(ev_EventPort); @@ -2933,10 +3414,7 @@ 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; + return category == c; } bool