David Robillard
27dfd8a7e3
In practice, this mostly means integers when presets leave off the ".0", but we implement all the numeric types here for good measure. Also while we're at it, warn about unknown types now so it doesn't take three people a half an hour to figure out what's going on the next time something like this happens.
3984 lines
126 KiB
C++
3984 lines
126 KiB
C++
/*
|
|
* Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net>
|
|
* Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
|
|
* Copyright (C) 2008-2019 David Robillard <d@drobilla.net>
|
|
* Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
|
|
* Copyright (C) 2013-2018 John Emmas <john@creativepost.co.uk>
|
|
* Copyright (C) 2013 Michael R. Fisher <mfisher@bketech.com>
|
|
* Copyright (C) 2014-2016 Tim Mayberry <mojofunk@gmail.com>
|
|
* Copyright (C) 2016-2017 Damien Zammit <damien@zamaudio.com>
|
|
* Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
|
|
* Copyright (C) 2017 Johannes Mueller <github@johannes-mueller.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <cctype>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <limits>
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "pbd/gstdio_compat.h"
|
|
#include <glib/gprintf.h>
|
|
#include <glibmm.h>
|
|
|
|
#include <boost/utility.hpp>
|
|
|
|
#include "pbd/file_utils.h"
|
|
#include "pbd/stl_delete.h"
|
|
#include "pbd/compose.h"
|
|
#include "pbd/error.h"
|
|
#include "pbd/locale_guard.h"
|
|
#include "pbd/pthread_utils.h"
|
|
#include "pbd/replace_all.h"
|
|
#include "pbd/xml++.h"
|
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
#include <shlobj.h> // CSIDL_*
|
|
#include "pbd/windows_special_dirs.h"
|
|
#endif
|
|
|
|
#ifdef WAF_BUILD
|
|
#include "libardour-config.h"
|
|
#endif
|
|
|
|
#include "ardour/audio_buffer.h"
|
|
#include "ardour/audioengine.h"
|
|
#include "ardour/directory_names.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/utils.h"
|
|
#include "ardour/worker.h"
|
|
#include "ardour/search_paths.h"
|
|
|
|
#include "pbd/i18n.h"
|
|
#include <locale.h>
|
|
|
|
#include <lilv/lilv.h>
|
|
|
|
#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
|
|
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
|
|
#include "lv2/lv2plug.in/ns/ext/log/log.h"
|
|
#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
|
|
#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h"
|
|
#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
|
|
#include "lv2/lv2plug.in/ns/ext/state/state.h"
|
|
#include "lv2/lv2plug.in/ns/ext/time/time.h"
|
|
#include "lv2/lv2plug.in/ns/ext/worker/worker.h"
|
|
#include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h"
|
|
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
|
|
#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"
|
|
#include "lv2/lv2plug.in/ns/ext/parameters/parameters.h"
|
|
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
|
|
#include "lv2/lv2plug.in/ns/ext/options/options.h"
|
|
|
|
#include "lv2_evbuf.h"
|
|
|
|
#ifdef HAVE_SUIL
|
|
#include <suil/suil.h>
|
|
#endif
|
|
|
|
// Compatibility for old LV2
|
|
#ifndef LV2_ATOM_CONTENTS_CONST
|
|
#define LV2_ATOM_CONTENTS_CONST(type, atom) \
|
|
((const void*)((const uint8_t*)(atom) + sizeof(type)))
|
|
#endif
|
|
#ifndef LV2_ATOM_BODY_CONST
|
|
#define LV2_ATOM_BODY_CONST(atom) LV2_ATOM_CONTENTS_CONST(LV2_Atom, atom)
|
|
#endif
|
|
#ifndef LV2_PATCH__property
|
|
#define LV2_PATCH__property LV2_PATCH_PREFIX "property"
|
|
#endif
|
|
#ifndef LV2_PATCH__value
|
|
#define LV2_PATCH__value LV2_PATCH_PREFIX "value"
|
|
#endif
|
|
#ifndef LV2_PATCH__writable
|
|
#define LV2_PATCH__writable LV2_PATCH_PREFIX "writable"
|
|
#endif
|
|
|
|
/** The number of MIDI buffers that will fit in a UI/worker comm buffer.
|
|
This needs to be roughly the number of cycles the UI will get around to
|
|
actually processing the traffic. Lower values are flakier but save memory.
|
|
*/
|
|
static const size_t NBUFS = 4;
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
using namespace Temporal;
|
|
|
|
bool LV2Plugin::force_state_save = false;
|
|
int32_t LV2Plugin::_ui_style_flat = 0;
|
|
int32_t LV2Plugin::_ui_style_boxy = 0;
|
|
uint32_t LV2Plugin::_ui_background_color = 0x000000ff; // RGBA
|
|
uint32_t LV2Plugin::_ui_foreground_color = 0xffffffff; // RGBA
|
|
uint32_t LV2Plugin::_ui_contrasting_color = 0x33ff33ff; // RGBA
|
|
unsigned long LV2Plugin::_ui_transient_win_id = 0;
|
|
|
|
|
|
class LV2World : boost::noncopyable {
|
|
public:
|
|
LV2World ();
|
|
~LV2World ();
|
|
|
|
void load_bundled_plugins(bool verbose=false);
|
|
|
|
LilvWorld* world;
|
|
|
|
LilvNode* atom_AtomPort;
|
|
LilvNode* atom_Chunk;
|
|
LilvNode* atom_Sequence;
|
|
LilvNode* atom_bufferType;
|
|
LilvNode* atom_eventTransfer;
|
|
LilvNode* atom_supports;
|
|
LilvNode* ev_EventPort;
|
|
LilvNode* ext_logarithmic;
|
|
LilvNode* ext_notOnGUI;
|
|
LilvNode* ext_expensive;
|
|
LilvNode* ext_causesArtifacts;
|
|
LilvNode* ext_notAutomatic;
|
|
LilvNode* ext_rangeSteps;
|
|
LilvNode* ext_displayPriority;
|
|
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* lv2_maximum;
|
|
LilvNode* lv2_reportsLatency;
|
|
LilvNode* lv2_sampleRate;
|
|
LilvNode* lv2_toggled;
|
|
LilvNode* midi_MidiEvent;
|
|
LilvNode* rdfs_comment;
|
|
LilvNode* rdfs_label;
|
|
LilvNode* rdfs_range;
|
|
LilvNode* rsz_minimumSize;
|
|
LilvNode* time_Position;
|
|
LilvNode* time_beatsPerMin;
|
|
LilvNode* ui_GtkUI;
|
|
LilvNode* ui_X11UI;
|
|
LilvNode* ui_external;
|
|
LilvNode* ui_externalkx;
|
|
LilvNode* units_hz;
|
|
LilvNode* units_db;
|
|
LilvNode* units_unit;
|
|
LilvNode* units_render;
|
|
LilvNode* units_midiNote;
|
|
LilvNode* patch_writable;
|
|
LilvNode* patch_Message;
|
|
LilvNode* opts_requiredOptions;
|
|
LilvNode* bufz_powerOf2BlockLength;
|
|
LilvNode* bufz_fixedBlockLength;
|
|
LilvNode* bufz_nominalBlockLength;
|
|
LilvNode* bufz_coarseBlockLength;
|
|
LilvNode* state_loadDefaultState;
|
|
|
|
#ifdef LV2_EXTENDED
|
|
LilvNode* lv2_noSampleAccurateCtrl;
|
|
LilvNode* routing_connectAllOutputs; // lv2:optionalFeature
|
|
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
|
|
LilvNode* inline_display_interface; // lv2:extensionData
|
|
LilvNode* inline_display_in_gui; // lv2:optionalFeature
|
|
LilvNode* inline_mixer_control; // lv2:PortProperty
|
|
#endif
|
|
|
|
private:
|
|
bool _bundle_checked;
|
|
};
|
|
|
|
static LV2World _world;
|
|
|
|
static bool
|
|
is_inside_bundle (std::string const& bundle_dir, std::string path)
|
|
{
|
|
assert (Glib::file_test (bundle_dir, Glib::FILE_TEST_IS_DIR | Glib::FILE_TEST_EXISTS));
|
|
|
|
if (bundle_dir.size () > path.size ()) {
|
|
return false;
|
|
}
|
|
if (bundle_dir == path) {
|
|
return true;
|
|
}
|
|
if (path == Glib::path_get_dirname (path)) {
|
|
/* path must be root */
|
|
return false;
|
|
}
|
|
while (bundle_dir.size () < path.size ()) {
|
|
/* step up, compare with bundle_dir */
|
|
path = Glib::path_get_dirname (path);
|
|
if (bundle_dir == path) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* worker extension */
|
|
|
|
/** Called by the plugin to schedule non-RT work. */
|
|
static LV2_Worker_Status
|
|
work_schedule(LV2_Worker_Schedule_Handle handle,
|
|
uint32_t size,
|
|
const void* data)
|
|
{
|
|
return (((Worker*)handle)->schedule(size, data)
|
|
? LV2_WORKER_SUCCESS
|
|
: LV2_WORKER_ERR_UNKNOWN);
|
|
}
|
|
|
|
/** Called by the plugin to respond to non-RT work. */
|
|
static LV2_Worker_Status
|
|
work_respond(LV2_Worker_Respond_Handle handle,
|
|
uint32_t size,
|
|
const void* data)
|
|
{
|
|
return (((Worker*)handle)->respond(size, data)
|
|
? LV2_WORKER_SUCCESS
|
|
: LV2_WORKER_ERR_UNKNOWN);
|
|
}
|
|
|
|
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;
|
|
|
|
float fvalue = 0.0f;
|
|
if (type == URIMap::instance ().urids.atom_Float) {
|
|
fvalue = *(const float*)value;
|
|
} else if (type == URIMap::instance ().urids.atom_Double) {
|
|
fvalue = static_cast<float> (*(const double*)value);
|
|
} else if (type == URIMap::instance ().urids.atom_Int) {
|
|
fvalue = static_cast<float> (*(const int32_t*)value);
|
|
} else if (type == URIMap::instance ().urids.atom_Long) {
|
|
fvalue = static_cast<float> (*(const int64_t*)value);
|
|
} else {
|
|
error << string_compose (
|
|
_("LV2<%1>: Preset value for port '%2' has unsupported datatype <%3>"),
|
|
self->name (),
|
|
port_symbol,
|
|
self->uri_map ().id_to_uri(type));
|
|
}
|
|
|
|
const uint32_t port_index = self->port_index(port_symbol);
|
|
if (port_index != (uint32_t)-1) {
|
|
self->set_parameter(port_index, fvalue, 0);
|
|
self->PresetPortSetValue (port_index, fvalue); /* EMIT SIGNAL */
|
|
}
|
|
}
|
|
|
|
#ifdef LV2_EXTENDED
|
|
/* inline display extension */
|
|
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 */
|
|
|
|
static int
|
|
log_vprintf(LV2_Log_Handle /*handle*/,
|
|
LV2_URID type,
|
|
const char* fmt,
|
|
va_list args)
|
|
{
|
|
char* str = NULL;
|
|
const int ret = g_vasprintf(&str, fmt, args);
|
|
/* 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);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
log_printf(LV2_Log_Handle handle,
|
|
LV2_URID type,
|
|
const char* fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
const int ret = log_vprintf(handle, type, fmt, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
|
|
struct LV2Plugin::Impl {
|
|
Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0)
|
|
, work_iface(0)
|
|
, opts_iface(0)
|
|
, state(0)
|
|
, block_length(0)
|
|
, options(0)
|
|
#ifdef LV2_EXTENDED
|
|
, queue_draw(0)
|
|
, midnam(0)
|
|
#endif
|
|
{}
|
|
|
|
/** Find the LV2 input port with the given designation.
|
|
* If found, bufptrs[port_index] will be set to bufptr.
|
|
*/
|
|
const LilvPort* designated_input (const char* uri, void** bufptrs[], void** bufptr);
|
|
|
|
const LilvPlugin* plugin;
|
|
const LilvUI* ui;
|
|
const LilvNode* ui_type;
|
|
LilvNode* name;
|
|
LilvNode* author;
|
|
LilvInstance* instance;
|
|
const LV2_Worker_Interface* work_iface;
|
|
const LV2_Options_Interface* opts_iface;
|
|
LilvState* state;
|
|
LV2_Atom_Forge forge;
|
|
LV2_Atom_Forge ui_forge;
|
|
int32_t block_length;
|
|
LV2_Options_Option* options;
|
|
#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,
|
|
samplecnt_t rate)
|
|
: Plugin (engine, session)
|
|
, Workee ()
|
|
, _impl(new Impl())
|
|
, _features(NULL)
|
|
, _worker(NULL)
|
|
, _state_worker(NULL)
|
|
, _insert_id("0")
|
|
, _bpm_control_port_index((uint32_t)-1)
|
|
, _patch_port_in_index((uint32_t)-1)
|
|
, _patch_port_out_index((uint32_t)-1)
|
|
, _uri_map(URIMap::instance())
|
|
, _no_sample_accurate_ctrl (false)
|
|
, _connect_all_audio_outputs (false)
|
|
{
|
|
init(c_plugin, rate);
|
|
latency_compute_run();
|
|
}
|
|
|
|
LV2Plugin::LV2Plugin (const LV2Plugin& other)
|
|
: Plugin (other)
|
|
, Workee ()
|
|
, _impl(new Impl())
|
|
, _features(NULL)
|
|
, _worker(NULL)
|
|
, _state_worker(NULL)
|
|
, _insert_id(other._insert_id)
|
|
, _bpm_control_port_index((uint32_t)-1)
|
|
, _patch_port_in_index((uint32_t)-1)
|
|
, _patch_port_out_index((uint32_t)-1)
|
|
, _uri_map(URIMap::instance())
|
|
, _no_sample_accurate_ctrl (false)
|
|
, _connect_all_audio_outputs (false)
|
|
{
|
|
init(other._impl->plugin, other._sample_rate);
|
|
|
|
XMLNode root (other.state_node_name ());
|
|
other.add_state (&root);
|
|
set_state (root, Stateful::current_state_version);
|
|
|
|
for (uint32_t i = 0; i < parameter_count(); ++i) {
|
|
_control_data[i] = other._shadow_data[i];
|
|
_shadow_data[i] = other._shadow_data[i];
|
|
}
|
|
|
|
latency_compute_run();
|
|
}
|
|
|
|
void
|
|
LV2Plugin::init(const void* c_plugin, samplecnt_t rate)
|
|
{
|
|
DEBUG_TRACE(DEBUG::LV2, "init\n");
|
|
|
|
_impl->plugin = (const LilvPlugin*)c_plugin;
|
|
_impl->ui = NULL;
|
|
_impl->ui_type = NULL;
|
|
_to_ui = NULL;
|
|
_from_ui = NULL;
|
|
_control_data = 0;
|
|
_shadow_data = 0;
|
|
_atom_ev_buffers = 0;
|
|
_ev_buffers = 0;
|
|
_bpm_control_port = 0;
|
|
_freewheel_control_port = 0;
|
|
_latency_control_port = 0;
|
|
_next_cycle_start = std::numeric_limits<samplepos_t>::max();
|
|
_next_cycle_speed = 1.0;
|
|
_next_cycle_beat = 0.0;
|
|
_current_bpm = 0.0;
|
|
_prev_time_scale = 0.0;
|
|
_seq_size = _engine.raw_buffer_size(DataType::MIDI);
|
|
_state_version = 0;
|
|
_was_activated = false;
|
|
_has_state_interface = false;
|
|
_can_write_automation = false;
|
|
#ifdef LV2_EXTENDED
|
|
_display_interface = 0;
|
|
_inline_display_in_gui = false;
|
|
#endif
|
|
_max_latency = 0;
|
|
_current_latency = 0;
|
|
_impl->block_length = _session.get_block_size();
|
|
|
|
_sample_rate = rate;
|
|
_fsample_rate = rate;
|
|
|
|
_instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
|
|
_data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access";
|
|
_make_path_feature.URI = LV2_STATE__makePath;
|
|
_log_feature.URI = LV2_LOG__log;
|
|
_work_schedule_feature.URI = LV2_WORKER__schedule;
|
|
_work_schedule_feature.data = NULL;
|
|
_def_state_feature.URI = LV2_STATE_PREFIX "loadDefaultState"; // Post LV2-1.2.0
|
|
_def_state_feature.data = NULL;
|
|
_block_length_feature.URI = LV2_BUF_SIZE__boundedBlockLength;
|
|
_block_length_feature.data = NULL;
|
|
|
|
const LilvPlugin* plugin = _impl->plugin;
|
|
|
|
LilvNode* state_iface_uri = lilv_new_uri(_world.world, LV2_STATE__interface);
|
|
LilvNode* state_uri = lilv_new_uri(_world.world, LV2_STATE_URI);
|
|
_has_state_interface =
|
|
// What plugins should have (lv2:extensionData state:Interface)
|
|
lilv_plugin_has_extension_data(plugin, state_iface_uri)
|
|
// What some outdated/incorrect ones have
|
|
|| lilv_plugin_has_feature(plugin, state_uri);
|
|
lilv_node_free(state_uri);
|
|
lilv_node_free(state_iface_uri);
|
|
|
|
_features = (LV2_Feature**)calloc (15, sizeof(LV2_Feature*));
|
|
_features[0] = &_instance_access_feature;
|
|
_features[1] = &_data_access_feature;
|
|
_features[2] = &_make_path_feature;
|
|
_features[3] = _uri_map.urid_map_feature();
|
|
_features[4] = _uri_map.urid_unmap_feature();
|
|
_features[5] = &_log_feature;
|
|
_features[6] = &_def_state_feature;
|
|
_features[7] = &_block_length_feature;
|
|
|
|
unsigned n_features = 8;
|
|
|
|
lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map());
|
|
lv2_atom_forge_init(&_impl->ui_forge, _uri_map.urid_map());
|
|
|
|
#ifdef LV2_EXTENDED
|
|
_impl->queue_draw = (LV2_Inline_Display*)
|
|
malloc (sizeof(LV2_Inline_Display));
|
|
_impl->queue_draw->handle = this;
|
|
_impl->queue_draw->queue_draw = queue_draw;
|
|
|
|
_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
|
|
|
|
LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
|
|
LV2_URID atom_Bool = _uri_map.uri_to_id(LV2_ATOM__Bool);
|
|
LV2_URID atom_Long = _uri_map.uri_to_id(LV2_ATOM__Long);
|
|
LV2_URID atom_Float = _uri_map.uri_to_id(LV2_ATOM__Float);
|
|
|
|
static const int32_t _min_block_length = 1; // may happen during split-cycles
|
|
static const int32_t _max_block_length = 8192; // max possible (with all engines and during export)
|
|
static const int32_t rt_policy = PBD_SCHED_FIFO;
|
|
static const int32_t rt_priority = pbd_absolute_rt_priority (PBD_SCHED_FIFO, AudioEngine::instance()->client_real_time_priority () - 1);
|
|
/* Consider updating max-block-size whenever the buffersize changes.
|
|
* It requires re-instantiating the plugin (which is a non-realtime operation),
|
|
* so it should be done lightly and only for plugins that require it.
|
|
*
|
|
* given that the block-size can change at any time (split-cycles) ardour currently
|
|
* does not support plugins that require bufz_fixedBlockLength.
|
|
*/
|
|
LV2_Options_Option options[] = {
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__minBlockLength),
|
|
sizeof(int32_t), atom_Int, &_min_block_length },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__maxBlockLength),
|
|
sizeof(int32_t), atom_Int, &_max_block_length },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_BUF_SIZE__sequenceSize),
|
|
sizeof(int32_t), atom_Int, &_seq_size },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id(LV2_PARAMETERS__sampleRate),
|
|
sizeof(float), atom_Float, &_fsample_rate },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"),
|
|
sizeof(int32_t), atom_Int, &_impl->block_length },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/threads/#schedPolicy"),
|
|
sizeof(int32_t), atom_Int, &rt_policy },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/threads/#schedPriority"),
|
|
sizeof(int32_t), atom_Int, &rt_priority },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/extensions/ui#backgroundColor"),
|
|
sizeof(int32_t), atom_Int, &_ui_background_color },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/extensions/ui#foregroundColor"),
|
|
sizeof(int32_t), atom_Int, &_ui_foreground_color },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/extensions/ui#contrastingColor"),
|
|
sizeof(int32_t), atom_Int, &_ui_contrasting_color },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/extensions/ui#scaleFactor"),
|
|
sizeof(float), atom_Float, &ARDOUR::ui_scale_factor },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/theme/#styleBoxy"),
|
|
sizeof(int32_t), atom_Bool, &_ui_style_boxy },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/theme/#styleFlat"),
|
|
sizeof(int32_t), atom_Bool, &_ui_style_flat },
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://kxstudio.sf.net/ns/lv2ext/props#TransientWindowId"),
|
|
sizeof(int32_t), atom_Long, &_ui_transient_win_id },
|
|
{ LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }
|
|
};
|
|
|
|
_impl->options = (LV2_Options_Option*) malloc (sizeof (options));
|
|
memcpy ((void*) _impl->options, (void*) options, sizeof (options));
|
|
|
|
_options_feature.URI = LV2_OPTIONS__options;
|
|
_options_feature.data = _impl->options;
|
|
_features[n_features++] = &_options_feature;
|
|
|
|
#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;
|
|
make_path->path = &lv2_state_make_path;
|
|
_make_path_feature.data = make_path;
|
|
|
|
LV2_Log_Log* log = (LV2_Log_Log*)malloc(sizeof(LV2_Log_Log));
|
|
log->handle = this;
|
|
log->printf = &log_printf;
|
|
log->vprintf = &log_vprintf;
|
|
_log_feature.data = log;
|
|
|
|
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));
|
|
_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);
|
|
|
|
if (_impl->instance == 0) {
|
|
error << _("LV2: Failed to instantiate plugin ") << uri() << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
_instance_access_feature.data = (void*)_impl->instance->lv2_handle;
|
|
_data_access_extension_data.extension_data = _impl->instance->lv2_descriptor->extension_data;
|
|
_data_access_feature.data = &_data_access_extension_data;
|
|
|
|
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);
|
|
|
|
LilvNode* options_iface_uri = lilv_new_uri(_world.world, LV2_OPTIONS__interface);
|
|
if (lilv_plugin_has_extension_data(plugin, options_iface_uri)) {
|
|
_impl->opts_iface = (const LV2_Options_Interface*)extension_data(
|
|
LV2_OPTIONS__interface);
|
|
}
|
|
lilv_node_free(options_iface_uri);
|
|
|
|
#ifdef LV2_EXTENDED
|
|
_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)) {
|
|
error << string_compose(
|
|
_("LV2: \"%1\" cannot be used, since it cannot do inplace processing."),
|
|
lilv_node_as_string(_impl->name)) << endmsg;
|
|
lilv_node_free(_impl->name);
|
|
lilv_node_free(_impl->author);
|
|
throw failed_constructor();
|
|
}
|
|
|
|
LilvNodes* optional_features = lilv_plugin_get_optional_features (plugin);
|
|
if (lilv_nodes_contains (optional_features, _world.bufz_coarseBlockLength)) {
|
|
_no_sample_accurate_ctrl = true;
|
|
}
|
|
|
|
#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.routing_connectAllOutputs)) {
|
|
_connect_all_audio_outputs = true;
|
|
}
|
|
if (lilv_nodes_contains (optional_features, _world.auto_can_write_automatation)) {
|
|
_can_write_automation = true;
|
|
}
|
|
if (lilv_plugin_has_extension_data(plugin, _world.inline_display_interface)) {
|
|
_display_interface = (const LV2_Inline_Display_Interface*) extension_data (LV2_INLINEDISPLAY__interface);
|
|
}
|
|
#ifndef NDEBUG
|
|
else if (extension_data (LV2_INLINEDISPLAY__interface)) {
|
|
warning << string_compose(_("LV2: Plugin '%1' has inline-display extension without lv2:extensionData meta-data "), name ()) << endmsg;
|
|
}
|
|
#endif
|
|
if (lilv_nodes_contains (optional_features, _world.inline_display_in_gui)) {
|
|
_inline_display_in_gui = true;
|
|
}
|
|
#endif
|
|
lilv_nodes_free(optional_features);
|
|
|
|
/* Snapshot default state -- https://lv2plug.in/ns/ext/state#loadDefaultState */
|
|
LilvState* state = NULL;
|
|
|
|
LilvNodes* required_features = lilv_plugin_get_required_features (plugin);
|
|
if (lilv_nodes_contains (required_features, _world.state_loadDefaultState)) {
|
|
state = lilv_state_new_from_world(_world.world, _uri_map.urid_map(), lilv_plugin_get_uri(_impl->plugin));
|
|
}
|
|
lilv_nodes_free (required_features);
|
|
|
|
const uint32_t num_ports = this->num_ports();
|
|
for (uint32_t i = 0; i < num_ports; ++i) {
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, i);
|
|
PortFlags flags = 0;
|
|
size_t minimumSize = 0;
|
|
|
|
if (lilv_port_is_a(_impl->plugin, port, _world.lv2_OutputPort)) {
|
|
flags |= PORT_OUTPUT;
|
|
} else if (lilv_port_is_a(_impl->plugin, port, _world.lv2_InputPort)) {
|
|
flags |= PORT_INPUT;
|
|
} else {
|
|
error << string_compose(
|
|
"LV2: \"%1\" port %2 is neither input nor output",
|
|
lilv_node_as_string(_impl->name), i) << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if (lilv_port_is_a(_impl->plugin, port, _world.lv2_ControlPort)) {
|
|
flags |= PORT_CONTROL;
|
|
} else if (lilv_port_is_a(_impl->plugin, port, _world.lv2_AudioPort)) {
|
|
flags |= PORT_AUDIO;
|
|
} else if (lilv_port_is_a(_impl->plugin, port, _world.atom_AtomPort)) {
|
|
LilvNodes* buffer_types = lilv_port_get_value(
|
|
_impl->plugin, port, _world.atom_bufferType);
|
|
LilvNodes* atom_supports = lilv_port_get_value(
|
|
_impl->plugin, port, _world.atom_supports);
|
|
|
|
if (lilv_nodes_contains(buffer_types, _world.atom_Sequence)) {
|
|
flags |= PORT_SEQUENCE;
|
|
if (lilv_nodes_contains(atom_supports, _world.midi_MidiEvent)) {
|
|
flags |= PORT_MIDI;
|
|
}
|
|
if (lilv_nodes_contains(atom_supports, _world.time_Position)) {
|
|
flags |= PORT_POSITION;
|
|
}
|
|
#ifdef LV2_EXTENDED
|
|
if (lilv_nodes_contains(atom_supports, _world.auto_automation_control)) {
|
|
flags |= PORT_AUTOCTRL;
|
|
}
|
|
#endif
|
|
if (lilv_nodes_contains(atom_supports, _world.patch_Message)) {
|
|
flags |= PORT_PATCHMSG;
|
|
if (flags & PORT_INPUT) {
|
|
_patch_port_in_index = i;
|
|
} else {
|
|
_patch_port_out_index = i;
|
|
}
|
|
}
|
|
}
|
|
LilvNodes* min_size_v = lilv_port_get_value(_impl->plugin, port, _world.rsz_minimumSize);
|
|
LilvNode* min_size = min_size_v ? lilv_nodes_get_first(min_size_v) : NULL;
|
|
if (min_size && lilv_node_is_int(min_size)) {
|
|
minimumSize = lilv_node_as_int(min_size);
|
|
}
|
|
lilv_nodes_free(min_size_v);
|
|
lilv_nodes_free(buffer_types);
|
|
lilv_nodes_free(atom_supports);
|
|
} else {
|
|
error << string_compose(
|
|
"LV2: \"%1\" port %2 has no known data type",
|
|
lilv_node_as_string(_impl->name), i) << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
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);
|
|
_port_minimumSize.push_back(minimumSize);
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("port %1 buffer %2 bytes\n", i, minimumSize));
|
|
}
|
|
|
|
_control_data = new float[num_ports];
|
|
_shadow_data = new float[num_ports];
|
|
_defaults = new float[num_ports];
|
|
_ev_buffers = new LV2_Evbuf*[num_ports];
|
|
memset(_ev_buffers, 0, sizeof(LV2_Evbuf*) * num_ports);
|
|
|
|
const bool latent = lilv_plugin_has_latency(plugin);
|
|
const uint32_t latency_index = (latent)
|
|
? lilv_plugin_get_latency_port_index(plugin)
|
|
: 0;
|
|
|
|
// Build an array of pointers to special parameter buffers
|
|
void*** params = new void**[num_ports];
|
|
for (uint32_t i = 0; i < num_ports; ++i) {
|
|
params[i] = NULL;
|
|
}
|
|
_impl->designated_input (LV2_TIME__beatsPerMinute, params, (void**)&_bpm_control_port);
|
|
_impl->designated_input (LV2_CORE__freeWheeling, params, (void**)&_freewheel_control_port);
|
|
|
|
const LilvPort* bpmport = lilv_plugin_get_port_by_designation(plugin, _world.lv2_InputPort, _world.time_beatsPerMin);
|
|
if (bpmport) {
|
|
_bpm_control_port_index = lilv_port_get_index (plugin, bpmport);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < num_ports; ++i) {
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(plugin, i);
|
|
const LilvNode* sym = lilv_port_get_symbol(plugin, port);
|
|
|
|
// Store index in map so we can look up index by symbol
|
|
_port_indices.insert(std::make_pair(lilv_node_as_string(sym), i));
|
|
|
|
// Get range and default value if applicable
|
|
if (parameter_is_control(i)) {
|
|
LilvNode* def;
|
|
lilv_port_get_range(plugin, port, &def, NULL, NULL);
|
|
_defaults[i] = def ? lilv_node_as_float(def) : 0.0f;
|
|
if (lilv_port_has_property (plugin, port, _world.lv2_sampleRate)) {
|
|
_defaults[i] *= _session.sample_rate ();
|
|
}
|
|
lilv_node_free(def);
|
|
|
|
lilv_instance_connect_port(_impl->instance, i, &_control_data[i]);
|
|
|
|
if (latent && i == latency_index) {
|
|
LilvNode *max;
|
|
lilv_port_get_range(_impl->plugin, port, NULL, NULL, &max);
|
|
_max_latency = max ? lilv_node_as_float(max) : .02 * _sample_rate;
|
|
_latency_control_port = &_control_data[i];
|
|
*_latency_control_port = 0;
|
|
}
|
|
|
|
if (parameter_is_input(i)) {
|
|
_shadow_data[i] = default_value(i);
|
|
if (params[i]) {
|
|
*params[i] = (void*)&_shadow_data[i];
|
|
}
|
|
} else {
|
|
_shadow_data[i] = 0;
|
|
}
|
|
_control_data[i] = _shadow_data[i];
|
|
} else {
|
|
_defaults[i] = 0.0f;
|
|
}
|
|
}
|
|
|
|
delete[] params;
|
|
|
|
LilvUIs* uis = lilv_plugin_get_uis(plugin);
|
|
|
|
if (lilv_uis_size(uis) > 0) {
|
|
#ifdef HAVE_SUIL
|
|
const LilvUI* this_ui = NULL;
|
|
const LilvNode* this_ui_type = NULL;
|
|
#if ! (defined(__APPLE__) || defined(PLATFORM_WINDOWS))
|
|
// Always prefer X11 UIs...
|
|
LILV_FOREACH(uis, i, uis) {
|
|
const LilvUI* ui = lilv_uis_get(uis, i);
|
|
if (lilv_ui_is_a(ui, _world.ui_X11UI) &&
|
|
lilv_ui_is_supported (ui,
|
|
suil_ui_supported,
|
|
_world.ui_GtkUI,
|
|
&this_ui_type)) {
|
|
this_ui = ui;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
// Then anything else...
|
|
if (this_ui == NULL) {
|
|
LILV_FOREACH(uis, i, uis) {
|
|
const LilvUI* ui = lilv_uis_get(uis, i);
|
|
if (lilv_ui_is_supported (ui,
|
|
suil_ui_supported,
|
|
_world.ui_GtkUI,
|
|
&this_ui_type)) {
|
|
this_ui = ui;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Found one that is supported by SUIL?...
|
|
if (this_ui != NULL) {
|
|
_impl->ui = this_ui;
|
|
_impl->ui_type = this_ui_type;
|
|
}
|
|
#else
|
|
// Look for Gtk native UI
|
|
LILV_FOREACH(uis, i, uis) {
|
|
const LilvUI* ui = lilv_uis_get(uis, i);
|
|
if (lilv_ui_is_a(ui, _world.ui_GtkUI)) {
|
|
_impl->ui = ui;
|
|
_impl->ui_type = _world.ui_GtkUI;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// If Gtk UI is not available, try to find external UI
|
|
if (!_impl->ui) {
|
|
LILV_FOREACH(uis, i, uis) {
|
|
const LilvUI* ui = lilv_uis_get(uis, i);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
load_supported_properties(_property_descriptors);
|
|
allocate_atom_event_buffers();
|
|
|
|
/* 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);
|
|
}
|
|
if (state) {
|
|
lilv_state_restore (state, _impl->instance, set_port_value, this, 0, _features);
|
|
lilv_state_free(state);
|
|
}
|
|
}
|
|
|
|
int
|
|
LV2Plugin::set_block_size (pframes_t nframes)
|
|
{
|
|
if (_impl->opts_iface) {
|
|
LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
|
|
_impl->block_length = nframes;
|
|
LV2_Options_Option block_size_option[] = {
|
|
{ LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id ("http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"),
|
|
sizeof(int32_t), atom_Int, (void*)&_impl->block_length},
|
|
{ LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }
|
|
};
|
|
_impl->opts_iface->set (_impl->instance->lv2_handle, block_size_option);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::requires_fixed_sized_buffers () const
|
|
{
|
|
/* This controls if Ardour will split the plugin's run()
|
|
* on automation events in order to pass sample-accurate automation
|
|
* via standard control-ports.
|
|
*
|
|
* When returning true Ardour will *not* sub-divide the process-cycle.
|
|
* Automation events that happen between cycle-start and cycle-end will be
|
|
* ignored (ctrl values are interpolated to cycle-start).
|
|
* NB. Atom Sequences are still sample accurate.
|
|
*
|
|
* Note: This does not guarantee a fixed block-size.
|
|
* e.g The process cycle may be split when looping, also
|
|
* the period-size may change any time: see set_block_size()
|
|
*/
|
|
if (get_info()->n_inputs.n_midi() > 0) {
|
|
/* we don't yet implement midi buffer offsets (for split cycles).
|
|
* Also connect_and_run() also uses _session.transport_sample() directly
|
|
* (for BBT) which is not offset for plugin cycle split.
|
|
*/
|
|
return true;
|
|
}
|
|
return _no_sample_accurate_ctrl;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::connect_all_audio_outputs () const
|
|
{
|
|
return _connect_all_audio_outputs;
|
|
}
|
|
|
|
LV2Plugin::~LV2Plugin ()
|
|
{
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 destroy\n", name()));
|
|
|
|
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);
|
|
lilv_node_free(_impl->author);
|
|
free(_impl->options);
|
|
#ifdef LV2_EXTENDED
|
|
free(_impl->queue_draw);
|
|
free(_impl->midnam);
|
|
free(_impl->bankpatch);
|
|
#endif
|
|
|
|
free(_features);
|
|
free(_log_feature.data);
|
|
free(_make_path_feature.data);
|
|
free(_work_schedule_feature.data);
|
|
|
|
delete _to_ui;
|
|
delete _from_ui;
|
|
delete _worker;
|
|
delete _state_worker;
|
|
|
|
if (_atom_ev_buffers) {
|
|
LV2_Evbuf** b = _atom_ev_buffers;
|
|
while (*b) {
|
|
free(*b);
|
|
b++;
|
|
}
|
|
free(_atom_ev_buffers);
|
|
}
|
|
|
|
delete [] _control_data;
|
|
delete [] _shadow_data;
|
|
delete [] _defaults;
|
|
delete [] _ev_buffers;
|
|
delete _impl;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::is_external_ui() const
|
|
{
|
|
return _impl->ui && (lilv_ui_is_a(_impl->ui, _world.ui_external) ||
|
|
lilv_ui_is_a(_impl->ui, _world.ui_externalkx));
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::is_external_kx() const
|
|
{
|
|
return _impl->ui && lilv_ui_is_a(_impl->ui, _world.ui_externalkx);
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::ui_is_resizable () const
|
|
{
|
|
const LilvNode* s = lilv_ui_get_uri(_impl->ui);
|
|
LilvNode* p = lilv_new_uri(_world.world, LV2_CORE__optionalFeature);
|
|
LilvNode* fs = lilv_new_uri(_world.world, LV2_UI__fixedSize);
|
|
LilvNode* nrs = lilv_new_uri(_world.world, LV2_UI__noUserResize);
|
|
|
|
LilvNodes* fs_matches = lilv_world_find_nodes(_world.world, s, p, fs);
|
|
LilvNodes* nrs_matches = lilv_world_find_nodes(_world.world, s, p, nrs);
|
|
|
|
lilv_nodes_free(nrs_matches);
|
|
lilv_nodes_free(fs_matches);
|
|
lilv_node_free(nrs);
|
|
lilv_node_free(fs);
|
|
lilv_node_free(p);
|
|
|
|
return !fs_matches && !nrs_matches;
|
|
}
|
|
|
|
#ifdef LV2_EXTENDED
|
|
bool
|
|
LV2Plugin::has_inline_display () {
|
|
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) {
|
|
/* Plugin::Display_Image_Surface is identical to
|
|
* LV2_Inline_Display_Image_Surface */
|
|
return (Plugin::Display_Image_Surface*) _display_interface->render ((void*)_impl->instance->lv2_handle, w, h);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
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) {
|
|
rv = MIDI::Name::MidiPatchManager::instance().update_custom_midnam (midnam_model(), 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
|
|
LV2Plugin::unique_id() const
|
|
{
|
|
return lilv_node_as_uri(lilv_plugin_get_uri(_impl->plugin));
|
|
}
|
|
|
|
const char*
|
|
LV2Plugin::uri() const
|
|
{
|
|
return lilv_node_as_uri(lilv_plugin_get_uri(_impl->plugin));
|
|
}
|
|
|
|
const char*
|
|
LV2Plugin::label() const
|
|
{
|
|
return lilv_node_as_string(_impl->name);
|
|
}
|
|
|
|
const char*
|
|
LV2Plugin::name() const
|
|
{
|
|
return lilv_node_as_string(_impl->name);
|
|
}
|
|
|
|
const char*
|
|
LV2Plugin::maker() const
|
|
{
|
|
return _impl->author ? lilv_node_as_string (_impl->author) : "Unknown";
|
|
}
|
|
|
|
uint32_t
|
|
LV2Plugin::num_ports() const
|
|
{
|
|
return lilv_plugin_get_num_ports(_impl->plugin);
|
|
}
|
|
|
|
uint32_t
|
|
LV2Plugin::parameter_count() const
|
|
{
|
|
return lilv_plugin_get_num_ports(_impl->plugin);
|
|
}
|
|
|
|
float
|
|
LV2Plugin::default_value(uint32_t port)
|
|
{
|
|
return _defaults[port];
|
|
}
|
|
|
|
const char*
|
|
LV2Plugin::port_symbol(uint32_t index) const
|
|
{
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, index);
|
|
if (!port) {
|
|
error << name() << ": Invalid port index " << index << endmsg;
|
|
}
|
|
|
|
const LilvNode* sym = lilv_port_get_symbol(_impl->plugin, port);
|
|
return lilv_node_as_string(sym);
|
|
}
|
|
|
|
uint32_t
|
|
LV2Plugin::port_index (const char* symbol) const
|
|
{
|
|
const map<string, uint32_t>::const_iterator i = _port_indices.find(symbol);
|
|
if (i != _port_indices.end()) {
|
|
return i->second;
|
|
} else {
|
|
warning << string_compose(_("LV2: Unknown port %1"), symbol) << endmsg;
|
|
return (uint32_t)-1;
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2Plugin::set_parameter(uint32_t which, float val, sampleoffset_t when)
|
|
{
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose(
|
|
"%1 set parameter %2 to %3\n", name(), which, val));
|
|
|
|
if (which < lilv_plugin_get_num_ports(_impl->plugin)) {
|
|
if (get_parameter (which) == val) {
|
|
return;
|
|
}
|
|
|
|
_shadow_data[which] = val;
|
|
} else {
|
|
warning << string_compose(
|
|
_("Illegal parameter number used with plugin \"%1\". "
|
|
"This is a bug in either %2 or the LV2 plugin <%3>"),
|
|
name(), PROGRAM_NAME, unique_id()) << endmsg;
|
|
}
|
|
|
|
Plugin::set_parameter(which, val, when);
|
|
}
|
|
|
|
float
|
|
LV2Plugin::get_parameter(uint32_t which) const
|
|
{
|
|
if (parameter_is_input(which)) {
|
|
return (float)_shadow_data[which];
|
|
} else {
|
|
return (float)_control_data[which];
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
std::string
|
|
LV2Plugin::get_docs() const
|
|
{
|
|
LilvNodes* comments = lilv_plugin_get_value(_impl->plugin, _world.rdfs_comment);
|
|
if (comments) {
|
|
const std::string docs(lilv_node_as_string(lilv_nodes_get_first(comments)));
|
|
lilv_nodes_free(comments);
|
|
return docs;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
std::string
|
|
LV2Plugin::get_parameter_docs(uint32_t which) const
|
|
{
|
|
LilvNodes* comments = lilv_port_get_value(
|
|
_impl->plugin,
|
|
lilv_plugin_get_port_by_index(_impl->plugin, which),
|
|
_world.rdfs_comment);
|
|
|
|
if (comments) {
|
|
const std::string docs(lilv_node_as_string(lilv_nodes_get_first(comments)));
|
|
lilv_nodes_free(comments);
|
|
return docs;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
uint32_t
|
|
LV2Plugin::nth_parameter(uint32_t n, bool& ok) const
|
|
{
|
|
ok = false;
|
|
for (uint32_t c = 0, x = 0; x < lilv_plugin_get_num_ports(_impl->plugin); ++x) {
|
|
if (parameter_is_control(x)) {
|
|
if (c++ == n) {
|
|
ok = true;
|
|
return x;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const void*
|
|
LV2Plugin::extension_data(const char* uri) const
|
|
{
|
|
return lilv_instance_get_extension_data(_impl->instance, uri);
|
|
}
|
|
|
|
const void*
|
|
LV2Plugin::c_plugin()
|
|
{
|
|
return _impl->plugin;
|
|
}
|
|
|
|
const void*
|
|
LV2Plugin::c_ui()
|
|
{
|
|
return (const void*)_impl->ui;
|
|
}
|
|
|
|
const void*
|
|
LV2Plugin::c_ui_type()
|
|
{
|
|
return (const void*)_impl->ui_type;
|
|
}
|
|
|
|
/** Directory for all plugin state. */
|
|
const std::string
|
|
LV2Plugin::plugin_dir() const
|
|
{
|
|
if (!_plugin_state_dir.empty ()){
|
|
return Glib::build_filename(_plugin_state_dir, _insert_id.to_s());
|
|
} else {
|
|
return Glib::build_filename(_session.plugins_dir(), _insert_id.to_s());
|
|
}
|
|
}
|
|
|
|
/** Directory for files created by the plugin (except during save). */
|
|
const std::string
|
|
LV2Plugin::scratch_dir() const
|
|
{
|
|
return Glib::build_filename(plugin_dir(), "scratch");
|
|
}
|
|
|
|
/** Directory for snapshots of files in the scratch directory. */
|
|
const std::string
|
|
LV2Plugin::file_dir() const
|
|
{
|
|
return Glib::build_filename(plugin_dir(), "files");
|
|
}
|
|
|
|
/** Directory to save state snapshot version @c num into. */
|
|
const std::string
|
|
LV2Plugin::state_dir(unsigned num) const
|
|
{
|
|
return Glib::build_filename(plugin_dir(), string("state") + PBD::to_string (num));
|
|
}
|
|
|
|
/** Implementation of state:makePath for files created at instantiation time.
|
|
* Note this is not used for files created at save time (Lilv deals with that).
|
|
*/
|
|
char*
|
|
LV2Plugin::lv2_state_make_path(LV2_State_Make_Path_Handle handle,
|
|
const char* path)
|
|
{
|
|
LV2Plugin* me = (LV2Plugin*)handle;
|
|
if (me->_insert_id == PBD::ID("0")) {
|
|
warning << string_compose(
|
|
"File path \"%1\" requested but LV2 %2 has no insert ID",
|
|
path, me->name()) << endmsg;
|
|
return g_strdup(path);
|
|
}
|
|
|
|
const std::string abs_path = Glib::build_filename(me->scratch_dir(), path);
|
|
const std::string dirname = Glib::path_get_dirname(abs_path);
|
|
g_mkdir_with_parents(dirname.c_str(), 0744);
|
|
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("new file path %1 => %2\n",
|
|
path, abs_path));
|
|
|
|
return g_strndup(abs_path.c_str(), abs_path.length());
|
|
}
|
|
|
|
void
|
|
LV2Plugin::add_state(XMLNode* root) const
|
|
{
|
|
assert(_insert_id != PBD::ID("0"));
|
|
|
|
XMLNode* child;
|
|
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->set_property("symbol", port_symbol(i));
|
|
child->set_property("value", _shadow_data[i]);
|
|
root->add_child_nocopy(*child);
|
|
}
|
|
}
|
|
|
|
if (!_plugin_state_dir.empty()) {
|
|
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);
|
|
|
|
std::string xternal_dir = _session.externals_dir ();
|
|
|
|
if (!_plugin_state_dir.empty()) {
|
|
xternal_dir = Glib::build_filename (_plugin_state_dir, externals_dir_name);
|
|
g_mkdir_with_parents(xternal_dir.c_str(), 0744);
|
|
}
|
|
|
|
LilvState* state = lilv_state_new_from_instance(
|
|
_impl->plugin,
|
|
_impl->instance,
|
|
_uri_map.urid_map(),
|
|
scratch_dir().c_str(),
|
|
file_dir().c_str(),
|
|
xternal_dir.c_str(),
|
|
new_dir.c_str(),
|
|
NULL,
|
|
const_cast<LV2Plugin*>(this),
|
|
0,
|
|
NULL);
|
|
|
|
if (!_plugin_state_dir.empty() || force_state_save
|
|
|| !_impl->state
|
|
|| !lilv_state_equals(state, _impl->state)) {
|
|
lilv_state_save(_world.world,
|
|
_uri_map.urid_map(),
|
|
_uri_map.urid_unmap(),
|
|
state,
|
|
NULL,
|
|
new_dir.c_str(),
|
|
"state.ttl");
|
|
|
|
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);
|
|
g_rmdir (xternal_dir.c_str()); // try remove unused dir
|
|
--_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->set_property("state-dir", string("state") + PBD::to_string (saved_state));
|
|
}
|
|
}
|
|
|
|
static LilvNode*
|
|
get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate)
|
|
{
|
|
return lilv_world_get(world, subject, predicate, NULL);
|
|
}
|
|
|
|
void
|
|
LV2Plugin::find_presets()
|
|
{
|
|
/* see also LV2PluginInfo::get_presets */
|
|
LilvNode* lv2_appliesTo = lilv_new_uri(_world.world, LV2_CORE__appliesTo);
|
|
LilvNode* pset_Preset = lilv_new_uri(_world.world, LV2_PRESETS__Preset);
|
|
LilvNode* rdfs_label = lilv_new_uri(_world.world, LILV_NS_RDFS "label");
|
|
LilvNode* rdfs_comment = lilv_new_uri(_world.world, LILV_NS_RDFS "comment");
|
|
LilvNode* rdfs_seeAlso = lilv_new_uri(_world.world, LILV_NS_RDFS "seeAlso");
|
|
|
|
/* query plugins bundle path */
|
|
const LilvNode* const bundle_uri = lilv_plugin_get_bundle_uri(_impl->plugin);
|
|
assert (bundle_uri);
|
|
char* bundle_path = lilv_file_uri_parse(lilv_node_as_uri(bundle_uri), NULL);
|
|
std::string bundle_dir = Glib::path_get_dirname (bundle_path);
|
|
lilv_free (bundle_path);
|
|
|
|
LilvNodes* presets = lilv_plugin_get_related(_impl->plugin, pset_Preset);
|
|
|
|
LILV_FOREACH(nodes, i, presets) {
|
|
const LilvNode* preset = lilv_nodes_get(presets, i);
|
|
lilv_world_load_resource(_world.world, preset);
|
|
LilvNode* name = get_value(_world.world, preset, rdfs_label);
|
|
LilvNode* comment = get_value(_world.world, preset, rdfs_comment);
|
|
LilvNode* seealso = get_value(_world.world, preset, rdfs_seeAlso);
|
|
|
|
/* TODO properly identify user vs factory presets.
|
|
* here's an indirect condition: only factory presets can have comments
|
|
*/
|
|
bool userpreset = comment ? false : true;
|
|
/* if the preset ttl file is read-only, or part of the plugin bundle
|
|
* it is also assumed to be a factory preset */
|
|
if (seealso) {
|
|
char* pset_ttl = lilv_file_uri_parse (lilv_node_as_uri(seealso), NULL);
|
|
if (!exists_and_writable (pset_ttl)) {
|
|
/* no write access */
|
|
userpreset = false;
|
|
} else if (is_inside_bundle (bundle_dir, pset_ttl)) {
|
|
userpreset = false;
|
|
}
|
|
lilv_free (pset_ttl);
|
|
}
|
|
// TODO also check for 3rd part preset bundles
|
|
// > 1 preset in any give manifest.ttl or preset.ttl
|
|
|
|
if (name) {
|
|
_presets.insert(std::make_pair(lilv_node_as_string(preset),
|
|
Plugin::PresetRecord(
|
|
lilv_node_as_string(preset),
|
|
lilv_node_as_string(name),
|
|
userpreset,
|
|
comment ? lilv_node_as_string (comment) : ""
|
|
)));
|
|
lilv_node_free(name);
|
|
} else {
|
|
warning << string_compose(
|
|
_("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;
|
|
}
|
|
if (comment) {
|
|
lilv_node_free(comment);
|
|
}
|
|
if (seealso) {
|
|
lilv_node_free(seealso);
|
|
}
|
|
}
|
|
lilv_nodes_free(presets);
|
|
|
|
lilv_node_free(rdfs_comment);
|
|
lilv_node_free(rdfs_label);
|
|
lilv_node_free(pset_Preset);
|
|
lilv_node_free(lv2_appliesTo);
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::load_preset(PresetRecord r)
|
|
{
|
|
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);
|
|
|
|
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, state_features);
|
|
lilv_state_free(state);
|
|
Plugin::load_preset(r);
|
|
}
|
|
|
|
lilv_node_free(pset);
|
|
return state;
|
|
}
|
|
|
|
const void*
|
|
ARDOUR::lv2plugin_get_port_value(const char* port_symbol,
|
|
void* user_data,
|
|
uint32_t* size,
|
|
uint32_t* type)
|
|
{
|
|
LV2Plugin *plugin = (LV2Plugin *) user_data;
|
|
|
|
uint32_t index = plugin->port_index(port_symbol);
|
|
if (index != (uint32_t) -1) {
|
|
if (plugin->parameter_is_input(index) && plugin->parameter_is_control(index)) {
|
|
float *value;
|
|
*size = sizeof(float);
|
|
*type = plugin->_uri_map.uri_to_id(LV2_ATOM__Float);
|
|
value = &plugin->_shadow_data[index];
|
|
|
|
return value;
|
|
}
|
|
}
|
|
|
|
*size = *type = 0;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
std::string
|
|
LV2Plugin::do_save_preset(string name)
|
|
{
|
|
LilvNode* plug_name = lilv_plugin_get_name(_impl->plugin);
|
|
const string prefix = legalize_for_uri(lilv_node_as_string(plug_name));
|
|
const string base_name = legalize_for_uri(name);
|
|
const string file_name = base_name + ".ttl";
|
|
#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
|
|
|
|
/* delete reference to old preset (if any) */
|
|
#if 0 // prefer this when https://github.com/lv2/lilv/issues/37 is resolved
|
|
do_remove_preset (name);
|
|
#else
|
|
/* this works around https://github.com/lv2/lilv/issues/37
|
|
*
|
|
* do_remove_preset() calls lilv_state_delete(); That
|
|
* deletes all mapped files without re-creating them.
|
|
* So for the time being we just leave them in place.
|
|
*/
|
|
const PresetRecord* r = preset_by_label(name);
|
|
if (r) {
|
|
LilvNode* pset = lilv_new_uri (_world.world, r->uri.c_str());
|
|
if (pset) {
|
|
lilv_world_unload_resource (_world.world, pset);
|
|
lilv_node_free(pset);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
LilvState* state = lilv_state_new_from_instance(
|
|
_impl->plugin,
|
|
_impl->instance,
|
|
_uri_map.urid_map(),
|
|
scratch_dir().c_str(), // file_dir
|
|
bundle.c_str(), // copy_dir
|
|
bundle.c_str(), // link_dir
|
|
bundle.c_str(), // save_dir
|
|
lv2plugin_get_port_value, // get_value
|
|
(void*)this, // user_data
|
|
LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE, // flags
|
|
_features // features
|
|
);
|
|
|
|
lilv_state_set_label(state, name.c_str());
|
|
lilv_state_save(
|
|
_world.world, // world
|
|
_uri_map.urid_map(), // map
|
|
_uri_map.urid_unmap(), // unmap
|
|
state, // state
|
|
NULL, // uri (NULL = use file URI)
|
|
bundle.c_str(), // dir
|
|
file_name.c_str() // filename
|
|
);
|
|
|
|
lilv_state_free(state);
|
|
|
|
std::string uri = Glib::filename_to_uri(Glib::build_filename(bundle, file_name));
|
|
LilvNode *node_bundle = lilv_new_uri(_world.world, Glib::filename_to_uri(Glib::build_filename(bundle, "/")).c_str());
|
|
LilvNode *node_preset = lilv_new_uri(_world.world, uri.c_str());
|
|
lilv_world_unload_resource(_world.world, node_preset);
|
|
lilv_world_unload_bundle(_world.world, node_bundle);
|
|
lilv_world_load_bundle(_world.world, node_bundle);
|
|
lilv_world_load_resource(_world.world, node_preset);
|
|
lilv_node_free(node_bundle);
|
|
lilv_node_free(node_preset);
|
|
lilv_node_free(plug_name);
|
|
return uri;
|
|
}
|
|
|
|
void
|
|
LV2Plugin::do_remove_preset(string name)
|
|
{
|
|
/* Look up preset record by label (FIXME: ick, label as ID) */
|
|
const PresetRecord* r = preset_by_label(name);
|
|
if (!r) {
|
|
return;
|
|
}
|
|
|
|
/* Load a LilvState for the preset. */
|
|
LilvWorld* world = _world.world;
|
|
LilvNode* pset = lilv_new_uri(world, r->uri.c_str());
|
|
LilvState* state = lilv_state_new_from_world(world, _uri_map.urid_map(), pset);
|
|
if (!state) {
|
|
lilv_node_free(pset);
|
|
return;
|
|
}
|
|
|
|
/* Unload preset from world. */
|
|
lilv_world_unload_resource(world, pset);
|
|
|
|
/* Delete it from the file system. This will remove the preset file and the entry
|
|
from the manifest. If this results in an empty manifest (i.e. the
|
|
preset is the only thing in the bundle), then the bundle is removed. */
|
|
lilv_state_delete(world, state);
|
|
|
|
lilv_state_free(state);
|
|
lilv_node_free(pset);
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::has_editor() const
|
|
{
|
|
return _impl->ui != NULL;
|
|
}
|
|
|
|
void
|
|
LV2Plugin::add_slave (boost::shared_ptr<Plugin> p, bool)
|
|
{
|
|
boost::shared_ptr<LV2Plugin> lv2 = boost::dynamic_pointer_cast<LV2Plugin> (p);
|
|
if (lv2) {
|
|
Glib::Threads::Mutex::Lock lm (_slave_lock);
|
|
_slaves.insert (lv2);
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2Plugin::remove_slave (boost::shared_ptr<Plugin> p)
|
|
{
|
|
boost::shared_ptr<LV2Plugin> lv2 = boost::dynamic_pointer_cast<LV2Plugin> (p);
|
|
if (lv2) {
|
|
Glib::Threads::Mutex::Lock lm (_slave_lock);
|
|
_slaves.erase (lv2);
|
|
}
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::has_message_output() const
|
|
{
|
|
for (uint32_t i = 0; i < num_ports(); ++i) {
|
|
if ((_port_flags[i] & PORT_SEQUENCE) &&
|
|
(_port_flags[i] & PORT_OUTPUT)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::write_to(RingBuffer<uint8_t>* dest,
|
|
uint32_t index,
|
|
uint32_t protocol,
|
|
uint32_t size,
|
|
const uint8_t* body)
|
|
{
|
|
const uint32_t buf_size = sizeof(UIMessage) + size;
|
|
|
|
if (dest->write_space () < buf_size) {
|
|
/* Do not write partial message */
|
|
return false;
|
|
}
|
|
|
|
vector<uint8_t> buf(buf_size);
|
|
UIMessage* msg = (UIMessage*)&buf[0];
|
|
msg->index = index;
|
|
msg->protocol = protocol;
|
|
msg->size = size;
|
|
memcpy(msg + 1, body, size);
|
|
|
|
return (dest->write(&buf[0], buf_size) == buf_size);
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::write_from_ui(uint32_t index,
|
|
uint32_t protocol,
|
|
uint32_t size,
|
|
const uint8_t* body)
|
|
{
|
|
if (!_from_ui) {
|
|
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]);
|
|
}
|
|
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);
|
|
}
|
|
|
|
if (_from_ui->write_space () < size) {
|
|
error << string_compose (_("LV2<%1>: Error writing from UI to plugin"), name()) << endmsg;
|
|
return false;
|
|
}
|
|
|
|
if (!write_to(_from_ui, index, protocol, size, body)) {
|
|
error << string_compose (_("LV2<%1>: Error writing from UI to plugin"), name()) << endmsg;
|
|
return false;
|
|
}
|
|
|
|
Glib::Threads::Mutex::Lock lm (_slave_lock, Glib::Threads::TRY_LOCK);
|
|
if (lm.locked()) {
|
|
for (auto const& i : _slaves) {
|
|
i->write_from_ui (index, protocol, size, body);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::write_to_ui(uint32_t index,
|
|
uint32_t protocol,
|
|
uint32_t size,
|
|
const uint8_t* body)
|
|
{
|
|
if (!write_to(_to_ui, index, protocol, size, body)) {
|
|
error << string_compose (_("LV2<%1>: Error writing from plugin to UI"), name()) << endmsg;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
forge_variant(LV2_Atom_Forge* forge, const Variant& value)
|
|
{
|
|
switch (value.type()) {
|
|
case Variant::NOTHING:
|
|
break;
|
|
case Variant::BEATS:
|
|
// No atom type for this, just forge a double
|
|
lv2_atom_forge_double(forge, DoubleableBeats(value.get_beats()).to_double());
|
|
break;
|
|
case Variant::BOOL:
|
|
lv2_atom_forge_bool(forge, value.get_bool());
|
|
break;
|
|
case Variant::DOUBLE:
|
|
lv2_atom_forge_double(forge, value.get_double());
|
|
break;
|
|
case Variant::FLOAT:
|
|
lv2_atom_forge_float(forge, value.get_float());
|
|
break;
|
|
case Variant::INT:
|
|
lv2_atom_forge_int(forge, value.get_int());
|
|
break;
|
|
case Variant::LONG:
|
|
lv2_atom_forge_long(forge, value.get_long());
|
|
break;
|
|
case Variant::PATH:
|
|
lv2_atom_forge_path(
|
|
forge, value.get_path().c_str(), value.get_path().size());
|
|
break;
|
|
case Variant::STRING:
|
|
lv2_atom_forge_string(
|
|
forge, value.get_string().c_str(), value.get_string().size());
|
|
break;
|
|
case Variant::URI:
|
|
lv2_atom_forge_uri(
|
|
forge, value.get_uri().c_str(), value.get_uri().size());
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Get a variant type from a URI, return false iff no match found. */
|
|
static bool
|
|
uri_to_variant_type(const std::string& uri, Variant::Type& type)
|
|
{
|
|
if (uri == LV2_ATOM__Bool) {
|
|
type = Variant::BOOL;
|
|
} else if (uri == LV2_ATOM__Double) {
|
|
type = Variant::DOUBLE;
|
|
} else if (uri == LV2_ATOM__Float) {
|
|
type = Variant::FLOAT;
|
|
} else if (uri == LV2_ATOM__Int) {
|
|
type = Variant::INT;
|
|
} else if (uri == LV2_ATOM__Long) {
|
|
type = Variant::LONG;
|
|
} else if (uri == LV2_ATOM__Path) {
|
|
type = Variant::PATH;
|
|
} else if (uri == LV2_ATOM__String) {
|
|
type = Variant::STRING;
|
|
} else if (uri == LV2_ATOM__URI) {
|
|
type = Variant::URI;
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
LV2Plugin::set_property(uint32_t key, const Variant& value)
|
|
{
|
|
if (_patch_port_in_index == (uint32_t)-1) {
|
|
error << string_compose (_("LV2<%1>: set_property called with unset patch_port_in_index"), name ()) << endmsg;
|
|
return;
|
|
} else if (value.type() == Variant::NOTHING) {
|
|
error << string_compose (_("LV2<%1>: set_property called with void value"), name ()) << endmsg;
|
|
return;
|
|
}
|
|
|
|
// Set up forge to write to temporary buffer on the stack
|
|
LV2_Atom_Forge* forge = &_impl->ui_forge;
|
|
LV2_Atom_Forge_Frame frame;
|
|
uint8_t buf[PATH_MAX]; // Ought to be enough for anyone...
|
|
|
|
lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
|
|
|
|
// Serialize patch:Set message to set property
|
|
#ifdef HAVE_LV2_1_10_0
|
|
lv2_atom_forge_object(forge, &frame, 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, 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);
|
|
#endif
|
|
|
|
forge_variant(forge, value);
|
|
|
|
// Write message to UI=>Plugin ring
|
|
const LV2_Atom* const atom = (const LV2_Atom*)buf;
|
|
write_from_ui(_patch_port_in_index,
|
|
_uri_map.urids.atom_eventTransfer,
|
|
lv2_atom_total_size(atom),
|
|
(const uint8_t*)atom);
|
|
}
|
|
|
|
const ParameterDescriptor&
|
|
LV2Plugin::get_property_descriptor(uint32_t id) const
|
|
{
|
|
PropertyDescriptors::const_iterator p = _property_descriptors.find(id);
|
|
if (p != _property_descriptors.end()) {
|
|
return p->second;
|
|
}
|
|
return Plugin::get_property_descriptor(id);
|
|
}
|
|
|
|
static void
|
|
load_parameter_descriptor_units(LilvWorld* lworld, ParameterDescriptor& desc, const LilvNodes* units)
|
|
{
|
|
if (lilv_nodes_contains(units, _world.units_midiNote)) {
|
|
desc.unit = ParameterDescriptor::MIDI_NOTE;
|
|
} else if (lilv_nodes_contains(units, _world.units_db)) {
|
|
desc.unit = ParameterDescriptor::DB;
|
|
} else if (lilv_nodes_contains(units, _world.units_hz)) {
|
|
desc.unit = ParameterDescriptor::HZ;
|
|
}
|
|
if (lilv_nodes_size(units) > 0) {
|
|
const LilvNode* unit = lilv_nodes_get_first(units);
|
|
LilvNode* render = get_value(lworld, unit, _world.units_render);
|
|
if (render) {
|
|
desc.print_fmt = lilv_node_as_string(render);
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
load_parameter_descriptor(LV2World& world,
|
|
ParameterDescriptor& desc,
|
|
Variant::Type datatype,
|
|
const LilvNode* subject)
|
|
{
|
|
LilvWorld* lworld = _world.world;
|
|
LilvNode* label = get_value(lworld, subject, _world.rdfs_label);
|
|
LilvNode* def = get_value(lworld, subject, _world.lv2_default);
|
|
LilvNode* minimum = get_value(lworld, subject, _world.lv2_minimum);
|
|
LilvNode* maximum = get_value(lworld, subject, _world.lv2_maximum);
|
|
LilvNodes* units = lilv_world_find_nodes(lworld, subject, _world.units_unit, NULL);
|
|
if (label) {
|
|
desc.label = lilv_node_as_string(label);
|
|
}
|
|
if (def) {
|
|
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) {
|
|
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) {
|
|
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;
|
|
desc.toggled |= datatype == Variant::BOOL;
|
|
desc.integer_step |= datatype == Variant::INT || datatype == Variant::LONG;
|
|
desc.update_steps();
|
|
|
|
lilv_nodes_free(units);
|
|
lilv_node_free(label);
|
|
lilv_node_free(def);
|
|
lilv_node_free(minimum);
|
|
lilv_node_free(maximum);
|
|
}
|
|
|
|
void
|
|
LV2Plugin::load_supported_properties(PropertyDescriptors& descs)
|
|
{
|
|
LilvWorld* lworld = _world.world;
|
|
const LilvNode* subject = lilv_plugin_get_uri(_impl->plugin);
|
|
LilvNodes* properties = lilv_world_find_nodes(
|
|
lworld, subject, _world.patch_writable, NULL);
|
|
LILV_FOREACH(nodes, p, properties) {
|
|
// Get label and range
|
|
const LilvNode* prop = lilv_nodes_get(properties, p);
|
|
LilvNode* range = get_value(lworld, prop, _world.rdfs_range);
|
|
if (!range) {
|
|
warning << string_compose(_("LV2<%1>: property <%2> has no range datatype, ignoring"),
|
|
name(), lilv_node_as_uri(prop)) << endmsg;
|
|
continue;
|
|
}
|
|
|
|
// Convert range to variant type (TODO: support for multiple range types)
|
|
Variant::Type datatype;
|
|
if (!uri_to_variant_type(lilv_node_as_uri(range), datatype)) {
|
|
error << string_compose(_("LV2<%1>: property <%2> has unsupported datatype <%3>"),
|
|
name(), lilv_node_as_uri(prop), lilv_node_as_uri(range)) << endmsg;
|
|
continue;
|
|
}
|
|
|
|
// Add description to result
|
|
ParameterDescriptor desc;
|
|
desc.key = _uri_map.uri_to_id(lilv_node_as_uri(prop));
|
|
desc.datatype = datatype;
|
|
load_parameter_descriptor(_world, desc, datatype, prop);
|
|
descs.insert(std::make_pair(desc.key, desc));
|
|
|
|
_property_values[desc.key] = Variant();
|
|
|
|
lilv_node_free(range);
|
|
}
|
|
lilv_nodes_free(properties);
|
|
}
|
|
|
|
Variant
|
|
LV2Plugin::get_property_value (uint32_t prop_id) const
|
|
{
|
|
std::map<uint32_t, Variant>::const_iterator it;
|
|
if ((it = _property_values.find (prop_id)) == _property_values.end()) {
|
|
return Variant();
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void
|
|
LV2Plugin::announce_property_values()
|
|
{
|
|
if (_patch_port_in_index == (uint32_t)-1) {
|
|
return;
|
|
}
|
|
|
|
// Set up forge to write to temporary buffer on the stack
|
|
LV2_Atom_Forge* forge = &_impl->ui_forge;
|
|
LV2_Atom_Forge_Frame frame;
|
|
uint8_t buf[PATH_MAX]; // Ought to be enough for anyone...
|
|
|
|
lv2_atom_forge_set_buffer(forge, buf, sizeof(buf));
|
|
|
|
// Serialize patch:Get message with no subject (implicitly plugin instance)
|
|
#ifdef HAVE_LV2_1_10_0
|
|
lv2_atom_forge_object(forge, &frame, 0, _uri_map.urids.patch_Get);
|
|
#else
|
|
lv2_atom_forge_blank(forge, &frame, 0, _uri_map.urids.patch_Get);
|
|
#endif
|
|
|
|
// Write message to UI=>Plugin ring
|
|
const LV2_Atom* const atom = (const LV2_Atom*)buf;
|
|
write_from_ui(_patch_port_in_index,
|
|
_uri_map.urids.atom_eventTransfer,
|
|
lv2_atom_total_size(atom),
|
|
(const uint8_t*)atom);
|
|
}
|
|
|
|
void
|
|
LV2Plugin::enable_ui_emission()
|
|
{
|
|
if (!_to_ui) {
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2Plugin::emit_to_ui(void* controller, UIMessageSink sink)
|
|
{
|
|
if (!_to_ui) {
|
|
return;
|
|
}
|
|
|
|
uint32_t read_space = _to_ui->read_space();
|
|
while (read_space > sizeof(UIMessage)) {
|
|
UIMessage msg;
|
|
if (_to_ui->read((uint8_t*)&msg, sizeof(msg)) != sizeof(msg)) {
|
|
error << string_compose (_("LV2<%1>: Error reading message header from Plugin => UI RingBuffer"), name()) << endmsg;
|
|
break;
|
|
}
|
|
vector<uint8_t> body(msg.size);
|
|
if (_to_ui->read(&body[0], msg.size) != msg.size) {
|
|
error << string_compose (_("LV2<%1>: Error reading message body from Plugin => UI RingBuffer"), name()) << endmsg;
|
|
break;
|
|
}
|
|
|
|
sink(controller, msg.index, msg.size, msg.protocol, &body[0]);
|
|
|
|
read_space -= sizeof(msg) + msg.size;
|
|
}
|
|
}
|
|
|
|
int
|
|
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, &worker, size, data);
|
|
}
|
|
|
|
int
|
|
LV2Plugin::work_response(uint32_t size, const void* data)
|
|
{
|
|
return _impl->work_iface->work_response(
|
|
_impl->instance->lv2_handle, size, data);
|
|
}
|
|
|
|
void
|
|
LV2Plugin::set_insert_id(PBD::ID id)
|
|
{
|
|
if (_insert_id == "0") {
|
|
_insert_id = id;
|
|
} else if (_insert_id != id) {
|
|
lilv_state_free(_impl->state);
|
|
_impl->state = NULL;
|
|
_insert_id = id;
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2Plugin::set_state_dir (const std::string& d)
|
|
{
|
|
_plugin_state_dir = d;
|
|
}
|
|
|
|
int
|
|
LV2Plugin::set_state(const XMLNode& node, int version)
|
|
{
|
|
XMLNodeList nodes;
|
|
XMLNodeConstIterator iter;
|
|
XMLNode* child;
|
|
LocaleGuard lg;
|
|
|
|
if (node.name() != state_node_name()) {
|
|
error << string_compose (_("LV2<%1>: Bad node sent to LV2Plugin::set_state"), name()) << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
if (version < 3000) {
|
|
nodes = node.children("port");
|
|
} else {
|
|
nodes = node.children("Port");
|
|
}
|
|
|
|
for (iter = nodes.begin(); iter != nodes.end(); ++iter) {
|
|
|
|
child = *iter;
|
|
|
|
std::string sym;
|
|
if (!child->get_property("symbol", sym)) {
|
|
warning << string_compose (_("LV2<%1>: port has no symbol '%2', ignored"), name(), sym) << 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 {
|
|
warning << string_compose (_("LV2<%1>: port '%2' has known index, ignored "), name(), sym) << endmsg;
|
|
continue;
|
|
}
|
|
|
|
float val;
|
|
if (!child->get_property("value", val)) {
|
|
warning << string_compose (_("LV2<%1>: port no value, ignored "), name(), sym) << endmsg;
|
|
continue;
|
|
}
|
|
|
|
set_parameter(port_id, val, 0);
|
|
}
|
|
|
|
std::string template_dir;
|
|
if (node.get_property("template-dir", template_dir)) {
|
|
set_state_dir (template_dir);
|
|
}
|
|
|
|
_state_version = 0;
|
|
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\"",
|
|
state_dir) << endmsg;
|
|
}
|
|
|
|
std::string state_file = Glib::build_filename(
|
|
plugin_dir(),
|
|
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());
|
|
|
|
lilv_state_restore(state, _impl->instance, NULL, NULL, 0, NULL);
|
|
lilv_state_free(_impl->state);
|
|
_impl->state = state;
|
|
}
|
|
|
|
if (!_plugin_state_dir.empty ()) {
|
|
// force save with session, next time (increment counter)
|
|
lilv_state_free (_impl->state);
|
|
_impl->state = NULL;
|
|
set_state_dir ("");
|
|
}
|
|
|
|
/* Do not call latency_compute_run() concurrently with connect_and_run().
|
|
* So far this can only guaranteed when the session is loading,
|
|
* and the plugin has not been added to the processor chain.
|
|
*
|
|
* Ideally this would clso be called when copying a plugin from another track,
|
|
* but NOT when copying the state from a plugin to another (active) plugin
|
|
* instance.
|
|
*/
|
|
if (_session.loading ()) {
|
|
latency_compute_run();
|
|
}
|
|
|
|
return Plugin::set_state(node, version);
|
|
}
|
|
|
|
int
|
|
LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) const
|
|
{
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which);
|
|
if (!port) {
|
|
error << string_compose("LV2<%1>: get descriptor of non-existent port %2", name(), which)
|
|
<< endmsg;
|
|
return 1;
|
|
}
|
|
|
|
LilvNodes* portunits;
|
|
LilvNode *def, *min, *max;
|
|
lilv_port_get_range(_impl->plugin, port, &def, &min, &max);
|
|
portunits = lilv_port_get_value(_impl->plugin, port, _world.units_unit);
|
|
|
|
LilvNode* steps = lilv_port_get(_impl->plugin, port, _world.ext_rangeSteps);
|
|
LilvNode* display_priority = lilv_port_get(_impl->plugin, port, _world.ext_displayPriority);
|
|
|
|
// TODO: Once we can rely on lilv 0.18.0 being present,
|
|
// load_parameter_descriptor() can be used for ports as well
|
|
desc.integer_step = lilv_port_has_property(_impl->plugin, port, _world.lv2_integer);
|
|
desc.toggled = lilv_port_has_property(_impl->plugin, port, _world.lv2_toggled);
|
|
desc.logarithmic = lilv_port_has_property(_impl->plugin, port, _world.ext_logarithmic);
|
|
desc.sr_dependent = lilv_port_has_property(_impl->plugin, port, _world.lv2_sampleRate);
|
|
desc.label = lilv_node_as_string(lilv_port_get_name(_impl->plugin, port)); // XXX leaks
|
|
desc.normal = def ? lilv_node_as_float(def) : 0.0f;
|
|
desc.lower = min ? lilv_node_as_float(min) : 0.0f;
|
|
desc.upper = max ? lilv_node_as_float(max) : 1.0f;
|
|
load_parameter_descriptor_units(_world.world, desc, portunits);
|
|
|
|
|
|
if (desc.sr_dependent) {
|
|
desc.lower *= _session.sample_rate ();
|
|
desc.upper *= _session.sample_rate ();
|
|
}
|
|
|
|
desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration);
|
|
desc.scale_points = get_scale_points(which);
|
|
|
|
#ifdef LV2_EXTENDED
|
|
desc.inline_ctrl = lilv_port_has_property(_impl->plugin, port, _world.inline_mixer_control);
|
|
#endif
|
|
|
|
if (steps) {
|
|
desc.rangesteps = lilv_node_as_float (steps);
|
|
}
|
|
if (display_priority) {
|
|
desc.display_priority = lilv_node_as_int (display_priority);
|
|
}
|
|
|
|
desc.update_steps();
|
|
|
|
lilv_node_free(def);
|
|
lilv_node_free(min);
|
|
lilv_node_free(max);
|
|
lilv_node_free(steps);
|
|
lilv_node_free(display_priority);
|
|
lilv_nodes_free(portunits);
|
|
|
|
return 0;
|
|
}
|
|
|
|
Plugin::IOPortDescription
|
|
LV2Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
|
|
{
|
|
PortFlags match = 0;
|
|
switch (dt) {
|
|
case DataType::AUDIO:
|
|
match = PORT_AUDIO;
|
|
break;
|
|
case DataType::MIDI:
|
|
match = PORT_SEQUENCE | PORT_MIDI;
|
|
break;
|
|
default:
|
|
return Plugin::IOPortDescription ("?");
|
|
break;
|
|
}
|
|
if (input) {
|
|
match |= PORT_INPUT;
|
|
} else {
|
|
match |= PORT_OUTPUT;
|
|
}
|
|
|
|
uint32_t p = 0;
|
|
uint32_t idx = UINT32_MAX;
|
|
|
|
uint32_t const num_ports = parameter_count();
|
|
for (uint32_t port_index = 0; port_index < num_ports; ++port_index) {
|
|
PortFlags flags = _port_flags[port_index];
|
|
if ((flags & match) == match) {
|
|
if (p == id) {
|
|
idx = port_index;
|
|
}
|
|
++p;
|
|
}
|
|
}
|
|
if (idx == UINT32_MAX) {
|
|
return Plugin::IOPortDescription ("?");
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
string
|
|
LV2Plugin::describe_parameter(Evoral::Parameter which)
|
|
{
|
|
if (( which.type() == PluginAutomation) && ( which.id() < parameter_count()) ) {
|
|
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, which.id());
|
|
|
|
if (lilv_port_has_property(_impl->plugin, port, _world.ext_notOnGUI)) {
|
|
return X_("hidden");
|
|
}
|
|
|
|
const LilvPort* fwport = lilv_plugin_get_port_by_designation(_impl->plugin, _world.lv2_InputPort, _world.lv2_freewheeling);
|
|
if (fwport && fwport == port) {
|
|
return X_("hidden");
|
|
}
|
|
|
|
const LilvPort* bpmport = lilv_plugin_get_port_by_designation(_impl->plugin, _world.lv2_InputPort, _world.time_beatsPerMin);
|
|
if (bpmport && bpmport == port) {
|
|
return X_("hidden");
|
|
}
|
|
|
|
if (lilv_port_has_property(_impl->plugin, port, _world.lv2_freewheeling)) {
|
|
return X_("hidden");
|
|
}
|
|
|
|
if (lilv_port_has_property(_impl->plugin, port, _world.lv2_reportsLatency)) {
|
|
return X_("latency");
|
|
}
|
|
|
|
LilvNode* name = lilv_port_get_name(_impl->plugin,
|
|
lilv_plugin_get_port_by_index(_impl->plugin, which.id()));
|
|
string ret(lilv_node_as_string(name));
|
|
lilv_node_free(name);
|
|
return ret;
|
|
} else {
|
|
return "??";
|
|
}
|
|
}
|
|
|
|
samplecnt_t
|
|
LV2Plugin::max_latency () const
|
|
{
|
|
return _max_latency;
|
|
}
|
|
|
|
samplecnt_t
|
|
LV2Plugin::plugin_latency() const
|
|
{
|
|
if (_latency_control_port) {
|
|
return (samplecnt_t)floor(*_latency_control_port);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
set<Evoral::Parameter>
|
|
LV2Plugin::automatable() const
|
|
{
|
|
set<Evoral::Parameter> ret;
|
|
|
|
for (uint32_t i = 0; i < parameter_count(); ++i) {
|
|
if (parameter_is_input(i) && parameter_is_control(i) && !(_port_flags[i] & PORT_NOAUTO)) {
|
|
ret.insert(ret.end(), Evoral::Parameter(PluginAutomation, 0, i));
|
|
}
|
|
}
|
|
|
|
for (PropertyDescriptors::const_iterator p = _property_descriptors.begin();
|
|
p != _property_descriptors.end();
|
|
++p) {
|
|
ret.insert(ret.end(), Evoral::Parameter(PluginPropertyAutomation, 0, p->first));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
LV2Plugin::set_automation_control (uint32_t i, boost::shared_ptr<AutomationControl> c)
|
|
{
|
|
if ((_port_flags[i] & (PORT_CTRLED | PORT_CTRLER))) {
|
|
DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Ctrl Port %1\n", i));
|
|
_ctrl_map [i] = AutomationCtrlPtr (new AutomationCtrl(c));
|
|
}
|
|
else if (i == _bpm_control_port_index) {
|
|
_ctrl_map [i] = AutomationCtrlPtr (new AutomationCtrl(c));
|
|
}
|
|
}
|
|
|
|
LV2Plugin::AutomationCtrlPtr
|
|
LV2Plugin::get_automation_control (uint32_t i)
|
|
{
|
|
if (_ctrl_map.find (i) == _ctrl_map.end()) {
|
|
return AutomationCtrlPtr ();
|
|
}
|
|
return _ctrl_map[i];
|
|
}
|
|
|
|
void
|
|
LV2Plugin::activate()
|
|
{
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 activate\n", name()));
|
|
|
|
if (!_was_activated) {
|
|
lilv_instance_activate(_impl->instance);
|
|
_was_activated = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2Plugin::deactivate()
|
|
{
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 deactivate\n", name()));
|
|
|
|
if (_was_activated) {
|
|
lilv_instance_deactivate(_impl->instance);
|
|
_was_activated = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2Plugin::cleanup()
|
|
{
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 cleanup\n", name()));
|
|
|
|
deactivate();
|
|
lilv_instance_free(_impl->instance);
|
|
_impl->instance = NULL;
|
|
}
|
|
|
|
void
|
|
LV2Plugin::allocate_atom_event_buffers()
|
|
{
|
|
/* reserve local scratch buffers for ATOM event-queues */
|
|
const LilvPlugin* p = _impl->plugin;
|
|
|
|
/* count non-MIDI atom event-ports
|
|
* TODO: nicely ask drobilla to make a lilv_ call for that
|
|
*/
|
|
int count_atom_out = 0;
|
|
int count_atom_in = 0;
|
|
int minimumSize = 32768; // TODO use a per-port minimum-size
|
|
for (uint32_t i = 0; i < lilv_plugin_get_num_ports(p); ++i) {
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(p, i);
|
|
if (lilv_port_is_a(p, port, _world.atom_AtomPort)) {
|
|
LilvNodes* buffer_types = lilv_port_get_value (p, port, _world.atom_bufferType);
|
|
|
|
if (lilv_nodes_contains(buffer_types, _world.atom_Sequence)) {
|
|
if (lilv_port_is_a(p, port, _world.lv2_InputPort)) {
|
|
count_atom_in++;
|
|
}
|
|
if (lilv_port_is_a(p, port, _world.lv2_OutputPort)) {
|
|
count_atom_out++;
|
|
}
|
|
LilvNodes* min_size_v = lilv_port_get_value(_impl->plugin, port, _world.rsz_minimumSize);
|
|
LilvNode* min_size = min_size_v ? lilv_nodes_get_first(min_size_v) : NULL;
|
|
if (min_size && lilv_node_is_int(min_size)) {
|
|
minimumSize = std::max(minimumSize, lilv_node_as_int(min_size));
|
|
}
|
|
lilv_nodes_free(min_size_v);
|
|
}
|
|
lilv_nodes_free(buffer_types);
|
|
}
|
|
}
|
|
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("%1 need buffers for %2 atom-in and %3 atom-out event-ports\n",
|
|
name(), count_atom_in, count_atom_out));
|
|
|
|
const int total_atom_buffers = (count_atom_in + count_atom_out);
|
|
if (_atom_ev_buffers || total_atom_buffers == 0) {
|
|
return;
|
|
}
|
|
|
|
DEBUG_TRACE(DEBUG::LV2, string_compose("allocate %1 atom_ev_buffers of %2 bytes\n", total_atom_buffers, minimumSize));
|
|
_atom_ev_buffers = (LV2_Evbuf**) malloc((total_atom_buffers + 1) * sizeof(LV2_Evbuf*));
|
|
for (int i = 0; i < total_atom_buffers; ++i ) {
|
|
_atom_ev_buffers[i] = lv2_evbuf_new(minimumSize,
|
|
_uri_map.urids.atom_Chunk,
|
|
_uri_map.urids.atom_Sequence);
|
|
}
|
|
_atom_ev_buffers[total_atom_buffers] = 0;
|
|
return;
|
|
}
|
|
|
|
/** Write an ardour position/time/tempo/meter as an LV2 event.
|
|
* @return true on success.
|
|
*/
|
|
static bool
|
|
write_position(LV2_Atom_Forge* forge,
|
|
LV2_Evbuf* buf,
|
|
TempoMetric const & t,
|
|
BBT_Time const & bbt,
|
|
double speed,
|
|
double time_scale,
|
|
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;
|
|
#ifdef HAVE_LV2_1_10_0
|
|
lv2_atom_forge_object(forge, &frame, 0, urids.time_Position);
|
|
lv2_atom_forge_key(forge, urids.time_frame);
|
|
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_barBeat);
|
|
lv2_atom_forge_float(forge, bbt.beats - 1 + (bbt.ticks / (float) Temporal::ticks_per_beat));
|
|
lv2_atom_forge_key(forge, urids.time_bar);
|
|
lv2_atom_forge_long(forge, bbt.bars - 1);
|
|
lv2_atom_forge_key(forge, urids.time_beatUnit);
|
|
lv2_atom_forge_int(forge, t.meter().note_value());
|
|
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, bpm);
|
|
lv2_atom_forge_key(forge, urids.time_scale);
|
|
lv2_atom_forge_float(forge, time_scale);
|
|
#else
|
|
lv2_atom_forge_blank(forge, &frame, 1, urids.time_Position);
|
|
lv2_atom_forge_property_head(forge, urids.time_frame, 0);
|
|
lv2_atom_forge_long(forge, position);
|
|
lv2_atom_forge_property_head(forge, urids.time_speed, 0);
|
|
lv2_atom_forge_float(forge, speed);
|
|
lv2_atom_forge_property_head(forge, urids.time_barBeat, 0);
|
|
lv2_atom_forge_float(forge, bbt.beats - 1 + (bbt.ticks / (float) Temporal::ticks_per_beat));
|
|
lv2_atom_forge_property_head(forge, urids.time_bar, 0);
|
|
lv2_atom_forge_long(forge, bbt.bars - 1);
|
|
lv2_atom_forge_property_head(forge, urids.time_beatUnit, 0);
|
|
lv2_atom_forge_int(forge, t.meter().note_divisor());
|
|
lv2_atom_forge_property_head(forge, urids.time_beatsPerBar, 0);
|
|
lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
|
|
lv2_atom_forge_property_head(forge, urids.time_beatsPerMinute, 0);
|
|
lv2_atom_forge_float(forge, bpm);
|
|
lv2_atom_forge_key(forge, urids.time_scale);
|
|
lv2_atom_forge_float(forge, time_scale);
|
|
#endif
|
|
|
|
LV2_Evbuf_Iterator end = lv2_evbuf_end(buf);
|
|
const LV2_Atom* const atom = (const LV2_Atom*)pos_buf;
|
|
return lv2_evbuf_write(&end, offset, 0, atom->type, atom->size,
|
|
(const uint8_t*)(atom + 1));
|
|
}
|
|
|
|
int
|
|
LV2Plugin::connect_and_run(BufferSet& bufs,
|
|
samplepos_t start, samplepos_t end, double speed,
|
|
ChanMapping const& in_map, ChanMapping const& out_map,
|
|
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);
|
|
|
|
cycles_t then = get_cycles();
|
|
|
|
/* remain at zero during pre-roll at zero */
|
|
speed = end > 0 ? speed : 0;
|
|
samplepos_t start0 = std::max (samplepos_t (0), start);
|
|
|
|
TempoMap::SharedPtr tmap (TempoMap::use());
|
|
TempoMetric metric (tmap->metric_at (samples_to_superclock (start0, AudioEngine::instance()->sample_rate())));
|
|
|
|
TempoMapPoints tempo_map_points;
|
|
tmap->get_grid (tempo_map_points,
|
|
samples_to_superclock (start0, AudioEngine::instance()->sample_rate()),
|
|
samples_to_superclock (end, AudioEngine::instance()->sample_rate()), 0);
|
|
|
|
if (_freewheel_control_port) {
|
|
*_freewheel_control_port = _session.engine().freewheeling() ? 1.f : 0.f;
|
|
}
|
|
|
|
if (_bpm_control_port) {
|
|
|
|
const double bpm = tmap->quarters_per_minute_at (timepos_t (start0));
|
|
|
|
if (*_bpm_control_port != bpm) {
|
|
AutomationCtrlPtr c = get_automation_control (_bpm_control_port_index);
|
|
if (c && c->ac) {
|
|
/* may be NULL for replicated instances - only one custom UI/ctrl */
|
|
c->ac->Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
|
|
}
|
|
}
|
|
|
|
*_bpm_control_port = bpm;
|
|
}
|
|
|
|
#ifdef LV2_EXTENDED
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ChanCount bufs_count;
|
|
bufs_count.set(DataType::AUDIO, 1);
|
|
bufs_count.set(DataType::MIDI, 1);
|
|
BufferSet& silent_bufs = _session.get_silent_buffers(bufs_count);
|
|
BufferSet& scratch_bufs = _session.get_scratch_buffers(bufs_count);
|
|
uint32_t const num_ports = parameter_count();
|
|
uint32_t const nil_index = std::numeric_limits<uint32_t>::max();
|
|
|
|
uint32_t audio_in_index = 0;
|
|
uint32_t audio_out_index = 0;
|
|
uint32_t midi_in_index = 0;
|
|
uint32_t midi_out_index = 0;
|
|
uint32_t atom_port_index = 0;
|
|
|
|
for (uint32_t port_index = 0; port_index < num_ports; ++port_index) {
|
|
void* buf = NULL;
|
|
uint32_t index = nil_index;
|
|
PortFlags flags = _port_flags[port_index];
|
|
bool valid = false;
|
|
if (flags & PORT_AUDIO) {
|
|
if (flags & PORT_INPUT) {
|
|
index = in_map.get(DataType::AUDIO, audio_in_index++, &valid);
|
|
buf = (valid)
|
|
? bufs.get_audio(index).data(offset)
|
|
: silent_bufs.get_audio(0).data(offset);
|
|
} else {
|
|
index = out_map.get(DataType::AUDIO, audio_out_index++, &valid);
|
|
buf = (valid)
|
|
? bufs.get_audio(index).data(offset)
|
|
: scratch_bufs.get_audio(0).data(offset);
|
|
}
|
|
} else if (flags & PORT_SEQUENCE) {
|
|
/* FIXME: The checks here for bufs.count().n_midi() > index shouldn't
|
|
be necessary, but the mapping is illegal in some cases. Ideally
|
|
that should be fixed, but this is easier...
|
|
*/
|
|
if (flags & PORT_MIDI) {
|
|
if (flags & PORT_INPUT) {
|
|
index = in_map.get(DataType::MIDI, midi_in_index++, &valid);
|
|
} else {
|
|
index = out_map.get(DataType::MIDI, midi_out_index++, &valid);
|
|
}
|
|
if (valid && bufs.count().n_midi() > index) {
|
|
/* Note, ensure_lv2_bufsize() is not RT safe!
|
|
* However free()/alloc() is only called if a
|
|
* plugin requires a rsz:minimumSize buffersize
|
|
* and the existing buffer if smaller.
|
|
*/
|
|
bufs.ensure_lv2_bufsize((flags & PORT_INPUT), index, _port_minimumSize[port_index]);
|
|
_ev_buffers[port_index] = bufs.get_lv2_midi(
|
|
(flags & PORT_INPUT), index);
|
|
} else {
|
|
/* Valid pin mapping, but no corresponding port-buffers */
|
|
valid = false;
|
|
}
|
|
} else if ((flags & PORT_POSITION) && (flags & PORT_INPUT)) {
|
|
lv2_evbuf_reset(_atom_ev_buffers[atom_port_index], true);
|
|
_ev_buffers[port_index] = _atom_ev_buffers[atom_port_index++];
|
|
valid = true;
|
|
}
|
|
|
|
if (valid && (flags & PORT_INPUT)) {
|
|
if ((flags & PORT_POSITION)) {
|
|
Temporal::BBT_Time bbt (metric.bbt_at (timepos_t (start0)));
|
|
double bpm = metric.tempo().note_types_per_minute();
|
|
double time_scale = Port::speed_ratio ();
|
|
double beatpos = (bbt.bars - 1) * metric.meter().divisions_per_bar()
|
|
+ (bbt.beats - 1)
|
|
+ (bbt.ticks / Temporal::ticks_per_beat);
|
|
beatpos *= metric.tempo().note_type() / 4.0;
|
|
if (start != _next_cycle_start ||
|
|
speed != _next_cycle_speed ||
|
|
time_scale != _prev_time_scale ||
|
|
rint (100 * beatpos) != rint(100 * _next_cycle_beat) ||
|
|
bpm != _current_bpm) {
|
|
// Transport or Tempo has changed, write position at cycle start
|
|
write_position(&_impl->forge, _ev_buffers[port_index],
|
|
metric, bbt, speed, time_scale, bpm, start, 0);
|
|
}
|
|
}
|
|
|
|
// Get MIDI iterator range (empty range if no MIDI)
|
|
MidiBuffer::iterator m = (index != nil_index)
|
|
? bufs.get_midi(index).begin()
|
|
: silent_bufs.get_midi(0).end();
|
|
MidiBuffer::iterator m_end = (index != nil_index)
|
|
? bufs.get_midi(index).end()
|
|
: m;
|
|
|
|
// Now merge MIDI and any transport events into the buffer
|
|
const uint32_t type = _uri_map.urids.midi_MidiEvent;
|
|
const samplepos_t tend = end;
|
|
|
|
/* move to next explicit point
|
|
* (if any)
|
|
*/
|
|
|
|
TempoMapPoints::const_iterator tempo_map_point (tempo_map_points.begin());
|
|
|
|
while (tempo_map_point != tempo_map_points.end()) {
|
|
tempo_map_point++;
|
|
if (tempo_map_point != tempo_map_points.end()) {
|
|
if (tempo_map_point->is_explicit()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (m != m_end || ((tempo_map_point != tempo_map_points.end()) && ((*tempo_map_point).sample(AudioEngine::instance()->sample_rate()) < tend))) {
|
|
|
|
if (m != m_end && ((tempo_map_point == tempo_map_points.end()) || (*tempo_map_point).sample(AudioEngine::instance()->sample_rate()) > (*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 {
|
|
assert (tempo_map_point != tempo_map_points.end());
|
|
const samplepos_t sample = tempo_map_point->sample (AudioEngine::instance()->sample_rate());
|
|
const Temporal::BBT_Time bbt = tempo_map_point->bbt();
|
|
double bpm = tempo_map_point->tempo().quarter_notes_per_minute ();
|
|
|
|
write_position(&_impl->forge, _ev_buffers[port_index],
|
|
*tempo_map_point, bbt, speed, Port::speed_ratio (),
|
|
bpm, sample, sample - start);
|
|
|
|
/* move to next explicit point
|
|
* (if any)
|
|
*/
|
|
|
|
while (tempo_map_point != tempo_map_points.end()) {
|
|
tempo_map_point++;
|
|
if (tempo_map_point != tempo_map_points.end()) {
|
|
if (tempo_map_point->is_explicit()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!valid) {
|
|
/* Nothing we understand or care about, but we have
|
|
* to provide valid buffers for DSP/UI communication.
|
|
* Note that Atom buffers scratch buffers must not be shared.
|
|
*/
|
|
lv2_evbuf_reset (_atom_ev_buffers[atom_port_index], (flags & PORT_INPUT));
|
|
_ev_buffers[port_index] = _atom_ev_buffers[atom_port_index++];
|
|
}
|
|
|
|
buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]);
|
|
} else {
|
|
continue; // Control port, leave buffer alone
|
|
}
|
|
lilv_instance_connect_port(_impl->instance, port_index, buf);
|
|
}
|
|
|
|
// Read messages from UI and push into appropriate buffers
|
|
if (_from_ui) {
|
|
uint32_t read_space = _from_ui->read_space();
|
|
while (read_space > sizeof(UIMessage)) {
|
|
UIMessage msg;
|
|
if (_from_ui->read((uint8_t*)&msg, sizeof(msg)) != sizeof(msg)) {
|
|
error << string_compose (_("LV2<%1>: Error reading message header from UI => Plugin RingBuffer"), name()) << endmsg;
|
|
break;
|
|
}
|
|
vector<uint8_t> body(msg.size);
|
|
if (_from_ui->read(&body[0], msg.size) != msg.size) {
|
|
error << string_compose (_("LV2<%1>: Error reading message body from UI => Plugin RingBuffer"), name()) << endmsg;
|
|
break;
|
|
}
|
|
if (msg.protocol == URIMap::instance().urids.atom_eventTransfer) {
|
|
LV2_Evbuf* buf = _ev_buffers[msg.index];
|
|
LV2_Evbuf_Iterator i = lv2_evbuf_end(buf);
|
|
const LV2_Atom* const atom = (const LV2_Atom*)&body[0];
|
|
if (!lv2_evbuf_write(&i, nframes - 1, 0, atom->type, atom->size,
|
|
(const uint8_t*)(atom + 1))) {
|
|
error << "Failed to write data to LV2 event buffer\n";
|
|
}
|
|
/* Intercept patch:Set messages from GUIs (custom,
|
|
* or generic via LV2Plugin::set_property).
|
|
*/
|
|
else if (atom->type == _uri_map.urids.atom_Blank ||
|
|
atom->type == _uri_map.urids.atom_Object) {
|
|
const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
|
|
if (obj->body.otype == _uri_map.urids.patch_Set) {
|
|
const LV2_Atom* property = NULL;
|
|
const LV2_Atom* value = NULL;
|
|
lv2_atom_object_get(obj,
|
|
_uri_map.urids.patch_property, &property,
|
|
_uri_map.urids.patch_value, &value,
|
|
0);
|
|
|
|
if (property && value && property->type == _uri_map.urids.atom_URID) {
|
|
/* check if it's a property we know or care about */
|
|
const uint32_t prop_id = ((const LV2_Atom_URID*)property)->body;
|
|
if (_property_values.find (prop_id) != _property_values.end()) {
|
|
Plugin::state_changed ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
error << string_compose (_("LV2<%1>: Received unknown message type from UI"), name()) << endmsg;
|
|
}
|
|
read_space -= sizeof(UIMessage) + msg.size;
|
|
}
|
|
}
|
|
|
|
run(nframes);
|
|
|
|
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;
|
|
|
|
/* 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_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_SEQUENCE)) {
|
|
const uint32_t buf_index = out_map.get(
|
|
DataType::MIDI, midi_out_index++, &valid);
|
|
if (valid) {
|
|
bufs.flush_lv2_midi(true, buf_index);
|
|
}
|
|
}
|
|
|
|
// Write messages to UI
|
|
if ((_to_ui || _can_write_automation || _patch_port_out_index != (uint32_t)-1) &&
|
|
(flags & PORT_OUTPUT) && (flags & PORT_SEQUENCE)) {
|
|
LV2_Evbuf* buf = _ev_buffers[port_index];
|
|
for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf);
|
|
lv2_evbuf_is_valid(i);
|
|
i = lv2_evbuf_next(i)) {
|
|
uint32_t samples, subframes, type, size;
|
|
uint8_t* data;
|
|
lv2_evbuf_get(i, &samples, &subframes, &type, &size, &data);
|
|
|
|
#ifdef LV2_EXTENDED
|
|
// Intercept Automation Write Events
|
|
if ((flags & PORT_AUTOCTRL)) {
|
|
LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
|
|
if (atom->type == _uri_map.urids.atom_Blank ||
|
|
atom->type == _uri_map.urids.atom_Object) {
|
|
LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
|
|
if (obj->body.otype == _uri_map.urids.auto_event) {
|
|
// only if transport_rolling ??
|
|
const LV2_Atom* parameter = NULL;
|
|
const LV2_Atom* value = NULL;
|
|
lv2_atom_object_get(obj,
|
|
_uri_map.urids.auto_parameter, ¶meter,
|
|
_uri_map.urids.auto_value, &value,
|
|
0);
|
|
if (parameter && value) {
|
|
const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
|
|
const float v = ((const LV2_Atom_Float*)value)->body;
|
|
// -> add automation event..
|
|
DEBUG_TRACE(DEBUG::LV2Automate,
|
|
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)
|
|
) {
|
|
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 (timepos_t (when), v, true, true);
|
|
} else {
|
|
c->ac->set_double (v, timepos_t (when), true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (obj->body.otype == _uri_map.urids.auto_setup) {
|
|
// TODO optional arguments, for now we assume the plugin
|
|
// writes automation for its own inputs
|
|
// -> put them in "touch" mode (preferably "exclusive plugin touch(TM)"
|
|
for (AutomationCtrlMap::iterator i = _ctrl_map.begin(); i != _ctrl_map.end(); ++i) {
|
|
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;
|
|
lv2_atom_object_get(obj,
|
|
_uri_map.urids.auto_parameter, ¶meter,
|
|
0);
|
|
if (parameter) {
|
|
const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
|
|
AutomationCtrlPtr c = get_automation_control (p);
|
|
DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("Start Touch p: %1\n", p));
|
|
if (c) {
|
|
c->ac->start_touch (timepos_t (std::max ((samplepos_t)0, start - _current_latency)));
|
|
c->guard = true;
|
|
}
|
|
}
|
|
}
|
|
else if (obj->body.otype == _uri_map.urids.auto_end) {
|
|
const LV2_Atom* parameter = NULL;
|
|
lv2_atom_object_get(obj,
|
|
_uri_map.urids.auto_parameter, ¶meter,
|
|
0);
|
|
if (parameter) {
|
|
const uint32_t p = ((const LV2_Atom_Int*)parameter)->body;
|
|
AutomationCtrlPtr c = get_automation_control (p);
|
|
DEBUG_TRACE(DEBUG::LV2Automate, string_compose ("End Touch p: %1\n", p));
|
|
if (c) {
|
|
c->ac->stop_touch (timepos_t (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) {
|
|
Plugin::state_changed ();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Intercept patch change messages to emit PropertyChanged signal
|
|
if ((flags & PORT_PATCHMSG)) {
|
|
LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
|
|
if (atom->type == _uri_map.urids.atom_Blank ||
|
|
atom->type == _uri_map.urids.atom_Object) {
|
|
LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
|
|
if (obj->body.otype == _uri_map.urids.patch_Set) {
|
|
const LV2_Atom* property = NULL;
|
|
const LV2_Atom* value = NULL;
|
|
lv2_atom_object_get(obj,
|
|
_uri_map.urids.patch_property, &property,
|
|
_uri_map.urids.patch_value, &value,
|
|
0);
|
|
|
|
if (property && value && property->type == _uri_map.urids.atom_URID) {
|
|
const uint32_t prop_id = ((const LV2_Atom_URID*)property)->body;
|
|
if (_property_values.find (prop_id) != _property_values.end()) {
|
|
if (value->type == _uri_map.urids.atom_Path) {
|
|
const char* path = (const char*)LV2_ATOM_BODY_CONST(value);
|
|
_property_values[prop_id] = Variant(Variant::PATH, path);
|
|
}
|
|
if (value->type == _uri_map.urids.atom_Float) {
|
|
const float* val = (const float*)LV2_ATOM_BODY_CONST(value);
|
|
_property_values[prop_id] = Variant(Variant::FLOAT, *val);
|
|
}
|
|
// TODO add support for other props (Int, Bool, ..)
|
|
|
|
// TODO: This should emit the control's Changed signal
|
|
PropertyChanged(prop_id, _property_values[prop_id]);
|
|
} else {
|
|
std::cerr << "warning: patch:Set for unknown property" << std::endl;
|
|
}
|
|
} else {
|
|
std::cerr << "warning: patch:Set for unsupported property" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!_to_ui) continue;
|
|
write_to_ui(port_index, URIMap::instance().urids.atom_eventTransfer,
|
|
size + sizeof(LV2_Atom),
|
|
data - sizeof(LV2_Atom));
|
|
}
|
|
}
|
|
}
|
|
|
|
cycles_t now = get_cycles();
|
|
set_cycles((uint32_t)(now - then));
|
|
|
|
// Update expected transport information for next cycle so we can detect changes
|
|
_next_cycle_speed = speed;
|
|
_next_cycle_start = end + (start - start0);
|
|
_prev_time_scale = Port::speed_ratio ();
|
|
|
|
{
|
|
/* 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.
|
|
*/
|
|
_current_bpm = metric.tempo().note_types_per_minute();
|
|
Temporal::BBT_Time bbt (metric.bbt_at (timepos_t (start0)));
|
|
double beatpos = (bbt.bars - 1) * metric.divisions_per_bar()
|
|
+ (bbt.beats - 1)
|
|
+ (bbt.ticks / Temporal::ticks_per_beat);
|
|
beatpos *= metric.note_value() / 4.0;
|
|
_next_cycle_beat = beatpos + nframes * speed * _current_bpm / (60.f * _session.sample_rate());
|
|
}
|
|
|
|
if (_latency_control_port) {
|
|
samplecnt_t new_latency = signal_latency ();
|
|
_current_latency = new_latency;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::parameter_is_control(uint32_t param) const
|
|
{
|
|
assert(param < _port_flags.size());
|
|
return _port_flags[param] & PORT_CONTROL;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::parameter_is_audio(uint32_t param) const
|
|
{
|
|
assert(param < _port_flags.size());
|
|
return _port_flags[param] & PORT_AUDIO;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::parameter_is_output(uint32_t param) const
|
|
{
|
|
assert(param < _port_flags.size());
|
|
return _port_flags[param] & PORT_OUTPUT;
|
|
}
|
|
|
|
bool
|
|
LV2Plugin::parameter_is_input(uint32_t param) const
|
|
{
|
|
assert(param < _port_flags.size());
|
|
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;
|
|
}
|
|
|
|
boost::shared_ptr<ScalePoints>
|
|
LV2Plugin::get_scale_points(uint32_t port_index) const
|
|
{
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, port_index);
|
|
LilvScalePoints* points = lilv_port_get_scale_points(_impl->plugin, port);
|
|
|
|
boost::shared_ptr<ScalePoints> ret;
|
|
if (!points) {
|
|
return ret;
|
|
}
|
|
|
|
ret = boost::shared_ptr<ScalePoints>(new ScalePoints());
|
|
|
|
LILV_FOREACH(scale_points, i, points) {
|
|
const LilvScalePoint* p = lilv_scale_points_get(points, i);
|
|
const LilvNode* label = lilv_scale_point_get_label(p);
|
|
const LilvNode* value = lilv_scale_point_get_value(p);
|
|
if (label && (lilv_node_is_float(value) || lilv_node_is_int(value))) {
|
|
ret->insert(make_pair(lilv_node_as_string(label),
|
|
lilv_node_as_float(value)));
|
|
}
|
|
}
|
|
|
|
lilv_scale_points_free(points);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
LV2Plugin::run(pframes_t nframes, bool sync_work)
|
|
{
|
|
uint32_t const N = parameter_count();
|
|
for (uint32_t i = 0; i < N; ++i) {
|
|
if (parameter_is_control(i) && parameter_is_input(i)) {
|
|
_control_data[i] = _shadow_data[i];
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2Plugin::latency_compute_run()
|
|
{
|
|
if (!_latency_control_port) {
|
|
return;
|
|
}
|
|
|
|
/* Run the plugin so that it can set its latency parameter
|
|
* Note: since Ardour 5, plugins can dynamically change latency
|
|
* so this call is not required anymore, but doing
|
|
* an intial silent-run is likely a good idea regardless.
|
|
*/
|
|
|
|
bool was_activated = _was_activated;
|
|
activate();
|
|
|
|
// this is done in the main thread. non realtime.
|
|
const samplecnt_t bufsize = _engine.samples_per_cycle();
|
|
float* buffer = (float*) malloc(_engine.samples_per_cycle() * sizeof(float));
|
|
|
|
std::vector<LV2_Evbuf*> ev_buffers;
|
|
memset(buffer, 0, sizeof(float) * bufsize);
|
|
|
|
// FIXME: Ensure plugins can handle in-place processing
|
|
|
|
for (uint32_t port_index = 0; port_index < parameter_count (); ++port_index) {
|
|
PortFlags flags = _port_flags[port_index];
|
|
if (flags & PORT_AUDIO) {
|
|
lilv_instance_connect_port(_impl->instance, port_index, buffer);
|
|
} else if (flags & PORT_SEQUENCE) {
|
|
int buf_size = 8192;
|
|
#if 1 /* honor the min port-size here */
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(_impl->plugin, port_index);
|
|
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)) {
|
|
buf_size = std::max (buf_size, lilv_node_as_int (min_size));
|
|
}
|
|
lilv_nodes_free(min_size_v);
|
|
#endif
|
|
ev_buffers.push_back (lv2_evbuf_new (buf_size, _uri_map.urids.atom_Chunk, _uri_map.urids.atom_Sequence));
|
|
void* buf = lv2_evbuf_get_buffer (ev_buffers.back ());
|
|
lilv_instance_connect_port(_impl->instance, port_index, buf);
|
|
}
|
|
}
|
|
|
|
run (bufsize, true); // XXX prefer run (0, true)
|
|
|
|
deactivate();
|
|
if (was_activated) {
|
|
activate();
|
|
}
|
|
while (!ev_buffers.empty ()) {
|
|
lv2_evbuf_free (ev_buffers.back ());
|
|
ev_buffers.pop_back ();
|
|
}
|
|
free(buffer);
|
|
}
|
|
|
|
const LilvPort*
|
|
LV2Plugin::Impl::designated_input (const char* uri, void** bufptrs[], void** bufptr)
|
|
{
|
|
const LilvPort* port = NULL;
|
|
LilvNode* designation = lilv_new_uri(_world.world, uri);
|
|
port = lilv_plugin_get_port_by_designation(
|
|
plugin, _world.lv2_InputPort, designation);
|
|
lilv_node_free(designation);
|
|
if (port) {
|
|
bufptrs[lilv_port_get_index(plugin, port)] = bufptr;
|
|
}
|
|
return port;
|
|
}
|
|
|
|
static bool lv2_filter (const string& str, void* /*arg*/)
|
|
{
|
|
/* Not a dotfile, has a prefix before a period, suffix is "lv2" */
|
|
|
|
return str[0] != '.' && (str.length() > 3 && str.find (".lv2") == (str.length() - 4));
|
|
}
|
|
|
|
|
|
LV2World::LV2World()
|
|
: world(lilv_world_new())
|
|
, _bundle_checked(false)
|
|
{
|
|
atom_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort);
|
|
atom_Chunk = lilv_new_uri(world, LV2_ATOM__Chunk);
|
|
atom_Sequence = lilv_new_uri(world, LV2_ATOM__Sequence);
|
|
atom_bufferType = lilv_new_uri(world, LV2_ATOM__bufferType);
|
|
atom_supports = lilv_new_uri(world, LV2_ATOM__supports);
|
|
atom_eventTransfer = lilv_new_uri(world, LV2_ATOM__eventTransfer);
|
|
ev_EventPort = lilv_new_uri(world, LILV_URI_EVENT_PORT);
|
|
ext_logarithmic = lilv_new_uri(world, LV2_PORT_PROPS__logarithmic);
|
|
ext_notOnGUI = lilv_new_uri(world, LV2_PORT_PROPS__notOnGUI);
|
|
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);
|
|
ext_displayPriority= lilv_new_uri(world, LV2_PORT_PROPS__displayPriority);
|
|
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_maximum = lilv_new_uri(world, LV2_CORE__maximum);
|
|
lv2_reportsLatency = lilv_new_uri(world, LV2_CORE__reportsLatency);
|
|
lv2_sampleRate = lilv_new_uri(world, LV2_CORE__sampleRate);
|
|
lv2_toggled = lilv_new_uri(world, LV2_CORE__toggled);
|
|
lv2_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);
|
|
rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment");
|
|
rdfs_label = lilv_new_uri(world, LILV_NS_RDFS "label");
|
|
rdfs_range = lilv_new_uri(world, LILV_NS_RDFS "range");
|
|
rsz_minimumSize = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize);
|
|
time_Position = lilv_new_uri(world, LV2_TIME__Position);
|
|
time_beatsPerMin = lilv_new_uri(world, LV2_TIME__beatsPerMinute);
|
|
ui_GtkUI = lilv_new_uri(world, LV2_UI__GtkUI);
|
|
ui_X11UI = lilv_new_uri(world, LV2_UI__X11UI);
|
|
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, LV2_UNITS__unit);
|
|
units_render = lilv_new_uri(world, LV2_UNITS__render);
|
|
units_hz = lilv_new_uri(world, LV2_UNITS__hz);
|
|
units_midiNote = lilv_new_uri(world, LV2_UNITS__midiNote);
|
|
units_db = lilv_new_uri(world, LV2_UNITS__db);
|
|
patch_writable = lilv_new_uri(world, LV2_PATCH__writable);
|
|
patch_Message = lilv_new_uri(world, LV2_PATCH__Message);
|
|
opts_requiredOptions = lilv_new_uri(world, LV2_OPTIONS__requiredOption);
|
|
#ifdef LV2_EXTENDED
|
|
lv2_noSampleAccurateCtrl = lilv_new_uri(world, "http://ardour.org/lv2/ext#noSampleAccurateControls"); // deprecated 2016-09-18
|
|
routing_connectAllOutputs = lilv_new_uri(world, LV2_ROUTING__connectAllOutputs);
|
|
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_interface = lilv_new_uri(world, LV2_INLINEDISPLAY__interface);
|
|
inline_display_in_gui = lilv_new_uri(world, LV2_INLINEDISPLAY__in_gui);
|
|
inline_mixer_control = lilv_new_uri(world, "http://ardour.org/lv2/ext#inlineMixerControl");
|
|
#endif
|
|
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");
|
|
state_loadDefaultState = lilv_new_uri(world, LV2_STATE_PREFIX "loadDefaultState");
|
|
}
|
|
|
|
LV2World::~LV2World()
|
|
{
|
|
if (!world) {
|
|
return;
|
|
}
|
|
lilv_node_free(state_loadDefaultState);
|
|
lilv_node_free(bufz_coarseBlockLength);
|
|
lilv_node_free(bufz_nominalBlockLength);
|
|
lilv_node_free(bufz_fixedBlockLength);
|
|
lilv_node_free(bufz_powerOf2BlockLength);
|
|
#ifdef LV2_EXTENDED
|
|
lilv_node_free(lv2_noSampleAccurateCtrl);
|
|
lilv_node_free(routing_connectAllOutputs);
|
|
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);
|
|
lilv_node_free(inline_display_interface);
|
|
lilv_node_free(inline_display_in_gui);
|
|
lilv_node_free(inline_mixer_control);
|
|
#endif
|
|
lilv_node_free(patch_Message);
|
|
lilv_node_free(opts_requiredOptions);
|
|
lilv_node_free(patch_writable);
|
|
lilv_node_free(units_hz);
|
|
lilv_node_free(units_midiNote);
|
|
lilv_node_free(units_db);
|
|
lilv_node_free(units_unit);
|
|
lilv_node_free(units_render);
|
|
lilv_node_free(ui_externalkx);
|
|
lilv_node_free(ui_external);
|
|
lilv_node_free(ui_X11UI);
|
|
lilv_node_free(ui_GtkUI);
|
|
lilv_node_free(time_beatsPerMin);
|
|
lilv_node_free(time_Position);
|
|
lilv_node_free(rsz_minimumSize);
|
|
lilv_node_free(rdfs_comment);
|
|
lilv_node_free(rdfs_label);
|
|
lilv_node_free(rdfs_range);
|
|
lilv_node_free(midi_MidiEvent);
|
|
lilv_node_free(lv2_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_OutputPort);
|
|
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_displayPriority);
|
|
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);
|
|
lilv_node_free(atom_supports);
|
|
lilv_node_free(atom_eventTransfer);
|
|
lilv_node_free(atom_bufferType);
|
|
lilv_node_free(atom_Sequence);
|
|
lilv_node_free(atom_Chunk);
|
|
lilv_node_free(atom_AtomPort);
|
|
lilv_world_free(world);
|
|
world = NULL;
|
|
}
|
|
|
|
void
|
|
LV2World::load_bundled_plugins(bool verbose)
|
|
{
|
|
if (!_bundle_checked) {
|
|
if (verbose) {
|
|
info << "Scanning folders for bundled LV2s: " << ARDOUR::lv2_bundled_search_path().to_string() << endmsg;
|
|
}
|
|
|
|
vector<string> plugin_objects;
|
|
find_paths_matching_filter (plugin_objects, ARDOUR::lv2_bundled_search_path(), lv2_filter, 0, true, true, true);
|
|
for ( vector<string>::iterator x = plugin_objects.begin(); x != plugin_objects.end (); ++x) {
|
|
#ifdef PLATFORM_WINDOWS
|
|
string uri = "file:///" + *x + "/";
|
|
#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);
|
|
}
|
|
|
|
lilv_world_load_all(world);
|
|
_bundle_checked = true;
|
|
}
|
|
}
|
|
|
|
LV2PluginInfo::LV2PluginInfo (const char* plugin_uri)
|
|
{
|
|
type = ARDOUR::LV2;
|
|
_plugin_uri = strdup(plugin_uri);
|
|
}
|
|
|
|
LV2PluginInfo::~LV2PluginInfo()
|
|
{
|
|
free(_plugin_uri);
|
|
_plugin_uri = NULL;
|
|
}
|
|
|
|
PluginPtr
|
|
LV2PluginInfo::load(Session& session)
|
|
{
|
|
try {
|
|
PluginPtr plugin;
|
|
const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world);
|
|
LilvNode* uri = lilv_new_uri(_world.world, _plugin_uri);
|
|
if (!uri) { throw failed_constructor(); }
|
|
const LilvPlugin* lp = lilv_plugins_get_by_uri(plugins, uri);
|
|
if (!lp) { throw failed_constructor(); }
|
|
plugin.reset(new LV2Plugin(session.engine(), session, lp, session.sample_rate()));
|
|
lilv_node_free(uri);
|
|
plugin->set_info(PluginInfoPtr(shared_from_this ()));
|
|
return plugin;
|
|
} catch (failed_constructor& err) {
|
|
return PluginPtr((Plugin*)0);
|
|
}
|
|
|
|
return PluginPtr();
|
|
}
|
|
|
|
std::vector<Plugin::PresetRecord>
|
|
LV2PluginInfo::get_presets (bool user_only) const
|
|
{
|
|
std::vector<Plugin::PresetRecord> p;
|
|
|
|
const LilvPlugin* lp = NULL;
|
|
try {
|
|
PluginPtr plugin;
|
|
const LilvPlugins* plugins = lilv_world_get_all_plugins(_world.world);
|
|
LilvNode* uri = lilv_new_uri(_world.world, _plugin_uri);
|
|
if (!uri) { throw failed_constructor(); }
|
|
lp = lilv_plugins_get_by_uri(plugins, uri);
|
|
if (!lp) { throw failed_constructor(); }
|
|
lilv_node_free(uri);
|
|
} catch (failed_constructor& err) {
|
|
return p;
|
|
}
|
|
assert (lp);
|
|
/* see also LV2Plugin::find_presets */
|
|
LilvNode* lv2_appliesTo = lilv_new_uri(_world.world, LV2_CORE__appliesTo);
|
|
LilvNode* pset_Preset = lilv_new_uri(_world.world, LV2_PRESETS__Preset);
|
|
LilvNode* rdfs_label = lilv_new_uri(_world.world, LILV_NS_RDFS "label");
|
|
LilvNode* rdfs_comment = lilv_new_uri(_world.world, LILV_NS_RDFS "comment");
|
|
LilvNode* rdfs_seeAlso = lilv_new_uri(_world.world, LILV_NS_RDFS "seeAlso");
|
|
|
|
/* query plugins bundle path */
|
|
const LilvNode* const bundle_uri = lilv_plugin_get_bundle_uri(lp);
|
|
assert (bundle_uri);
|
|
char* bundle_path = lilv_file_uri_parse(lilv_node_as_uri(bundle_uri), NULL);
|
|
std::string bundle_dir = Glib::path_get_dirname (bundle_path);
|
|
lilv_free (bundle_path);
|
|
|
|
LilvNodes* presets = lilv_plugin_get_related(lp, pset_Preset);
|
|
LILV_FOREACH(nodes, i, presets) {
|
|
const LilvNode* preset = lilv_nodes_get(presets, i);
|
|
lilv_world_load_resource(_world.world, preset);
|
|
LilvNode* name = get_value(_world.world, preset, rdfs_label);
|
|
LilvNode* comment = get_value(_world.world, preset, rdfs_comment);
|
|
LilvNode* seealso = get_value(_world.world, preset, rdfs_seeAlso);
|
|
|
|
/* TODO properly identify user vs factory presets.
|
|
* here's an indirect condition: only factory presets can have comments
|
|
*/
|
|
bool userpreset = comment ? false : true;
|
|
|
|
/* if the preset ttl file is read-only, or part of the plugin bundle
|
|
* it is also assumed to be a factory preset */
|
|
if (seealso) {
|
|
char* pset_ttl = lilv_file_uri_parse (lilv_node_as_uri(seealso), NULL);
|
|
if (!exists_and_writable (pset_ttl)) {
|
|
/* no write access */
|
|
userpreset = false;
|
|
} else if (is_inside_bundle (bundle_dir, pset_ttl)) {
|
|
userpreset = false;
|
|
}
|
|
lilv_free (pset_ttl);
|
|
}
|
|
// TODO also check for 3rd part preset bundles
|
|
// > 1 preset in any give manifest.ttl or preset.ttl
|
|
|
|
if ((!user_only || userpreset) && name) {
|
|
p.push_back (Plugin::PresetRecord (lilv_node_as_string(preset), lilv_node_as_string(name), userpreset, comment ? lilv_node_as_string (comment) : ""));
|
|
lilv_node_free(name);
|
|
}
|
|
|
|
if (comment) {
|
|
lilv_node_free(comment);
|
|
}
|
|
if (seealso) {
|
|
lilv_node_free(seealso);
|
|
}
|
|
}
|
|
lilv_nodes_free(presets);
|
|
lilv_node_free(rdfs_comment);
|
|
lilv_node_free(rdfs_label);
|
|
lilv_node_free(pset_Preset);
|
|
lilv_node_free(lv2_appliesTo);
|
|
|
|
return p;
|
|
}
|
|
|
|
PluginInfoList*
|
|
LV2PluginInfo::discover (boost::function <void (std::string const&, PluginScanLogEntry::PluginScanResult, std::string const&, bool)> cb)
|
|
{
|
|
LV2World world;
|
|
world.load_bundled_plugins();
|
|
_world.load_bundled_plugins(true);
|
|
|
|
PluginInfoList* plugs = new PluginInfoList;
|
|
const LilvPlugins* plugins = lilv_world_get_all_plugins(world.world);
|
|
|
|
LILV_FOREACH(plugins, i, plugins) {
|
|
const LilvPlugin* p = lilv_plugins_get(plugins, i);
|
|
const LilvNode* pun = lilv_plugin_get_uri(p);
|
|
if (!pun) continue;
|
|
std::string const uri (lilv_node_as_string(pun));
|
|
cb (uri, PluginScanLogEntry::OK, string_compose (_("URI: %1"), uri), true);
|
|
cb (uri, PluginScanLogEntry::OK, string_compose (_("Bundle: %1"), lilv_node_as_uri (lilv_plugin_get_bundle_uri (p))), false);
|
|
|
|
LV2PluginInfoPtr info(new LV2PluginInfo(lilv_node_as_string(pun)));
|
|
|
|
LilvNode* name = lilv_plugin_get_name(p);
|
|
if (!name || !lilv_plugin_get_port_by_index(p, 0)) {
|
|
cb (uri, PluginScanLogEntry::Error, _("Ignoring invalid LV2 plugin (missing name, no ports)"), false);
|
|
lilv_node_free(name);
|
|
continue;
|
|
}
|
|
|
|
if (lilv_plugin_has_feature(p, world.lv2_inPlaceBroken)) {
|
|
cb (uri, PluginScanLogEntry::Error, _("Ignoring LV2 plugin since it cannot do inplace processing."), false);
|
|
lilv_node_free(name);
|
|
continue;
|
|
}
|
|
|
|
int err = 0;
|
|
LilvNodes* required_features = lilv_plugin_get_required_features (p);
|
|
LILV_FOREACH(nodes, i, required_features) {
|
|
const char* rf = lilv_node_as_uri (lilv_nodes_get (required_features, i));
|
|
bool ok = false;
|
|
if (!strcmp (rf, "http://lv2plug.in/ns/lv2core#isLive")) { ok = true; }
|
|
if (!strcmp (rf, "http://lv2plug.in/ns/ext/instance-access")) { ok = true; }
|
|
if (!strcmp (rf, "http://lv2plug.in/ns/ext/data-access")) { ok = true; }
|
|
if (!strcmp (rf, LV2_STATE__makePath)) { ok = true; }
|
|
if (!strcmp (rf, LV2_STATE__mapPath)) { ok = true; }
|
|
if (!strcmp (rf, "http://lv2plug.in/ns/ext/state#threadSafeRestore")) { ok = true; }
|
|
if (!strcmp (rf, LV2_STATE_PREFIX "loadDefaultState")) { ok = true; }
|
|
if (!strcmp (rf, LV2_LOG__log)) { ok = true; }
|
|
if (!strcmp (rf, LV2_WORKER__schedule)) { ok = true; }
|
|
if (!strcmp (rf, LV2_URID_MAP_URI)) { ok = true; }
|
|
if (!strcmp (rf, LV2_URID_UNMAP_URI)) { ok = true; }
|
|
if (!strcmp (rf, LV2_BUF_SIZE__boundedBlockLength)) { ok = true; }
|
|
if (!strcmp (rf, "http://lv2plug.in/ns/ext/buf-size#coarseBlockLength" /*LV2_BUF_SIZE__coarseBlockLength*/)) { ok = true; }
|
|
if (!strcmp (rf, LV2_OPTIONS__options)) { ok = true; }
|
|
#ifdef LV2_EXTENDED
|
|
if (!strcmp (rf, LV2_INLINEDISPLAY__queue_draw)) { ok = true; }
|
|
if (!strcmp (rf, LV2_MIDNAM__update)) { ok = true; }
|
|
if (!strcmp (rf, LV2_BANKPATCH__notify)) { ok = true; }
|
|
#endif
|
|
if (!ok) {
|
|
cb (uri, PluginScanLogEntry::Error, string_compose (_("Unsupported required LV2 feature: '%1'."), rf), false);
|
|
err = 1;
|
|
}
|
|
}
|
|
|
|
lilv_nodes_free (required_features);
|
|
|
|
if (err) {
|
|
continue;
|
|
}
|
|
|
|
LilvNodes* required_options = lilv_world_find_nodes (world.world, lilv_plugin_get_uri (p), world.opts_requiredOptions, NULL);
|
|
if (required_options) {
|
|
LILV_FOREACH(nodes, i, required_options) {
|
|
const char* ro = lilv_node_as_uri (lilv_nodes_get (required_options, i));
|
|
bool ok = false;
|
|
if (!strcmp (ro, LV2_PARAMETERS__sampleRate)) { ok = true; }
|
|
if (!strcmp (ro, LV2_BUF_SIZE__minBlockLength)) { ok = true; }
|
|
if (!strcmp (ro, LV2_BUF_SIZE__maxBlockLength)) { ok = true; }
|
|
if (!strcmp (ro, LV2_BUF_SIZE__sequenceSize)) { ok = true; }
|
|
if (!ok) {
|
|
cb (uri, PluginScanLogEntry::Error, string_compose (_("Unsupported required LV2 option: '%1'."), ro), false);
|
|
err = 1;
|
|
}
|
|
}
|
|
}
|
|
lilv_nodes_free(required_options);
|
|
|
|
if (err) {
|
|
continue;
|
|
}
|
|
|
|
info->type = LV2;
|
|
|
|
info->name = string(lilv_node_as_string(name));
|
|
lilv_node_free(name);
|
|
ARDOUR::PluginScanMessage(_("LV2"), info->name, false);
|
|
|
|
const LilvPluginClass* pclass = lilv_plugin_get_class(p);
|
|
const LilvNode* label = lilv_plugin_class_get_label(pclass);
|
|
|
|
info->category = lilv_node_as_string(label);
|
|
|
|
cb (uri, PluginScanLogEntry::OK, string_compose (_("LV2 Category: '%1'"), info->category), false);
|
|
|
|
/* check main category */
|
|
const char* pcat = lilv_node_as_uri (lilv_plugin_class_get_uri (pclass));
|
|
assert (pcat);
|
|
info->_is_instrument = 0 == strcmp (pcat, LV2_CORE__InstrumentPlugin);
|
|
info->_is_utility = 0 == strcmp (pcat, LV2_CORE__UtilityPlugin);
|
|
info->_is_analyzer = 0 == strcmp (pcat, LV2_CORE__AnalyserPlugin);
|
|
|
|
/* check parent category, if any */
|
|
const LilvNode* lpc = lilv_plugin_class_get_parent_uri (pclass);
|
|
if (lpc) {
|
|
const char* pcu = lilv_node_as_uri (lpc);
|
|
info->_is_instrument |= 0 == strcmp (pcu, LV2_CORE__InstrumentPlugin);
|
|
info->_is_utility |= 0 == strcmp (pcu, LV2_CORE__UtilityPlugin);
|
|
info->_is_analyzer |= 0 == strcmp (pcu, LV2_CORE__AnalyserPlugin);
|
|
cb (uri, PluginScanLogEntry::OK, string_compose (_("LV2 Parent Class URI: '%1'"), pcu), false);
|
|
}
|
|
|
|
#if 0
|
|
/* iterate over child classes */
|
|
LilvPluginClasses* classes = lilv_plugin_class_get_children (pclass);
|
|
LILV_FOREACH(plugin_classes, i, classes) {
|
|
const LilvPluginClass* lclass = lilv_plugin_classes_get (classes, i);
|
|
const LilvNode* lcnode = lilv_plugin_class_get_uri (lclass);
|
|
const LilvNode* lclbl = lilv_plugin_class_get_label (lclass);
|
|
const char* lcuri = lilv_node_as_uri (lcnode);
|
|
info->_is_instrument |= 0 == strcmp (lcuri, LV2_CORE__InstrumentPlugin);
|
|
info->_is_utility |= 0 == strcmp (lcuri, LV2_CORE__UtilityPlugin);
|
|
info->_is_analyzer |= 0 == strcmp (lcuri, LV2_CORE__AnalyserPlugin);
|
|
cb (uri, PluginScanLogEntry::OK, string_compose (_("LV2 Class: '%1'"), lilv_node_as_string (lclbl)), false);
|
|
}
|
|
lilv_plugin_classes_free (classes);
|
|
#endif
|
|
|
|
LilvNode* author_name = lilv_plugin_get_author_name(p);
|
|
info->creator = author_name ? string(lilv_node_as_string(author_name)) : "Unknown";
|
|
lilv_node_free(author_name);
|
|
|
|
info->path = "/NOPATH"; // Meaningless for LV2
|
|
|
|
int count_midi_out = 0;
|
|
int count_midi_in = 0;
|
|
int count_atom_out = 0;
|
|
int count_atom_in = 0;
|
|
int count_ctrl_out = 0;
|
|
int count_ctrl_in = 0;
|
|
|
|
for (uint32_t i = 0; i < lilv_plugin_get_num_ports(p); ++i) {
|
|
const LilvPort* port = lilv_plugin_get_port_by_index(p, i);
|
|
if (lilv_port_is_a(p, port, world.atom_AtomPort)) {
|
|
LilvNodes* buffer_types = lilv_port_get_value (p, port, world.atom_bufferType);
|
|
LilvNodes* atom_supports = lilv_port_get_value (p, port, world.atom_supports);
|
|
|
|
if (lilv_port_is_a(p, port, world.lv2_InputPort)) {
|
|
count_atom_in++;
|
|
} else if (lilv_port_is_a(p, port, world.lv2_OutputPort)) {
|
|
count_atom_out++;
|
|
} else {
|
|
cb (uri, PluginScanLogEntry::Error, _("Found Atom port not marked for input or output."), false);
|
|
err = 1;
|
|
}
|
|
|
|
if (!lilv_nodes_contains(buffer_types, world.atom_Sequence)) {
|
|
cb (uri, PluginScanLogEntry::Error, _("Found Atom port without sequence support, ignored"), false);
|
|
/* ignore non-sequence Atom ports */
|
|
err = 1;
|
|
}
|
|
else if (lilv_nodes_contains(atom_supports, world.midi_MidiEvent)) {
|
|
if (lilv_port_is_a(p, port, world.lv2_InputPort)) {
|
|
count_midi_in++;
|
|
}
|
|
if (lilv_port_is_a(p, port, world.lv2_OutputPort)) {
|
|
count_midi_out++;
|
|
}
|
|
}
|
|
|
|
lilv_nodes_free(buffer_types);
|
|
lilv_nodes_free(atom_supports);
|
|
}
|
|
else if (lilv_port_is_a(p, port, world.lv2_ControlPort)) {
|
|
if (lilv_port_is_a(p, port, world.lv2_InputPort)) {
|
|
count_ctrl_in++;
|
|
}
|
|
if (lilv_port_is_a(p, port, world.lv2_OutputPort)) {
|
|
count_ctrl_out++;
|
|
}
|
|
}
|
|
else if (!lilv_port_is_a (p, port, world.lv2_AudioPort)) {
|
|
err = 1;
|
|
LilvNode* name = lilv_port_get_name(p, port);
|
|
cb (uri, PluginScanLogEntry::Error, string_compose (_("Port %1 ('%2') has no known data type"), i, lilv_node_as_string (name)), false);
|
|
lilv_node_free(name);
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
continue;
|
|
}
|
|
|
|
info->n_inputs.set_audio(
|
|
lilv_plugin_get_num_ports_of_class(
|
|
p, world.lv2_InputPort, world.lv2_AudioPort, NULL));
|
|
info->n_inputs.set_midi(
|
|
lilv_plugin_get_num_ports_of_class(
|
|
p, world.lv2_InputPort, world.ev_EventPort, NULL)
|
|
+ count_midi_in);
|
|
|
|
info->n_outputs.set_audio(
|
|
lilv_plugin_get_num_ports_of_class(
|
|
p, world.lv2_OutputPort, world.lv2_AudioPort, NULL));
|
|
info->n_outputs.set_midi(
|
|
lilv_plugin_get_num_ports_of_class(
|
|
p, world.lv2_OutputPort, world.ev_EventPort, NULL)
|
|
+ count_midi_out);
|
|
|
|
info->unique_id = lilv_node_as_uri(lilv_plugin_get_uri(p));
|
|
info->index = 0; // Meaningless for LV2
|
|
|
|
cb (uri, PluginScanLogEntry::OK, string_compose (
|
|
_("LV2 Ports: Atom-in: %1, Atom-out: %2, Audio-in: %3 Audio-out: %4 MIDI-in: %5 MIDI-out: %6 Ctrl-in: %7 Ctrl-out: %8"),
|
|
count_atom_in, count_atom_out,
|
|
info->n_inputs.n_audio (), info->n_outputs.n_audio (),
|
|
count_midi_in, count_midi_out,
|
|
count_ctrl_in, count_ctrl_out), false);
|
|
plugs->push_back(info);
|
|
}
|
|
|
|
return plugs;
|
|
}
|
|
|
|
bool
|
|
LV2PluginInfo::is_instrument () const
|
|
{
|
|
if (_is_instrument) {
|
|
return true;
|
|
}
|
|
return PluginInfo::is_instrument ();
|
|
}
|
|
|
|
bool
|
|
LV2PluginInfo::is_utility () const
|
|
{
|
|
if (_is_utility) {
|
|
return true;
|
|
}
|
|
return PluginInfo::is_utility ();
|
|
}
|
|
|
|
bool
|
|
LV2PluginInfo::is_analyzer () const
|
|
{
|
|
if (_is_analyzer) {
|
|
return true;
|
|
}
|
|
return PluginInfo::is_analyzer ();
|
|
}
|