#include "pbd/stl_delete.h"
#include "pbd/compose.h"
#include "pbd/error.h"
+#include "pbd/locale_guard.h"
#include "pbd/replace_all.h"
#include "pbd/xml++.h"
+#ifdef PLATFORM_WINDOWS
+#include <shlobj.h> // CSIDL_*
+#include "pbd/windows_special_dirs.h"
+#endif
+
#include "libardour-config.h"
#include "ardour/audio_buffer.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/lv2_plugin.h"
+#include "ardour/midi_patch_manager.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/types.h"
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "lv2/lv2plug.in/ns/extensions/units/units.h"
#include "lv2/lv2plug.in/ns/ext/patch/patch.h"
+#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h"
#ifdef HAVE_LV2_1_2_0
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
#include "lv2/lv2plug.in/ns/ext/options/options.h"
using namespace ARDOUR;
using namespace PBD;
+bool LV2Plugin::force_state_save = false;
+
class LV2World : boost::noncopyable {
public:
LV2World ();
LilvNode* ext_causesArtifacts;
LilvNode* ext_notAutomatic;
LilvNode* ext_rangeSteps;
+ LilvNode* groups_group;
+ LilvNode* groups_element;
LilvNode* lv2_AudioPort;
LilvNode* lv2_ControlPort;
LilvNode* lv2_InputPort;
LilvNode* lv2_OutputPort;
+ LilvNode* lv2_designation;
LilvNode* lv2_enumeration;
LilvNode* lv2_freewheeling;
LilvNode* lv2_inPlaceBroken;
LilvNode* lv2_isSideChain;
+ LilvNode* lv2_index;
LilvNode* lv2_integer;
LilvNode* lv2_default;
LilvNode* lv2_minimum;
LilvNode* bufz_powerOf2BlockLength;
LilvNode* bufz_fixedBlockLength;
LilvNode* bufz_nominalBlockLength;
+ LilvNode* bufz_coarseBlockLength;
#endif
#ifdef HAVE_LV2_1_10_0
LilvNode* auto_automation_control; // atom:supports
LilvNode* auto_automation_controlled; // lv2:portProperty
LilvNode* auto_automation_controller; // lv2:portProperty
+ LilvNode* inline_display_in_gui; // lv2:optionalFeature
#endif
private:
#ifdef LV2_EXTENDED
/* inline display extension */
-static void
-queue_draw (LV2_Inline_Display_Handle handle)
+void
+LV2Plugin::queue_draw (LV2_Inline_Display_Handle handle)
{
LV2Plugin* plugin = (LV2Plugin*)handle;
plugin->QueueDraw(); /* EMIT SIGNAL */
}
+
+void
+LV2Plugin::midnam_update (LV2_Midnam_Handle handle)
+{
+ LV2Plugin* plugin = (LV2Plugin*)handle;
+ plugin->_midnam_dirty = true;
+ plugin->UpdateMidnam (); /* EMIT SIGNAL */
+}
+
+void
+LV2Plugin::bankpatch_notify (LV2_BankPatch_Handle handle, uint8_t chn, uint32_t bank, uint8_t pgm)
+{
+ LV2Plugin* plugin = (LV2Plugin*)handle;
+ if (chn > 15) {
+ return;
+ }
+ plugin->seen_bankpatch = true;
+ if (pgm > 127 || bank > 16383) {
+ plugin->_bankpatch[chn] = UINT32_MAX;
+ } else {
+ plugin->_bankpatch[chn] = (bank << 7) | pgm;
+ }
+ plugin->BankPatchChange (chn); /* EMIT SIGNAL */
+}
#endif
/* log extension */
#endif
#ifdef LV2_EXTENDED
, queue_draw(0)
+ , midnam(0)
#endif
{}
#endif
#ifdef LV2_EXTENDED
LV2_Inline_Display* queue_draw;
+ LV2_Midnam* midnam;
+ LV2_BankPatch* bankpatch;
#endif
};
LV2Plugin::LV2Plugin (AudioEngine& engine,
Session& session,
const void* c_plugin,
- framecnt_t rate)
+ samplecnt_t rate)
: Plugin (engine, session)
, Workee ()
, _impl(new Impl())
}
void
-LV2Plugin::init(const void* c_plugin, framecnt_t rate)
+LV2Plugin::init(const void* c_plugin, samplecnt_t rate)
{
DEBUG_TRACE(DEBUG::LV2, "init\n");
_bpm_control_port = 0;
_freewheel_control_port = 0;
_latency_control_port = 0;
- _next_cycle_start = std::numeric_limits<framepos_t>::max();
+ _next_cycle_start = std::numeric_limits<samplepos_t>::max();
_next_cycle_speed = 1.0;
_seq_size = _engine.raw_buffer_size(DataType::MIDI);
_state_version = 0;
_was_activated = false;
_has_state_interface = false;
_can_write_automation = false;
+ _inline_display_in_gui = false;
_max_latency = 0;
_current_latency = 0;
_impl->block_length = _session.get_block_size();
lilv_node_free(state_uri);
lilv_node_free(state_iface_uri);
- _features = (LV2_Feature**)calloc(12, sizeof(LV2_Feature*));
+ _features = (LV2_Feature**)calloc(14, sizeof(LV2_Feature*));
_features[0] = &_instance_access_feature;
_features[1] = &_data_access_feature;
_features[2] = &_make_path_feature;
_queue_draw_feature.URI = LV2_INLINEDISPLAY__queue_draw;
_queue_draw_feature.data = _impl->queue_draw;
_features[n_features++] = &_queue_draw_feature;
+
+ _impl->midnam = (LV2_Midnam*)
+ malloc (sizeof(LV2_Midnam));
+ _impl->midnam->handle = this;
+ _impl->midnam->update = midnam_update;
+
+ _midnam_feature.URI = LV2_MIDNAM__update;
+ _midnam_feature.data = _impl->midnam;
+ _features[n_features++] = &_midnam_feature;
+
+ _impl->bankpatch = (LV2_BankPatch*)
+ malloc (sizeof(LV2_BankPatch));
+ _impl->bankpatch->handle = this;
+ _impl->bankpatch->notify = bankpatch_notify;
+
+ _bankpatch_feature.URI = LV2_BANKPATCH__notify;
+ _bankpatch_feature.data = _impl->bankpatch;
+ _features[n_features++] = &_bankpatch_feature;
#endif
#ifdef HAVE_LV2_1_2_0
_features[n_features++] = &_options_feature;
#endif
+#ifdef LV2_EXTENDED
+ seen_bankpatch = false;
+ for (uint32_t chn = 0; chn < 16; ++chn) {
+ _bankpatch[chn] = UINT32_MAX;
+ }
+#endif
+
LV2_State_Make_Path* make_path = (LV2_State_Make_Path*)malloc(
sizeof(LV2_State_Make_Path));
make_path->handle = this;
#ifdef LV2_EXTENDED
_display_interface = (const LV2_Inline_Display_Interface*)
extension_data (LV2_INLINEDISPLAY__interface);
+
+ _midname_interface = (const LV2_Midnam_Interface*)
+ extension_data (LV2_MIDNAM__interface);
+ if (_midname_interface) {
+ _midnam_dirty = true;
+ read_midnam ();
+ }
#endif
if (lilv_plugin_has_feature(plugin, _world.lv2_inPlaceBroken)) {
lilv_nodes_free(required_features);
#endif
-#ifdef LV2_EXTENDED
LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin);
+#ifdef HAVE_LV2_1_2_0
+ if (lilv_nodes_contains (optional_features, _world.bufz_coarseBlockLength)) {
+ _no_sample_accurate_ctrl = true;
+ }
+#endif
+#ifdef LV2_EXTENDED
if (lilv_nodes_contains (optional_features, _world.lv2_noSampleAccurateCtrl)) {
+ /* deprecated 2016-Sep-18 in favor of bufz_coarseBlockLength */
_no_sample_accurate_ctrl = true;
}
if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) {
_can_write_automation = true;
}
+ if (lilv_nodes_contains (optional_features, _world.inline_display_in_gui)) {
+ _inline_display_in_gui = true;
+ }
lilv_nodes_free(optional_features);
#endif
#ifdef HAVE_LILV_0_16_0
// Load default state
+ if (_worker) {
+ /* immediately schedule any work,
+ * so that state restore later will not find a busy
+ * worker. latency_compute_run() flushes any replies
+ */
+ _worker->set_synchronous(true);
+ }
LilvState* state = lilv_state_new_from_world(
_world.world, _uri_map.urid_map(), lilv_plugin_get_uri(_impl->plugin));
if (state && _has_state_interface) {
lilv_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 ();
+ _defaults[i] *= _session.sample_rate ();
}
lilv_node_free(def);
*/
if (get_info()->n_inputs.n_midi() > 0) {
/* we don't yet implement midi buffer offsets (for split cycles).
- * Also connect_and_run() also uses _session.transport_frame() directly
+ * Also connect_and_run() also uses _session.transport_sample() directly
* (for BBT) which is not offset for plugin cycle split.
*/
return true;
deactivate();
cleanup();
+#ifdef LV2_EXTENDED
+ if (has_midnam ()) {
+ std::stringstream ss;
+ ss << (void*)this;
+ ss << unique_id();
+ MIDI::Name::MidiPatchManager::instance().remove_custom_midnam (ss.str());
+ }
+#endif
+
lilv_instance_free(_impl->instance);
lilv_state_free(_impl->state);
lilv_node_free(_impl->name);
#endif
#ifdef LV2_EXTENDED
free(_impl->queue_draw);
+ free(_impl->midnam);
+ free(_impl->bankpatch);
#endif
free(_features);
return _display_interface ? true : false;
}
+bool
+LV2Plugin::inline_display_in_gui () {
+ return _inline_display_in_gui;
+}
+
Plugin::Display_Image_Surface*
LV2Plugin::render_inline_display (uint32_t w, uint32_t h) {
if (_display_interface) {
}
return NULL;
}
+
+bool
+LV2Plugin::has_midnam () {
+ return _midname_interface ? true : false;
+}
+
+bool
+LV2Plugin::read_midnam () {
+ bool rv = false;
+ if (!_midname_interface || !_midnam_dirty) {
+ return rv;
+ }
+ char* midnam = _midname_interface->midnam ((void*)_impl->instance->lv2_handle);
+ if (midnam) {
+ std::stringstream ss;
+ ss << (void*)this;
+ ss << unique_id();
+ rv = MIDI::Name::MidiPatchManager::instance().update_custom_midnam (ss.str(), midnam);
+ }
+#ifndef NDEBUG
+ if (rv) {
+ info << string_compose(_("LV2: update midnam for plugin '%1'"), name ()) << endmsg;
+ } else {
+ warning << string_compose(_("LV2: Failed to parse midnam of plugin '%1'"), name ()) << endmsg;
+ }
+#endif
+ _midname_interface->free (midnam);
+ if (rv) {
+ UpdatedMidnam ();
+ _midnam_dirty = false;
+ }
+ return rv;
+}
+
+std::string
+LV2Plugin::midnam_model () {
+ std::string rv;
+ if (!_midname_interface) {
+ return rv;
+ }
+ char* model = _midname_interface->model ((void*)_impl->instance->lv2_handle);
+ if (model) {
+ rv = model;
+ }
+ _midname_interface->free (model);
+ return rv;
+}
#endif
string
case 2: h.x0 = 1; h.x1 = 3; h.y0 = 2; h.y1 = 3; break; // Frequency 1
case 3: h.x0 = 1; h.x1 = 3; h.y0 = 0; h.y1 = 1; break; // Gain 1
- case 4: h.x0 = 2; h.x1 = 4; h.y0 = 1; h.y1 = 2; break; // Bandwidth 1
+ case 4: h.x0 = 1; h.x1 = 3; h.y0 = 1; h.y1 = 2; break; // Bandwidth 1
case 18: h.x0 = 1; h.x1 = 4; h.y0 = 5; h.y1 = 6; break; // enable 1
case 5: h.x0 = 4; h.x1 = 6; h.y0 = 2; h.y1 = 3; break; // Frequency 2
case 6: h.x0 = 4; h.x1 = 6; h.y0 = 0; h.y1 = 1; break; // Gain 2
- case 7: h.x0 = 5; h.x1 = 7; h.y0 = 1; h.y1 = 2; break; // Bandwidth 2
+ case 7: h.x0 = 4; h.x1 = 6; h.y0 = 1; h.y1 = 2; break; // Bandwidth 2
case 19: h.x0 = 4; h.x1 = 7; h.y0 = 5; h.y1 = 6; break; // enable 2
case 8: h.x0 = 7; h.x1 = 9; h.y0 = 2; h.y1 = 3; break; // Frequency 3
case 9: h.x0 = 7; h.x1 = 9; h.y0 = 0; h.y1 = 1; break; // Gain 3
- case 10: h.x0 = 8; h.x1 = 10; h.y0 = 1; h.y1 = 2; break; // Bandwidth 3
+ case 10: h.x0 = 7; h.x1 = 9; h.y0 = 1; h.y1 = 2; break; // Bandwidth 3
case 20: h.x0 = 7; h.x1 = 10; h.y0 = 5; h.y1 = 6; break; // enable 3
case 11: h.x0 = 10; h.x1 = 12; h.y0 = 2; h.y1 = 3; break; // Frequency 4
case 12: h.x0 = 10; h.x1 = 12; h.y0 = 0; h.y1 = 1; break; // Gain 4
- case 13: h.x0 = 11; h.x1 = 13; h.y0 = 1; h.y1 = 2; break; // Bandwidth 4
+ case 13: h.x0 = 10; h.x1 = 12; h.y0 = 1; h.y1 = 2; break; // Bandwidth 4
case 21: h.x0 = 10; h.x1 = 13; h.y0 = 5; h.y1 = 6; break; // enable 4
case 14: h.x0 = 13; h.x1 = 14; h.y0 = 2; h.y1 = 3; break; // Frequency H
case 15: h.x0 = 13; h.x1 = 14; h.y0 = 0; h.y1 = 1; break; // Gain H
case 22: h.x0 = 13; h.x1 = 14; h.y0 = 5; h.y1 = 6; break; // enable H
-
- case 16: h.x0 = 14; h.x1 = 15; h.y0 = 4; h.y1 = 6; break; // Master Gain
+ case 16: h.x0 = 14; h.x1 = 15; h.y0 = 1; h.y1 = 3; break; // Master Gain
+ case 23: h.x0 = 14; h.x1 = 15; h.y0 = 5; h.y1 = 6; break; // Master Enable
default:
return false;
}
const std::string
LV2Plugin::state_dir(unsigned num) const
{
- return Glib::build_filename(plugin_dir(), string_compose("state%1", num));
+ return Glib::build_filename(plugin_dir(), string("state") + PBD::to_string (num));
}
/** Implementation of state:makePath for files created at instantiation time.
assert(_insert_id != PBD::ID("0"));
XMLNode* child;
- char buf[32];
LocaleGuard lg;
for (uint32_t i = 0; i < parameter_count(); ++i) {
if (parameter_is_input(i) && parameter_is_control(i)) {
child = new XMLNode("Port");
- child->add_property("symbol", port_symbol(i));
- snprintf(buf, sizeof(buf), "%+f", _shadow_data[i]);
- child->add_property("value", string(buf));
+ child->set_property("symbol", port_symbol(i));
+ child->set_property("value", _shadow_data[i]);
root->add_child_nocopy(*child);
}
}
if (!_plugin_state_dir.empty()) {
- root->add_property("template-dir", _plugin_state_dir);
+ root->set_property("template-dir", _plugin_state_dir);
}
if (_has_state_interface) {
// Provisionally increment state version and create directory
const std::string new_dir = state_dir(++_state_version);
+ // and keep track of it (for templates & archive)
+ unsigned int saved_state = _state_version;;
g_mkdir_with_parents(new_dir.c_str(), 0744);
LilvState* state = lilv_state_new_from_instance(
0,
NULL);
- if (!_plugin_state_dir.empty()
+ if (!_plugin_state_dir.empty() || force_state_save
|| !_impl->state
|| !lilv_state_equals(state, _impl->state)) {
lilv_state_save(_world.world,
new_dir.c_str(),
"state.ttl");
- if (_plugin_state_dir.empty()) {
+ if (force_state_save) {
+ // archive or save-as
+ lilv_state_free(state);
+ --_state_version;
+ }
+ else if (_plugin_state_dir.empty()) {
// normal session save
lilv_state_free(_impl->state);
_impl->state = state;
} else {
// template save (dedicated state-dir)
lilv_state_free(state);
+ --_state_version;
}
} else {
// State is identical, decrement version and nuke directory
lilv_state_free(state);
PBD::remove_directory(new_dir);
--_state_version;
+ saved_state = _state_version;
}
- root->add_property("state-dir", string_compose("state%1", _state_version));
+ root->set_property("state-dir", string("state") + PBD::to_string (saved_state));
}
}
const uint32_t port_index = self->port_index(port_symbol);
if (port_index != (uint32_t)-1) {
self->set_parameter(port_index, *(const float*)value);
+ self->PresetPortSetValue (port_index, *(const float*)value); /* EMIT SIGNAL */
}
}
const string prefix = legalize_for_uri(lilv_node_as_string(plug_name));
const string base_name = legalize_for_uri(name);
const string file_name = base_name + ".ttl";
+#ifdef PLATFORM_WINDOWS
+ /* http://lv2plug.in/pages/filesystem-hierarchy-standard.html */
+ std::string appdata = PBD::get_win_special_folder_path (CSIDL_APPDATA);
+ if (appdata.empty ()) {
+ // TODO consider a fallback location
+ return "";
+ }
+ const string bundle = Glib::build_filename (
+ appdata, "LV2", prefix + "_" + base_name + ".lv2");
+#else
+ /* while macOS/OSX user-specific path is
+ *
+ * $HOME/Library/Audio/Plug-Ins/LV2/
+ *
+ * liblilv's LV2 search path on all unices does include ~/.lv2/
+ * Ardour has been saving lv2 presets to ~/.lv2 for along time,
+ * so just keep them there.
+ */
const string bundle = Glib::build_filename(
Glib::get_home_dir(),
Glib::build_filename(".lv2", prefix + "_" + base_name + ".lv2"));
+#endif
#ifdef HAVE_LILV_0_21_3
/* delete reference to old preset (if any) */
if (_atom_ev_buffers && _atom_ev_buffers[0]) {
bufsiz = lv2_evbuf_get_capacity(_atom_ev_buffers[0]);
}
- rbs = max((size_t) bufsiz * 8, rbs);
+ int fact = ceilf(_session.sample_rate () / 3000.f);
+ rbs = max((size_t) bufsiz * std::max (8, fact), rbs);
_from_ui = new RingBuffer<uint8_t>(rbs);
}
// Set up forge to write to temporary buffer on the stack
LV2_Atom_Forge* forge = &_impl->ui_forge;
- LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Frame sample;
uint8_t buf[PATH_MAX]; // Ought to be enough for anyone...
lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
// Serialize patch:Set message to set property
#ifdef HAVE_LV2_1_10_0
- lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Set);
+ lv2_atom_forge_object(forge, &sample, 0, _uri_map.urids.patch_Set);
lv2_atom_forge_key(forge, _uri_map.urids.patch_property);
lv2_atom_forge_urid(forge, key);
lv2_atom_forge_key(forge, _uri_map.urids.patch_value);
#else
- lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Set);
+ lv2_atom_forge_blank(forge, &sample, 0, _uri_map.urids.patch_Set);
lv2_atom_forge_property_head(forge, _uri_map.urids.patch_property, 0);
lv2_atom_forge_urid(forge, key);
lv2_atom_forge_property_head(forge, _uri_map.urids.patch_value, 0);
LilvNode* render = get_value(lworld, unit, _world.units_render);
if (render) {
desc.print_fmt = lilv_node_as_string(render);
- replace_all (desc.print_fmt, "%f", "%.2f");
+ /* override lilv's default "%f" format */
+ if (desc.integer_step) {
+ replace_all (desc.print_fmt, "%f", "%.0f");
+ } else if (desc.upper - desc.lower >= 1000) {
+ replace_all (desc.print_fmt, "%f", "%.1f");
+ } else if (desc.upper - desc.lower >= 100) {
+ replace_all (desc.print_fmt, "%f", "%.2f");
+ } else {
+ replace_all (desc.print_fmt, "%f", "%.3f");
+ }
lilv_node_free(render);
}
}
if (label) {
desc.label = lilv_node_as_string(label);
}
- if (def && lilv_node_is_float(def)) {
- desc.normal = lilv_node_as_float(def);
+ if (def) {
+ if (lilv_node_is_float(def)) {
+ desc.normal = lilv_node_as_float(def);
+ } else if (lilv_node_is_int(def)) {
+ desc.normal = lilv_node_as_int(def);
+ }
}
- if (minimum && lilv_node_is_float(minimum)) {
- desc.lower = lilv_node_as_float(minimum);
+ if (minimum) {
+ if (lilv_node_is_float(minimum)) {
+ desc.lower = lilv_node_as_float(minimum);
+ } else if (lilv_node_is_int(minimum)) {
+ desc.lower = lilv_node_as_int(minimum);
+ }
}
- if (maximum && lilv_node_is_float(maximum)) {
- desc.upper = lilv_node_as_float(maximum);
+ if (maximum) {
+ if (lilv_node_is_float(maximum)) {
+ desc.upper = lilv_node_as_float(maximum);
+ } else if (lilv_node_is_int(maximum)) {
+ desc.upper = lilv_node_as_int(maximum);
+ }
}
load_parameter_descriptor_units(lworld, desc, units);
desc.datatype = datatype;
// Set up forge to write to temporary buffer on the stack
LV2_Atom_Forge* forge = &_impl->ui_forge;
- LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Frame sample;
uint8_t buf[PATH_MAX]; // Ought to be enough for anyone...
lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
// Serialize patch:Get message with no subject (implicitly plugin instance)
#ifdef HAVE_LV2_1_10_0
- lv2_atom_forge_object(forge, &frame, 1, _uri_map.urids.patch_Get);
+ lv2_atom_forge_object(forge, &sample, 0, _uri_map.urids.patch_Get);
#else
- lv2_atom_forge_blank(forge, &frame, 1, _uri_map.urids.patch_Get);
+ lv2_atom_forge_blank(forge, &sample, 0, _uri_map.urids.patch_Get);
#endif
// Write message to UI=>Plugin ring
LV2Plugin::set_state(const XMLNode& node, int version)
{
XMLNodeList nodes;
- XMLProperty const * prop;
XMLNodeConstIterator iter;
XMLNode* child;
- const char* sym;
- const char* value;
- uint32_t port_id;
LocaleGuard lg;
if (node.name() != state_node_name()) {
child = *iter;
- if ((prop = child->property("symbol")) != 0) {
- sym = prop->value().c_str();
- } else {
+ std::string sym;
+ if (!child->get_property("symbol", sym)) {
warning << _("LV2: port has no symbol, ignored") << endmsg;
continue;
}
map<string, uint32_t>::iterator i = _port_indices.find(sym);
+ uint32_t port_id;
+
if (i != _port_indices.end()) {
port_id = i->second;
} else {
continue;
}
- if ((prop = child->property("value")) != 0) {
- value = prop->value().c_str();
- } else {
+ float val;
+ if (!child->get_property("value", val)) {
warning << _("LV2: port has no value, ignored") << endmsg;
continue;
}
- set_parameter(port_id, atof(value));
+ set_parameter(port_id, val);
}
- if ((prop = node.property("template-dir")) != 0) {
- set_state_dir (prop->value ());
+ std::string template_dir;
+ if (node.get_property("template-dir", template_dir)) {
+ set_state_dir (template_dir);
}
_state_version = 0;
- if ((prop = node.property("state-dir")) != 0) {
- if (sscanf(prop->value().c_str(), "state%u", &_state_version) != 1) {
+ std::string state_dir;
+ if (node.get_property("state-dir", state_dir) != 0) {
+ if (sscanf(state_dir.c_str(), "state%u", &_state_version) != 1) {
error << string_compose(
"LV2: failed to parse state version from \"%1\"",
- prop->value()) << endmsg;
+ state_dir) << endmsg;
}
std::string state_file = Glib::build_filename(
plugin_dir(),
- Glib::build_filename(prop->value(), "state.ttl"));
+ Glib::build_filename(state_dir, "state.ttl"));
LilvState* state = lilv_state_new_from_file(
_world.world, _uri_map.urid_map(), NULL, state_file.c_str());
load_parameter_descriptor_units(_world.world, desc, portunits);
if (desc.sr_dependent) {
- desc.lower *= _session.frame_rate ();
- desc.upper *= _session.frame_rate ();
+ desc.lower *= _session.sample_rate ();
+ desc.upper *= _session.sample_rate ();
}
- desc.min_unbound = false; // TODO: LV2 extension required
- desc.max_unbound = false; // TODO: LV2 extension required
-
desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration);
desc.scale_points = get_scale_points(which);
- desc.update_steps();
-
if (steps) {
- //override auto-calculated steps in update_steps()
- float s = lilv_node_as_float (steps);
- const float delta = desc.upper - desc.lower;
-
- desc.step = desc.smallstep = (delta / s);
- desc.largestep = std::min ((delta / 5.0f), 10.f * desc.smallstep);
-
- if (desc.logarithmic) {
- // TODO marry AutomationControl::internal_to_interface () with
- // http://lv2plug.in/ns/ext/port-props/#rangeSteps
- desc.smallstep = desc.smallstep / logf(s);
- desc.step = desc.step / logf(s);
- desc.largestep = desc.largestep / logf(s);
- } else if (desc.integer_step) {
- desc.smallstep = 1.0;
- desc.step = std::max(1.f, rintf (desc.step));
- desc.largestep = std::max(1.f, rintf (desc.largestep));
- }
- DEBUG_TRACE(DEBUG::LV2, string_compose("parameter %1 small: %2, step: %3 largestep: %4\n",
- which, desc.smallstep, desc.step, desc.largestep));
+ desc.rangesteps = lilv_node_as_float (steps);
}
+ desc.update_steps();
lilv_node_free(def);
lilv_node_free(min);
return Plugin::IOPortDescription ("?");
}
- LilvNode* name = lilv_port_get_name(_impl->plugin,
- lilv_plugin_get_port_by_index(_impl->plugin, idx));
+ const LilvPort* pport = lilv_plugin_get_port_by_index (_impl->plugin, idx);
+
+ LilvNode* name = lilv_port_get_name(_impl->plugin, pport);
Plugin::IOPortDescription iod (lilv_node_as_string (name));
lilv_node_free(name);
- if (lilv_port_has_property(_impl->plugin,
- lilv_plugin_get_port_by_index(_impl->plugin, idx), _world.lv2_isSideChain)) {
+ /* get the port's pg:group */
+ LilvNodes* groups = lilv_port_get_value (_impl->plugin, pport, _world.groups_group);
+ if (lilv_nodes_size (groups) > 0) {
+ const LilvNode* group = lilv_nodes_get_first (groups);
+ LilvNodes* grouplabel = lilv_world_find_nodes (_world.world, group, _world.rdfs_label, NULL);
+
+ /* get the name of the port-group */
+ if (lilv_nodes_size (grouplabel) > 0) {
+ const LilvNode* grpname = lilv_nodes_get_first (grouplabel);
+ iod.group_name = lilv_node_as_string (grpname);
+ }
+ lilv_nodes_free (grouplabel);
+
+ /* get all port designations.
+ * we're interested in e.g. lv2:designation pg:right */
+ LilvNodes* designations = lilv_port_get_value (_impl->plugin, pport, _world.lv2_designation);
+ if (lilv_nodes_size (designations) > 0) {
+ /* get all pg:elements of the pg:group */
+ LilvNodes* group_childs = lilv_world_find_nodes (_world.world, group, _world.groups_element, NULL);
+ if (lilv_nodes_size (group_childs) > 0) {
+ /* iterate over all port designations .. */
+ LILV_FOREACH (nodes, i, designations) {
+ const LilvNode* designation = lilv_nodes_get (designations, i);
+ /* match the lv2:designation's element against the port-group's element */
+ LILV_FOREACH (nodes, j, group_childs) {
+ const LilvNode* group_element = lilv_nodes_get (group_childs, j);
+ LilvNodes* elem = lilv_world_find_nodes (_world.world, group_element, _world.lv2_designation, designation);
+ /* found it. Now look up the index (channel-number) of the pg:Element */
+ if (lilv_nodes_size (elem) > 0) {
+ LilvNodes* idx = lilv_world_find_nodes (_world.world, lilv_nodes_get_first (elem), _world.lv2_index, NULL);
+ if (lilv_node_is_int (lilv_nodes_get_first (idx))) {
+ iod.group_channel = lilv_node_as_int(lilv_nodes_get_first (idx));
+ }
+ }
+ }
+ }
+ }
+ }
+ lilv_nodes_free (groups);
+ lilv_nodes_free (designations);
+ }
+
+ if (lilv_port_has_property(_impl->plugin, pport, _world.lv2_isSideChain)) {
iod.is_sidechain = true;
}
return iod;
}
}
-framecnt_t
+samplecnt_t
LV2Plugin::max_latency () const
{
return _max_latency;
}
-framecnt_t
+samplecnt_t
LV2Plugin::signal_latency() const
{
if (_latency_control_port) {
- return (framecnt_t)floor(*_latency_control_port);
+ return (samplecnt_t)floor(*_latency_control_port);
} else {
return 0;
}
const TempoMetric& t,
Timecode::BBT_Time& bbt,
double speed,
- framepos_t position,
- framecnt_t offset)
+ double bpm,
+ samplepos_t position,
+ samplecnt_t offset)
{
const URIMap::URIDs& urids = URIMap::instance().urids;
uint8_t pos_buf[256];
lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf));
- LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Frame sample;
#ifdef HAVE_LV2_1_10_0
- lv2_atom_forge_object(forge, &frame, 1, urids.time_Position);
- lv2_atom_forge_key(forge, urids.time_frame);
+ lv2_atom_forge_object(forge, &sample, 0, urids.time_Position);
+ lv2_atom_forge_key(forge, urids.time_sample);
lv2_atom_forge_long(forge, position);
lv2_atom_forge_key(forge, urids.time_speed);
lv2_atom_forge_float(forge, speed);
lv2_atom_forge_key(forge, urids.time_beatsPerBar);
lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
lv2_atom_forge_key(forge, urids.time_beatsPerMinute);
- lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
+ lv2_atom_forge_float(forge, bpm);
#else
- lv2_atom_forge_blank(forge, &frame, 1, urids.time_Position);
- lv2_atom_forge_property_head(forge, urids.time_frame, 0);
+ lv2_atom_forge_blank(forge, &sample, 1, urids.time_Position);
+ lv2_atom_forge_property_head(forge, urids.time_sample, 0);
lv2_atom_forge_long(forge, position);
lv2_atom_forge_property_head(forge, urids.time_speed, 0);
lv2_atom_forge_float(forge, speed);
lv2_atom_forge_property_head(forge, urids.time_beatsPerBar, 0);
lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
lv2_atom_forge_property_head(forge, urids.time_beatsPerMinute, 0);
- lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
+ lv2_atom_forge_float(forge, bpm);
#endif
LV2_Evbuf_Iterator end = lv2_evbuf_end(buf);
int
LV2Plugin::connect_and_run(BufferSet& bufs,
- framepos_t start, framepos_t end, double speed,
+ samplepos_t start, samplepos_t end, double speed,
ChanMapping in_map, ChanMapping out_map,
- pframes_t nframes, framecnt_t offset)
+ pframes_t nframes, samplecnt_t offset)
{
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 run %2 offset %3\n", name(), nframes, offset));
Plugin::connect_and_run(bufs, start, end, speed, in_map, out_map, nframes, offset);
}
if (_bpm_control_port) {
- *_bpm_control_port = tmetric.tempo().beats_per_minute();
+ *_bpm_control_port = tmap.tempo_at_sample (start).note_types_per_minute();
}
#ifdef LV2_EXTENDED
}
if (valid && (flags & PORT_INPUT)) {
- Timecode::BBT_Time bbt;
if ((flags & PORT_POSITION)) {
+ Timecode::BBT_Time bbt (tmap.bbt_at_sample (start));
+ double bpm = tmap.tempo_at_sample (start).note_types_per_minute();
+ double beatpos = (bbt.bars - 1) * tmetric.meter().divisions_per_bar()
+ + (bbt.beats - 1)
+ + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat);
+ beatpos *= tmetric.meter().note_divisor() / 4.0;
if (start != _next_cycle_start ||
- speed != _next_cycle_speed) {
- // Transport has changed, write position at cycle start
- bbt = tmap.bbt_at_frame (start);
+ speed != _next_cycle_speed ||
+ rint (1000 * beatpos) != rint(1000 * _next_cycle_beat) ||
+ bpm != _current_bpm) {
+ // Transport or Tempo has changed, write position at cycle start
write_position(&_impl->forge, _ev_buffers[port_index],
- tmetric, bbt, speed, start, 0);
+ tmetric, bbt, speed, bpm, start, 0);
}
}
// Now merge MIDI and any transport events into the buffer
const uint32_t type = _uri_map.urids.midi_MidiEvent;
- const framepos_t tend = end;
+ const samplepos_t tend = end;
++metric_i;
while (m != m_end || (metric_i != tmap.metrics_end() &&
- (*metric_i)->frame() < tend)) {
+ (*metric_i)->sample() < 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);
+ if (m != m_end && (!metric || metric->sample() > (*m).time())) {
+ const Evoral::Event<samplepos_t> ev(*m, false);
if (ev.time() < nframes) {
LV2_Evbuf_Iterator eend = lv2_evbuf_end(_ev_buffers[port_index]);
lv2_evbuf_write(&eend, ev.time(), 0, type, ev.size(), ev.buffer());
++m;
} else {
tmetric.set_metric(metric);
- bbt = tmap.bbt_at_pulse (metric->pulse());
+ Timecode::BBT_Time bbt;
+ bbt = tmap.bbt_at_sample (metric->sample());
+ double bpm = tmap.tempo_at_sample (start/*XXX*/).note_types_per_minute();
write_position(&_impl->forge, _ev_buffers[port_index],
- tmetric, bbt, speed,
- metric->frame(),
- metric->frame() - start);
+ tmetric, bbt, speed, bpm,
+ metric->sample(),
+ metric->sample() - start);
++metric_i;
}
}
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;
+ uint32_t samples, subframes, type, size;
uint8_t* data;
- lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
+ lv2_evbuf_get(i, &samples, &subframes, &type, &size, &data);
#ifdef LV2_EXTENDED
// Intercept Automation Write Events
const float v = ((const LV2_Atom_Float*)value)->body;
// -> add automation event..
DEBUG_TRACE(DEBUG::LV2Automate,
- string_compose ("Event p: %1 t: %2 v: %3\n", p, frames, v));
+ string_compose ("Event p: %1 t: %2 v: %3\n", p, samples, v));
AutomationCtrlPtr c = get_automation_control (p);
if (c &&
(c->ac->automation_state() == Touch || c->ac->automation_state() == Write)
) {
- framepos_t when = std::max ((framepos_t) 0, start + frames - _current_latency);
- assert (start + frames - _current_latency >= 0);
+ samplepos_t when = std::max ((samplepos_t) 0, start + samples - _current_latency);
+ assert (start + samples - _current_latency >= 0);
if (c->guard) {
c->guard = false;
c->ac->list()->add (when, v, true, true);
AutomationCtrlPtr c = get_automation_control (p);
DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Start Touch p: %1\n", p));
if (c) {
- c->ac->start_touch (std::max ((framepos_t)0, start - _current_latency));
+ c->ac->start_touch (std::max ((samplepos_t)0, start - _current_latency));
c->guard = true;
}
}
AutomationCtrlPtr c = get_automation_control (p);
DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("End Touch p: %1\n", p));
if (c) {
- c->ac->stop_touch (true, std::max ((framepos_t)0, start - _current_latency));
+ c->ac->stop_touch (std::max ((samplepos_t)0, start - _current_latency));
}
}
}
}
}
#endif
+ // Intercept state dirty message
+ if (_has_state_interface /* && (flags & PORT_DIRTYMSG)*/) {
+ LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
+ if (atom->type == _uri_map.urids.atom_Blank ||
+ atom->type == _uri_map.urids.atom_Object) {
+ LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
+ if (obj->body.otype == _uri_map.urids.state_StateChanged) {
+ _session.set_dirty ();
+ }
+ }
+ }
// Intercept patch change messages to emit PropertyChanged signal
if ((flags & PORT_PATCHMSG)) {
_next_cycle_speed = speed;
_next_cycle_start = end;
+ {
+ /* keep track of lv2:timePosition like plugins can do.
+ * Note: for no-midi plugins, we only ever send information at cycle-start,
+ * so it needs to be realative to that.
+ */
+ TempoMetric t = tmap.metric_at(start);
+ _current_bpm = tmap.tempo_at_sample (start).note_types_per_minute();
+ Timecode::BBT_Time bbt (tmap.bbt_at_sample (start));
+ double beatpos = (bbt.bars - 1) * t.meter().divisions_per_bar()
+ + (bbt.beats - 1)
+ + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat);
+ beatpos *= tmetric.meter().note_divisor() / 4.0;
+ _next_cycle_beat = beatpos + nframes * speed * _current_bpm / (60.f * _session.sample_rate());
+ }
+
if (_latency_control_port) {
- framecnt_t new_latency = signal_latency ();
+ samplecnt_t new_latency = signal_latency ();
_current_latency = new_latency;
}
return 0;
uint32_t
LV2Plugin::designated_bypass_port ()
{
-#ifdef LV2_EXTENDED
const LilvPort* port = NULL;
- LilvNode* designation = lilv_new_uri (_world.world, LV2_PROCESSING_URI__enable);
+ LilvNode* designation = lilv_new_uri (_world.world, LV2_CORE_PREFIX "enabled");
+ port = lilv_plugin_get_port_by_designation (
+ _impl->plugin, _world.lv2_InputPort, designation);
+ lilv_node_free(designation);
+ if (port) {
+ return lilv_port_get_index (_impl->plugin, port);
+ }
+#ifdef LV2_EXTENDED
+ /* deprecated on 2016-Sep-18 in favor of lv2:enabled */
+ designation = lilv_new_uri (_world.world, LV2_PROCESSING_URI__enable);
port = lilv_plugin_get_port_by_designation (
_impl->plugin, _world.lv2_InputPort, designation);
lilv_node_free(designation);
}
void
-LV2Plugin::run(pframes_t nframes)
+LV2Plugin::run(pframes_t nframes, bool sync_work)
{
uint32_t const N = parameter_count();
for (uint32_t i = 0; i < N; ++i) {
if (_worker) {
// Execute work synchronously if we're freewheeling (export)
- _worker->set_synchronous(session().engine().freewheeling());
+ _worker->set_synchronous(sync_work || session().engine().freewheeling());
}
// Run the plugin for this cycle
uint32_t out_index = 0;
// this is done in the main thread. non realtime.
- const framecnt_t bufsize = _engine.samples_per_cycle();
+ const samplecnt_t bufsize = _engine.samples_per_cycle();
float *buffer = (float*) malloc(_engine.samples_per_cycle() * sizeof(float));
memset(buffer, 0, sizeof(float) * bufsize);
port_index++;
}
- run(bufsize);
+ run(bufsize, true);
deactivate();
if (was_activated) {
activate();
ext_causesArtifacts= lilv_new_uri(world, LV2_PORT_PROPS__causesArtifacts);
ext_notAutomatic = lilv_new_uri(world, LV2_PORT_PROPS__notAutomatic);
ext_rangeSteps = lilv_new_uri(world, LV2_PORT_PROPS__rangeSteps);
+ groups_group = lilv_new_uri(world, LV2_PORT_GROUPS__group);
+ groups_element = lilv_new_uri(world, LV2_PORT_GROUPS__element);
lv2_AudioPort = lilv_new_uri(world, LILV_URI_AUDIO_PORT);
lv2_ControlPort = lilv_new_uri(world, LILV_URI_CONTROL_PORT);
lv2_InputPort = lilv_new_uri(world, LILV_URI_INPUT_PORT);
lv2_OutputPort = lilv_new_uri(world, LILV_URI_OUTPUT_PORT);
lv2_inPlaceBroken = lilv_new_uri(world, LV2_CORE__inPlaceBroken);
lv2_isSideChain = lilv_new_uri(world, LV2_CORE_PREFIX "isSideChain");
+ lv2_index = lilv_new_uri(world, LV2_CORE__index);
lv2_integer = lilv_new_uri(world, LV2_CORE__integer);
lv2_default = lilv_new_uri(world, LV2_CORE__default);
lv2_minimum = lilv_new_uri(world, LV2_CORE__minimum);
lv2_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_designation = lilv_new_uri(world, LV2_CORE__designation);
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);
patch_writable = lilv_new_uri(world, LV2_PATCH__writable);
patch_Message = lilv_new_uri(world, LV2_PATCH__Message);
#ifdef LV2_EXTENDED
- lv2_noSampleAccurateCtrl = lilv_new_uri(world, "http://ardour.org/lv2/ext#noSampleAccurateControls");
+ lv2_noSampleAccurateCtrl = lilv_new_uri(world, "http://ardour.org/lv2/ext#noSampleAccurateControls"); // deprecated 2016-09-18
auto_can_write_automatation = lilv_new_uri(world, LV2_AUTOMATE_URI__can_write);
auto_automation_control = lilv_new_uri(world, LV2_AUTOMATE_URI__control);
auto_automation_controlled = lilv_new_uri(world, LV2_AUTOMATE_URI__controlled);
auto_automation_controller = lilv_new_uri(world, LV2_AUTOMATE_URI__controller);
+ inline_display_in_gui = lilv_new_uri(world, LV2_INLINEDISPLAY__in_gui);
#endif
#ifdef HAVE_LV2_1_2_0
bufz_powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength);
bufz_fixedBlockLength = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength);
bufz_nominalBlockLength = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength");
+ bufz_coarseBlockLength = lilv_new_uri(world, "http://lv2plug.in/ns/ext/buf-size#coarseBlockLength");
#endif
}
return;
}
#ifdef HAVE_LV2_1_2_0
+ lilv_node_free(bufz_coarseBlockLength);
lilv_node_free(bufz_nominalBlockLength);
lilv_node_free(bufz_fixedBlockLength);
lilv_node_free(bufz_powerOf2BlockLength);
lilv_node_free(rdfs_label);
lilv_node_free(rdfs_range);
lilv_node_free(midi_MidiEvent);
+ lilv_node_free(lv2_designation);
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_index);
lilv_node_free(lv2_integer);
lilv_node_free(lv2_isSideChain);
lilv_node_free(lv2_inPlaceBroken);
lilv_node_free(lv2_InputPort);
lilv_node_free(lv2_ControlPort);
lilv_node_free(lv2_AudioPort);
+ lilv_node_free(groups_group);
+ lilv_node_free(groups_element);
lilv_node_free(ext_rangeSteps);
lilv_node_free(ext_notAutomatic);
lilv_node_free(ext_causesArtifacts);
if (!uri) { throw failed_constructor(); }
const LilvPlugin* lp = lilv_plugins_get_by_uri(plugins, uri);
if (!lp) { throw failed_constructor(); }
- plugin.reset(new LV2Plugin(session.engine(), session, lp, session.frame_rate()));
+ plugin.reset(new LV2Plugin(session.engine(), session, lp, session.sample_rate()));
lilv_node_free(uri);
plugin->set_info(PluginInfoPtr(shared_from_this ()));
return plugin;
{
// TODO use untranslated lilv_plugin_get_class()
// match gtk2_ardour/plugin_selector.cc
- if (category == c) {
- return true;
- }
- return false;
+ return category == c;
}
bool