Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
+#include <cctype>
#include <string>
#include <vector>
#include <limits>
#include "pbd/stl_delete.h"
#include "pbd/compose.h"
#include "pbd/error.h"
+#include "pbd/replace_all.h"
#include "pbd/xml++.h"
#include "libardour-config.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 "ardour/worker.h"
#include "ardour/search_paths.h"
-#include "i18n.h"
+#include "pbd/i18n.h"
#include <locale.h>
#include <lilv/lilv.h>
using namespace ARDOUR;
using namespace PBD;
+bool LV2Plugin::force_state_save = false;
+
class LV2World : boost::noncopyable {
public:
LV2World ();
LilvNode* ev_EventPort;
LilvNode* ext_logarithmic;
LilvNode* ext_notOnGUI;
+ LilvNode* ext_expensive;
+ LilvNode* ext_causesArtifacts;
+ LilvNode* ext_notAutomatic;
+ LilvNode* ext_rangeSteps;
LilvNode* lv2_AudioPort;
LilvNode* lv2_ControlPort;
LilvNode* lv2_InputPort;
LilvNode* bufz_powerOf2BlockLength;
LilvNode* bufz_fixedBlockLength;
LilvNode* bufz_nominalBlockLength;
+ LilvNode* bufz_coarseBlockLength;
#endif
#ifdef HAVE_LV2_1_10_0
LilvNode* auto_can_write_automatation; // lv2:optionalFeature
LilvNode* auto_automation_control; // atom:supports
LilvNode* auto_automation_controlled; // lv2:portProperty
+ LilvNode* auto_automation_controller; // lv2:portProperty
#endif
private:
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;
- }
+ return (((Worker*)handle)->schedule(size, data)
+ ? LV2_WORKER_SUCCESS
+ : LV2_WORKER_ERR_UNKNOWN);
}
/** Called by the plugin to respond to non-RT work. */
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;
- }
+ return (((Worker*)handle)->respond(size, data)
+ ? LV2_WORKER_SUCCESS
+ : LV2_WORKER_ERR_UNKNOWN);
}
#ifdef LV2_EXTENDED
LV2Plugin* plugin = (LV2Plugin*)handle;
plugin->QueueDraw(); /* EMIT SIGNAL */
}
+
+static void
+midnam_update (LV2_Midnam_Handle handle)
+{
+ LV2Plugin* plugin = (LV2Plugin*)handle;
+ plugin->UpdateMidnam (); /* EMIT SIGNAL */
+}
#endif
/* log extension */
{
char* str = NULL;
const int ret = g_vasprintf(&str, fmt, args);
+ /* strip trailing whitespace */
+ while (strlen (str) > 0 && isspace (str[strlen (str) - 1])) {
+ str[strlen (str) - 1] = '\0';
+ }
+ if (strlen (str) == 0) {
+ return 0;
+ }
+
if (type == URIMap::instance().urids.log_Error) {
error << str << endmsg;
} else if (type == URIMap::instance().urids.log_Warning) {
warning << str << endmsg;
} else if (type == URIMap::instance().urids.log_Note) {
info << str << endmsg;
+ } else if (type == URIMap::instance().urids.log_Trace) {
+ DEBUG_TRACE(DEBUG::LV2, str);
}
- // TODO: Toggleable log:Trace message support
return ret;
}
#endif
#ifdef LV2_EXTENDED
, queue_draw(0)
+ , midnam(0)
#endif
{}
#endif
#ifdef LV2_EXTENDED
LV2_Inline_Display* queue_draw;
+ LV2_Midnam* midnam;
#endif
};
, _impl(new Impl())
, _features(NULL)
, _worker(NULL)
+ , _state_worker(NULL)
, _insert_id("0")
, _patch_port_in_index((uint32_t)-1)
, _patch_port_out_index((uint32_t)-1)
, _impl(new Impl())
, _features(NULL)
, _worker(NULL)
+ , _state_worker(NULL)
, _insert_id(other._insert_id)
, _patch_port_in_index((uint32_t)-1)
, _patch_port_out_index((uint32_t)-1)
lilv_node_free(state_uri);
lilv_node_free(state_iface_uri);
- _features = (LV2_Feature**)calloc(12, sizeof(LV2_Feature*));
+ _features = (LV2_Feature**)calloc(13, 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;
#endif
#ifdef HAVE_LV2_1_2_0
log->vprintf = &log_vprintf;
_log_feature.data = log;
+ const size_t ring_size = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS;
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;
+ _worker = new Worker(this, ring_size);
+ schedule->handle = _worker;
schedule->schedule_work = work_schedule;
_work_schedule_feature.data = schedule;
_features[n_features++] = &_work_schedule_feature;
}
lilv_node_free(worker_schedule);
+ if (_has_state_interface) {
+ // Create a non-threaded worker for use by state restore
+ _state_worker = new Worker(this, ring_size, false);
+ }
+
_impl->instance = lilv_plugin_instantiate(plugin, rate, _features);
_impl->name = lilv_plugin_get_name(plugin);
_impl->author = lilv_plugin_get_author_name(plugin);
#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) {
+ 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)) {
#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) {
throw failed_constructor();
}
+ if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) {
+ if (lilv_port_has_property(_impl->plugin, port, _world.ext_causesArtifacts)) {
+ flags |= PORT_NOAUTO;
+ }
+ if (lilv_port_has_property(_impl->plugin, port, _world.ext_notAutomatic)) {
+ flags |= PORT_NOAUTO;
+ }
+ if (lilv_port_has_property(_impl->plugin, port, _world.ext_expensive)) {
+ flags |= PORT_NOAUTO;
+ }
+ }
#ifdef LV2_EXTENDED
if (lilv_port_has_property(_impl->plugin, port, _world.auto_automation_controlled)) {
if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) {
flags |= PORT_CTRLED;
}
}
+ if (lilv_port_has_property(_impl->plugin, port, _world.auto_automation_controller)) {
+ if ((flags & PORT_INPUT) && (flags & PORT_CONTROL)) {
+ flags |= PORT_CTRLER;
+ }
+ }
#endif
_port_flags.push_back(flags);
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);
#endif
free(_features);
delete _to_ui;
delete _from_ui;
delete _worker;
+ delete _state_worker;
if (_atom_ev_buffers) {
LV2_Evbuf** b = _atom_ev_buffers;
}
return NULL;
}
+
+bool
+LV2Plugin::has_midnam () {
+ return _midname_interface ? true : false;
+}
+
+bool
+LV2Plugin::read_midnam () {
+ bool rv = false;
+ if (!_midname_interface) {
+ 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);
+ 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
return "";
}
+bool
+LV2Plugin::get_layout (uint32_t which, UILayoutHint& h) const
+{
+ /// TODO lookup port-properties
+ if (unique_id () != "urn:ardour:a-eq") {
+ return false;
+ }
+ h.knob = true;
+ switch (which) {
+ case 0: h.x0 = 0; h.x1 = 1; h.y0 = 2; h.y1 = 3; break; // Frequency L
+ case 1: h.x0 = 0; h.x1 = 1; h.y0 = 0; h.y1 = 1; break; // Gain L
+ case 17: h.x0 = 0; h.x1 = 1; h.y0 = 5; h.y1 = 6; break; // enable L
+
+ 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 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 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 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 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 = 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;
+ }
+ return true;
+}
+
uint32_t
LV2Plugin::nth_parameter(uint32_t n, bool& ok) const
{
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
LilvNode* pset = lilv_new_uri(world, r.uri.c_str());
LilvState* state = lilv_state_new_from_world(world, _uri_map.urid_map(), pset);
+ const LV2_Feature* state_features[2] = { NULL, NULL };
+ LV2_Worker_Schedule schedule = { _state_worker, work_schedule };
+ const LV2_Feature state_sched_feature = { LV2_WORKER__schedule, &schedule };
+ if (_state_worker) {
+ state_features[0] = &state_sched_feature;
+ }
+
if (state) {
- lilv_state_restore(state, _impl->instance, set_port_value, this, 0, NULL);
+ lilv_state_restore(state, _impl->instance, set_port_value, this, 0, state_features);
lilv_state_free(state);
Plugin::load_preset(r);
}
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");
lilv_node_free(render);
}
}
}
int
-LV2Plugin::work(uint32_t size, const void* data)
+LV2Plugin::work(Worker& worker, uint32_t size, const void* data)
{
+ Glib::Threads::Mutex::Lock lm(_work_mutex);
return _impl->work_iface->work(
- _impl->instance->lv2_handle, work_respond, this, size, data);
+ _impl->instance->lv2_handle, work_respond, &worker, size, data);
}
int
const char* sym;
const char* value;
uint32_t port_id;
- LocaleGuard lg ();
+ LocaleGuard lg;
if (node.name() != state_node_name()) {
error << _("Bad node sent to LV2Plugin::set_state") << endmsg;
lilv_port_get_range(_impl->plugin, port, &def, &min, &max);
portunits = lilv_port_get_value(_impl->plugin, port, _world.units_unit);
+ LilvNode* steps = lilv_port_get(_impl->plugin, port, _world.ext_rangeSteps);
+
// TODO: Once we can rely on lilv 0.18.0 being present,
// load_parameter_descriptor() can be used for ports as well
desc.integer_step = lilv_port_has_property(_impl->plugin, port, _world.lv2_integer);
desc.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));
+ }
+
+
lilv_node_free(def);
lilv_node_free(min);
lilv_node_free(max);
+ lilv_node_free(steps);
lilv_nodes_free(portunits);
return 0;
set<Evoral::Parameter> ret;
for (uint32_t i = 0; i < parameter_count(); ++i) {
- if (parameter_is_input(i) && parameter_is_control(i)) {
+ if (parameter_is_input(i) && parameter_is_control(i) && !(_port_flags[i] & PORT_NOAUTO)) {
ret.insert(ret.end(), Evoral::Parameter(PluginAutomation, 0, i));
}
}
void
LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr<AutomationControl> c)
{
- if ((_port_flags[i] & PORT_CTRLED)) {
+ if ((_port_flags[i] & (PORT_CTRLED | PORT_CTRLER))) {
+ DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Ctrl Port %1\n", i));
_ctrl_map [i] = AutomationCtrlPtr (new AutomationCtrl(c));
}
}
const TempoMetric& t,
Timecode::BBT_Time& bbt,
double speed,
+ double bpm,
framepos_t position,
framecnt_t offset)
{
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_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,
- ChanMapping in_map, ChanMapping out_map,
- pframes_t nframes, framecnt_t offset)
+ framepos_t start, framepos_t end, double speed,
+ ChanMapping in_map, ChanMapping out_map,
+ pframes_t nframes, framecnt_t offset)
{
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 run %2 offset %3\n", name(), nframes, offset));
- Plugin::connect_and_run(bufs, in_map, out_map, nframes, offset);
+ Plugin::connect_and_run(bufs, start, end, speed, in_map, out_map, nframes, offset);
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);
+ TempoMetric tmetric = tmap.metric_at(start, &metric_i);
if (_freewheel_control_port) {
*_freewheel_control_port = _session.engine().freewheeling() ? 1.f : 0.f;
}
if (_bpm_control_port) {
- *_bpm_control_port = tmetric.tempo().beats_per_minute();
+ *_bpm_control_port = tmap.tempo_at_frame (start).beats_per_minute();
}
#ifdef LV2_EXTENDED
- if (_can_write_automation && _session.transport_frame() != _next_cycle_start) {
+ if (_can_write_automation && start != _next_cycle_start) {
// add guard-points after locating
for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
i->second->guard = 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);
+ Timecode::BBT_Time bbt (tmap.bbt_at_frame (start));
+ double bpm = tmap.tempo_at_frame (start).beats_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 ||
+ 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, _session.transport_speed(),
- _session.transport_frame(), 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 = _session.transport_frame() + nframes;
+ const framepos_t tend = end;
++metric_i;
while (m != m_end || (metric_i != tmap.metrics_end() &&
(*metric_i)->frame() < tend)) {
++m;
} else {
tmetric.set_metric(metric);
- bbt = metric->start();
+ Timecode::BBT_Time bbt;
+ bbt = tmap.bbt_at_pulse (metric->pulse());
+ double bpm = tmap.tempo_at_frame (start/*XXX*/).beats_per_minute();
write_position(&_impl->forge, _ev_buffers[port_index],
- tmetric, bbt, _session.transport_speed(),
+ tmetric, bbt, speed, bpm,
metric->frame(),
- metric->frame() - _session.transport_frame());
+ metric->frame() - start);
++metric_i;
}
}
const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
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));
AutomationCtrlPtr c = get_automation_control (p);
- if (c && c->ac->automation_state() == Touch) {
+ 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);
if (c->guard) {
c->guard = false;
- c->ac->list()->add (_session.transport_frame() + frames, v, true, true);
+ c->ac->list()->add (when, v, true, true);
} else {
- c->ac->set_double (v, _session.transport_frame() + frames, true);
+ c->ac->set_double (v, when, true);
}
}
}
// writes automation for its own inputs
// -> put them in "touch" mode (preferably "exclusive plugin touch(TM)"
for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
- i->second->ac->set_automation_state (Touch);
+ if (_port_flags[i->first] & PORT_CTRLED) {
+ DEBUG_TRACE(DEBUG::LV2Automate,
+ string_compose ("Setup p: %1\n", i->first));
+ i->second->ac->set_automation_state (Touch);
+ }
}
}
else if (obj->body.otype == _uri_map.urids.auto_finalize) {
// set [touched] parameters to "play" ??
+ // allow plugin to change its mode (from analyze to apply)
+ const LV2_Atom* parameter = NULL;
+ const LV2_Atom* value = NULL;
+ lv2_atom_object_get(obj,
+ _uri_map.urids.auto_parameter, ¶meter,
+ _uri_map.urids.auto_value, &value,
+ 0);
+ if (parameter && value) {
+ const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
+ const float v = ((const LV2_Atom_Float*)value)->body;
+ AutomationCtrlPtr c = get_automation_control (p);
+ DEBUG_TRACE(DEBUG::LV2Automate,
+ string_compose ("Finalize p: %1 v: %2\n", p, v));
+ if (c && _port_flags[p] & PORT_CTRLER) {
+ c->ac->set_value(v, Controllable::NoGroup);
+ }
+ } else {
+ DEBUG_TRACE(DEBUG::LV2Automate, "Finalize\n");
+ }
+ for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
+ // guard will be false if an event was written
+ if ((_port_flags[i->first] & PORT_CTRLED) && !i->second->guard) {
+ DEBUG_TRACE(DEBUG::LV2Automate,
+ string_compose ("Thin p: %1\n", i->first));
+ i->second->ac->alist ()->thin (20);
+ }
+ }
}
else if (obj->body.otype == _uri_map.urids.auto_start) {
const LV2_Atom* parameter = NULL;
if (parameter) {
const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
AutomationCtrlPtr c = get_automation_control (p);
+ DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Start Touch p: %1\n", p));
if (c) {
- c->ac->start_touch (_session.transport_frame());
+ c->ac->start_touch (std::max ((framepos_t)0, start - _current_latency));
c->guard = true;
}
}
if (parameter) {
const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
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, _session.transport_frame());
+ c->ac->stop_touch (true, std::max ((framepos_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)) {
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);
+ _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_frame (start).beats_per_minute();
+ Timecode::BBT_Time bbt (tmap.bbt_at_frame (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.frame_rate());
+ }
if (_latency_control_port) {
framecnt_t new_latency = signal_latency ();
- if (_current_latency != new_latency) {
- LatencyChanged (_current_latency, new_latency); /* EMIT SIGNAL */
- }
_current_latency = new_latency;
}
return 0;
return _port_flags[param] & PORT_INPUT;
}
+uint32_t
+LV2Plugin::designated_bypass_port ()
+{
+ const LilvPort* port = NULL;
+ 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);
+ if (port) {
+ return lilv_port_get_index (_impl->plugin, port);
+ }
+#endif
+ return UINT32_MAX;
+}
+
void
LV2Plugin::print_parameter(uint32_t param, char* buf, uint32_t len) const
{
}
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(sync_work || session().engine().freewheeling());
+ }
+
+ // Run the plugin for this cycle
lilv_instance_run(_impl->instance, nframes);
- if (_impl->work_iface) {
+ // Emit any queued worker responses (calls a plugin callback)
+ if (_state_worker) {
+ _state_worker->emit_responses();
+ }
+ if (_worker) {
_worker->emit_responses();
+ }
+
+ // Notify the plugin that a work run cycle is complete
+ if (_impl->work_iface) {
if (_impl->work_iface->end_run) {
_impl->work_iface->end_run(_impl->instance->lv2_handle);
}
port_index++;
}
- run(bufsize);
+ run(bufsize, true);
deactivate();
if (was_activated) {
activate();
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);
+ ext_expensive = lilv_new_uri(world, LV2_PORT_PROPS__expensive);
+ 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);
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);
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);
#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(auto_can_write_automatation);
lilv_node_free(auto_automation_control);
lilv_node_free(auto_automation_controlled);
+ lilv_node_free(auto_automation_controller);
#endif
lilv_node_free(patch_Message);
lilv_node_free(patch_writable);
lilv_node_free(lv2_InputPort);
lilv_node_free(lv2_ControlPort);
lilv_node_free(lv2_AudioPort);
+ lilv_node_free(ext_rangeSteps);
+ lilv_node_free(ext_notAutomatic);
+ lilv_node_free(ext_causesArtifacts);
+ lilv_node_free(ext_expensive);
lilv_node_free(ext_notOnGUI);
lilv_node_free(ext_logarithmic);
lilv_node_free(ev_EventPort);