#include <boost/utility.hpp>
+#include "pbd/pathscanner.h"
#include "pbd/compose.h"
#include "pbd/error.h"
#include "pbd/xml++.h"
#include "libardour-config.h"
-#include "ardour/types.h"
#include "ardour/audio_buffer.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/lv2_plugin.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 "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 <suil/suil.h>
#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.
+*/
+static const size_t NBUFS = 4;
+
using namespace std;
using namespace ARDOUR;
using namespace PBD;
LV2World ();
~LV2World ();
+ void load_bundled_plugins();
+
LilvWorld* world;
LilvNode* atom_AtomPort;
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;
+ LilvNode* ui_externalkx;
+ LilvNode* units_unit;
+ LilvNode* units_midiNote;
+
+private:
+ bool _bundle_checked;
};
static LV2World _world;
/* log extension */
static int
-log_vprintf(LV2_Log_Handle handle,
+log_vprintf(LV2_Log_Handle /*handle*/,
LV2_URID type,
const char* fmt,
va_list args)
/** Find the LV2 input port with the given designation.
* If found, bufptrs[port_index] will be set to bufptr.
*/
- LilvPort* designated_input (const char* uri, void** bufptrs[], void** bufptr);
+ const LilvPort* designated_input (const char* uri, void** bufptrs[], void** bufptr);
const LilvPlugin* plugin;
const LilvUI* ui;
_bpm_control_port = 0;
_freewheel_control_port = 0;
_latency_control_port = 0;
- _position_seq_port_idx = std::numeric_limits<uint32_t>::max();
_next_cycle_start = std::numeric_limits<framepos_t>::max();
_next_cycle_speed = 1.0;
- _block_length = _engine.frames_per_cycle();
+ _block_length = _engine.samples_per_cycle();
_seq_size = _engine.raw_buffer_size(DataType::MIDI);
_state_version = 0;
_was_activated = false;
_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;
lilv_node_free(state_uri);
lilv_node_free(state_iface_uri);
- _features = (LV2_Feature**)calloc(10, sizeof(LV2_Feature*));
+ _features = (LV2_Feature**)calloc(11, sizeof(LV2_Feature*));
_features[0] = &_instance_access_feature;
_features[1] = &_data_access_feature;
_features[2] = &_make_path_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
+
lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map());
- unsigned n_features = 7;
#ifdef HAVE_NEW_LV2
LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
LV2_Options_Option options[] = {
if (lilv_plugin_has_feature(plugin, worker_schedule)) {
LV2_Worker_Schedule* schedule = (LV2_Worker_Schedule*)malloc(
sizeof(LV2_Worker_Schedule));
- _worker = new Worker(this, 4096);
+ 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;
_data_access_extension_data.extension_data = _impl->instance->lv2_descriptor->extension_data;
_data_access_feature.data = &_data_access_extension_data;
- _impl->work_iface = (const LV2_Worker_Interface*)extension_data(
- LV2_WORKER__interface);
+ 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(
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();
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;
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);
_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_MESSAGE;
- } else {
- flags |= PORT_ATOM;
+ flags |= PORT_MIDI;
}
if (lilv_nodes_contains(atom_supports, _world.time_Position)) {
- _position_seq_port_idx = i;
+ 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 {
}
_port_flags.push_back(flags);
+ _port_minimumSize.push_back(minimumSize);
}
_control_data = new float[num_ports];
if (!_impl->ui) {
LILV_FOREACH(uis, i, uis) {
const LilvUI* ui = lilv_uis_get(uis, i);
- if (lilv_ui_is_a(ui, _world.ui_external)) {
+ if (lilv_ui_is_a(ui, _world.ui_externalkx)) {
_impl->ui = ui;
_impl->ui_type = _world.ui_external;
break;
}
+ if (lilv_ui_is_a(ui, _world.ui_external)) {
+ _impl->ui = ui;
+ _impl->ui_type = _world.ui_external;
+ }
}
}
}
if (!_impl->ui) {
return false;
}
- return lilv_ui_is_a(_impl->ui, _world.ui_external);
+ return lilv_ui_is_a(_impl->ui, _world.ui_external) || lilv_ui_is_a(_impl->ui, _world.ui_externalkx);
+}
+
+bool
+LV2Plugin::is_external_kx() const
+{
+ if (!_impl->ui) {
+ return false;
+ }
+ return lilv_ui_is_a(_impl->ui, _world.ui_externalkx);
}
bool
DEBUG_TRACE(DEBUG::LV2, string_compose("new file path %1 => %2\n",
path, abs_path));
- std::cerr << "MAKE PATH " << path
- << " => " << g_strndup(abs_path.c_str(), abs_path.length())
- << std::endl;
return g_strndup(abs_path.c_str(), abs_path.length());
}
}
if (_has_state_interface) {
- cout << "LV2 " << name() << " has state interface" << endl;
// 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);
- cout << "NEW DIR: " << new_dir << endl;
-
LilvState* state = lilv_state_new_from_instance(
_impl->plugin,
_impl->instance,
lilv_state_free(_impl->state);
_impl->state = state;
-
- cout << "Saved LV2 state to " << state_dir(_state_version) << endl;
} else {
// State is identical, decrement version and nuke directory
- cout << "LV2 state identical, not saving" << endl;
lilv_state_free(state);
remove_directory(new_dir);
--_state_version;
}
root->add_property("state-dir", string_compose("state%1", _state_version));
- } else {
- cout << "LV2 " << name() << " has no state interface." << endl;
}
}
lilv_node_as_string(name))));
} else {
warning << string_compose(
- _("Plugin \"%1\% preset \"%2%\" is missing a label\n"),
+ _("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;
}
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
+ }
+
+ 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)
{
- std::map<std::string,uint32_t>::iterator it;
-
- LilvNode* lv2_port = lilv_new_uri(_world.world, LILV_NS_LV2 "port");
- LilvNode* lv2_symbol = lilv_new_uri(_world.world, LILV_NS_LV2 "symbol");
- LilvNode* preset = lilv_new_uri(_world.world, r.uri.c_str());
- LilvNode* pset_value = lilv_new_uri(_world.world, LV2_PRESETS__value);
-
- LilvNodes* ports = lilv_world_find_nodes(_world.world, preset, lv2_port, NULL);
- LILV_FOREACH(nodes, i, ports) {
- const LilvNode* port = lilv_nodes_get(ports, i);
- const LilvNode* symbol = get_value(_world.world, port, lv2_symbol);
- const LilvNode* value = get_value(_world.world, port, pset_value);
- if (value && lilv_node_is_float(value)) {
- it = _port_indices.find(lilv_node_as_string(symbol));
- if (it != _port_indices.end()) {
- set_parameter(it->second,lilv_node_as_float(value));
- }
- }
- }
- lilv_nodes_free(ports);
+ 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);
- lilv_node_free(pset_value);
- lilv_node_free(preset);
- lilv_node_free(lv2_symbol);
- lilv_node_free(lv2_port);
-
- Plugin::load_preset(r);
+ if (state) {
+ lilv_state_restore(state, _impl->instance, set_port_value, this, 0, NULL);
+ lilv_state_free(state);
+ }
- return true;
+ lilv_node_free(pset);
+ return state;
}
const void*
uint32_t* size,
uint32_t* type)
{
- // cerr << "get_port_value(" << port_symbol << ", ...) ... ";
LV2Plugin *plugin = (LV2Plugin *) user_data;
uint32_t index = plugin->port_index(port_symbol);
*size = sizeof(float);
*type = plugin->_uri_map.uri_to_id(LV2_ATOM__Float);
value = &plugin->_shadow_data[index];
- // cerr << "index="<< index << ",*size=" << *size << ",*type=" << *type << ",*value=" << *value << endl;
return value;
}
- // cerr << "port is not input control port! ";
}
- // cerr << "returning NULL!" << endl;
*size = *type = 0;
return NULL;
}
std::string
LV2Plugin::do_save_preset(string name)
{
- // cerr << "LV2Plugin::do_save_preset(" << name << ")" << endl;
-
- string pset_uri = uri();
- pset_uri += "#";
- pset_uri += name;
-
- string save_dir = Glib::build_filename(
+ 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", "presets")
- );
+ 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
- NULL, // copy_dir
- NULL, // link_dir
- save_dir.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
+ 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
+ _uri_map.urid_map(), // map
+ _uri_map.urid_unmap(), // unmap
state, // state
- pset_uri.c_str(), // uri
- save_dir.c_str(), // dir
- (name + ".ttl").c_str() // filename
+ NULL, // uri (NULL = use file URI)
+ bundle.c_str(), // dir
+ file_name.c_str() // filename
);
lilv_state_free(state);
- return pset_uri;
+
+ return Glib::filename_to_uri(Glib::build_filename(bundle, file_name));
}
void
LV2Plugin::has_message_output() const
{
for (uint32_t i = 0; i < num_ports(); ++i) {
- if ((_port_flags[i] & (PORT_MESSAGE|PORT_ATOM)) && _port_flags[i] & PORT_OUTPUT) {
+ if ((_port_flags[i] & PORT_SEQUENCE) &&
+ (_port_flags[i] & PORT_OUTPUT)) {
return true;
}
}
return false;
}
-void
+bool
LV2Plugin::write_to(RingBuffer<uint8_t>* dest,
uint32_t index,
uint32_t protocol,
msg->size = size;
memcpy(msg + 1, body, size);
- if (dest->write(buf, buf_size) != buf_size) {
- error << "Error writing to UI=>Plugin RingBuffer" << endmsg;
- }
+ return (dest->write(buf, buf_size) == buf_size);
}
-void
+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>(4096);
+ size_t rbs = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS;
+ /* buffer data communication from plugin UI to plugin instance.
+ * this buffer needs to potentially hold
+ * (port's minimumSize) * (audio-periods) / (UI-periods)
+ * bytes.
+ *
+ * e.g 48kSPS / 128fpp -> audio-periods = 375 Hz
+ * ui-periods = 25 Hz (SuperRapidScreenUpdate)
+ * default minimumSize = 32K (see LV2Plugin::allocate_atom_event_buffers()
+ *
+ * it is NOT safe to overflow (msg.size will be misinterpreted)
+ */
+ uint32_t bufsiz = 32768;
+ if (_atom_ev_buffers && _atom_ev_buffers[0]) {
+ bufsiz = lv2_evbuf_get_capacity(_atom_ev_buffers[0]);
+ }
+ rbs = max((size_t) bufsiz * 8, rbs);
+ _from_ui = new RingBuffer<uint8_t>(rbs);
}
- write_to(_from_ui, index, protocol, size, body);
+ if (!write_to(_from_ui, index, protocol, size, body)) {
+ error << "Error writing from UI to plugin" << endmsg;
+ return false;
+ }
+ return true;
}
-void
+bool
LV2Plugin::write_to_ui(uint32_t index,
uint32_t protocol,
uint32_t size,
const uint8_t* body)
{
- write_to(_to_ui, index, protocol, size, 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>(4096);
+ /* see note in LV2Plugin::write_from_ui() */
+ uint32_t bufsiz = 32768;
+ if (_atom_ev_buffers && _atom_ev_buffers[0]) {
+ bufsiz = lv2_evbuf_get_capacity(_atom_ev_buffers[0]);
+ }
+ size_t rbs = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS;
+ rbs = max((size_t) bufsiz * 8, rbs);
+ _to_ui = new RingBuffer<uint8_t>(rbs);
}
}
return -1;
}
+#ifndef NO_PLUGIN_STATE
+
if (version < 3000) {
nodes = node.children("port");
} else {
plugin_dir(),
Glib::build_filename(prop->value(), "state.ttl"));
- cout << "Loading LV2 state from " << state_file << endl;
LilvState* state = lilv_state_new_from_file(
_world.world, _uri_map.urid_map(), NULL, state_file.c_str());
}
latency_compute_run();
+#endif
return Plugin::set_state(node, version);
}
{
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which);
+ 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);
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.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;
+ desc.midinote = lilv_nodes_contains(portunits, _world.units_midiNote);
+
if (desc.sr_dependent) {
desc.lower *= _session.frame_rate ();
desc.upper *= _session.frame_rate ();
lilv_node_free(def);
lilv_node_free(min);
lilv_node_free(max);
+ lilv_nodes_free(portunits);
return 0;
}
LV2Plugin::describe_parameter(Evoral::Parameter which)
{
if (( which.type() == PluginAutomation) && ( which.id() < parameter_count()) ) {
+
+ 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_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));
*/
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)) {
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);
return;
}
- DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers\n", total_atom_buffers));
+ DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers of %d 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(32768, LV2_EVBUF_ATOM,
+ _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;
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_float(forge, bbt.bars - 1);
+ lv2_atom_forge_long(forge, bbt.bars - 1);
lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatUnit, 0);
- lv2_atom_forge_float(forge, t.meter().note_divisor());
+ 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);
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;
uint32_t atom_port_index = 0;
- bool valid;
for (uint32_t port_index = 0; port_index < num_ports; ++port_index) {
void* buf = NULL;
- uint32_t index = 0;
+ 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);
? bufs.get_audio(index).data(offset)
: scratch_bufs.get_audio(0).data(offset);
}
- } else if (flags & (PORT_EVENT|PORT_MESSAGE)) {
+ } 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_INPUT) {
- index = in_map.get(DataType::MIDI, midi_in_index++, &valid);
- _ev_buffers[port_index] = (valid && bufs.count().n_midi() > index)
- ? bufs.get_lv2_midi(true, index, flags & PORT_EVENT)
- : silent_bufs.get_lv2_midi(true, 0, flags & PORT_EVENT);
- buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]);
- } else {
- index = out_map.get(DataType::MIDI, midi_out_index++, &valid);
- _ev_buffers[port_index] = (valid && bufs.count().n_midi() > index)
- ? bufs.get_lv2_midi(false, index, flags & PORT_EVENT)
- : scratch_bufs.get_lv2_midi(false, 0, flags & PORT_EVENT);
- buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]);
- }
- } else if (flags & (PORT_ATOM)) {
- assert(_atom_ev_buffers && _atom_ev_buffers[atom_port_index]);
- if (flags & PORT_INPUT) {
+ 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 (port_index == _position_seq_port_idx) {
- Timecode::BBT_Time bbt;
+ 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) {
- // Something has changed, write the position at cycle start
+ // 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);
}
+ }
- // Write a position event for every metric change within this cycle
- while (++metric_i != tmap.metrics_end() &&
- (*metric_i)->frame() < _session.transport_frame() + nframes) {
- MetricSection* section = *metric_i;
- tmetric.set_metric(section);
- bbt = section->start();
+ // 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(),
- section->frame(),
- section->frame() - _session.transport_frame());
+ metric->frame(),
+ metric->frame() - _session.transport_frame());
+ ++metric_i;
}
}
- } else {
- lv2_evbuf_reset(_atom_ev_buffers[atom_port_index], false);
- _ev_buffers[port_index] = _atom_ev_buffers[atom_port_index++];
+ } else if (!valid) {
+ // Nothing we understand or care about, connect to scratch
+ // see note for midi-buffer size above
+ scratch_bufs.ensure_lv2_bufsize((flags & PORT_INPUT),
+ 0, _port_minimumSize[port_index]);
+ _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]);
- assert(buf);
} else {
continue; // Control port, leave buffer alone
}
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))) {
- cerr << "LV2: failed to write data to event buffer\n";
+ error << "Failed to write data to LV2 event buffer\n";
}
} else {
error << "Received unknown message type from UI" << endmsg;
midi_out_index = 0;
for (uint32_t port_index = 0; port_index < num_ports; ++port_index) {
PortFlags flags = _port_flags[port_index];
+ bool valid = false;
- // Flush MIDI (write back to Ardour MIDI buffers)
- if ((flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_MESSAGE))) {
+ /* 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) {
}
// Write messages to UI
- if (_to_ui && (flags & PORT_OUTPUT) && (flags & (PORT_MESSAGE|PORT_ATOM))) {
+ 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);
deactivate();
}
-LilvPort*
+const LilvPort*
LV2Plugin::Impl::designated_input (const char* uri, void** bufptrs[], void** bufptr)
{
- LilvPort* port = NULL;
+ const LilvPort* port = NULL;
LilvNode* designation = lilv_new_uri(_world.world, uri);
port = lilv_plugin_get_port_by_designation(
plugin, _world.lv2_InputPort, designation);
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(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_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");
+ 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");
}
LV2World::~LV2World()
{
+ lilv_node_free(units_midiNote);
+ lilv_node_free(units_unit);
+ 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(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_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);
+
+ _bundle_checked = true;
+ }
+}
+
LV2PluginInfo::LV2PluginInfo (const void* c_plugin)
: _c_plugin(c_plugin)
{
PluginInfoList*
LV2PluginInfo::discover()
{
+ _world.load_bundled_plugins();
+
PluginInfoList* plugs = new PluginInfoList;
const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world);
LV2PluginInfoPtr info(new LV2PluginInfo((const void*)p));
LilvNode* name = lilv_plugin_get_name(p);
- if (!name) {
- cerr << "LV2: invalid plugin\n";
+ 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;
}