/*
- Copyright (C) 2008-2011 Paul Davis
+ Copyright (C) 2008-2012 Paul Davis
Author: David Robillard
This program is free software; you can redistribute it and/or modify
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
*/
#include <string>
#include <vector>
+#include <limits>
#include <cmath>
#include <cstdlib>
#include <cstring>
+#include <giomm/file.h>
+#include <glib/gprintf.h>
#include <glibmm.h>
#include <boost/utility.hpp>
+#include "pbd/pathscanner.h"
#include "pbd/compose.h"
#include "pbd/error.h"
-#include "pbd/pathscanner.h"
-#include "pbd/stl_delete.h"
#include "pbd/xml++.h"
#include "libardour-config.h"
-#include "ardour/ardour.h"
#include "ardour/audio_buffer.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
-#include "ardour/lv2_event_buffer.h"
#include "ardour/lv2_plugin.h"
-#include "ardour/lv2_state.h"
#include "ardour/session.h"
+#include "ardour/tempo.h"
+#include "ardour/types.h"
+#include "ardour/utils.h"
+#include "ardour/worker.h"
+#include "ardour/lv2_bundled_search_path.h"
#include "i18n.h"
#include <locale.h>
-#include <slv2/slv2.h>
+#include <lilv/lilv.h>
+
+#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
+#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
+#include "lv2/lv2plug.in/ns/ext/log/log.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h"
+#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
+#include "lv2/lv2plug.in/ns/ext/time/time.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/ext/buf-size/buf-size.h"
+#include "lv2/lv2plug.in/ns/ext/options/options.h"
+#endif
+
+#include "lv2_evbuf.h"
-#include "lv2ext/lv2_files.h"
-#include "lv2ext/lv2_persist.h"
-#include "rdff.h"
#ifdef HAVE_SUIL
#include <suil/suil.h>
#endif
-#define NS_DC "http://dublincore.org/documents/dcmi-namespace/"
-#define NS_LV2 "http://lv2plug.in/ns/lv2core#"
-#define NS_PSET "http://lv2plug.in/ns/dev/presets#"
-#define NS_UI "http://lv2plug.in/ns/extensions/ui#"
+/** 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.
+*/
+static const size_t NBUFS = 4;
using namespace std;
using namespace ARDOUR;
using namespace PBD;
URIMap LV2Plugin::_uri_map;
-uint32_t LV2Plugin::_midi_event_type = _uri_map.uri_to_id(
- "http://lv2plug.in/ns/ext/event",
- "http://lv2plug.in/ns/ext/midi#MidiEvent");
+
+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 ();
- SLV2World world;
- SLV2Value input_class; ///< Input port
- SLV2Value output_class; ///< Output port
- SLV2Value audio_class; ///< Audio port
- SLV2Value control_class; ///< Control port
- SLV2Value event_class; ///< Event port
- SLV2Value midi_class; ///< MIDI event
- SLV2Value in_place_broken;
- SLV2Value integer;
- SLV2Value toggled;
- SLV2Value srate;
- SLV2Value gtk_gui;
- SLV2Value external_gui;
- SLV2Value logarithmic;
+ void load_bundled_plugins();
+
+ LilvWorld* world;
+
+ LilvNode* atom_AtomPort;
+ LilvNode* atom_Chunk;
+ LilvNode* atom_Sequence;
+ LilvNode* atom_bufferType;
+ LilvNode* atom_eventTransfer;
+ LilvNode* atom_supports;
+ LilvNode* ev_EventPort;
+ LilvNode* ext_logarithmic;
+ LilvNode* ext_notOnGUI;
+ LilvNode* lv2_AudioPort;
+ LilvNode* lv2_ControlPort;
+ LilvNode* lv2_InputPort;
+ LilvNode* lv2_OutputPort;
+ LilvNode* lv2_enumeration;
+ LilvNode* lv2_freewheeling;
+ LilvNode* lv2_inPlaceBroken;
+ LilvNode* lv2_integer;
+ LilvNode* lv2_reportsLatency;
+ LilvNode* lv2_sampleRate;
+ LilvNode* lv2_toggled;
+ LilvNode* midi_MidiEvent;
+ LilvNode* rdfs_comment;
+ LilvNode* rsz_minimumSize;
+ LilvNode* time_Position;
+ LilvNode* ui_GtkUI;
+ LilvNode* ui_external;
+
+private:
+ bool _bundle_checked;
};
static LV2World _world;
+/* worker extension */
+
+/** Called by the plugin to schedule non-RT work. */
+static LV2_Worker_Status
+work_schedule(LV2_Worker_Schedule_Handle handle,
+ uint32_t size,
+ const void* data)
+{
+ LV2Plugin* plugin = (LV2Plugin*)handle;
+ if (plugin->session().engine().freewheeling()) {
+ // Freewheeling, do the work immediately in this (audio) thread
+ return (LV2_Worker_Status)plugin->work(size, data);
+ } else {
+ // Enqueue message for the worker thread
+ return plugin->worker()->schedule(size, data) ?
+ LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN;
+ }
+}
+
+/** Called by the plugin to respond to non-RT work. */
+static LV2_Worker_Status
+work_respond(LV2_Worker_Respond_Handle handle,
+ uint32_t size,
+ const void* data)
+{
+ LV2Plugin* plugin = (LV2Plugin*)handle;
+ if (plugin->session().engine().freewheeling()) {
+ // Freewheeling, respond immediately in this (audio) thread
+ return (LV2_Worker_Status)plugin->work_response(size, data);
+ } else {
+ // Enqueue response for the worker
+ return plugin->worker()->respond(size, data) ?
+ LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN;
+ }
+}
+
+/* log extension */
+
+static int
+log_vprintf(LV2_Log_Handle /*handle*/,
+ LV2_URID type,
+ const char* fmt,
+ va_list args)
+{
+ char* str = NULL;
+ const int ret = g_vasprintf(&str, fmt, args);
+ if (type == LV2Plugin::urids.log_Error) {
+ error << str << endmsg;
+ } else if (type == LV2Plugin::urids.log_Warning) {
+ warning << str << endmsg;
+ } else if (type == LV2Plugin::urids.log_Note) {
+ info << str << endmsg;
+ }
+ // TODO: Toggleable log:Trace message support
+ return ret;
+}
+
+static int
+log_printf(LV2_Log_Handle handle,
+ LV2_URID type,
+ const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ const int ret = log_vprintf(handle, type, fmt, args);
+ va_end(args);
+ return ret;
+}
+
struct LV2Plugin::Impl {
- Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0) {}
- SLV2Plugin plugin;
- SLV2UI ui;
- SLV2Value ui_type;
- SLV2Value name;
- SLV2Value author;
- SLV2Instance instance;
+ Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0)
+ , work_iface(0)
+ , state(0)
+ {}
+
+ /** Find the LV2 input port with the given designation.
+ * If found, bufptrs[port_index] will be set to bufptr.
+ */
+ 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;
};
LV2Plugin::LV2Plugin (AudioEngine& engine,
Session& session,
- void* c_plugin,
+ const void* c_plugin,
framecnt_t rate)
- : Plugin(engine, session)
+ : Plugin (engine, session)
+ , Workee ()
, _impl(new Impl())
, _features(NULL)
+ , _worker(NULL)
, _insert_id("0")
{
init(c_plugin, rate);
}
LV2Plugin::LV2Plugin (const LV2Plugin& other)
- : Plugin(other)
+ : Plugin (other)
+ , Workee ()
, _impl(new Impl())
, _features(NULL)
+ , _worker(NULL)
, _insert_id(other._insert_id)
{
init(other._impl->plugin, other._sample_rate);
}
void
-LV2Plugin::init(void* c_plugin, framecnt_t rate)
+LV2Plugin::init(const void* c_plugin, framecnt_t rate)
{
DEBUG_TRACE(DEBUG::LV2, "init\n");
- _impl->plugin = (SLV2Plugin)c_plugin;
- _impl->ui = NULL;
- _impl->ui_type = NULL;
- _control_data = 0;
- _shadow_data = 0;
- _latency_control_port = 0;
- _was_activated = false;
-
- _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
- _data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access";
- _path_support_feature.URI = LV2_FILES_PATH_SUPPORT_URI;
- _new_file_support_feature.URI = LV2_FILES_NEW_FILE_SUPPORT_URI;
- _persist_feature.URI = "http://lv2plug.in/ns/ext/persist";
- _persist_feature.data = NULL;
+ _impl->plugin = (const LilvPlugin*)c_plugin;
+ _impl->ui = NULL;
+ _impl->ui_type = NULL;
+ _to_ui = NULL;
+ _from_ui = NULL;
+ _control_data = 0;
+ _shadow_data = 0;
+ _atom_ev_buffers = 0;
+ _ev_buffers = 0;
+ _bpm_control_port = 0;
+ _freewheel_control_port = 0;
+ _latency_control_port = 0;
+ _next_cycle_start = std::numeric_limits<framepos_t>::max();
+ _next_cycle_speed = 1.0;
+ _block_length = _engine.frames_per_cycle();
+ _seq_size = _engine.raw_buffer_size(DataType::MIDI);
+ _state_version = 0;
+ _was_activated = false;
+ _has_state_interface = false;
+
+ _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
+ _data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access";
+ _make_path_feature.URI = LV2_STATE__makePath;
+ _log_feature.URI = LV2_LOG__log;
+ _work_schedule_feature.URI = LV2_WORKER__schedule;
+ _work_schedule_feature.data = NULL;
+ _def_state_feature.URI = LV2_STATE_PREFIX "loadDefaultState"; // Post LV2-1.2.0
+ _def_state_feature.data = NULL;
+
+ const LilvPlugin* plugin = _impl->plugin;
+
+ LilvNode* state_iface_uri = lilv_new_uri(_world.world, LV2_STATE__interface);
+ LilvNode* state_uri = lilv_new_uri(_world.world, LV2_STATE_URI);
+ _has_state_interface =
+ // What plugins should have (lv2:extensionData state:Interface)
+ lilv_plugin_has_extension_data(plugin, state_iface_uri)
+ // What some outdated/incorrect ones have
+ || lilv_plugin_has_feature(plugin, state_uri);
+ lilv_node_free(state_uri);
+ lilv_node_free(state_iface_uri);
+
+ _features = (LV2_Feature**)calloc(11, sizeof(LV2_Feature*));
+ _features[0] = &_instance_access_feature;
+ _features[1] = &_data_access_feature;
+ _features[2] = &_make_path_feature;
+ _features[3] = _uri_map.uri_map_feature();
+ _features[4] = _uri_map.urid_map_feature();
+ _features[5] = _uri_map.urid_unmap_feature();
+ _features[6] = &_log_feature;
+
+ unsigned n_features = 7;
+#ifdef HAVE_NEW_LV2
+ _features[n_features++] = &_def_state_feature;
+#endif
- SLV2Plugin plugin = _impl->plugin;
+ lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map());
+
+#ifdef HAVE_NEW_LV2
+ LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
+ LV2_Options_Option options[] = {
+ { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__minBlockLength),
+ sizeof(int32_t), atom_Int, &_block_length },
+ { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__maxBlockLength),
+ sizeof(int32_t), atom_Int, &_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, 0, 0, 0, NULL }
+ };
+
+ _options_feature.URI = LV2_OPTIONS__options;
+ _options_feature.data = options;
+ _features[n_features++] = &_options_feature;
+#endif
- SLV2Value persist_uri = slv2_value_new_uri(_world.world, _persist_feature.URI);
- _supports_persist = slv2_plugin_has_feature(plugin, persist_uri);
- slv2_value_free(persist_uri);
+ LV2_State_Make_Path* make_path = (LV2_State_Make_Path*)malloc(
+ sizeof(LV2_State_Make_Path));
+ make_path->handle = this;
+ make_path->path = &lv2_state_make_path;
+ _make_path_feature.data = make_path;
+
+ LV2_Log_Log* log = (LV2_Log_Log*)malloc(sizeof(LV2_Log_Log));
+ log->handle = this;
+ log->printf = &log_printf;
+ log->vprintf = &log_vprintf;
+ _log_feature.data = log;
+
+ 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;
+ schedule->schedule_work = work_schedule;
+ _work_schedule_feature.data = schedule;
+ _features[n_features++] = &_work_schedule_feature;
+ }
+ lilv_node_free(worker_schedule);
- _features = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 7);
- _features[0] = &_instance_access_feature;
- _features[1] = &_data_access_feature;
- _features[2] = &_path_support_feature;
- _features[3] = &_new_file_support_feature;
- _features[4] = &_persist_feature;
- _features[5] = _uri_map.feature();
- _features[6] = NULL;
-
- LV2_Files_Path_Support* path_support = (LV2_Files_Path_Support*)malloc(
- sizeof(LV2_Files_Path_Support));
- path_support->host_data = this;
- path_support->abstract_path = &lv2_files_abstract_path;
- path_support->absolute_path = &lv2_files_absolute_path;
- _path_support_feature.data = path_support;
-
- LV2_Files_New_File_Support* new_file_support = (LV2_Files_New_File_Support*)malloc(
- sizeof(LV2_Files_New_File_Support));
- new_file_support->host_data = this;
- new_file_support->new_file_path = &lv2_files_new_file_path;
- _new_file_support_feature.data = new_file_support;
-
- _impl->instance = slv2_plugin_instantiate(plugin, rate, _features);
- _impl->name = slv2_plugin_get_name(plugin);
- _impl->author = slv2_plugin_get_author_name(plugin);
+ _impl->instance = lilv_plugin_instantiate(plugin, rate, _features);
+ _impl->name = lilv_plugin_get_name(plugin);
+ _impl->author = lilv_plugin_get_author_name(plugin);
if (_impl->instance == 0) {
error << _("LV2: Failed to instantiate plugin ") << uri() << endmsg;
_data_access_extension_data.extension_data = _impl->instance->lv2_descriptor->extension_data;
_data_access_feature.data = &_data_access_extension_data;
- if (slv2_plugin_has_feature(plugin, _world.in_place_broken)) {
+ LilvNode* worker_iface_uri = lilv_new_uri(_world.world, LV2_WORKER__interface);
+ if (lilv_plugin_has_extension_data(plugin, worker_iface_uri)) {
+ _impl->work_iface = (const LV2_Worker_Interface*)extension_data(
+ LV2_WORKER__interface);
+ }
+ lilv_node_free(worker_iface_uri);
+
+ if (lilv_plugin_has_feature(plugin, _world.lv2_inPlaceBroken)) {
error << string_compose(
_("LV2: \"%1\" cannot be used, since it cannot do inplace processing"),
- slv2_value_as_string(_impl->name)) << endmsg;
- slv2_value_free(_impl->name);
- slv2_value_free(_impl->author);
+ lilv_node_as_string(_impl->name)) << endmsg;
+ lilv_node_free(_impl->name);
+ lilv_node_free(_impl->author);
throw failed_constructor();
}
+#ifdef HAVE_NEW_LILV
+ // 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);
+ }
+#endif
+
_sample_rate = rate;
- const uint32_t num_ports = this->num_ports();
- const bool latent = slv2_plugin_has_latency(plugin);
- const uint32_t latency_port = (latent)
- ? slv2_plugin_get_latency_port_index(plugin)
- : 0;
+ const uint32_t num_ports = this->num_ports();
+ for (uint32_t i = 0; i < num_ports; ++i) {
+ const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, i);
+ PortFlags flags = 0;
+ size_t minimumSize = 0;
+
+ if (lilv_port_is_a(_impl->plugin, port, _world.lv2_OutputPort)) {
+ flags |= PORT_OUTPUT;
+ } else if (lilv_port_is_a(_impl->plugin, port, _world.lv2_InputPort)) {
+ flags |= PORT_INPUT;
+ } else {
+ error << string_compose(
+ "LV2: \"%1\" port %2 is neither input nor output",
+ lilv_node_as_string(_impl->name), i) << endmsg;
+ throw failed_constructor();
+ }
+
+ if (lilv_port_is_a(_impl->plugin, port, _world.lv2_ControlPort)) {
+ flags |= PORT_CONTROL;
+ } else if (lilv_port_is_a(_impl->plugin, port, _world.lv2_AudioPort)) {
+ flags |= PORT_AUDIO;
+ } else if (lilv_port_is_a(_impl->plugin, port, _world.ev_EventPort)) {
+ flags |= PORT_EVENT;
+ flags |= PORT_MIDI; // We assume old event API ports are for MIDI
+ } else if (lilv_port_is_a(_impl->plugin, port, _world.atom_AtomPort)) {
+ LilvNodes* buffer_types = lilv_port_get_value(
+ _impl->plugin, port, _world.atom_bufferType);
+ LilvNodes* atom_supports = lilv_port_get_value(
+ _impl->plugin, port, _world.atom_supports);
+
+ if (lilv_nodes_contains(buffer_types, _world.atom_Sequence)) {
+ flags |= PORT_SEQUENCE;
+ if (lilv_nodes_contains(atom_supports, _world.midi_MidiEvent)) {
+ flags |= PORT_MIDI;
+ }
+ if (lilv_nodes_contains(atom_supports, _world.time_Position)) {
+ flags |= PORT_POSITION;
+ }
+ }
+ 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;
+ if (min_size && lilv_node_is_int(min_size)) {
+ minimumSize = lilv_node_as_int(min_size);
+ }
+ lilv_nodes_free(min_size_v);
+ lilv_nodes_free(buffer_types);
+ lilv_nodes_free(atom_supports);
+ } else {
+ error << string_compose(
+ "LV2: \"%1\" port %2 has no known data type",
+ lilv_node_as_string(_impl->name), i) << endmsg;
+ throw failed_constructor();
+ }
+
+ _port_flags.push_back(flags);
+ _port_minimumSize.push_back(minimumSize);
+ }
_control_data = new float[num_ports];
_shadow_data = new float[num_ports];
_defaults = new float[num_ports];
+ _ev_buffers = new LV2_Evbuf*[num_ports];
+ memset(_ev_buffers, 0, sizeof(LV2_Evbuf*) * num_ports);
+
+ const bool latent = lilv_plugin_has_latency(plugin);
+ const uint32_t latency_index = (latent)
+ ? lilv_plugin_get_latency_port_index(plugin)
+ : 0;
+ // Build an array of pointers to special parameter buffers
+ void*** params = new void**[num_ports];
for (uint32_t i = 0; i < num_ports; ++i) {
- SLV2Port port = slv2_plugin_get_port_by_index(plugin, i);
- SLV2Value sym = slv2_port_get_symbol(plugin, port);
+ params[i] = NULL;
+ }
+ _impl->designated_input (LV2_TIME__beatsPerMinute, params, (void**)&_bpm_control_port);
+ _impl->designated_input (LV2_CORE__freeWheeling, params, (void**)&_freewheel_control_port);
+
+ for (uint32_t i = 0; i < num_ports; ++i) {
+ const LilvPort* port = lilv_plugin_get_port_by_index(plugin, i);
+ const LilvNode* sym = lilv_port_get_symbol(plugin, port);
// Store index in map so we can look up index by symbol
- _port_indices.insert(std::make_pair(slv2_value_as_string(sym), i));
+ _port_indices.insert(std::make_pair(lilv_node_as_string(sym), i));
// Get range and default value if applicable
if (parameter_is_control(i)) {
- SLV2Value def;
- slv2_port_get_range(plugin, port, &def, NULL, NULL);
- _defaults[i] = def ? slv2_value_as_float(def) : 0.0f;
- slv2_value_free(def);
+ LilvNode* def;
+ lilv_port_get_range(plugin, port, &def, NULL, NULL);
+ _defaults[i] = def ? lilv_node_as_float(def) : 0.0f;
+ if (lilv_port_has_property (plugin, port, _world.lv2_sampleRate)) {
+ _defaults[i] *= _session.frame_rate ();
+ }
+ lilv_node_free(def);
- slv2_instance_connect_port(_impl->instance, i, &_control_data[i]);
+ lilv_instance_connect_port(_impl->instance, i, &_control_data[i]);
- if (latent && ( i == latency_port) ) {
+ if (latent && i == latency_index) {
_latency_control_port = &_control_data[i];
*_latency_control_port = 0;
}
if (parameter_is_input(i)) {
_shadow_data[i] = default_value(i);
+ if (params[i]) {
+ *params[i] = (void*)&_shadow_data[i];
+ }
}
} else {
_defaults[i] = 0.0f;
}
}
- SLV2UIs uis = slv2_plugin_get_uis(plugin);
- if (slv2_uis_size(uis) > 0) {
-#if defined(HAVE_NEW_SLV2) and defined(HAVE_SUIL)
+ delete[] params;
+
+ LilvUIs* uis = lilv_plugin_get_uis(plugin);
+ if (lilv_uis_size(uis) > 0) {
+#ifdef HAVE_SUIL
// Look for embeddable UI
- SLV2Value ui_type = NULL;
- SLV2_FOREACH(uis, u, uis) {
- SLV2UI this_ui = slv2_uis_get(uis, u);
- SLV2Value this_ui_type = NULL;
- if (slv2_ui_is_supported(this_ui,
+ LILV_FOREACH(uis, u, uis) {
+ const LilvUI* this_ui = lilv_uis_get(uis, u);
+ const LilvNode* this_ui_type = NULL;
+ if (lilv_ui_is_supported(this_ui,
suil_ui_supported,
- _world.gtk_gui,
+ _world.ui_GtkUI,
&this_ui_type)) {
// TODO: Multiple UI support
- _ui = this_ui;
- _ui_type = this_ui_type;
+ _impl->ui = this_ui;
+ _impl->ui_type = this_ui_type;
break;
- }
+ }
}
#else
// Look for Gtk native UI
- for (unsigned i = 0; i < slv2_uis_size(uis); ++i) {
- SLV2UI ui = slv2_uis_get_at(uis, i);
- if (slv2_ui_is_a(ui, _world.gtk_gui)) {
+ LILV_FOREACH(uis, i, uis) {
+ const LilvUI* ui = lilv_uis_get(uis, i);
+ if (lilv_ui_is_a(ui, _world.ui_GtkUI)) {
_impl->ui = ui;
- _impl->ui_type = _world.gtk_gui;
+ _impl->ui_type = _world.ui_GtkUI;
break;
}
}
// If Gtk UI is not available, try to find external UI
if (!_impl->ui) {
- for (unsigned i = 0; i < slv2_uis_size(uis); ++i) {
- SLV2UI ui = slv2_uis_get_at(uis, i);
- if (slv2_ui_is_a(ui, _world.external_gui)) {
+ LILV_FOREACH(uis, i, uis) {
+ const LilvUI* ui = lilv_uis_get(uis, i);
+ if (lilv_ui_is_a(ui, _world.ui_external)) {
_impl->ui = ui;
- _impl->ui_type = _world.external_gui;
+ _impl->ui_type = _world.ui_external;
break;
}
}
}
}
+ allocate_atom_event_buffers();
latency_compute_run();
}
deactivate();
cleanup();
- slv2_instance_free(_impl->instance);
- slv2_value_free(_impl->name);
- slv2_value_free(_impl->author);
-#if defined(HAVE_NEW_SLV2) and defined(HAVE_SUIL)
- slv2_value_free(_impl->ui_type);
-#else
- /* _ui_type points to a global, so leave it alone */
-#endif
+ lilv_instance_free(_impl->instance);
+ lilv_node_free(_impl->name);
+ lilv_node_free(_impl->author);
+
+ free(_features);
+ free(_make_path_feature.data);
+ free(_work_schedule_feature.data);
+
+ delete _to_ui;
+ delete _from_ui;
+ delete _worker;
+
+ if (_atom_ev_buffers) {
+ LV2_Evbuf** b = _atom_ev_buffers;
+ while (*b) {
+ free(*b);
+ b++;
+ }
+ free(_atom_ev_buffers);
+ }
+
delete [] _control_data;
delete [] _shadow_data;
+ delete [] _ev_buffers;
}
bool
if (!_impl->ui) {
return false;
}
- return slv2_ui_is_a(_impl->ui, _world.external_gui);
+ return lilv_ui_is_a(_impl->ui, _world.ui_external);
+}
+
+bool
+LV2Plugin::ui_is_resizable () const
+{
+ const LilvNode* s = lilv_ui_get_uri(_impl->ui);
+ LilvNode* p = lilv_new_uri(_world.world, LV2_CORE__optionalFeature);
+ LilvNode* fs = lilv_new_uri(_world.world, LV2_UI__fixedSize);
+ LilvNode* nrs = lilv_new_uri(_world.world, LV2_UI__noUserResize);
+
+ LilvNodes* fs_matches = lilv_world_find_nodes(_world.world, s, p, fs);
+ LilvNodes* nrs_matches = lilv_world_find_nodes(_world.world, s, p, nrs);
+
+ lilv_nodes_free(nrs_matches);
+ lilv_nodes_free(fs_matches);
+ lilv_node_free(nrs);
+ lilv_node_free(fs);
+ lilv_node_free(p);
+
+ return !fs_matches && !nrs_matches;
}
string
LV2Plugin::unique_id() const
{
- return slv2_value_as_uri(slv2_plugin_get_uri(_impl->plugin));
+ return lilv_node_as_uri(lilv_plugin_get_uri(_impl->plugin));
}
const char*
LV2Plugin::uri() const
{
- return slv2_value_as_uri(slv2_plugin_get_uri(_impl->plugin));
+ return lilv_node_as_uri(lilv_plugin_get_uri(_impl->plugin));
}
const char*
LV2Plugin::label() const
{
- return slv2_value_as_string(_impl->name);
+ return lilv_node_as_string(_impl->name);
}
const char*
LV2Plugin::name() const
{
- return slv2_value_as_string(_impl->name);
+ return lilv_node_as_string(_impl->name);
}
const char*
LV2Plugin::maker() const
{
- return _impl->author ? slv2_value_as_string (_impl->author) : "Unknown";
+ return _impl->author ? lilv_node_as_string (_impl->author) : "Unknown";
}
uint32_t
LV2Plugin::num_ports() const
{
- return slv2_plugin_get_num_ports(_impl->plugin);
+ return lilv_plugin_get_num_ports(_impl->plugin);
}
uint32_t
LV2Plugin::parameter_count() const
{
- return slv2_plugin_get_num_ports(_impl->plugin);
+ return lilv_plugin_get_num_ports(_impl->plugin);
}
float
const char*
LV2Plugin::port_symbol(uint32_t index) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, index);
+ const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, index);
if (!port) {
error << name() << ": Invalid port index " << index << endmsg;
}
- SLV2Value sym = slv2_port_get_symbol(_impl->plugin, port);
- return slv2_value_as_string(sym);
+ const LilvNode* sym = lilv_port_get_symbol(_impl->plugin, port);
+ return lilv_node_as_string(sym);
+}
+
+uint32_t
+LV2Plugin::port_index (const char* symbol) const
+{
+ const map<string, uint32_t>::const_iterator i = _port_indices.find(symbol);
+ if (i != _port_indices.end()) {
+ return i->second;
+ } else {
+ warning << string_compose(_("LV2: Unknown port %1"), symbol) << endmsg;
+ return (uint32_t)-1;
+ }
}
void
DEBUG_TRACE(DEBUG::LV2, string_compose(
"%1 set parameter %2 to %3\n", name(), which, val));
- if (which < slv2_plugin_get_num_ports(_impl->plugin)) {
+ if (which < lilv_plugin_get_num_ports(_impl->plugin)) {
+ if (get_parameter (which) == val) {
+ return;
+ }
+
_shadow_data[which] = val;
} else {
warning << string_compose(
return 0.0f;
}
+std::string
+LV2Plugin::get_docs() const
+{
+ LilvNodes* comments = lilv_plugin_get_value(_impl->plugin, _world.rdfs_comment);
+ if (comments) {
+ const std::string docs(lilv_node_as_string(lilv_nodes_get_first(comments)));
+ lilv_nodes_free(comments);
+ return docs;
+ }
+
+ return "";
+}
+
+std::string
+LV2Plugin::get_parameter_docs(uint32_t which) const
+{
+ LilvNodes* comments = lilv_port_get_value(
+ _impl->plugin,
+ lilv_plugin_get_port_by_index(_impl->plugin, which),
+ _world.rdfs_comment);
+
+ if (comments) {
+ const std::string docs(lilv_node_as_string(lilv_nodes_get_first(comments)));
+ lilv_nodes_free(comments);
+ return docs;
+ }
+
+ return "";
+}
+
uint32_t
LV2Plugin::nth_parameter(uint32_t n, bool& ok) const
{
ok = false;
- for (uint32_t c = 0, x = 0; x < slv2_plugin_get_num_ports(_impl->plugin); ++x) {
+ for (uint32_t c = 0, x = 0; x < lilv_plugin_get_num_ports(_impl->plugin); ++x) {
if (parameter_is_control(x)) {
if (c++ == n) {
ok = true;
}
const void*
-LV2Plugin::extension_data (const char* uri) const
+LV2Plugin::extension_data(const char* uri) const
{
- return slv2_instance_get_extension_data(_impl->instance, uri);
+ return lilv_instance_get_extension_data(_impl->instance, uri);
}
-void*
-LV2Plugin::c_plugin ()
+const void*
+LV2Plugin::c_plugin()
{
return _impl->plugin;
}
-void*
-LV2Plugin::c_ui ()
+const void*
+LV2Plugin::c_ui()
{
- return _impl->ui;
+ return (const void*)_impl->ui;
}
-void*
-LV2Plugin::c_ui_type ()
+const void*
+LV2Plugin::c_ui_type()
{
- return _impl->ui_type;
+ return (const void*)_impl->ui_type;
}
-int
-LV2Plugin::lv2_persist_store_callback(void* host_data,
- uint32_t key,
- const void* value,
- size_t size,
- uint32_t type,
- uint32_t flags)
+/** Directory for all plugin state. */
+const std::string
+LV2Plugin::plugin_dir() const
{
- DEBUG_TRACE(DEBUG::LV2, string_compose(
- "persist store %1 (size: %2, type: %3)\n",
- _uri_map.id_to_uri(NULL, key),
- size,
- _uri_map.id_to_uri(NULL, type)));
-
- LV2PersistState* state = (LV2PersistState*)host_data;
- state->add_uri(key, _uri_map.id_to_uri(NULL, key));
- state->add_uri(type, _uri_map.id_to_uri(NULL, type));
- return state->add_value(key, value, size, type, flags);
+ return Glib::build_filename(_session.plugins_dir(), _insert_id.to_s());
}
-const void*
-LV2Plugin::lv2_persist_retrieve_callback(void* host_data,
- uint32_t key,
- size_t* size,
- uint32_t* type,
- uint32_t* flags)
-{
- LV2PersistState* state = (LV2PersistState*)host_data;
- LV2PersistState::Values::const_iterator i = state->values.find(key);
- if (i == state->values.end()) {
- warning << "LV2 plugin attempted to retrieve nonexistent key: "
- << _uri_map.id_to_uri(NULL, key) << endmsg;
- return NULL;
- }
- *size = i->second.size;
- *type = i->second.type;
- *flags = LV2_PERSIST_IS_POD | LV2_PERSIST_IS_PORTABLE; // FIXME
- DEBUG_TRACE(DEBUG::LV2, string_compose(
- "persist retrieve %1 = %2 (size: %3, type: %4)\n",
- _uri_map.id_to_uri(NULL, key),
- i->second.value, *size, *type));
- return i->second.value;
+/** Directory for files created by the plugin (except during save). */
+const std::string
+LV2Plugin::scratch_dir() const
+{
+ return Glib::build_filename(plugin_dir(), "scratch");
}
-char*
-LV2Plugin::lv2_files_abstract_path(LV2_Files_Host_Data host_data,
- const char* absolute_path)
+/** Directory for snapshots of files in the scratch directory. */
+const std::string
+LV2Plugin::file_dir() const
{
- LV2Plugin* me = (LV2Plugin*)host_data;
- if (me->_insert_id == PBD::ID("0")) {
- return g_strdup(absolute_path);
- }
-
- const std::string state_dir = Glib::build_filename(me->_session.plugins_dir(),
- me->_insert_id.to_s());
-
- char* ret = NULL;
- if (strncmp(absolute_path, state_dir.c_str(), state_dir.length())) {
- ret = g_strdup(absolute_path);
- } else {
- const std::string path(absolute_path + state_dir.length() + 1);
- ret = g_strndup(path.c_str(), path.length());
- }
-
- DEBUG_TRACE(DEBUG::LV2, string_compose("abstract path %1 => %2\n",
- absolute_path, ret));
+ return Glib::build_filename(plugin_dir(), "files");
+}
- return ret;
+/** Directory to save state snapshot version @c num into. */
+const std::string
+LV2Plugin::state_dir(unsigned num) const
+{
+ return Glib::build_filename(plugin_dir(), string_compose("state%1", num));
}
+/** Implementation of state:makePath for files created at instantiation time.
+ * Note this is not used for files created at save time (Lilv deals with that).
+ */
char*
-LV2Plugin::lv2_files_absolute_path(LV2_Files_Host_Data host_data,
- const char* abstract_path)
+LV2Plugin::lv2_state_make_path(LV2_State_Make_Path_Handle handle,
+ const char* path)
{
- LV2Plugin* me = (LV2Plugin*)host_data;
+ LV2Plugin* me = (LV2Plugin*)handle;
if (me->_insert_id == PBD::ID("0")) {
- return g_strdup(abstract_path);
+ warning << string_compose(
+ "File path \"%1\" requested but LV2 %2 has no insert ID",
+ path, me->name()) << endmsg;
+ return g_strdup(path);
}
- char* ret = NULL;
- if (g_path_is_absolute(abstract_path)) {
- ret = g_strdup(abstract_path);
- } else {
- const std::string apath(abstract_path);
- const std::string state_dir = Glib::build_filename(me->_session.plugins_dir(),
- me->_insert_id.to_s());
- const std::string path = Glib::build_filename(state_dir,
- apath);
- ret = g_strndup(path.c_str(), path.length());
- }
+ const std::string abs_path = Glib::build_filename(me->scratch_dir(), path);
+ const std::string dirname = Glib::path_get_dirname(abs_path);
+ g_mkdir_with_parents(dirname.c_str(), 0744);
- DEBUG_TRACE(DEBUG::LV2, string_compose("absolute path %1 => %2\n",
- abstract_path, ret));
+ DEBUG_TRACE(DEBUG::LV2, string_compose("new file path %1 => %2\n",
+ path, abs_path));
- return ret;
+ return g_strndup(abs_path.c_str(), abs_path.length());
}
-char*
-LV2Plugin::lv2_files_new_file_path(LV2_Files_Host_Data host_data,
- const char* relative_path)
+static void
+remove_directory(const std::string& path)
{
- LV2Plugin* me = (LV2Plugin*)host_data;
- if (me->_insert_id == PBD::ID("0")) {
- return g_strdup(relative_path);
+ if (!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+ warning << string_compose("\"%1\" is not a directory", path) << endmsg;
+ return;
}
- const std::string state_dir = Glib::build_filename(me->_session.plugins_dir(),
- me->_insert_id.to_s());
- const std::string path = Glib::build_filename(state_dir,
- relative_path);
-
- char* dirname = g_path_get_dirname(path.c_str());
- g_mkdir_with_parents(dirname, 0744);
- free(dirname);
-
- DEBUG_TRACE(DEBUG::LV2, string_compose("new file path %1 => %2\n",
- relative_path, path));
-
- return g_strndup(path.c_str(), path.length());
+ Glib::RefPtr<Gio::File> dir = Gio::File::create_for_path(path);
+ Glib::RefPtr<Gio::FileEnumerator> e = dir->enumerate_children();
+ Glib::RefPtr<Gio::FileInfo> fi;
+ while ((fi = e->next_file())) {
+ if (fi->get_type() == Gio::FILE_TYPE_DIRECTORY) {
+ remove_directory(fi->get_name());
+ } else {
+ dir->get_child(fi->get_name())->remove();
+ }
+ }
+ dir->remove();
}
void
}
}
- if (_supports_persist) {
- // Create state directory for this plugin instance
- const std::string state_filename = _insert_id.to_s() + ".rdff";
- const std::string state_path = Glib::build_filename(
- _session.plugins_dir(), state_filename);
-
- cout << "Saving LV2 plugin state to " << state_path << endl;
-
- // Get LV2 Persist extension data from plugin instance
- LV2_Persist* persist = (LV2_Persist*)extension_data(
- "http://lv2plug.in/ns/ext/persist");
- if (!persist) {
- warning << string_compose(
- _("Plugin \"%1\% failed to return LV2 persist data"),
- unique_id());
- return;
+ if (_has_state_interface) {
+ // Provisionally increment state version and create directory
+ const std::string new_dir = state_dir(++_state_version);
+ g_mkdir_with_parents(new_dir.c_str(), 0744);
+
+ LilvState* state = lilv_state_new_from_instance(
+ _impl->plugin,
+ _impl->instance,
+ _uri_map.urid_map(),
+ scratch_dir().c_str(),
+ file_dir().c_str(),
+ _session.externals_dir().c_str(),
+ new_dir.c_str(),
+ NULL,
+ const_cast<LV2Plugin*>(this),
+ 0,
+ NULL);
+
+ if (!_impl->state || !lilv_state_equals(state, _impl->state)) {
+ lilv_state_save(_world.world,
+ _uri_map.urid_map(),
+ _uri_map.urid_unmap(),
+ state,
+ NULL,
+ new_dir.c_str(),
+ "state.ttl");
+
+ lilv_state_free(_impl->state);
+ _impl->state = state;
+ } else {
+ // State is identical, decrement version and nuke directory
+ lilv_state_free(state);
+ remove_directory(new_dir);
+ --_state_version;
}
- // Save plugin state to state object
- LV2PersistState state(_uri_map);
- persist->save(_impl->instance->lv2_handle,
- &LV2Plugin::lv2_persist_store_callback,
- &state);
-
- // Write state object to RDFF file
- RDFF file = rdff_open(state_path.c_str(), true);
- state.write(file);
- rdff_close(file);
-
- root->add_property("state-file", state_filename);
+ root->add_property("state-dir", string_compose("state%1", _state_version));
}
}
-static inline SLV2Value
-get_value(SLV2Plugin p, SLV2Value subject, SLV2Value predicate)
+static inline const LilvNode*
+get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate)
{
- SLV2Values vs = slv2_plugin_get_value_for_subject(p, subject, predicate);
- return vs ? slv2_values_get_at(vs, 0) : NULL;
+ LilvNodes* vs = lilv_world_find_nodes(world, subject, predicate, NULL);
+ return vs ? lilv_nodes_get_first(vs) : NULL;
}
void
LV2Plugin::find_presets()
{
- SLV2Value dc_title = slv2_value_new_uri(_world.world, NS_DC "title");
- SLV2Value pset_hasPreset = slv2_value_new_uri(_world.world, NS_PSET "hasPreset");
-
- SLV2Values presets = slv2_plugin_get_value(_impl->plugin, pset_hasPreset);
- for (unsigned i = 0; i < slv2_values_size(presets); ++i) {
- SLV2Value preset = slv2_values_get_at(presets, i);
- SLV2Value name = get_value(_impl->plugin, preset, dc_title);
+ 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(_impl->plugin, pset_Preset);
+ 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);
if (name) {
- _presets.insert(std::make_pair(slv2_value_as_string(preset),
- PresetRecord(
- slv2_value_as_string(preset),
- slv2_value_as_string(name))));
+ _presets.insert(std::make_pair(lilv_node_as_string(preset),
+ Plugin::PresetRecord(
+ lilv_node_as_string(preset),
+ lilv_node_as_string(name))));
} else {
warning << string_compose(
- _("Plugin \"%1\% preset \"%2%\" is missing a dc:title\n"),
- unique_id(), slv2_value_as_string(preset));
+ _("Plugin \"%1\% preset \"%2%\" is missing a label\n"),
+ lilv_node_as_string(lilv_plugin_get_uri(_impl->plugin)),
+ lilv_node_as_string(preset)) << endmsg;
}
}
- slv2_values_free(presets);
+ lilv_nodes_free(presets);
+
+ lilv_node_free(rdfs_label);
+ lilv_node_free(pset_Preset);
+ lilv_node_free(lv2_appliesTo);
+}
+
+static void
+set_port_value(const char* port_symbol,
+ void* user_data,
+ const void* value,
+ uint32_t /*size*/,
+ uint32_t type)
+{
+ LV2Plugin* self = (LV2Plugin*)user_data;
+ if (type != 0 && type != self->_uri_map.uri_to_id(LV2_ATOM__Float)) {
+ return; // TODO: Support non-float ports
+ }
- slv2_value_free(pset_hasPreset);
- slv2_value_free(dc_title);
+ const uint32_t port_index = self->port_index(port_symbol);
+ if (port_index != (uint32_t)-1) {
+ self->set_parameter(port_index, *(const float*)value);
+ }
}
bool
LV2Plugin::load_preset(PresetRecord r)
{
- Plugin::load_preset(r);
-
-#ifdef HAVE_NEW_SLV2
- // New (>= 0.7.0) slv2 no longer supports SPARQL, but exposes blank nodes
- // so querying ports is possible with the simple/fast API
- SLV2Value lv2_port = slv2_value_new_uri(_world.world, NS_LV2 "port");
- SLV2Value lv2_symbol = slv2_value_new_uri(_world.world, NS_LV2 "symbol");
- SLV2Value pset_value = slv2_value_new_uri(_world.world, NS_PSET "value");
- SLV2Value preset = slv2_value_new_uri(_world.world, r.uri.c_str());
-
- SLV2Values ports = slv2_plugin_get_value_for_subject(_impl->plugin, preset, lv2_port);
- for (unsigned i = 0; i < slv2_values_size(ports); ++i) {
- SLV2Value port = slv2_values_get_at(ports, i);
- SLV2Value symbol = get_value(_impl->plugin, port, lv2_symbol);
- SLV2Value value = get_value(_impl->plugin, port, pset_value);
- if (value && slv2_value_is_float(value)) {
- set_parameter(_port_indices[slv2_value_as_string(symbol)],
- slv2_value_as_float(value));
- }
+ 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_state_restore(state, _impl->instance, set_port_value, this, 0, NULL);
+ lilv_state_free(state);
}
- slv2_values_free(ports);
- slv2_value_free(preset);
- slv2_value_free(pset_value);
- slv2_value_free(lv2_symbol);
- slv2_value_free(lv2_port);
-#else
- const string query = string(
- "PREFIX lv2p: <http://lv2plug.in/ns/dev/presets#>\n"
- "PREFIX dc: <http://dublincore.org/documents/dcmi-namespace/>\n"
- "SELECT ?sym ?val WHERE { <") + r.uri + "> lv2:port ?port . "
- " ?port lv2:symbol ?sym ; lv2p:value ?val . }";
- SLV2Results values = slv2_plugin_query_sparql(_impl->plugin, query.c_str());
- for (; !slv2_results_finished(values); slv2_results_next(values)) {
- SLV2Value sym = slv2_results_get_binding_value(values, 0);
- SLV2Value val = slv2_results_get_binding_value(values, 1);
- if (slv2_value_is_float(val)) {
- uint32_t index = _port_indices[slv2_value_as_string(sym)];
- set_parameter(index, slv2_value_as_float(val));
+ lilv_node_free(pset);
+ return state;
+}
+
+const void*
+ARDOUR::lv2plugin_get_port_value(const char* port_symbol,
+ void* user_data,
+ uint32_t* size,
+ uint32_t* type)
+{
+ LV2Plugin *plugin = (LV2Plugin *) user_data;
+
+ uint32_t index = plugin->port_index(port_symbol);
+ if (index != (uint32_t) -1) {
+ if (plugin->parameter_is_input(index) && plugin->parameter_is_control(index)) {
+ float *value;
+ *size = sizeof(float);
+ *type = plugin->_uri_map.uri_to_id(LV2_ATOM__Float);
+ value = &plugin->_shadow_data[index];
+
+ return value;
}
}
- slv2_results_free(values);
-#endif
- return true;
+
+ *size = *type = 0;
+ return NULL;
}
+
std::string
-LV2Plugin::do_save_preset(string /*name*/)
+LV2Plugin::do_save_preset(string name)
{
- return "";
+ 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"));
+
+ LilvState* state = lilv_state_new_from_instance(
+ _impl->plugin,
+ _impl->instance,
+ _uri_map.urid_map(),
+ scratch_dir().c_str(), // file_dir
+ bundle.c_str(), // copy_dir
+ bundle.c_str(), // link_dir
+ bundle.c_str(), // save_dir
+ lv2plugin_get_port_value, // get_value
+ (void*)this, // user_data
+ LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE, // flags
+ _features // features
+ );
+
+ lilv_state_set_label(state, name.c_str());
+ lilv_state_save(
+ _world.world, // world
+ _uri_map.urid_map(), // map
+ _uri_map.urid_unmap(), // unmap
+ state, // state
+ NULL, // uri (NULL = use file URI)
+ bundle.c_str(), // dir
+ file_name.c_str() // filename
+ );
+
+ lilv_state_free(state);
+
+ return Glib::filename_to_uri(Glib::build_filename(bundle, file_name));
}
void
-LV2Plugin::do_remove_preset(string /*name*/)
-{}
+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"
+ )
+ );
+ unlink(preset_file.c_str());
+}
bool
LV2Plugin::has_editor() const
return _impl->ui != NULL;
}
+bool
+LV2Plugin::has_message_output() const
+{
+ for (uint32_t i = 0; i < num_ports(); ++i) {
+ if ((_port_flags[i] & PORT_SEQUENCE) &&
+ (_port_flags[i] & PORT_OUTPUT)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+LV2Plugin::write_to(RingBuffer<uint8_t>* dest,
+ uint32_t index,
+ uint32_t protocol,
+ uint32_t size,
+ const uint8_t* body)
+{
+ const uint32_t buf_size = sizeof(UIMessage) + size;
+ uint8_t buf[buf_size];
+
+ UIMessage* msg = (UIMessage*)buf;
+ msg->index = index;
+ msg->protocol = protocol;
+ msg->size = size;
+ memcpy(msg + 1, body, size);
+
+ return (dest->write(buf, buf_size) == buf_size);
+}
+
+bool
+LV2Plugin::write_from_ui(uint32_t index,
+ uint32_t protocol,
+ uint32_t size,
+ const uint8_t* body)
+{
+ if (!_from_ui) {
+ _from_ui = new RingBuffer<uint8_t>(
+ _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS);
+ }
+
+ if (!write_to(_from_ui, index, protocol, size, body)) {
+ error << "Error writing from UI to plugin" << endmsg;
+ return false;
+ }
+ return true;
+}
+
+bool
+LV2Plugin::write_to_ui(uint32_t index,
+ uint32_t protocol,
+ uint32_t size,
+ const uint8_t* body)
+{
+ if (!write_to(_to_ui, index, protocol, size, body)) {
+ error << "Error writing from plugin to UI" << endmsg;
+ return false;
+ }
+ return true;
+}
+
+void
+LV2Plugin::enable_ui_emmission()
+{
+ if (!_to_ui) {
+ _to_ui = new RingBuffer<uint8_t>(
+ _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS);
+ }
+}
+
+void
+LV2Plugin::emit_to_ui(void* controller, UIMessageSink sink)
+{
+ if (!_to_ui) {
+ return;
+ }
+
+ uint32_t read_space = _to_ui->read_space();
+ while (read_space > sizeof(UIMessage)) {
+ UIMessage msg;
+ if (_to_ui->read((uint8_t*)&msg, sizeof(msg)) != sizeof(msg)) {
+ error << "Error reading from Plugin=>UI RingBuffer" << endmsg;
+ break;
+ }
+ uint8_t body[msg.size];
+ if (_to_ui->read(body, msg.size) != msg.size) {
+ error << "Error reading from Plugin=>UI RingBuffer" << endmsg;
+ break;
+ }
+
+ sink(controller, msg.index, msg.size, msg.protocol, body);
+
+ read_space -= sizeof(msg) + msg.size;
+ }
+}
+
+int
+LV2Plugin::work(uint32_t size, const void* data)
+{
+ return _impl->work_iface->work(
+ _impl->instance->lv2_handle, work_respond, this, size, data);
+}
+
+int
+LV2Plugin::work_response(uint32_t size, const void* data)
+{
+ return _impl->work_iface->work_response(
+ _impl->instance->lv2_handle, size, data);
+}
+
void
LV2Plugin::set_insert_info(const PluginInsert* insert)
{
set_parameter(port_id, atof(value));
}
- if ((prop = node.property("state-file")) != 0) {
- std::string state_path = Glib::build_filename(_session.plugins_dir(),
- prop->value());
-
- cout << "LV2 state path " << state_path << endl;
-
- // Get LV2 Persist extension data from plugin instance
- LV2_Persist* persist = (LV2_Persist*)extension_data(
- "http://lv2plug.in/ns/ext/persist");
- if (persist) {
- cout << "Loading LV2 state from " << state_path << endl;
- RDFF file = rdff_open(state_path.c_str(), false);
- LV2PersistState state(_uri_map);
- state.read(file);
- persist->restore(_impl->instance->lv2_handle,
- &LV2Plugin::lv2_persist_retrieve_callback,
- &state);
- rdff_close(file);
- } else {
- warning << string_compose(
- _("Plugin \"%1\% failed to return LV2 persist data"),
- unique_id());
+ _state_version = 0;
+ if ((prop = node.property("state-dir")) != 0) {
+ if (sscanf(prop->value().c_str(), "state%u", &_state_version) != 1) {
+ error << string_compose(
+ "LV2: failed to parse state version from \"%1\"",
+ prop->value()) << endmsg;
}
+
+ std::string state_file = Glib::build_filename(
+ plugin_dir(),
+ Glib::build_filename(prop->value(), "state.ttl"));
+
+ LilvState* state = lilv_state_new_from_file(
+ _world.world, _uri_map.urid_map(), NULL, state_file.c_str());
+
+ lilv_state_restore(state, _impl->instance, NULL, NULL, 0, NULL);
}
latency_compute_run();
int
LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, which);
-
- SLV2Value def, min, max;
- slv2_port_get_range(_impl->plugin, port, &def, &min, &max);
+ const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which);
+
+ LilvNode *def, *min, *max;
+ lilv_port_get_range(_impl->plugin, port, &def, &min, &max);
+
+ 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.lower = min ? lilv_node_as_float(min) : 0.0f;
+ desc.upper = max ? lilv_node_as_float(max) : 1.0f;
+ if (desc.sr_dependent) {
+ desc.lower *= _session.frame_rate ();
+ desc.upper *= _session.frame_rate ();
+ }
- desc.integer_step = slv2_port_has_property(_impl->plugin, port, _world.integer);
- desc.toggled = slv2_port_has_property(_impl->plugin, port, _world.toggled);
- desc.logarithmic = slv2_port_has_property(_impl->plugin, port, _world.logarithmic);
- desc.sr_dependent = slv2_port_has_property(_impl->plugin, port, _world.srate);
- desc.label = slv2_value_as_string(slv2_port_get_name(_impl->plugin, port));
- desc.lower = min ? slv2_value_as_float(min) : 0.0f;
- desc.upper = max ? slv2_value_as_float(max) : 1.0f;
desc.min_unbound = false; // TODO: LV2 extension required
desc.max_unbound = false; // TODO: LV2 extension required
desc.largestep = delta / 10.0f;
}
- slv2_value_free(def);
- slv2_value_free(min);
- slv2_value_free(max);
+ desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration);
+
+ lilv_node_free(def);
+ lilv_node_free(min);
+ lilv_node_free(max);
return 0;
}
LV2Plugin::describe_parameter(Evoral::Parameter which)
{
if (( which.type() == PluginAutomation) && ( which.id() < parameter_count()) ) {
- SLV2Value name = slv2_port_get_name(_impl->plugin,
- slv2_plugin_get_port_by_index(_impl->plugin, which.id()));
- string ret(slv2_value_as_string(name));
- slv2_value_free(name);
+
+ if (lilv_port_has_property(_impl->plugin,
+ lilv_plugin_get_port_by_index(_impl->plugin, which.id()), _world.ext_notOnGUI)) {
+ return X_("hidden");
+ }
+
+ if (lilv_port_has_property(_impl->plugin,
+ lilv_plugin_get_port_by_index(_impl->plugin, which.id()), _world.lv2_freewheeling)) {
+ return X_("hidden");
+ }
+
+ if (lilv_port_has_property(_impl->plugin,
+ lilv_plugin_get_port_by_index(_impl->plugin, which.id()), _world.lv2_sampleRate)) {
+ return X_("hidden");
+ }
+
+ if (lilv_port_has_property(_impl->plugin,
+ lilv_plugin_get_port_by_index(_impl->plugin, which.id()), _world.lv2_reportsLatency)) {
+ return X_("latency");
+ }
+
+ LilvNode* name = lilv_port_get_name(_impl->plugin,
+ lilv_plugin_get_port_by_index(_impl->plugin, which.id()));
+ string ret(lilv_node_as_string(name));
+ lilv_node_free(name);
return ret;
} else {
return "??";
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 activate\n", name()));
if (!_was_activated) {
- slv2_instance_activate(_impl->instance);
+ lilv_instance_activate(_impl->instance);
_was_activated = true;
}
}
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 deactivate\n", name()));
if (_was_activated) {
- slv2_instance_deactivate(_impl->instance);
+ lilv_instance_deactivate(_impl->instance);
_was_activated = false;
}
}
activate();
deactivate();
- slv2_instance_free(_impl->instance);
+ lilv_instance_free(_impl->instance);
_impl->instance = NULL;
}
+void
+LV2Plugin::allocate_atom_event_buffers()
+{
+ /* reserve local scratch buffers for ATOM event-queues */
+ const LilvPlugin* p = _impl->plugin;
+
+ /* count non-MIDI atom event-ports
+ * TODO: nicely ask drobilla to make a lilv_ call for that
+ */
+ int count_atom_out = 0;
+ int count_atom_in = 0;
+ int minimumSize = 32768; // TODO use a per-port minimum-size
+ 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)) {
+ LilvNodes* buffer_types = lilv_port_get_value(
+ p, port, _world.atom_bufferType);
+ 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_port_is_a(p, port, _world.lv2_InputPort)) {
+ count_atom_in++;
+ }
+ if (lilv_port_is_a(p, port, _world.lv2_OutputPort)) {
+ count_atom_out++;
+ }
+ 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;
+ if (min_size && lilv_node_is_int(min_size)) {
+ minimumSize = std::max(minimumSize, lilv_node_as_int(min_size));
+ }
+ lilv_nodes_free(min_size_v);
+ }
+ lilv_nodes_free(buffer_types);
+ lilv_nodes_free(atom_supports);
+ }
+ }
+
+ DEBUG_TRACE(DEBUG::LV2, string_compose("%1 need buffers for %2 atom-in and %3 atom-out event-ports\n",
+ name(), count_atom_in, count_atom_out));
+
+ const int total_atom_buffers = (count_atom_in + count_atom_out);
+ if (_atom_ev_buffers || total_atom_buffers == 0) {
+ return;
+ }
+
+ DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers\n", total_atom_buffers));
+ _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);
+ }
+ _atom_ev_buffers[total_atom_buffers] = 0;
+ return;
+}
+
+/** Write an ardour position/time/tempo/meter as an LV2 event.
+ * @return true on success.
+ */
+static bool
+write_position(LV2_Atom_Forge* forge,
+ LV2_Evbuf* buf,
+ const TempoMetric& t,
+ Timecode::BBT_Time& bbt,
+ double speed,
+ framepos_t position,
+ framecnt_t offset)
+{
+ 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);
+ lv2_atom_forge_long(forge, position);
+ lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_speed, 0);
+ lv2_atom_forge_float(forge, speed);
+ lv2_atom_forge_property_head(forge, LV2Plugin::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, LV2Plugin::urids.time_bar, 0);
+ lv2_atom_forge_long(forge, bbt.bars - 1);
+ lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatUnit, 0);
+ lv2_atom_forge_int(forge, t.meter().note_divisor());
+ lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerBar, 0);
+ lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
+ lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerMinute, 0);
+ lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
+
+ LV2_Evbuf_Iterator end = lv2_evbuf_end(buf);
+ const LV2_Atom* const atom = (const LV2_Atom*)pos_buf;
+ return lv2_evbuf_write(&end, offset, 0, atom->type, atom->size,
+ (const uint8_t*)(atom + 1));
+}
+
int
LV2Plugin::connect_and_run(BufferSet& bufs,
ChanMapping in_map, ChanMapping out_map,
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);
+
+ if (_freewheel_control_port) {
+ *_freewheel_control_port = _session.engine().freewheeling();
+ }
+
+ if (_bpm_control_port) {
+ *_bpm_control_port = tmetric.tempo().beats_per_minute();
+ }
+
+ ChanCount bufs_count;
+ bufs_count.set(DataType::AUDIO, 1);
+ bufs_count.set(DataType::MIDI, 1);
+ BufferSet& silent_bufs = _session.get_silent_buffers(bufs_count);
+ BufferSet& scratch_bufs = _session.get_scratch_buffers(bufs_count);
+ uint32_t const num_ports = parameter_count();
+ uint32_t const nil_index = std::numeric_limits<uint32_t>::max();
+
uint32_t audio_in_index = 0;
uint32_t audio_out_index = 0;
uint32_t midi_in_index = 0;
uint32_t midi_out_index = 0;
- for (uint32_t port_index = 0; port_index < parameter_count(); ++port_index) {
- if (parameter_is_audio(port_index)) {
- if (parameter_is_input(port_index)) {
- const uint32_t buf_index = in_map.get(DataType::AUDIO, audio_in_index++);
- slv2_instance_connect_port(_impl->instance, port_index,
- bufs.get_audio(buf_index).data(offset));
- } else if (parameter_is_output(port_index)) {
- const uint32_t buf_index = out_map.get(DataType::AUDIO, audio_out_index++);
- //cerr << port_index << " : " << " AUDIO OUT " << buf_index << endl;
- slv2_instance_connect_port(_impl->instance, port_index,
- bufs.get_audio(buf_index).data(offset));
+ uint32_t atom_port_index = 0;
+ for (uint32_t port_index = 0; port_index < num_ports; ++port_index) {
+ void* buf = NULL;
+ uint32_t index = nil_index;
+ PortFlags flags = _port_flags[port_index];
+ bool valid = false;
+ if (flags & PORT_AUDIO) {
+ if (flags & PORT_INPUT) {
+ index = in_map.get(DataType::AUDIO, audio_in_index++, &valid);
+ buf = (valid)
+ ? bufs.get_audio(index).data(offset)
+ : silent_bufs.get_audio(0).data(offset);
+ } else {
+ index = out_map.get(DataType::AUDIO, audio_out_index++, &valid);
+ buf = (valid)
+ ? bufs.get_audio(index).data(offset)
+ : scratch_bufs.get_audio(0).data(offset);
}
- } else if (parameter_is_midi(port_index)) {
- if (parameter_is_input(port_index)) {
- const uint32_t buf_index = in_map.get(DataType::MIDI, midi_in_index++);
- slv2_instance_connect_port(_impl->instance, port_index,
- bufs.get_lv2_midi(true, buf_index).data());
- } else if (parameter_is_output(port_index)) {
- const uint32_t buf_index = out_map.get(DataType::MIDI, midi_out_index++);
- slv2_instance_connect_port(_impl->instance, port_index,
- bufs.get_lv2_midi(false, buf_index).data());
+ } else if (flags & (PORT_EVENT|PORT_SEQUENCE)) {
+ /* FIXME: The checks here for bufs.count().n_midi() > index shouldn't
+ be necessary, but the mapping is illegal in some cases. Ideally
+ that should be fixed, but this is easier...
+ */
+ if (flags & PORT_MIDI) {
+ if (flags & PORT_INPUT) {
+ index = in_map.get(DataType::MIDI, midi_in_index++, &valid);
+ } else {
+ index = out_map.get(DataType::MIDI, midi_out_index++, &valid);
+ }
+ if (valid && bufs.count().n_midi() > index) {
+ /* Note, ensure_lv2_bufsize() is not RT safe!
+ * However free()/alloc() is only called if a
+ * plugin requires a rsz:minimumSize buffersize
+ * and the existing buffer if smaller.
+ */
+ bufs.ensure_lv2_bufsize((flags & PORT_INPUT), index, _port_minimumSize[port_index]);
+ _ev_buffers[port_index] = bufs.get_lv2_midi(
+ (flags & PORT_INPUT), index, (flags & PORT_EVENT));
+ }
+ } else if ((flags & PORT_POSITION) && (flags & PORT_INPUT)) {
+ lv2_evbuf_reset(_atom_ev_buffers[atom_port_index], true);
+ _ev_buffers[port_index] = _atom_ev_buffers[atom_port_index++];
+ valid = true;
+ }
+
+ 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);
+ write_position(&_impl->forge, _ev_buffers[port_index],
+ tmetric, bbt, _session.transport_speed(),
+ _session.transport_frame(), 0);
+ }
+ }
+
+ // Get MIDI iterator range (empty range if no MIDI)
+ MidiBuffer::iterator m = (index != nil_index)
+ ? bufs.get_midi(index).begin()
+ : silent_bufs.get_midi(0).end();
+ MidiBuffer::iterator m_end = (index != nil_index)
+ ? bufs.get_midi(index).end()
+ : m;
+
+ // Now merge MIDI and any transport events into the buffer
+ const uint32_t type = LV2Plugin::urids.midi_MidiEvent;
+ const framepos_t tend = _session.transport_frame() + nframes;
+ ++metric_i;
+ while (m != m_end || (metric_i != tmap.metrics_end() &&
+ (*metric_i)->frame() < tend)) {
+ MetricSection* metric = (metric_i != tmap.metrics_end())
+ ? *metric_i : NULL;
+ if (m != m_end && (!metric || metric->frame() > (*m).time())) {
+ const Evoral::MIDIEvent<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());
+ ++m;
+ } else {
+ tmetric.set_metric(metric);
+ bbt = metric->start();
+ write_position(&_impl->forge, _ev_buffers[port_index],
+ tmetric, bbt, _session.transport_speed(),
+ metric->frame(),
+ metric->frame() - _session.transport_frame());
+ ++metric_i;
+ }
+ }
+ } else if (!valid) {
+ // Nothing we understand or care about, connect to scratch
+ _ev_buffers[port_index] = silent_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
+ }
+ lilv_instance_connect_port(_impl->instance, port_index, buf);
+ }
+
+ // Read messages from UI and push into appropriate buffers
+ if (_from_ui) {
+ uint32_t read_space = _from_ui->read_space();
+ while (read_space > sizeof(UIMessage)) {
+ UIMessage msg;
+ if (_from_ui->read((uint8_t*)&msg, sizeof(msg)) != sizeof(msg)) {
+ error << "Error reading from UI=>Plugin RingBuffer" << endmsg;
+ break;
+ }
+ uint8_t body[msg.size];
+ if (_from_ui->read(body, msg.size) != msg.size) {
+ error << "Error reading from UI=>Plugin RingBuffer" << endmsg;
+ break;
+ }
+ if (msg.protocol == 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;
+ if (!lv2_evbuf_write(&i, nframes, 0, atom->type, atom->size,
+ (const uint8_t*)(atom + 1))) {
+ error << "Failed to write data to LV2 event buffer\n";
+ }
+ } else {
+ error << "Received unknown message type from UI" << endmsg;
}
- } else if (!parameter_is_control(port_index)) {
- // Optional port (it'd better be if we've made it this far...)
- slv2_instance_connect_port(_impl->instance, port_index, NULL);
+ read_space -= sizeof(UIMessage) + msg.size;
}
}
run(nframes);
midi_out_index = 0;
- for (uint32_t port_index = 0; port_index < parameter_count(); ++port_index) {
- if (parameter_is_midi(port_index) && parameter_is_output(port_index)) {
- const uint32_t buf_index = out_map.get(DataType::MIDI, midi_out_index++);
- bufs.flush_lv2_midi(true, buf_index);
+ for (uint32_t port_index = 0; port_index < num_ports; ++port_index) {
+ PortFlags flags = _port_flags[port_index];
+ bool valid = false;
+
+ /* TODO ask drobilla about comment
+ * "Make Ardour event buffers generic so plugins can communicate"
+ * in libs/ardour/buffer_set.cc:310
+ *
+ * ideally the user could choose which of the following two modes
+ * to use (e.g. instrument/effect chains MIDI OUT vs MIDI TRHU).
+ *
+ * This implementation follows the discussion on IRC Mar 16 2013 16:47 UTC
+ * 16:51 < drobilla> rgareus: [..] i.e always replace with MIDI output [of LV2 plugin] if it's there
+ * 16:52 < drobilla> rgareus: That would probably be good enough [..] to make users not complain
+ * for quite a while at least ;)
+ */
+ // copy output of LV2 plugin's MIDI port to Ardour MIDI buffers -- MIDI OUT
+ if ((flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE|PORT_MIDI))) {
+ const uint32_t buf_index = out_map.get(
+ DataType::MIDI, midi_out_index++, &valid);
+ if (valid) {
+ bufs.forward_lv2_midi(_ev_buffers[port_index], buf_index);
+ }
+ }
+ // Flush MIDI (write back to Ardour MIDI buffers) -- MIDI THRU
+ else if ((flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
+ const uint32_t buf_index = out_map.get(
+ DataType::MIDI, midi_out_index++, &valid);
+ if (valid) {
+ bufs.flush_lv2_midi(true, buf_index);
+ }
+ }
+
+ // Write messages to UI
+ if (_to_ui && (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);
+ i = lv2_evbuf_next(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,
+ size + sizeof(LV2_Atom),
+ data - sizeof(LV2_Atom));
+ }
}
}
cycles_t now = get_cycles();
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);
+
return 0;
}
bool
LV2Plugin::parameter_is_control(uint32_t param) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, param);
- return slv2_port_is_a(_impl->plugin, port, _world.control_class);
+ assert(param < _port_flags.size());
+ return _port_flags[param] & PORT_CONTROL;
}
bool
LV2Plugin::parameter_is_audio(uint32_t param) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, param);
- return slv2_port_is_a(_impl->plugin, port, _world.audio_class);
+ assert(param < _port_flags.size());
+ return _port_flags[param] & PORT_AUDIO;
}
bool
-LV2Plugin::parameter_is_midi(uint32_t param) const
+LV2Plugin::parameter_is_event(uint32_t param) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, param);
- return slv2_port_is_a(_impl->plugin, port, _world.event_class);
- // && slv2_port_supports_event(_impl->plugin, port, _world.midi_class);
+ assert(param < _port_flags.size());
+ return _port_flags[param] & PORT_EVENT;
}
bool
LV2Plugin::parameter_is_output(uint32_t param) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, param);
- return slv2_port_is_a(_impl->plugin, port, _world.output_class);
+ assert(param < _port_flags.size());
+ return _port_flags[param] & PORT_OUTPUT;
}
bool
LV2Plugin::parameter_is_input(uint32_t param) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, param);
- return slv2_port_is_a(_impl->plugin, port, _world.input_class);
+ assert(param < _port_flags.size());
+ return _port_flags[param] & PORT_INPUT;
}
void
boost::shared_ptr<Plugin::ScalePoints>
LV2Plugin::get_scale_points(uint32_t port_index) const
{
- SLV2Port port = slv2_plugin_get_port_by_index(_impl->plugin, port_index);
- SLV2ScalePoints points = slv2_port_get_scale_points(_impl->plugin, port);
+ 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;
if (!points) {
ret = boost::shared_ptr<Plugin::ScalePoints>(new ScalePoints());
- for (unsigned i = 0; i < slv2_scale_points_size(points); ++i) {
- SLV2ScalePoint p = slv2_scale_points_get_at(points, i);
- SLV2Value label = slv2_scale_point_get_label(p);
- SLV2Value value = slv2_scale_point_get_value(p);
- if (label && (slv2_value_is_float(value) || slv2_value_is_int(value))) {
- ret->insert(make_pair(slv2_value_as_string(label),
- slv2_value_as_float(value)));
+ LILV_FOREACH(scale_points, i, points) {
+ const LilvScalePoint* p = lilv_scale_points_get(points, i);
+ const LilvNode* label = lilv_scale_point_get_label(p);
+ const LilvNode* value = lilv_scale_point_get_value(p);
+ if (label && (lilv_node_is_float(value) || lilv_node_is_int(value))) {
+ ret->insert(make_pair(lilv_node_as_string(label),
+ lilv_node_as_float(value)));
}
}
- slv2_scale_points_free(points);
+ lilv_scale_points_free(points);
return ret;
}
void
LV2Plugin::run(pframes_t nframes)
{
- for (uint32_t i = 0; i < parameter_count(); ++i) {
+ uint32_t const N = parameter_count();
+ for (uint32_t i = 0; i < N; ++i) {
if (parameter_is_control(i) && parameter_is_input(i)) {
_control_data[i] = _shadow_data[i];
}
}
- slv2_instance_run(_impl->instance, nframes);
+ lilv_instance_run(_impl->instance, nframes);
+
+ if (_impl->work_iface) {
+ _worker->emit_responses();
+ if (_impl->work_iface->end_run) {
+ _impl->work_iface->end_run(_impl->instance->lv2_handle);
+ }
+ }
}
void
while (port_index < parameter_count()) {
if (parameter_is_audio(port_index)) {
if (parameter_is_input(port_index)) {
- slv2_instance_connect_port(_impl->instance, port_index, buffer);
+ lilv_instance_connect_port(_impl->instance, port_index, buffer);
in_index++;
} else if (parameter_is_output(port_index)) {
- slv2_instance_connect_port(_impl->instance, port_index, buffer);
+ lilv_instance_connect_port(_impl->instance, port_index, buffer);
out_index++;
}
}
deactivate();
}
+const LilvPort*
+LV2Plugin::Impl::designated_input (const char* uri, void** bufptrs[], void** bufptr)
+{
+ const LilvPort* port = NULL;
+ LilvNode* designation = lilv_new_uri(_world.world, uri);
+ port = lilv_plugin_get_port_by_designation(
+ plugin, _world.lv2_InputPort, designation);
+ lilv_node_free(designation);
+ if (port) {
+ bufptrs[lilv_port_get_index(plugin, port)] = bufptr;
+ }
+ return port;
+}
+
+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));
+}
+
+
LV2World::LV2World()
- : world(slv2_world_new())
-{
- slv2_world_load_all(world);
- input_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_INPUT);
- output_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_OUTPUT);
- control_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_CONTROL);
- audio_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_AUDIO);
- event_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_EVENT);
- midi_class = slv2_value_new_uri(world, SLV2_EVENT_CLASS_MIDI);
- in_place_broken = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "inPlaceBroken");
- integer = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "integer");
- toggled = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "toggled");
- srate = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "sampleRate");
- gtk_gui = slv2_value_new_uri(world, NS_UI "GtkUI");
- external_gui = slv2_value_new_uri(world, NS_UI "external");
- logarithmic = slv2_value_new_uri(world, "http://lv2plug.in/ns/dev/extportinfo#logarithmic");
+ : 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);
+ atom_bufferType = lilv_new_uri(world, LV2_ATOM__bufferType);
+ atom_supports = lilv_new_uri(world, LV2_ATOM__supports);
+ atom_eventTransfer = lilv_new_uri(world, LV2_ATOM__eventTransfer);
+ 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);
+ 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_integer = lilv_new_uri(world, LV2_CORE__integer);
+ 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_enumeration = lilv_new_uri(world, LV2_CORE__enumeration);
+ lv2_freewheeling = lilv_new_uri(world, LV2_CORE__freeWheeling);
+ midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT);
+ rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment");
+ 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");
}
LV2World::~LV2World()
{
- slv2_value_free(input_class);
- slv2_value_free(output_class);
- slv2_value_free(control_class);
- slv2_value_free(audio_class);
- slv2_value_free(event_class);
- slv2_value_free(midi_class);
- slv2_value_free(in_place_broken);
+ 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(midi_MidiEvent);
+ lilv_node_free(lv2_enumeration);
+ lilv_node_free(lv2_freewheeling);
+ lilv_node_free(lv2_toggled);
+ lilv_node_free(lv2_sampleRate);
+ lilv_node_free(lv2_reportsLatency);
+ lilv_node_free(lv2_integer);
+ 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_notOnGUI);
+ lilv_node_free(ext_logarithmic);
+ lilv_node_free(ev_EventPort);
+ lilv_node_free(atom_supports);
+ lilv_node_free(atom_eventTransfer);
+ lilv_node_free(atom_bufferType);
+ lilv_node_free(atom_Sequence);
+ lilv_node_free(atom_Chunk);
+ lilv_node_free(atom_AtomPort);
+}
+
+void
+LV2World::load_bundled_plugins()
+{
+ if (!_bundle_checked) {
+ cout << "Scanning folders for bundled LV2s: " << ARDOUR::lv2_bundled_search_path().to_string() << endl;
+ PathScanner scanner;
+ vector<string *> *plugin_objects = scanner (ARDOUR::lv2_bundled_search_path().to_string(), lv2_filter, 0, true, true);
+ if (plugin_objects) {
+ for ( vector<string *>::iterator x = plugin_objects->begin(); x != plugin_objects->end (); ++x) {
+#ifdef WINDOWS
+ string uri = "file:///" + **x + "/";
+#else
+ string uri = "file://" + **x + "/";
+#endif
+ LilvNode *node = lilv_new_uri(world, uri.c_str());
+ lilv_world_load_bundle(world, node);
+ lilv_node_free(node);
+ }
+ }
+ delete (plugin_objects);
+ }
}
-LV2PluginInfo::LV2PluginInfo (void* c_plugin)
+LV2PluginInfo::LV2PluginInfo (const void* c_plugin)
: _c_plugin(c_plugin)
{
type = ARDOUR::LV2;
PluginPtr plugin;
plugin.reset(new LV2Plugin(session.engine(), session,
- (SLV2Plugin)_c_plugin,
+ (const LilvPlugin*)_c_plugin,
session.frame_rate()));
plugin->set_info(PluginInfoPtr(new LV2PluginInfo(*this)));
PluginInfoList*
LV2PluginInfo::discover()
{
- PluginInfoList* plugs = new PluginInfoList;
- SLV2Plugins plugins = slv2_world_get_all_plugins(_world.world);
+ _world.load_bundled_plugins();
- cerr << "LV2: Discovering " << slv2_plugins_size(plugins) << " plugins" << endl;
+ PluginInfoList* plugs = new PluginInfoList;
+ const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world);
- for (unsigned i = 0; i < slv2_plugins_size(plugins); ++i) {
- SLV2Plugin p = slv2_plugins_get_at(plugins, i);
- LV2PluginInfoPtr info(new LV2PluginInfo(p));
+ info << "LV2: Discovering " << lilv_plugins_size(plugins) << " plugins" << endmsg;
- SLV2Value name = slv2_plugin_get_name(p);
+ LILV_FOREACH(plugins, i, plugins) {
+ const LilvPlugin* p = lilv_plugins_get(plugins, i);
+ LV2PluginInfoPtr info(new LV2PluginInfo((const void*)p));
- if (!name) {
- cerr << "LV2: invalid plugin\n";
+ LilvNode* name = lilv_plugin_get_name(p);
+ if (!name || !lilv_plugin_get_port_by_index(p, 0)) {
+ warning << "Ignoring invalid LV2 plugin "
+ << lilv_node_as_string(lilv_plugin_get_uri(p))
+ << endmsg;
continue;
}
info->type = LV2;
- info->name = string(slv2_value_as_string(name));
- slv2_value_free(name);
+ info->name = string(lilv_node_as_string(name));
+ lilv_node_free(name);
- SLV2PluginClass pclass = slv2_plugin_get_class(p);
- SLV2Value label = slv2_plugin_class_get_label(pclass);
- info->category = slv2_value_as_string(label);
+ const LilvPluginClass* pclass = lilv_plugin_get_class(p);
+ const LilvNode* label = lilv_plugin_class_get_label(pclass);
+ info->category = lilv_node_as_string(label);
- SLV2Value author_name = slv2_plugin_get_author_name(p);
- info->creator = author_name ? string(slv2_value_as_string(author_name)) : "Unknown";
- slv2_value_free(author_name);
+ LilvNode* author_name = lilv_plugin_get_author_name(p);
+ info->creator = author_name ? string(lilv_node_as_string(author_name)) : "Unknown";
+ lilv_node_free(author_name);
info->path = "/NOPATH"; // Meaningless for LV2
+ /* count atom-event-ports that feature
+ * atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent>
+ *
+ * TODO: nicely ask drobilla to make a lilv_ call for that
+ */
+ int count_midi_out = 0;
+ 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)) {
+ LilvNodes* buffer_types = lilv_port_get_value(
+ p, port, _world.atom_bufferType);
+ 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_port_is_a(p, port, _world.lv2_InputPort)) {
+ count_midi_in++;
+ }
+ if (lilv_port_is_a(p, port, _world.lv2_OutputPort)) {
+ count_midi_out++;
+ }
+ }
+ lilv_nodes_free(buffer_types);
+ lilv_nodes_free(atom_supports);
+ }
+ }
+
info->n_inputs.set_audio(
- slv2_plugin_get_num_ports_of_class(
- p, _world.input_class, _world.audio_class, NULL));
+ lilv_plugin_get_num_ports_of_class(
+ p, _world.lv2_InputPort, _world.lv2_AudioPort, NULL));
info->n_inputs.set_midi(
- slv2_plugin_get_num_ports_of_class(
- p, _world.input_class, _world.event_class, NULL));
+ lilv_plugin_get_num_ports_of_class(
+ p, _world.lv2_InputPort, _world.ev_EventPort, NULL)
+ + count_midi_in);
info->n_outputs.set_audio(
- slv2_plugin_get_num_ports_of_class(
- p, _world.output_class, _world.audio_class, NULL));
+ lilv_plugin_get_num_ports_of_class(
+ p, _world.lv2_OutputPort, _world.lv2_AudioPort, NULL));
info->n_outputs.set_midi(
- slv2_plugin_get_num_ports_of_class(
- p, _world.output_class, _world.event_class, NULL));
+ lilv_plugin_get_num_ports_of_class(
+ p, _world.lv2_OutputPort, _world.ev_EventPort, NULL)
+ + count_midi_out);
- info->unique_id = slv2_value_as_uri(slv2_plugin_get_uri(p));
+ info->unique_id = lilv_node_as_uri(lilv_plugin_get_uri(p));
info->index = 0; // Meaningless for LV2
plugs->push_back(info);
}
- cerr << "Done LV2 discovery" << endl;
-
return plugs;
}