#include <cstdlib>
#include <cstring>
-#include <glib/gstdio.h>
+#include "pbd/gstdio_compat.h"
#include <glib/gprintf.h>
#include <glibmm.h>
#include <boost/utility.hpp>
-#include "pbd/clear_dir.h"
#include "pbd/file_utils.h"
#include "pbd/stl_delete.h"
#include "pbd/compose.h"
#include "lv2/lv2plug.in/ns/ext/worker/worker.h"
#include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h"
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
-#ifdef HAVE_NEW_LV2
+#include "lv2/lv2plug.in/ns/extensions/units/units.h"
+#include "lv2/lv2plug.in/ns/ext/patch/patch.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"
#endif
#include <suil/suil.h>
#endif
+// Compatibility for old LV2
+#ifndef LV2_ATOM_CONTENTS_CONST
+#define LV2_ATOM_CONTENTS_CONST(type, atom) \
+ ((const void*)((const uint8_t*)(atom) + sizeof(type)))
+#endif
+#ifndef LV2_ATOM_BODY_CONST
+#define LV2_ATOM_BODY_CONST(atom) LV2_ATOM_CONTENTS_CONST(LV2_Atom, atom)
+#endif
+#ifndef LV2_PATCH__property
+#define LV2_PATCH__property LV2_PATCH_PREFIX "property"
+#endif
+#ifndef LV2_PATCH__value
+#define LV2_PATCH__value LV2_PATCH_PREFIX "value"
+#endif
+#ifndef LV2_PATCH__writable
+#define LV2_PATCH__writable LV2_PATCH_PREFIX "writable"
+#endif
+
/** The number of MIDI buffers that will fit in a UI/worker comm buffer.
This needs to be roughly the number of cycles the UI will get around to
actually processing the traffic. Lower values are flakier but save memory.
using namespace ARDOUR;
using namespace PBD;
-URIMap LV2Plugin::_uri_map;
-
-LV2Plugin::URIDs LV2Plugin::urids = {
- _uri_map.uri_to_id(LV2_ATOM__Chunk),
- _uri_map.uri_to_id(LV2_ATOM__Path),
- _uri_map.uri_to_id(LV2_ATOM__Sequence),
- _uri_map.uri_to_id(LV2_ATOM__eventTransfer),
- _uri_map.uri_to_id(LV2_LOG__Error),
- _uri_map.uri_to_id(LV2_LOG__Note),
- _uri_map.uri_to_id(LV2_LOG__Warning),
- _uri_map.uri_to_id(LV2_MIDI__MidiEvent),
- _uri_map.uri_to_id(LV2_TIME__Position),
- _uri_map.uri_to_id(LV2_TIME__bar),
- _uri_map.uri_to_id(LV2_TIME__barBeat),
- _uri_map.uri_to_id(LV2_TIME__beatUnit),
- _uri_map.uri_to_id(LV2_TIME__beatsPerBar),
- _uri_map.uri_to_id(LV2_TIME__beatsPerMinute),
- _uri_map.uri_to_id(LV2_TIME__frame),
- _uri_map.uri_to_id(LV2_TIME__speed)
-};
-
class LV2World : boost::noncopyable {
public:
LV2World ();
~LV2World ();
- void load_bundled_plugins();
+ void load_bundled_plugins(bool verbose=false);
LilvWorld* world;
LilvNode* lv2_enumeration;
LilvNode* lv2_freewheeling;
LilvNode* lv2_inPlaceBroken;
+ LilvNode* lv2_isSideChain;
LilvNode* lv2_integer;
+ LilvNode* lv2_default;
+ LilvNode* lv2_minimum;
+ LilvNode* lv2_maximum;
LilvNode* lv2_reportsLatency;
LilvNode* lv2_sampleRate;
LilvNode* lv2_toggled;
LilvNode* midi_MidiEvent;
LilvNode* rdfs_comment;
+ LilvNode* rdfs_label;
+ LilvNode* rdfs_range;
LilvNode* rsz_minimumSize;
LilvNode* time_Position;
LilvNode* ui_GtkUI;
LilvNode* ui_external;
LilvNode* ui_externalkx;
+ LilvNode* units_hz;
+ LilvNode* units_db;
LilvNode* units_unit;
+ LilvNode* units_render;
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
{
char* str = NULL;
const int ret = g_vasprintf(&str, fmt, args);
- if (type == LV2Plugin::urids.log_Error) {
+ if (type == URIMap::instance().urids.log_Error) {
error << str << endmsg;
- } else if (type == LV2Plugin::urids.log_Warning) {
+ } else if (type == URIMap::instance().urids.log_Warning) {
warning << str << endmsg;
- } else if (type == LV2Plugin::urids.log_Note) {
+ } else if (type == URIMap::instance().urids.log_Note) {
info << str << endmsg;
}
// TODO: Toggleable log:Trace message support
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;
+ 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,
, _features(NULL)
, _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);
}
, _features(NULL)
, _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(12, sizeof(LV2_Feature*));
_features[0] = &_instance_access_feature;
_features[1] = &_data_access_feature;
_features[2] = &_make_path_feature;
_features[6] = &_log_feature;
unsigned n_features = 7;
-#ifdef HAVE_NEW_LV2
+#ifdef HAVE_LV2_1_2_0
_features[n_features++] = &_def_state_feature;
#endif
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;
-#ifdef HAVE_NEW_LV2
+ _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_NEW_LILV
+#ifdef HAVE_LILV_0_16_0
// Load default state
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) {
+ _patch_port_in_index = i;
+ } else {
+ _patch_port_out_index = i;
+ }
+ }
}
LilvNodes* min_size_v = lilv_port_get_value(_impl->plugin, port, _world.rsz_minimumSize);
LilvNode* min_size = min_size_v ? lilv_nodes_get_first(min_size_v) : NULL;
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;
}
}
}
+ load_supported_properties(_property_descriptors);
allocate_atom_event_buffers();
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);
}
}
-static inline const LilvNode*
+// TODO: Once we can rely on lilv 0.16.0, lilv_world_get can replace this
+static LilvNode*
get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate)
{
LilvNodes* vs = lilv_world_find_nodes(world, subject, predicate, NULL);
- return vs ? lilv_nodes_get_first(vs) : NULL;
+ if (vs) {
+ LilvNode* node = lilv_node_duplicate(lilv_nodes_get_first(vs));
+ lilv_nodes_free(vs);
+ return node;
+ }
+ return NULL;
}
void
LILV_FOREACH(nodes, i, presets) {
const LilvNode* preset = lilv_nodes_get(presets, i);
lilv_world_load_resource(_world.world, preset);
- const LilvNode* name = get_value(_world.world, preset, rdfs_label);
+ 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(
_("Plugin \"%1\" preset \"%2\" is missing a label\n"),
uint32_t type)
{
LV2Plugin* self = (LV2Plugin*)user_data;
- if (type != 0 && type != self->_uri_map.uri_to_id(LV2_ATOM__Float)) {
+ if (type != 0 && type != URIMap::instance().urids.atom_Float) {
return; // TODO: Support non-float ports
}
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_21_3
+ lilv_world_unload_resource(_world.world, node_preset);
+ lilv_world_unload_bundle(_world.world, node_bundle);
+#endif
lilv_world_load_bundle(_world.world, node_bundle);
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
return true;
}
+static void
+forge_variant(LV2_Atom_Forge* forge, const Variant& value)
+{
+ 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;
+ case Variant::DOUBLE:
+ lv2_atom_forge_double(forge, value.get_double());
+ break;
+ case Variant::FLOAT:
+ lv2_atom_forge_float(forge, value.get_float());
+ break;
+ case Variant::INT:
+ lv2_atom_forge_int(forge, value.get_int());
+ break;
+ case Variant::LONG:
+ lv2_atom_forge_long(forge, value.get_long());
+ break;
+ case Variant::PATH:
+ lv2_atom_forge_path(
+ forge, value.get_path().c_str(), value.get_path().size());
+ break;
+ case Variant::STRING:
+ lv2_atom_forge_string(
+ forge, value.get_string().c_str(), value.get_string().size());
+ break;
+ case Variant::URI:
+ lv2_atom_forge_uri(
+ forge, value.get_uri().c_str(), value.get_uri().size());
+ break;
+ }
+}
+
+/** Get a variant type from a URI, return false iff no match found. */
+static bool
+uri_to_variant_type(const std::string& uri, Variant::Type& type)
+{
+ if (uri == LV2_ATOM__Bool) {
+ type = Variant::BOOL;
+ } else if (uri == LV2_ATOM__Double) {
+ type = Variant::DOUBLE;
+ } else if (uri == LV2_ATOM__Float) {
+ type = Variant::FLOAT;
+ } else if (uri == LV2_ATOM__Int) {
+ type = Variant::INT;
+ } else if (uri == LV2_ATOM__Long) {
+ type = Variant::LONG;
+ } else if (uri == LV2_ATOM__Path) {
+ type = Variant::PATH;
+ } else if (uri == LV2_ATOM__String) {
+ type = Variant::STRING;
+ } else if (uri == LV2_ATOM__URI) {
+ type = Variant::URI;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+void
+LV2Plugin::set_property(uint32_t key, const Variant& value)
+{
+ if (_patch_port_in_index == (uint32_t)-1) {
+ error << "LV2: set_property called with unset patch_port_in_index" << endmsg;
+ return;
+ } else if (value.type() == Variant::NOTHING) {
+ error << "LV2: set_property called with void value" << endmsg;
+ return;
+ }
+
+ // Set up forge to write to temporary buffer on the stack
+ LV2_Atom_Forge* forge = &_impl->ui_forge;
+ LV2_Atom_Forge_Frame frame;
+ uint8_t buf[PATH_MAX]; // Ought to be enough for anyone...
+
+ lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
+
+ // 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_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_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);
+#endif
+
+ forge_variant(forge, value);
+
+ // Write message to UI=>Plugin ring
+ const LV2_Atom* const atom = (const LV2_Atom*)buf;
+ write_from_ui(_patch_port_in_index,
+ _uri_map.urids.atom_eventTransfer,
+ lv2_atom_total_size(atom),
+ (const uint8_t*)atom);
+}
+
+const ParameterDescriptor&
+LV2Plugin::get_property_descriptor(uint32_t id) const
+{
+ PropertyDescriptors::const_iterator p = _property_descriptors.find(id);
+ if (p != _property_descriptors.end()) {
+ return p->second;
+ }
+ return Plugin::get_property_descriptor(id);
+}
+
+static void
+load_parameter_descriptor_units(LilvWorld* lworld, ParameterDescriptor& desc, const LilvNodes* units)
+{
+ if (lilv_nodes_contains(units, _world.units_midiNote)) {
+ desc.unit = ParameterDescriptor::MIDI_NOTE;
+ } else if (lilv_nodes_contains(units, _world.units_db)) {
+ desc.unit = ParameterDescriptor::DB;
+ } else if (lilv_nodes_contains(units, _world.units_hz)) {
+ desc.unit = ParameterDescriptor::HZ;
+ }
+ if (lilv_nodes_size(units) > 0) {
+ const LilvNode* unit = lilv_nodes_get_first(units);
+ LilvNode* render = get_value(lworld, unit, _world.units_render);
+ if (render) {
+ desc.print_fmt = lilv_node_as_string(render);
+ lilv_node_free(render);
+ }
+ }
+}
+
+static void
+load_parameter_descriptor(LV2World& world,
+ ParameterDescriptor& desc,
+ Variant::Type datatype,
+ const LilvNode* subject)
+{
+ LilvWorld* lworld = _world.world;
+ LilvNode* label = get_value(lworld, subject, _world.rdfs_label);
+ LilvNode* def = get_value(lworld, subject, _world.lv2_default);
+ LilvNode* minimum = get_value(lworld, subject, _world.lv2_minimum);
+ LilvNode* maximum = get_value(lworld, subject, _world.lv2_maximum);
+ LilvNodes* units = lilv_world_find_nodes(lworld, subject, _world.units_unit, NULL);
+ if (label) {
+ desc.label = lilv_node_as_string(label);
+ }
+ if (def && lilv_node_is_float(def)) {
+ desc.normal = lilv_node_as_float(def);
+ }
+ if (minimum && lilv_node_is_float(minimum)) {
+ desc.lower = lilv_node_as_float(minimum);
+ }
+ if (maximum && lilv_node_is_float(maximum)) {
+ desc.upper = lilv_node_as_float(maximum);
+ }
+ load_parameter_descriptor_units(lworld, desc, units);
+ desc.datatype = datatype;
+ desc.toggled |= datatype == Variant::BOOL;
+ desc.integer_step |= datatype == Variant::INT || datatype == Variant::LONG;
+ desc.update_steps();
+
+ lilv_nodes_free(units);
+ lilv_node_free(label);
+ lilv_node_free(def);
+ lilv_node_free(minimum);
+ lilv_node_free(maximum);
+}
+
+void
+LV2Plugin::load_supported_properties(PropertyDescriptors& descs)
+{
+ LilvWorld* lworld = _world.world;
+ const LilvNode* subject = lilv_plugin_get_uri(_impl->plugin);
+ LilvNodes* properties = lilv_world_find_nodes(
+ lworld, subject, _world.patch_writable, NULL);
+ LILV_FOREACH(nodes, p, properties) {
+ // Get label and range
+ const LilvNode* prop = lilv_nodes_get(properties, p);
+ LilvNode* range = get_value(lworld, prop, _world.rdfs_range);
+ if (!range) {
+ warning << string_compose(_("LV2: property <%1> has no range datatype, ignoring"),
+ lilv_node_as_uri(prop)) << endmsg;
+ continue;
+ }
+
+ // Convert range to variant type (TODO: support for multiple range types)
+ Variant::Type datatype;
+ if (!uri_to_variant_type(lilv_node_as_uri(range), datatype)) {
+ error << string_compose(_("LV2: property <%1> has unsupported datatype <%1>"),
+ lilv_node_as_uri(prop), lilv_node_as_uri(range)) << endmsg;
+ continue;
+ }
+
+ // Add description to result
+ ParameterDescriptor desc;
+ desc.key = _uri_map.uri_to_id(lilv_node_as_uri(prop));
+ desc.datatype = datatype;
+ load_parameter_descriptor(_world, desc, datatype, prop);
+ descs.insert(std::make_pair(desc.key, desc));
+
+ lilv_node_free(range);
+ }
+ lilv_nodes_free(properties);
+}
+
+void
+LV2Plugin::announce_property_values()
+{
+ if (_patch_port_in_index == (uint32_t)-1) {
+ return;
+ }
+
+ // Set up forge to write to temporary buffer on the stack
+ LV2_Atom_Forge* forge = &_impl->ui_forge;
+ LV2_Atom_Forge_Frame frame;
+ uint8_t buf[PATH_MAX]; // Ought to be enough for anyone...
+
+ lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
+
+ // 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);
+#else
+ lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Get);
+#endif
+
+ // Write message to UI=>Plugin ring
+ const LV2_Atom* const atom = (const LV2_Atom*)buf;
+ write_from_ui(_patch_port_in_index,
+ _uri_map.urids.atom_eventTransfer,
+ lv2_atom_total_size(atom),
+ (const uint8_t*)atom);
+}
+
void
-LV2Plugin::enable_ui_emmission()
+LV2Plugin::enable_ui_emission()
{
if (!_to_ui) {
/* see note in LV2Plugin::write_from_ui() */
}
void
-LV2Plugin::set_insert_info(const PluginInsert* insert)
+LV2Plugin::set_insert_id(PBD::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)
{
- _insert_id = insert->id();
+ _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();
LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) const
{
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which);
+ if (!port) {
+ error << string_compose("LV2: get descriptor of non-existent port %1", which)
+ << endmsg;
+ return 1;
+ }
LilvNodes* portunits;
LilvNode *def, *min, *max;
lilv_port_get_range(_impl->plugin, port, &def, &min, &max);
portunits = lilv_port_get_value(_impl->plugin, port, _world.units_unit);
+ // 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.toggled = lilv_port_has_property(_impl->plugin, port, _world.lv2_toggled);
desc.logarithmic = lilv_port_has_property(_impl->plugin, port, _world.ext_logarithmic);
desc.sr_dependent = lilv_port_has_property(_impl->plugin, port, _world.lv2_sampleRate);
desc.label = lilv_node_as_string(lilv_port_get_name(_impl->plugin, port));
+ desc.normal = def ? lilv_node_as_float(def) : 0.0f;
desc.lower = min ? lilv_node_as_float(min) : 0.0f;
desc.upper = max ? lilv_node_as_float(max) : 1.0f;
- desc.midinote = lilv_nodes_contains(portunits, _world.units_midiNote);
+ load_parameter_descriptor_units(_world.world, desc, portunits);
if (desc.sr_dependent) {
desc.lower *= _session.frame_rate ();
desc.min_unbound = false; // TODO: LV2 extension required
desc.max_unbound = false; // TODO: LV2 extension required
- if (desc.integer_step) {
- desc.step = 1.0;
- desc.smallstep = 0.1;
- desc.largestep = 10.0;
- } else {
- const float delta = desc.upper - desc.lower;
- desc.step = delta / 1000.0f;
- desc.smallstep = delta / 10000.0f;
- desc.largestep = delta / 10.0f;
- }
-
desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration);
+ desc.scale_points = get_scale_points(which);
+
+ desc.update_steps();
lilv_node_free(def);
lilv_node_free(min);
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
{
}
}
+ for (PropertyDescriptors::const_iterator p = _property_descriptors.begin();
+ p != _property_descriptors.end();
+ ++p) {
+ ret.insert(ret.end(), Evoral::Parameter(PluginPropertyAutomation, 0, p->first));
+ }
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,
- LV2Plugin::urids.atom_Chunk, LV2Plugin::urids.atom_Sequence);
+ _uri_map.urids.atom_Chunk, _uri_map.urids.atom_Sequence);
}
_atom_ev_buffers[total_atom_buffers] = 0;
return;
framepos_t position,
framecnt_t offset)
{
+ const URIMap::URIDs& urids = URIMap::instance().urids;
+
uint8_t pos_buf[256];
lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf));
LV2_Atom_Forge_Frame frame;
- lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.time_Position);
- lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_frame, 0);
+#ifdef HAVE_LV2_1_10_0
+ lv2_atom_forge_object(forge, &frame, 1, urids.time_Position);
+ lv2_atom_forge_key(forge, urids.time_frame);
lv2_atom_forge_long(forge, position);
- lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_speed, 0);
+ lv2_atom_forge_key(forge, urids.time_speed);
lv2_atom_forge_float(forge, speed);
- lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_barBeat, 0);
+ lv2_atom_forge_key(forge, urids.time_barBeat);
lv2_atom_forge_float(forge, bbt.beats - 1 +
(bbt.ticks / Timecode::BBT_Time::ticks_per_beat));
- lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_bar, 0);
+ lv2_atom_forge_key(forge, urids.time_bar);
lv2_atom_forge_long(forge, bbt.bars - 1);
- lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatUnit, 0);
+ lv2_atom_forge_key(forge, urids.time_beatUnit);
lv2_atom_forge_int(forge, t.meter().note_divisor());
- lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerBar, 0);
+ lv2_atom_forge_key(forge, urids.time_beatsPerBar);
lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
- lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerMinute, 0);
+ lv2_atom_forge_key(forge, urids.time_beatsPerMinute);
lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
+#else
+ lv2_atom_forge_blank(forge, &frame, 1, urids.time_Position);
+ lv2_atom_forge_property_head(forge, urids.time_frame, 0);
+ lv2_atom_forge_long(forge, position);
+ lv2_atom_forge_property_head(forge, urids.time_speed, 0);
+ lv2_atom_forge_float(forge, speed);
+ lv2_atom_forge_property_head(forge, urids.time_barBeat, 0);
+ lv2_atom_forge_float(forge, bbt.beats - 1 +
+ (bbt.ticks / Timecode::BBT_Time::ticks_per_beat));
+ lv2_atom_forge_property_head(forge, urids.time_bar, 0);
+ lv2_atom_forge_long(forge, bbt.bars - 1);
+ lv2_atom_forge_property_head(forge, urids.time_beatUnit, 0);
+ lv2_atom_forge_int(forge, t.meter().note_divisor());
+ 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());
+#endif
LV2_Evbuf_Iterator end = lv2_evbuf_end(buf);
const LV2_Atom* const atom = (const LV2_Atom*)pos_buf;
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);
: m;
// Now merge MIDI and any transport events into the buffer
- const uint32_t type = LV2Plugin::urids.midi_MidiEvent;
+ const uint32_t type = _uri_map.urids.midi_MidiEvent;
const framepos_t tend = _session.transport_frame() + nframes;
++metric_i;
while (m != m_end || (metric_i != tmap.metrics_end() &&
? *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);
_ev_buffers[port_index] = scratch_bufs.get_lv2_midi(
(flags & PORT_INPUT), 0, (flags & PORT_EVENT));
}
+
buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]);
} else {
continue; // Control port, leave buffer alone
error << "Error reading from UI=>Plugin RingBuffer" << endmsg;
break;
}
- if (msg.protocol == urids.atom_eventTransfer) {
+ if (msg.protocol == URIMap::instance().urids.atom_eventTransfer) {
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 && (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
+ 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);
lv2_evbuf_is_valid(i);
uint32_t frames, subframes, type, size;
uint8_t* data;
lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
- write_to_ui(port_index, urids.atom_eventTransfer,
+
+#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));
+ 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.patch_Set) {
+ const LV2_Atom* property = NULL;
+ const LV2_Atom* value = NULL;
+ lv2_atom_object_get(obj,
+ _uri_map.urids.patch_property, &property,
+ _uri_map.urids.patch_value, &value,
+ 0);
+
+ 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;
+ }
+ }
+ }
+ }
+
+ if (!_to_ui) continue;
+ write_to_ui(port_index, URIMap::instance().urids.atom_eventTransfer,
size + sizeof(LV2_Atom),
data - sizeof(LV2_Atom));
}
_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;
}
}
}
-boost::shared_ptr<Plugin::ScalePoints>
+boost::shared_ptr<ScalePoints>
LV2Plugin::get_scale_points(uint32_t port_index) const
{
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, port_index);
LilvScalePoints* points = lilv_port_get_scale_points(_impl->plugin, port);
- boost::shared_ptr<Plugin::ScalePoints> ret;
+ boost::shared_ptr<ScalePoints> ret;
if (!points) {
return ret;
}
- ret = boost::shared_ptr<Plugin::ScalePoints>(new ScalePoints());
+ ret = boost::shared_ptr<ScalePoints>(new ScalePoints());
LILV_FOREACH(scale_points, i, points) {
const LilvScalePoint* p = lilv_scale_points_get(points, i);
// Run the plugin so that it can set its latency parameter
+ bool was_activated = _was_activated;
activate();
uint32_t port_index = 0;
uint32_t in_index = 0;
uint32_t out_index = 0;
- const framecnt_t bufsize = 1024;
- float buffer[bufsize];
+ // this is done in the main thread. non realtime.
+ const framecnt_t bufsize = _engine.samples_per_cycle();
+ float *buffer = (float*) malloc(_engine.samples_per_cycle() * sizeof(float));
memset(buffer, 0, sizeof(float) * bufsize);
run(bufsize);
deactivate();
+ if (was_activated) {
+ activate();
+ }
+ free(buffer);
}
const LilvPort*
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);
+ lv2_maximum = lilv_new_uri(world, LV2_CORE__maximum);
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_freewheeling = lilv_new_uri(world, LV2_CORE__freeWheeling);
midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT);
rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment");
+ rdfs_label = lilv_new_uri(world, LILV_NS_RDFS "label");
+ rdfs_range = lilv_new_uri(world, LILV_NS_RDFS "range");
rsz_minimumSize = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize);
time_Position = lilv_new_uri(world, LV2_TIME__Position);
ui_GtkUI = lilv_new_uri(world, LV2_UI__GtkUI);
ui_external = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/ui#external");
ui_externalkx = lilv_new_uri(world, "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget");
- units_unit = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#unit");
- units_midiNote = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#midiNote");
+ units_unit = lilv_new_uri(world, LV2_UNITS__unit);
+ units_render = lilv_new_uri(world, LV2_UNITS__render);
+ units_hz = lilv_new_uri(world, LV2_UNITS__hz);
+ units_midiNote = lilv_new_uri(world, LV2_UNITS__midiNote);
+ 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(units_midiNote);
+ lilv_node_free(units_db);
lilv_node_free(units_unit);
+ lilv_node_free(units_render);
lilv_node_free(ui_externalkx);
lilv_node_free(ui_external);
lilv_node_free(ui_GtkUI);
lilv_node_free(time_Position);
lilv_node_free(rsz_minimumSize);
lilv_node_free(rdfs_comment);
+ lilv_node_free(rdfs_label);
+ lilv_node_free(rdfs_range);
lilv_node_free(midi_MidiEvent);
lilv_node_free(lv2_enumeration);
lilv_node_free(lv2_freewheeling);
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_Sequence);
lilv_node_free(atom_Chunk);
lilv_node_free(atom_AtomPort);
+ lilv_world_free(world);
+ world = NULL;
}
void
-LV2World::load_bundled_plugins()
+LV2World::load_bundled_plugins(bool verbose)
{
if (!_bundle_checked) {
- cout << "Scanning folders for bundled LV2s: " << ARDOUR::lv2_bundled_search_path().to_string() << endl;
+ if (verbose) {
+ cout << "Scanning folders for bundled LV2s: " << ARDOUR::lv2_bundled_search_path().to_string() << endl;
+ }
vector<string> plugin_objects;
- find_files_matching_filter (plugin_objects, ARDOUR::lv2_bundled_search_path().to_string(), lv2_filter, 0, true, true);
+ find_paths_matching_filter (plugin_objects, ARDOUR::lv2_bundled_search_path(), lv2_filter, 0, true, true, true);
for ( vector<string>::iterator x = plugin_objects.begin(); x != plugin_objects.end (); ++x) {
#ifdef PLATFORM_WINDOWS
string uri = "file:///" + *x + "/";
lilv_node_free(node);
}
+ lilv_world_load_all(world);
_bundle_checked = true;
}
}
-LV2PluginInfo::LV2PluginInfo (const void* c_plugin)
- : _c_plugin(c_plugin)
+LV2PluginInfo::LV2PluginInfo (const char* plugin_uri)
{
type = ARDOUR::LV2;
+ _plugin_uri = strdup(plugin_uri);
}
LV2PluginInfo::~LV2PluginInfo()
-{}
+{
+ free(_plugin_uri);
+ _plugin_uri = NULL;
+}
PluginPtr
LV2PluginInfo::load(Session& session)
{
try {
PluginPtr plugin;
-
- plugin.reset(new LV2Plugin(session.engine(), session,
- (const LilvPlugin*)_c_plugin,
- session.frame_rate()));
-
- plugin->set_info(PluginInfoPtr(new LV2PluginInfo(*this)));
+ const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world);
+ LilvNode* uri = lilv_new_uri(_world.world, _plugin_uri);
+ if (!uri) { throw failed_constructor(); }
+ const LilvPlugin* lp = lilv_plugins_get_by_uri(plugins, uri);
+ if (!lp) { throw failed_constructor(); }
+ plugin.reset(new LV2Plugin(session.engine(), session, lp, session.frame_rate()));
+ lilv_node_free(uri);
+ plugin->set_info(PluginInfoPtr(shared_from_this ()));
return plugin;
} catch (failed_constructor& err) {
return PluginPtr((Plugin*)0);
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()
{
- _world.load_bundled_plugins();
+ LV2World world;
+ world.load_bundled_plugins();
+ _world.load_bundled_plugins(true);
PluginInfoList* plugs = new PluginInfoList;
- const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world);
-
- if (!Config->get_show_plugin_scan_window()) {
- info << "LV2: Discovering " << lilv_plugins_size(plugins) << " plugins" << endmsg;
- }
+ const LilvPlugins* plugins = lilv_world_get_all_plugins(world.world);
LILV_FOREACH(plugins, i, plugins) {
const LilvPlugin* p = lilv_plugins_get(plugins, i);
- LV2PluginInfoPtr info(new LV2PluginInfo((const void*)p));
+ const LilvNode* pun = lilv_plugin_get_uri(p);
+ if (!pun) continue;
+ LV2PluginInfoPtr info(new LV2PluginInfo(lilv_node_as_string(pun)));
LilvNode* name = lilv_plugin_get_name(p);
if (!name || !lilv_plugin_get_port_by_index(p, 0)) {
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));
int count_midi_in = 0;
for (uint32_t i = 0; i < lilv_plugin_get_num_ports(p); ++i) {
const LilvPort* port = lilv_plugin_get_port_by_index(p, i);
- if (lilv_port_is_a(p, port, _world.atom_AtomPort)) {
+ if (lilv_port_is_a(p, port, world.atom_AtomPort)) {
LilvNodes* buffer_types = lilv_port_get_value(
- p, port, _world.atom_bufferType);
+ p, port, world.atom_bufferType);
LilvNodes* atom_supports = lilv_port_get_value(
- p, port, _world.atom_supports);
+ p, port, world.atom_supports);
- if (lilv_nodes_contains(buffer_types, _world.atom_Sequence)
- && lilv_nodes_contains(atom_supports, _world.midi_MidiEvent)) {
- if (lilv_port_is_a(p, port, _world.lv2_InputPort)) {
+ if (lilv_nodes_contains(buffer_types, world.atom_Sequence)
+ && lilv_nodes_contains(atom_supports, world.midi_MidiEvent)) {
+ if (lilv_port_is_a(p, port, world.lv2_InputPort)) {
count_midi_in++;
}
- if (lilv_port_is_a(p, port, _world.lv2_OutputPort)) {
+ if (lilv_port_is_a(p, port, world.lv2_OutputPort)) {
count_midi_out++;
}
}
info->n_inputs.set_audio(
lilv_plugin_get_num_ports_of_class(
- p, _world.lv2_InputPort, _world.lv2_AudioPort, NULL));
+ p, world.lv2_InputPort, world.lv2_AudioPort, NULL));
info->n_inputs.set_midi(
lilv_plugin_get_num_ports_of_class(
- p, _world.lv2_InputPort, _world.ev_EventPort, NULL)
+ p, world.lv2_InputPort, world.ev_EventPort, NULL)
+ count_midi_in);
info->n_outputs.set_audio(
lilv_plugin_get_num_ports_of_class(
- p, _world.lv2_OutputPort, _world.lv2_AudioPort, NULL));
+ p, world.lv2_OutputPort, world.lv2_AudioPort, NULL));
info->n_outputs.set_midi(
lilv_plugin_get_num_ports_of_class(
- p, _world.lv2_OutputPort, _world.ev_EventPort, NULL)
+ p, world.lv2_OutputPort, world.ev_EventPort, NULL)
+ count_midi_out);
info->unique_id = lilv_node_as_uri(lilv_plugin_get_uri(p));