#include <cstdlib>
#include <cstring>
-#include <glib/gstdio.h>
+#include "pbd/gstdio_compat.h"
#include <glib/gprintf.h>
#include <glibmm.h>
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;
+#endif
+
+#ifdef HAVE_LV2_1_10_0
+ LilvNode* atom_int;
+ LilvNode* atom_float;
+ LilvNode* atom_object; // new in 1.8
+ LilvNode* atom_vector;
+#endif
+#ifdef LV2_EXTENDED
+ LilvNode* lv2_noSampleAccurateCtrl;
+ LilvNode* auto_can_write_automatation; // lv2:optionalFeature
+ LilvNode* auto_automation_control; // atom:supports
+ LilvNode* auto_automation_controlled; // lv2:portProperty
+#endif
private:
bool _bundle_checked;
}
}
+#ifdef LV2_EXTENDED
+/* inline display extension */
+static void
+queue_draw (LV2_Inline_Display_Handle handle)
+{
+ LV2Plugin* plugin = (LV2Plugin*)handle;
+ plugin->QueueDraw(); /* EMIT SIGNAL */
+}
+#endif
+
/* log extension */
static int
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)
+#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;
+#endif
};
LV2Plugin::LV2Plugin (AudioEngine& engine,
, _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);
}
, _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(12, 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;
+#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
}
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);
+#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
+
+#ifdef LV2_EXTENDED
+ LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin);
+ if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) {
+ _no_sample_accurate_ctrl = true;
+ }
+ if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) {
+ _can_write_automation = true;
+ }
+ lilv_nodes_free(optional_features);
+#endif
#ifdef HAVE_LILV_0_16_0
// Load default state
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();
}
+#ifdef LV2_EXTENDED
+ if (lilv_port_has_property(_impl->plugin, port, _world.auto_automation_controlled)) {
+ if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) {
+ flags |= PORT_CTRLED;
+ }
+ }
+#endif
+
_port_flags.push_back(flags);
_port_minimumSize.push_back(minimumSize);
+ 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()));
cleanup();
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);
+#endif
free(_features);
+ free(_log_feature.data);
free(_make_path_feature.data);
free(_work_schedule_feature.data);
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;
+}
+#endif
+
string
LV2Plugin::unique_id() 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_("POSIX"));
+ 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()
+ || !_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 (_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);
+ }
} 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(
std::string
LV2Plugin::do_save_preset(string name)
{
+ LilvNode* plug_name = lilv_plugin_get_name(_impl->plugin);
+ 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";
const string bundle = Glib::build_filename(
Glib::get_home_dir(),
- Glib::build_filename(".lv2", base_name + ".lv2"));
+ 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,
std::string uri = Glib::filename_to_uri(Glib::build_filename(bundle, file_name));
LilvNode *node_bundle = lilv_new_uri(_world.world, Glib::filename_to_uri(Glib::build_filename(bundle, "/")).c_str());
LilvNode *node_preset = lilv_new_uri(_world.world, uri.c_str());
-#ifdef HAVE_LILV_0_19_2
+#ifdef HAVE_LILV_0_21_3
lilv_world_unload_resource(_world.world, node_preset);
lilv_world_unload_bundle(_world.world, node_bundle);
#endif
lilv_world_load_resource(_world.world, node_preset);
lilv_node_free(node_bundle);
lilv_node_free(node_preset);
+ lilv_node_free(plug_name);
return uri;
}
void
LV2Plugin::do_remove_preset(string name)
{
- string preset_file = Glib::build_filename(
- Glib::get_home_dir(),
- Glib::build_filename(
- Glib::build_filename(".lv2", "presets"),
- name + ".ttl"
- )
- );
- ::g_unlink(preset_file.c_str());
+#ifdef HAVE_LILV_0_21_3
+ /* Look up preset record by label (FIXME: ick, label as ID) */
+ const PresetRecord* r = preset_by_label(name);
+ if (!r) {
+ return;
+ }
+
+ /* Load a LilvState for the preset. */
+ LilvWorld* world = _world.world;
+ LilvNode* pset = lilv_new_uri(world, r->uri.c_str());
+ LilvState* state = lilv_state_new_from_world(world, _uri_map.urid_map(), pset);
+ if (!state) {
+ lilv_node_free(pset);
+ 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. */
+ lilv_state_delete(world, state);
+
+ lilv_state_free(state);
+ lilv_node_free(pset);
+#endif
+ /* Without lilv_state_delete(), we could delete the preset file, but this
+ would leave a broken bundle/manifest around, so the preset would still
+ be visible, but broken. Naively deleting a bundle is too dangerous, so
+ we simply do not support preset deletion with older Lilv */
}
bool
switch (value.type()) {
case Variant::NOTHING:
break;
+ case Variant::BEATS:
+ // No atom type for this, just forge a double
+ lv2_atom_forge_double(forge, value.get_beats().to_double());
+ break;
case Variant::BOOL:
lv2_atom_forge_bool(forge, value.get_bool());
break;
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_("POSIX"));
+ 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();
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
{
return ret;
}
+void
+LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr<AutomationControl> c)
+{
+ if ((_port_flags[i] & PORT_CTRLED)) {
+ _ctrl_map [i] = AutomationCtrlPtr (new AutomationCtrl(c));
+ }
+}
+
+LV2Plugin::AutomationCtrlPtr
+LV2Plugin::get_automation_control (uint32_t i)
+{
+ if (_ctrl_map.find (i) == _ctrl_map.end()) {
+ return AutomationCtrlPtr ();
+ }
+ return _ctrl_map[i];
+}
+
void
LV2Plugin::activate()
{
{
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,
TempoMetric tmetric = tmap.metric_at(_session.transport_frame(), &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();
}
+#ifdef LV2_EXTENDED
+ if (_can_write_automation && _session.transport_frame() != _next_cycle_start) {
+ // add guard-points after locating
+ for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
+ i->second->guard = true;
+ }
+ }
+#endif
+
ChanCount bufs_count;
bufs_count.set(DataType::AUDIO, 1);
bufs_count.set(DataType::MIDI, 1);
? *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);
LV2_Evbuf* buf = _ev_buffers[msg.index];
LV2_Evbuf_Iterator i = lv2_evbuf_end(buf);
const LV2_Atom* const atom = (const LV2_Atom*)&body[0];
- if (!lv2_evbuf_write(&i, nframes, 0, atom->type, atom->size,
+ if (!lv2_evbuf_write(&i, nframes - 1, 0, atom->type, atom->size,
(const uint8_t*)(atom + 1))) {
error << "Failed to write data to LV2 event buffer\n";
}
}
}
-
// 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..
+ AutomationCtrlPtr c = get_automation_control (p);
+ if (c && c->ac->automation_state() == Touch) {
+ if (c->guard) {
+ c->guard = false;
+ c->ac->list()->add (_session.transport_frame() + frames, v, true, true);
+ } else {
+ c->ac->set_double (v, _session.transport_frame() + frames, true);
+ }
+ }
+ }
+ }
+ else if (obj->body.otype == _uri_map.urids.auto_setup) {
+ // TODO optional arguments, for now we assume the plugin
+ // writes automation for its own inputs
+ // -> put them in "touch" mode (preferably "exclusive plugin touch(TM)"
+ for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
+ i->second->ac->set_automation_state (Touch);
+ }
+ }
+ else if (obj->body.otype == _uri_map.urids.auto_finalize) {
+ // set [touched] parameters to "play" ??
+ }
+ else if (obj->body.otype == _uri_map.urids.auto_start) {
+ const LV2_Atom* parameter = NULL;
+ lv2_atom_object_get(obj,
+ _uri_map.urids.auto_parameter, ¶meter,
+ 0);
+ if (parameter) {
+ const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
+ AutomationCtrlPtr c = get_automation_control (p);
+ if (c) {
+ c->ac->start_touch (_session.transport_frame());
+ c->guard = true;
+ }
+ }
+ }
+ else if (obj->body.otype == _uri_map.urids.auto_end) {
+ const LV2_Atom* parameter = NULL;
+ lv2_atom_object_get(obj,
+ _uri_map.urids.auto_parameter, ¶meter,
+ 0);
+ if (parameter) {
+ const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
+ AutomationCtrlPtr c = get_automation_control (p);
+ if (c) {
+ c->ac->stop_touch (true, _session.transport_frame());
+ }
+ }
+ }
+ }
+ }
+#endif
+
// Intercept patch change messages to emit PropertyChanged signal
if ((flags & PORT_PATCHMSG)) {
LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
_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));
}
}
}
_next_cycle_speed = _session.transport_speed();
_next_cycle_start = _session.transport_frame() + (nframes * _next_cycle_speed);
+ if (_latency_control_port) {
+ framecnt_t new_latency = signal_latency ();
+ if (_current_latency != new_latency) {
+ LatencyChanged (_current_latency, new_latency); /* EMIT SIGNAL */
+ }
+ _current_latency = new_latency;
+ }
return 0;
}
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));
}
: world(lilv_world_new())
, _bundle_checked(false)
{
- lilv_world_load_all(world);
-
atom_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort);
atom_Chunk = lilv_new_uri(world, LV2_ATOM__Chunk);
atom_Sequence = lilv_new_uri(world, LV2_ATOM__Sequence);
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");
+ auto_can_write_automatation = lilv_new_uri(world, LV2_AUTOMATE_URI__can_write);
+ auto_automation_control = lilv_new_uri(world, LV2_AUTOMATE_URI__control);
+ auto_automation_controlled = lilv_new_uri(world, LV2_AUTOMATE_URI__controlled);
+#endif
+#ifdef HAVE_LV2_1_2_0
+ bufz_powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength);
+ bufz_fixedBlockLength = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength);
+ bufz_nominalBlockLength = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength");
+#endif
+
}
LV2World::~LV2World()
{
+ if (!world) {
+ return;
+ }
+#ifdef HAVE_LV2_1_2_0
+ 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);
+#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(atom_Chunk);
lilv_node_free(atom_AtomPort);
lilv_world_free(world);
+ world = NULL;
}
void
lilv_node_free(node);
}
+ lilv_world_load_all(world);
_bundle_checked = true;
}
}
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));