Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
+#include <cctype>
#include <string>
#include <vector>
#include <limits>
#include <cstdlib>
#include <cstring>
-#include <glib/gstdio.h>
+#include "pbd/gstdio_compat.h"
#include <glib/gprintf.h>
#include <glibmm.h>
#include "pbd/stl_delete.h"
#include "pbd/compose.h"
#include "pbd/error.h"
+#include "pbd/replace_all.h"
#include "pbd/xml++.h"
#include "libardour-config.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"
#include "ardour/worker.h"
#include "ardour/search_paths.h"
-#include "i18n.h"
+#include "pbd/i18n.h"
#include <locale.h>
#include <lilv/lilv.h>
using namespace ARDOUR;
using namespace PBD;
+bool LV2Plugin::force_state_save = false;
+
class LV2World : boost::noncopyable {
public:
LV2World ();
LilvNode* ev_EventPort;
LilvNode* ext_logarithmic;
LilvNode* ext_notOnGUI;
+ LilvNode* ext_expensive;
+ LilvNode* ext_causesArtifacts;
+ LilvNode* ext_notAutomatic;
+ LilvNode* ext_rangeSteps;
LilvNode* lv2_AudioPort;
LilvNode* lv2_ControlPort;
LilvNode* lv2_InputPort;
LilvNode* lv2_enumeration;
LilvNode* lv2_freewheeling;
LilvNode* lv2_inPlaceBroken;
+ LilvNode* lv2_isSideChain;
LilvNode* lv2_integer;
LilvNode* lv2_default;
LilvNode* lv2_minimum;
LilvNode* units_midiNote;
LilvNode* patch_writable;
LilvNode* patch_Message;
+#ifdef HAVE_LV2_1_2_0
+ LilvNode* bufz_powerOf2BlockLength;
+ LilvNode* bufz_fixedBlockLength;
+ LilvNode* bufz_nominalBlockLength;
+ LilvNode* bufz_coarseBlockLength;
+#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
+ LilvNode* auto_automation_controller; // lv2:portProperty
+#endif
private:
bool _bundle_checked;
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. */
work_respond(LV2_Worker_Respond_Handle handle,
uint32_t size,
const void* data)
+{
+ 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)
{
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;
- }
+ plugin->QueueDraw(); /* EMIT SIGNAL */
}
+static void
+midnam_update (LV2_Midnam_Handle handle)
+{
+ LV2Plugin* plugin = (LV2Plugin*)handle;
+ plugin->UpdateMidnam (); /* EMIT SIGNAL */
+}
+#endif
+
/* log extension */
static int
{
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;
}
struct LV2Plugin::Impl {
Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0)
, work_iface(0)
+#ifdef HAVE_LV2_1_2_0
+ , opts_iface(0)
+#endif
, state(0)
+ , block_length(0)
+#ifdef HAVE_LV2_1_2_0
+ , options(0)
+#endif
+#ifdef LV2_EXTENDED
+ , queue_draw(0)
+ , midnam(0)
+#endif
{}
/** Find the LV2 input port with the given designation.
*/
const LilvPort* designated_input (const char* uri, void** bufptrs[], void** bufptr);
- const LilvPlugin* plugin;
- const LilvUI* ui;
- const LilvNode* ui_type;
- LilvNode* name;
- LilvNode* author;
- LilvInstance* instance;
- const LV2_Worker_Interface* work_iface;
- LilvState* state;
- LV2_Atom_Forge forge;
- LV2_Atom_Forge ui_forge;
+ const LilvPlugin* plugin;
+ const LilvUI* ui;
+ const LilvNode* ui_type;
+ LilvNode* name;
+ LilvNode* author;
+ LilvInstance* instance;
+ const LV2_Worker_Interface* work_iface;
+#ifdef HAVE_LV2_1_2_0
+ const LV2_Options_Interface* opts_iface;
+#endif
+ LilvState* state;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge ui_forge;
+ int32_t block_length;
+#ifdef HAVE_LV2_1_2_0
+ LV2_Options_Option* options;
+#endif
+#ifdef LV2_EXTENDED
+ LV2_Inline_Display* queue_draw;
+ LV2_Midnam* midnam;
+#endif
};
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)
, _uri_map(URIMap::instance())
+ , _no_sample_accurate_ctrl (false)
{
init(c_plugin, rate);
}
, _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)
, _uri_map(URIMap::instance())
+ , _no_sample_accurate_ctrl (false)
{
init(other._impl->plugin, other._sample_rate);
_latency_control_port = 0;
_next_cycle_start = std::numeric_limits<framepos_t>::max();
_next_cycle_speed = 1.0;
- _block_length = _engine.samples_per_cycle();
_seq_size = _engine.raw_buffer_size(DataType::MIDI);
_state_version = 0;
_was_activated = false;
_has_state_interface = false;
+ _can_write_automation = 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";
_data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access";
lilv_node_free(state_uri);
lilv_node_free(state_iface_uri);
- _features = (LV2_Feature**)calloc(11, sizeof(LV2_Feature*));
+ _features = (LV2_Feature**)calloc(13, sizeof(LV2_Feature*));
_features[0] = &_instance_access_feature;
_features[1] = &_data_access_feature;
_features[2] = &_make_path_feature;
lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map());
lv2_atom_forge_init(&_impl->ui_forge, _uri_map.urid_map());
+#ifdef LV2_EXTENDED
+ _impl->queue_draw = (LV2_Inline_Display*)
+ malloc (sizeof(LV2_Inline_Display));
+ _impl->queue_draw->handle = this;
+ _impl->queue_draw->queue_draw = queue_draw;
+
+ _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;
+#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)
+ /* 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.
+ *
+ * given that the block-size can change at any time (split-cycles) ardour currently
+ * does not support plugins that require bufz_fixedBlockLength.
+ */
LV2_Options_Option options[] = {
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__minBlockLength),
- sizeof(int32_t), atom_Int, &_block_length },
+ sizeof(int32_t), atom_Int, &_min_block_length },
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__maxBlockLength),
- sizeof(int32_t), atom_Int, &_block_length },
+ sizeof(int32_t), atom_Int, &_max_block_length },
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__sequenceSize),
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, 0, 0, 0, NULL }
};
+ _impl->options = (LV2_Options_Option*) malloc (sizeof (options));
+ memcpy ((void*) _impl->options, (void*) options, sizeof (options));
+
_options_feature.URI = LV2_OPTIONS__options;
- _options_feature.data = options;
+ _options_feature.data = _impl->options;
_features[n_features++] = &_options_feature;
#endif
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);
}
lilv_node_free(worker_iface_uri);
+
+#ifdef HAVE_LV2_1_2_0
+ LilvNode* options_iface_uri = lilv_new_uri(_world.world, LV2_OPTIONS__interface);
+ if (lilv_plugin_has_extension_data(plugin, options_iface_uri)) {
+ _impl->opts_iface = (const LV2_Options_Interface*)extension_data(
+ LV2_OPTIONS__interface);
+ }
+ lilv_node_free(options_iface_uri);
+#endif
+
+#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) {
+ read_midnam ();
+ }
+#endif
+
if (lilv_plugin_has_feature(plugin, _world.lv2_inPlaceBroken)) {
error << string_compose(
- _("LV2: \"%1\" cannot be used, since it cannot do inplace processing"),
+ _("LV2: \"%1\" cannot be used, since it cannot do inplace processing."),
+ lilv_node_as_string(_impl->name)) << endmsg;
+ lilv_node_free(_impl->name);
+ lilv_node_free(_impl->author);
+ throw failed_constructor();
+ }
+
+#ifdef HAVE_LV2_1_2_0
+ LilvNodes *required_features = lilv_plugin_get_required_features (plugin);
+ if (lilv_nodes_contains (required_features, _world.bufz_powerOf2BlockLength) ||
+ lilv_nodes_contains (required_features, _world.bufz_fixedBlockLength)
+ ) {
+ error << string_compose(
+ _("LV2: \"%1\" buffer-size requirements cannot be satisfied."),
lilv_node_as_string(_impl->name)) << endmsg;
lilv_node_free(_impl->name);
lilv_node_free(_impl->author);
+ lilv_nodes_free(required_features);
throw failed_constructor();
}
+ lilv_nodes_free(required_features);
+#endif
+
+ 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;
+ }
+ 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) {
lilv_state_restore(state, _impl->instance, NULL, NULL, 0, NULL);
}
+ lilv_state_free(state);
#endif
_sample_rate = 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) {
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];
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;
}
latency_compute_run();
}
+int
+LV2Plugin::set_block_size (pframes_t nframes)
+{
+#ifdef HAVE_LV2_1_2_0
+ if (_impl->opts_iface) {
+ LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
+ _impl->block_length = nframes;
+ LV2_Options_Option block_size_option = {
+ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id ("http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"),
+ sizeof(int32_t), atom_Int, (void*)&_impl->block_length
+ };
+ _impl->opts_iface->set (_impl->instance->lv2_handle, &block_size_option);
+ }
+#endif
+ return 0;
+}
+
+bool
+LV2Plugin::requires_fixed_sized_buffers () const
+{
+ /* This controls if Ardour will split the plugin's run()
+ * on automation events in order to pass sample-accurate automation
+ * via standard control-ports.
+ *
+ * When returning true Ardour will *not* sub-divide the process-cycle.
+ * Automation events that happen between cycle-start and cycle-end will be
+ * ignored (ctrl values are interpolated to cycle-start).
+ * NB. Atom Sequences are still sample accurate.
+ *
+ * Note: This does not guarantee a fixed block-size.
+ * e.g The process cycle may be split when looping, also
+ * the period-size may change any time: see set_block_size()
+ */
+ 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
+ * (for BBT) which is not offset for plugin cycle split.
+ */
+ return true;
+ }
+ return _no_sample_accurate_ctrl;
+}
+
LV2Plugin::~LV2Plugin ()
{
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 destroy\n", name()));
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);
lilv_node_free(_impl->author);
+#ifdef HAVE_LV2_1_2_0
+ free(_impl->options);
+#endif
+#ifdef LV2_EXTENDED
+ free(_impl->queue_draw);
+ free(_impl->midnam);
+#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;
delete [] _control_data;
delete [] _shadow_data;
+ delete [] _defaults;
delete [] _ev_buffers;
+ delete _impl;
}
bool
return !fs_matches && !nrs_matches;
}
+#ifdef LV2_EXTENDED
+bool
+LV2Plugin::has_inline_display () {
+ return _display_interface ? true : false;
+}
+
+Plugin::Display_Image_Surface*
+LV2Plugin::render_inline_display (uint32_t w, uint32_t h) {
+ if (_display_interface) {
+ /* Plugin::Display_Image_Surface is identical to
+ * LV2_Inline_Display_Image_Surface */
+ return (Plugin::Display_Image_Surface*) _display_interface->render ((void*)_impl->instance->lv2_handle, w, h);
+ }
+ return NULL;
+}
+
+bool
+LV2Plugin::has_midnam () {
+ return _midname_interface ? true : false;
+}
+
+bool
+LV2Plugin::read_midnam () {
+ bool rv = false;
+ if (!_midname_interface) {
+ 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);
+ 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
LV2Plugin::unique_id() 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 = 2; h.x1 = 4; 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 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 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 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
{
const std::string
LV2Plugin::plugin_dir() const
{
- return Glib::build_filename(_session.plugins_dir(), _insert_id.to_s());
+ if (!_plugin_state_dir.empty ()){
+ return Glib::build_filename(_plugin_state_dir, _insert_id.to_s());
+ } else {
+ return Glib::build_filename(_session.plugins_dir(), _insert_id.to_s());
+ }
}
/** Directory for files created by the plugin (except during save). */
assert(_insert_id != PBD::ID("0"));
XMLNode* child;
- char buf[16];
- LocaleGuard lg(X_("C"));
+ char buf[32];
+ LocaleGuard lg;
for (uint32_t i = 0; i < parameter_count(); ++i) {
if (parameter_is_input(i) && parameter_is_control(i)) {
}
}
+ if (!_plugin_state_dir.empty()) {
+ root->add_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);
0,
NULL);
- if (!_impl->state || !lilv_state_equals(state, _impl->state)) {
+ if (!_plugin_state_dir.empty() || force_state_save
+ || !_impl->state
+ || !lilv_state_equals(state, _impl->state)) {
lilv_state_save(_world.world,
_uri_map.urid_map(),
_uri_map.urid_unmap(),
new_dir.c_str(),
"state.ttl");
- lilv_state_free(_impl->state);
- _impl->state = state;
+ 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);
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
if (name) {
_presets.insert(std::make_pair(lilv_node_as_string(preset),
Plugin::PresetRecord(
lilv_node_as_string(preset),
- lilv_node_as_string(name))));
+ lilv_node_as_string(name),
+ userpreset)));
lilv_node_free(name);
} else {
warning << string_compose(
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);
}
Glib::get_home_dir(),
Glib::build_filename(".lv2", prefix + "_" + base_name + ".lv2"));
+#ifdef HAVE_LILV_0_21_3
+ /* delete reference to old preset (if any) */
+ const PresetRecord* r = preset_by_label(name);
+ if (r) {
+ LilvNode* pset = lilv_new_uri (_world.world, r->uri.c_str());
+ if (pset) {
+ lilv_world_unload_resource (_world.world, pset);
+ lilv_node_free(pset);
+ }
+ }
+#endif
+
LilvState* state = lilv_state_new_from_instance(
_impl->plugin,
_impl->instance,
return;
}
+ /* Unload preset from world. */
+ lilv_world_unload_resource(world, pset);
+
/* Delete it from the file system. This will remove the preset file and the entry
from the manifest. If this results in an empty manifest (i.e. the
preset is the only thing in the bundle), then the bundle is removed. */
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");
lilv_node_free(render);
}
}
}
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
void
LV2Plugin::set_insert_id(PBD::ID id)
{
- _insert_id = id;
+ if (_insert_id == "0") {
+ _insert_id = id;
+ } else if (_insert_id != id) {
+ lilv_state_free(_impl->state);
+ _impl->state = NULL;
+ _insert_id = id;
+ }
+}
+
+void
+LV2Plugin::set_state_dir (const std::string& d)
+{
+ _plugin_state_dir = d;
}
int
LV2Plugin::set_state(const XMLNode& node, int version)
{
XMLNodeList nodes;
- const XMLProperty* prop;
+ XMLProperty const * 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;
set_parameter(port_id, atof(value));
}
+ if ((prop = node.property("template-dir")) != 0) {
+ set_state_dir (prop->value ());
+ }
+
_state_version = 0;
if ((prop = node.property("state-dir")) != 0) {
if (sscanf(prop->value().c_str(), "state%u", &_state_version) != 1) {
_world.world, _uri_map.urid_map(), NULL, state_file.c_str());
lilv_state_restore(state, _impl->instance, NULL, NULL, 0, NULL);
+ lilv_state_free(_impl->state);
+ _impl->state = state;
+ }
+
+ if (!_plugin_state_dir.empty ()) {
+ // force save with session, next time (increment counter)
+ lilv_state_free (_impl->state);
+ _impl->state = NULL;
+ set_state_dir ("");
}
latency_compute_run();
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);
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));
+ }
+
+
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) const
+{
+ PortFlags match = 0;
+ switch (dt) {
+ case DataType::AUDIO:
+ match = PORT_AUDIO;
+ break;
+ case DataType::MIDI:
+ match = PORT_SEQUENCE | PORT_MIDI; // ignore old PORT_EVENT
+ break;
+ default:
+ return Plugin::IOPortDescription ("?");
+ break;
+ }
+ if (input) {
+ match |= PORT_INPUT;
+ } else {
+ match |= PORT_OUTPUT;
+ }
+
+ uint32_t p = 0;
+ uint32_t idx = UINT32_MAX;
+
+ uint32_t const num_ports = parameter_count();
+ for (uint32_t port_index = 0; port_index < num_ports; ++port_index) {
+ PortFlags flags = _port_flags[port_index];
+ if ((flags & match) == match) {
+ if (p == id) {
+ idx = port_index;
+ }
+ ++p;
+ }
+ }
+ if (idx == UINT32_MAX) {
+ return Plugin::IOPortDescription ("?");
+ }
+
+ LilvNode* name = lilv_port_get_name(_impl->plugin,
+ lilv_plugin_get_port_by_index(_impl->plugin, idx));
+ 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)) {
+ iod.is_sidechain = true;
+ }
+ return iod;
+}
+
string
LV2Plugin::describe_parameter(Evoral::Parameter which)
{
}
}
+framecnt_t
+LV2Plugin::max_latency () const
+{
+ return _max_latency;
+}
+
framecnt_t
LV2Plugin::signal_latency() const
{
set<Evoral::Parameter> 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));
}
}
return ret;
}
+void
+LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr<AutomationControl> c)
+{
+ 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));
+ }
+}
+
+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()
{
{
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 cleanup\n", name()));
- activate();
deactivate();
lilv_instance_free(_impl->instance);
_impl->instance = NULL;
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++;
}
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,
const TempoMetric& t,
Timecode::BBT_Time& bbt,
double speed,
+ double bpm,
framepos_t position,
framecnt_t offset)
{
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);
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);
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();
+ *_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).beats_per_minute();
}
+#ifdef LV2_EXTENDED
+ 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;
+ }
+ }
+#endif
+
ChanCount bufs_count;
bufs_count.set(DataType::AUDIO, 1);
bufs_count.set(DataType::MIDI, 1);
}
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).beats_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);
}
}
// 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)) {
? *metric_i : NULL;
if (m != m_end && (!metric || metric->frame() > (*m).time())) {
const Evoral::MIDIEvent<framepos_t> ev(*m, false);
- LV2_Evbuf_Iterator eend = lv2_evbuf_end(_ev_buffers[port_index]);
- lv2_evbuf_write(&eend, ev.time(), 0, type, ev.size(), ev.buffer());
+ 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());
+ }
++m;
} else {
tmetric.set_metric(metric);
- bbt = metric->start();
+ Timecode::BBT_Time bbt;
+ bbt = tmap.bbt_at_pulse (metric->pulse());
+ double bpm = tmap.tempo_at_frame (start/*XXX*/).beats_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;
}
}
}
}
-
// 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);
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..
+ 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 || 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 (when, v, true, true);
+ } else {
+ c->ac->set_double (v, when, 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) {
+ 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;
+ 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);
+ 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->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);
+ 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));
+ }
+ }
+ }
+ }
+ }
+#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)) {
LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
_uri_map.urids.patch_value, &value,
0);
- if (!property || !value ||
- property->type != _uri_map.urids.atom_URID ||
- value->type != _uri_map.urids.atom_Path) {
+ if (property && value &&
+ property->type == _uri_map.urids.atom_URID &&
+ value->type == _uri_map.urids.atom_Path) {
+ const uint32_t prop_id = ((const LV2_Atom_URID*)property)->body;
+ const char* path = (const char*)LV2_ATOM_BODY_CONST(value);
+
+ // Emit PropertyChanged signal for UI
+ // TODO: This should emit the control's Changed signal
+ PropertyChanged(prop_id, Variant(Variant::PATH, path));
+ } else {
std::cerr << "warning: patch:Set for unknown property" << std::endl;
- continue;
}
-
- const uint32_t prop_id = ((const LV2_Atom_URID*)property)->body;
- const char* path = (const char*)LV2_ATOM_BODY_CONST(value);
-
- // Emit PropertyChanged signal for UI
- // TODO: This should emit the control's Changed signal
- PropertyChanged(prop_id, Variant(Variant::PATH, path));
}
}
}
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).beats_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;
}
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
{
}
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) {
}
}
+ 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);
}
port_index++;
}
- run(bufsize);
+ run(bufsize, true);
deactivate();
if (was_activated) {
activate();
static bool lv2_filter (const string& str, void* /*arg*/)
{
/* Not a dotfile, has a prefix before a period, suffix is "lv2" */
-
+
return str[0] != '.' && (str.length() > 3 && str.find (".lv2") == (str.length() - 4));
}
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);
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_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);
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);
+#ifdef LV2_EXTENDED
+ 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);
+#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
+
}
LV2World::~LV2World()
{
+ if (!world) {
+ 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);
+#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);
+ lilv_node_free(auto_automation_controller);
+#endif
lilv_node_free(patch_Message);
lilv_node_free(patch_writable);
lilv_node_free(units_hz);
lilv_node_free(lv2_sampleRate);
lilv_node_free(lv2_reportsLatency);
lilv_node_free(lv2_integer);
+ lilv_node_free(lv2_isSideChain);
lilv_node_free(lv2_inPlaceBroken);
lilv_node_free(lv2_OutputPort);
lilv_node_free(lv2_InputPort);
lilv_node_free(lv2_ControlPort);
lilv_node_free(lv2_AudioPort);
+ 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);
lilv_node_free(atom_Chunk);
lilv_node_free(atom_AtomPort);
lilv_world_free(world);
+ world = NULL;
}
void
return PluginPtr();
}
+std::vector<Plugin::PresetRecord>
+LV2PluginInfo::get_presets (bool /*user_only*/) const
+{
+ std::vector<Plugin::PresetRecord> p;
+#ifndef NO_PLUGIN_STATE
+ const LilvPlugin* lp = NULL;
+ try {
+ PluginPtr plugin;
+ const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world);
+ LilvNode* uri = lilv_new_uri(_world.world, _plugin_uri);
+ if (!uri) { throw failed_constructor(); }
+ lp = lilv_plugins_get_by_uri(plugins, uri);
+ if (!lp) { throw failed_constructor(); }
+ lilv_node_free(uri);
+ } catch (failed_constructor& err) {
+ return p;
+ }
+ assert (lp);
+ // see LV2Plugin::find_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");
+
+ 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
+ if (name) {
+ p.push_back (Plugin::PresetRecord (lilv_node_as_string(preset), lilv_node_as_string(name), userpreset));
+ lilv_node_free(name);
+ }
+ }
+ lilv_nodes_free(presets);
+ lilv_node_free(rdfs_label);
+ lilv_node_free(pset_Preset);
+ lilv_node_free(lv2_appliesTo);
+#endif
+ 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()
{
continue;
}
+ if (lilv_plugin_has_feature(p, world.lv2_inPlaceBroken)) {
+ warning << string_compose(
+ _("Ignoring LV2 plugin \"%1\" since it cannot do inplace processing."),
+ lilv_node_as_string(name)) << endmsg;
+ lilv_node_free(name);
+ continue;
+ }
+
+#ifdef HAVE_LV2_1_2_0
+ LilvNodes *required_features = lilv_plugin_get_required_features (p);
+ if (lilv_nodes_contains (required_features, world.bufz_powerOf2BlockLength) ||
+ lilv_nodes_contains (required_features, world.bufz_fixedBlockLength)
+ ) {
+ warning << string_compose(
+ _("Ignoring LV2 plugin \"%1\" because its buffer-size requirements cannot be satisfied."),
+ lilv_node_as_string(name)) << endmsg;
+ lilv_nodes_free(required_features);
+ lilv_node_free(name);
+ continue;
+ }
+ lilv_nodes_free(required_features);
+#endif
+
info->type = LV2;
info->name = string(lilv_node_as_string(name));