More generic RT-safe implementation of LV2 properties.

This commit is contained in:
David Robillard 2014-10-31 20:44:02 -04:00
parent 324ab35abc
commit 5de6c21ec1
7 changed files with 422 additions and 198 deletions

View file

@ -41,9 +41,6 @@
#include "ardour/plugin.h" #include "ardour/plugin.h"
#include "ardour/plugin_insert.h" #include "ardour/plugin_insert.h"
#include "ardour/session.h" #include "ardour/session.h"
#ifdef LV2_SUPPORT
#include "ardour/lv2_plugin.h"
#endif
#include "ardour_ui.h" #include "ardour_ui.h"
#include "prompter.h" #include "prompter.h"
@ -68,9 +65,6 @@ GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrol
, scroller_view(hAdjustment, vAdjustment) , scroller_view(hAdjustment, vAdjustment)
, automation_menu (0) , automation_menu (0)
, is_scrollable(scrollable) , is_scrollable(scrollable)
#ifdef LV2_SUPPORT
, _fcb(0)
#endif
{ {
set_name ("PluginEditor"); set_name ("PluginEditor");
set_border_width (10); set_border_width (10);
@ -143,9 +137,6 @@ GenericPluginUI::~GenericPluginUI ()
if (output_controls.size() > 0) { if (output_controls.size() > 0) {
screen_update_connection.disconnect(); screen_update_connection.disconnect();
} }
#ifdef LV2_SUPPORT
free(_fcb);
#endif
} }
// Some functions for calculating the 'similarity' of two plugin // Some functions for calculating the 'similarity' of two plugin
@ -314,33 +305,42 @@ GenericPluginUI::build ()
} }
} }
#ifdef LV2_SUPPORT // Add property controls (currently file chooser button for paths only)
boost::shared_ptr<ARDOUR::LV2Plugin> lv2p = boost::dynamic_pointer_cast<LV2Plugin> (plugin); typedef std::vector<Plugin::ParameterDescriptor> Descs;
if (lv2p) { Descs descs;
_fcb = (Gtk::FileChooserButton**) malloc(lv2p->patch_count() * sizeof(Gtk::FileChooserButton*)); plugin->get_supported_properties(descs);
for (uint32_t p = 0; p < lv2p->patch_count(); ++p) { for (Descs::const_iterator d = descs.begin(); d != descs.end(); ++d) {
_fcb[p] = manage (new Gtk::FileChooserButton (Gtk::FILE_CHOOSER_ACTION_OPEN)); if (d->datatype == Variant::PATH) {
_fcb[p]->signal_file_set().connect (sigc::bind(sigc::mem_fun (*this, &GenericPluginUI::patch_set_file), p)); // Create/add label
lv2p->PatchChanged.connect (*this, invalidator (*this), boost::bind (&GenericPluginUI::patch_changed, this, _1), gui_context()); Gtk::Label* label = manage(new Label(d->label));
// when user cancels file selection the FileChooserButton will display "None" button_table.attach(*label,
// TODO hack away around this.. 0, button_cols, button_row, button_row + 1,
if (lv2p->patch_val(p)) { FILL|EXPAND, FILL);
_fcb[p]->set_filename(lv2p->patch_val(p));
}
if (lv2p->patch_key(p)) {
_fcb[p]->set_title(lv2p->patch_key(p));
Gtk::Label* fcl = manage (new Label (lv2p->patch_key(p)));
button_table.attach (*fcl, 0, button_cols, button_row, button_row + 1, FILL|EXPAND, FILL);
++button_row;
} else {
_fcb[p]->set_title(_("LV2 Patch"));
}
button_table.attach (*_fcb[p], 0, button_cols, button_row, button_row + 1, FILL|EXPAND, FILL);
++button_row; ++button_row;
// Create/add controller
Gtk::FileChooserButton* widget = manage(
new Gtk::FileChooserButton(Gtk::FILE_CHOOSER_ACTION_OPEN));
widget->set_title(d->label);
_property_controls.insert(std::make_pair(d->key, widget));
button_table.attach(*widget,
0, button_cols, button_row, button_row + 1,
FILL|EXPAND, FILL);
++button_row;
// Connect signals
widget->signal_file_set().connect(
sigc::bind(sigc::mem_fun(*this, &GenericPluginUI::set_property), *d, widget));
plugin->PropertyChanged.connect(*this, invalidator(*this),
boost::bind(&GenericPluginUI::property_changed, this, _1, _2),
gui_context());
} else {
// TODO: widgets for other datatypes, use ControlUI?
std::cerr << "warning: unsupported property " << d->key
<< " type " << d->datatype << std::endl;
} }
} }
#endif plugin->announce_property_values();
// Iterate over the list of controls to find which adjacent controls // Iterate over the list of controls to find which adjacent controls
// are similar enough to be grouped together. // are similar enough to be grouped together.
@ -971,20 +971,20 @@ GenericPluginUI::output_update ()
} }
} }
#ifdef LV2_SUPPORT
void void
GenericPluginUI::patch_set_file (uint32_t p) GenericPluginUI::set_property (const Plugin::ParameterDescriptor& desc,
Gtk::FileChooserButton* widget)
{ {
boost::shared_ptr<ARDOUR::LV2Plugin> lv2p = boost::dynamic_pointer_cast<LV2Plugin> (plugin); plugin->set_property(desc.key, Variant(Variant::PATH, widget->get_filename()));
lv2p->patch_set(p, _fcb[p]->get_filename ().c_str());
} }
void void
GenericPluginUI::patch_changed (uint32_t p) GenericPluginUI::property_changed (uint32_t key, const Variant& value)
{ {
boost::shared_ptr<ARDOUR::LV2Plugin> lv2p = boost::dynamic_pointer_cast<LV2Plugin> (plugin); PropertyControls::iterator c = _property_controls.find(key);
_fcb[p]->set_filename(lv2p->patch_val(p)); if (c != _property_controls.end()) {
c->second->set_filename(value.get_path());
} else {
std::cerr << "warning: property change for property with no control" << std::endl;
}
} }
#endif

View file

@ -47,6 +47,7 @@
#include "ardour/types.h" #include "ardour/types.h"
#include "ardour/plugin.h" #include "ardour/plugin.h"
#include "ardour/variant.h"
#include "automation_controller.h" #include "automation_controller.h"
#include "ardour_button.h" #include "ardour_button.h"
@ -279,11 +280,12 @@ class GenericPluginUI : public PlugUIBase, public Gtk::VBox
bool integer_printer (char* buf, Gtk::Adjustment &, ControlUI *); bool integer_printer (char* buf, Gtk::Adjustment &, ControlUI *);
bool midinote_printer(char* buf, Gtk::Adjustment &, ControlUI *); bool midinote_printer(char* buf, Gtk::Adjustment &, ControlUI *);
#ifdef LV2_SUPPORT void set_property (const ARDOUR::Plugin::ParameterDescriptor& desc,
void patch_set_file (uint32_t patch_idx); Gtk::FileChooserButton* widget);
void patch_changed (uint32_t patch_idx); void property_changed (uint32_t key, const ARDOUR::Variant& value);
Gtk::FileChooserButton **_fcb;
#endif typedef std::map<uint32_t, Gtk::FileChooserButton*> PropertyControls;
PropertyControls _property_controls;
}; };
class PluginUIWindow : public ArdourWindow class PluginUIWindow : public ArdourWindow

View file

@ -146,16 +146,13 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
Worker* worker() { return _worker; } Worker* worker() { return _worker; }
uint32_t patch_count() const { return _patch_count; }
const char * patch_uri(const uint32_t p) const { if (p < _patch_count) return _patch_value_uri[p]; else return NULL; }
const char * patch_key(const uint32_t p) const { if (p < _patch_count) return _patch_value_key[p]; else return NULL; }
const char * patch_val(const uint32_t p) const { if (p < _patch_count) return _patch_value_cur[p]; else return NULL; }
bool patch_set(const uint32_t p, const char * val);
PBD::Signal1<void,const uint32_t> PatchChanged;
int work(uint32_t size, const void* data); int work(uint32_t size, const void* data);
int work_response(uint32_t size, const void* data); int work_response(uint32_t size, const void* data);
void set_property(uint32_t key, const Variant& value);
void get_supported_properties(std::vector<ParameterDescriptor>& descs);
void announce_property_values();
static URIMap _uri_map; static URIMap _uri_map;
struct URIDs { struct URIDs {
@ -165,6 +162,7 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
uint32_t atom_eventTransfer; uint32_t atom_eventTransfer;
uint32_t atom_URID; uint32_t atom_URID;
uint32_t atom_Blank; uint32_t atom_Blank;
uint32_t atom_Object;
uint32_t log_Error; uint32_t log_Error;
uint32_t log_Note; uint32_t log_Note;
uint32_t log_Warning; uint32_t log_Warning;
@ -177,6 +175,7 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
uint32_t time_beatsPerMinute; uint32_t time_beatsPerMinute;
uint32_t time_frame; uint32_t time_frame;
uint32_t time_speed; uint32_t time_speed;
uint32_t patch_Get;
uint32_t patch_Set; uint32_t patch_Set;
uint32_t patch_property; uint32_t patch_property;
uint32_t patch_value; uint32_t patch_value;
@ -202,13 +201,8 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
framepos_t _next_cycle_start; ///< Expected start frame of next run cycle framepos_t _next_cycle_start; ///< Expected start frame of next run cycle
double _next_cycle_speed; ///< Expected start frame of next run cycle double _next_cycle_speed; ///< Expected start frame of next run cycle
PBD::ID _insert_id; PBD::ID _insert_id;
uint32_t _patch_port_in_index;
uint32_t _patch_count; uint32_t _patch_port_out_index;
char ** _patch_value_uri;
char ** _patch_value_key;
char (*_patch_value_cur)[PATH_MAX]; ///< current value
char (*_patch_value_set)[PATH_MAX]; ///< new value to set
Glib::Threads::Mutex _patch_set_lock;
friend const void* lv2plugin_get_port_value(const char* port_symbol, friend const void* lv2plugin_get_port_value(const char* port_symbol,
void* user_data, void* user_data,

View file

@ -30,10 +30,11 @@
#include "ardour/chan_mapping.h" #include "ardour/chan_mapping.h"
#include "ardour/cycles.h" #include "ardour/cycles.h"
#include "ardour/latent.h" #include "ardour/latent.h"
#include "ardour/plugin_insert.h"
#include "ardour/libardour_visibility.h" #include "ardour/libardour_visibility.h"
#include "ardour/types.h"
#include "ardour/midi_state_tracker.h" #include "ardour/midi_state_tracker.h"
#include "ardour/plugin_insert.h"
#include "ardour/types.h"
#include "ardour/variant.h"
#include <vector> #include <vector>
#include <set> #include <set>
@ -128,6 +129,8 @@ class LIBARDOUR_API Plugin : public PBD::StatefulDestructible, public Latent
bool max_unbound; bool max_unbound;
bool enumeration; bool enumeration;
bool midinote; ///< only used if integer_step is also true bool midinote; ///< only used if integer_step is also true
uint32_t key; ///< for properties
Variant::Type datatype; ///< for properties
}; };
XMLNode& get_state (); XMLNode& get_state ();
@ -267,6 +270,31 @@ class LIBARDOUR_API Plugin : public PBD::StatefulDestructible, public Latent
void set_cycles (uint32_t c) { _cycles = c; } void set_cycles (uint32_t c) { _cycles = c; }
cycles_t cycles() const { return _cycles; } cycles_t cycles() const { return _cycles; }
/** Get a descrption of all properties supported by this plugin.
*
* Properties are distinct from parameters in that they are potentially
* dynamic, referred to by key, and do not correspond 1:1 with ports.
*
* For LV2 plugins, properties are implemented by sending/receiving set/get
* messages to/from the plugin via event ports.
*/
virtual void get_supported_properties(std::vector<ParameterDescriptor>& descs) {}
/** Set a property from the UI.
*
* This is not UI-specific, but may only be used by one thread. If the
* Ardour UI is present, that is the UI thread, but otherwise, any thread
* except the audio thread may call this function as long as it is not
* called concurrently.
*/
virtual void set_property(uint32_t key, const Variant& value) {}
/** Emit PropertyChanged for all current property values. */
virtual void announce_property_values() {}
/** Emitted when a property is changed in the plugin. */
PBD::Signal2<void, uint32_t, Variant> PropertyChanged;
PBD::Signal1<void,uint32_t> StartTouch; PBD::Signal1<void,uint32_t> StartTouch;
PBD::Signal1<void,uint32_t> EndTouch; PBD::Signal1<void,uint32_t> EndTouch;

View file

@ -0,0 +1,102 @@
/*
Copyright (C) 2014 Paul Davis
Author: David Robillard
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.,
675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_variant_h__
#define __ardour_variant_h__
#include <stdint.h>
#include <stdexcept>
#include "ardour/libardour_visibility.h"
#include "pbd/compose.h"
namespace ARDOUR {
/** A value with dynamic type (tagged union). */
class LIBARDOUR_API Variant
{
public:
enum Type {
BOOL, ///< Boolean
DOUBLE, ///< C double (64-bit IEEE-754)
FLOAT, ///< C float (32-bit IEEE-754)
INT, ///< Signed 32-bit int
LONG, ///< Signed 64-bit int
PATH, ///< File path string
STRING, ///< Raw string (no semantics)
URI ///< URI string
};
explicit Variant(bool value) : _type(BOOL) { _bool = value; }
explicit Variant(double value) : _type(DOUBLE) { _double = value; }
explicit Variant(float value) : _type(FLOAT) { _float = value; }
explicit Variant(int value) : _type(INT) { _int = value; }
explicit Variant(long value) : _type(LONG) { _long = value; }
Variant(Type type, const std::string& value)
: _type(type)
, _string(value)
{}
bool get_bool() const { ensure_type(BOOL); return _bool; }
double get_double() const { ensure_type(DOUBLE); return _double; }
float get_float() const { ensure_type(FLOAT); return _float; }
int get_int() const { ensure_type(INT); return _int; }
long get_long() const { ensure_type(LONG); return _long; }
const std::string& get_path() const { ensure_type(PATH); return _string; }
const std::string& get_string() const { ensure_type(STRING); return _string; }
const std::string& get_uri() const { ensure_type(URI); return _string; }
Type type() const { return _type; }
private:
static const char* type_name(const Type type) {
static const char* names[] = {
"bool", "double", "float", "int", "long", "path", "string", "uri"
};
return names[type];
}
void ensure_type(const Type type) const {
if (_type != type) {
throw std::domain_error(
string_compose("get_%1 called on %2 variant",
type_name(type), type_name(_type)));
}
}
Type _type; ///< Type tag
std::string _string; ///< For all string types (PATH, STRING, URI)
// Union of all primitive numeric types
union {
bool _bool;
double _double;
float _float;
int32_t _int;
int64_t _long;
};
};
} // namespace ARDOUR
#endif // __ardour_variant_h__

View file

@ -78,6 +78,15 @@
#include <suil/suil.h> #include <suil/suil.h>
#endif #endif
// Compatibility for lv2-1.0.0
#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
/** The number of MIDI buffers that will fit in a UI/worker comm buffer. /** 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 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. actually processing the traffic. Lower values are flakier but save memory.
@ -97,6 +106,7 @@ LV2Plugin::URIDs LV2Plugin::urids = {
_uri_map.uri_to_id(LV2_ATOM__eventTransfer), _uri_map.uri_to_id(LV2_ATOM__eventTransfer),
_uri_map.uri_to_id(LV2_ATOM__URID), _uri_map.uri_to_id(LV2_ATOM__URID),
_uri_map.uri_to_id(LV2_ATOM__Blank), _uri_map.uri_to_id(LV2_ATOM__Blank),
_uri_map.uri_to_id(LV2_ATOM__Object),
_uri_map.uri_to_id(LV2_LOG__Error), _uri_map.uri_to_id(LV2_LOG__Error),
_uri_map.uri_to_id(LV2_LOG__Note), _uri_map.uri_to_id(LV2_LOG__Note),
_uri_map.uri_to_id(LV2_LOG__Warning), _uri_map.uri_to_id(LV2_LOG__Warning),
@ -109,6 +119,7 @@ LV2Plugin::URIDs LV2Plugin::urids = {
_uri_map.uri_to_id(LV2_TIME__beatsPerMinute), _uri_map.uri_to_id(LV2_TIME__beatsPerMinute),
_uri_map.uri_to_id(LV2_TIME__frame), _uri_map.uri_to_id(LV2_TIME__frame),
_uri_map.uri_to_id(LV2_TIME__speed), _uri_map.uri_to_id(LV2_TIME__speed),
_uri_map.uri_to_id(LV2_PATCH__Get),
_uri_map.uri_to_id(LV2_PATCH__Set), _uri_map.uri_to_id(LV2_PATCH__Set),
_uri_map.uri_to_id(LV2_PATCH__property), _uri_map.uri_to_id(LV2_PATCH__property),
_uri_map.uri_to_id(LV2_PATCH__value) _uri_map.uri_to_id(LV2_PATCH__value)
@ -145,6 +156,8 @@ public:
LilvNode* lv2_toggled; LilvNode* lv2_toggled;
LilvNode* midi_MidiEvent; LilvNode* midi_MidiEvent;
LilvNode* rdfs_comment; LilvNode* rdfs_comment;
LilvNode* rdfs_label;
LilvNode* rdfs_range;
LilvNode* rsz_minimumSize; LilvNode* rsz_minimumSize;
LilvNode* time_Position; LilvNode* time_Position;
LilvNode* ui_GtkUI; LilvNode* ui_GtkUI;
@ -250,6 +263,7 @@ struct LV2Plugin::Impl {
const LV2_Worker_Interface* work_iface; const LV2_Worker_Interface* work_iface;
LilvState* state; LilvState* state;
LV2_Atom_Forge forge; LV2_Atom_Forge forge;
LV2_Atom_Forge ui_forge;
}; };
LV2Plugin::LV2Plugin (AudioEngine& engine, LV2Plugin::LV2Plugin (AudioEngine& engine,
@ -262,11 +276,8 @@ LV2Plugin::LV2Plugin (AudioEngine& engine,
, _features(NULL) , _features(NULL)
, _worker(NULL) , _worker(NULL)
, _insert_id("0") , _insert_id("0")
, _patch_count(0) , _patch_port_in_index((uint32_t)-1)
, _patch_value_uri(NULL) , _patch_port_out_index((uint32_t)-1)
, _patch_value_key(NULL)
, _patch_value_cur(NULL)
, _patch_value_set(NULL)
{ {
init(c_plugin, rate); init(c_plugin, rate);
} }
@ -278,11 +289,8 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other)
, _features(NULL) , _features(NULL)
, _worker(NULL) , _worker(NULL)
, _insert_id(other._insert_id) , _insert_id(other._insert_id)
, _patch_count(0) , _patch_port_in_index((uint32_t)-1)
, _patch_value_uri(NULL) , _patch_port_out_index((uint32_t)-1)
, _patch_value_key(NULL)
, _patch_value_cur(NULL)
, _patch_value_set(NULL)
{ {
init(other._impl->plugin, other._sample_rate); init(other._impl->plugin, other._sample_rate);
@ -353,6 +361,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
#endif #endif
lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map()); lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map());
lv2_atom_forge_init(&_impl->ui_forge, _uri_map.urid_map());
#ifdef HAVE_LV2_1_2_0 #ifdef HAVE_LV2_1_2_0
LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int); LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int);
@ -476,6 +485,11 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
} }
if (lilv_nodes_contains(atom_supports, _world.patch_Message)) { if (lilv_nodes_contains(atom_supports, _world.patch_Message)) {
flags |= PORT_PATCHMSG; 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); LilvNodes* min_size_v = lilv_port_get_value(_impl->plugin, port, _world.rsz_minimumSize);
@ -553,32 +567,6 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
delete[] params; delete[] params;
/* scan supported patch:writable for this plugin.
* Note: the first Atom-port (in every direction) that supports patch:Message will be used
*/
LilvNode* rdfs_label = lilv_new_uri(_world.world, LILV_NS_RDFS "label");
LilvNode* rdfs_range = lilv_new_uri(_world.world, LILV_NS_RDFS "range");
LilvNodes* properties = lilv_world_find_nodes (_world.world, lilv_plugin_get_uri(plugin), _world.patch_writable, NULL);
LILV_FOREACH(nodes, p, properties) {
const LilvNode* property = lilv_nodes_get(properties, p);
LilvNode* label = lilv_nodes_get_first (lilv_world_find_nodes (_world.world, property, rdfs_label, NULL));
LilvNode* range = lilv_nodes_get_first (lilv_world_find_nodes (_world.world, property, rdfs_range, NULL));
if (!range || _uri_map.uri_to_id(lilv_node_as_uri(range)) != LV2Plugin::urids.atom_Path) {
continue;
}
_patch_value_uri = (char**) realloc (_patch_value_uri, (_patch_count + 1) * sizeof(char**));
_patch_value_key = (char**) realloc (_patch_value_key, (_patch_count + 1) * sizeof(char**));
_patch_value_uri[_patch_count] = strdup(lilv_node_as_uri(property));
_patch_value_key[_patch_count] = strdup(lilv_node_as_string(label ? label : property));
++_patch_count;
}
lilv_node_free(rdfs_label);
lilv_node_free(rdfs_range);
lilv_nodes_free(properties);
_patch_value_cur = (char(*)[PATH_MAX]) calloc(_patch_count, sizeof(char[PATH_MAX]));
_patch_value_set = (char(*)[PATH_MAX]) calloc(_patch_count, sizeof(char[PATH_MAX]));
LilvUIs* uis = lilv_plugin_get_uis(plugin); LilvUIs* uis = lilv_plugin_get_uis(plugin);
if (lilv_uis_size(uis) > 0) { if (lilv_uis_size(uis) > 0) {
#ifdef HAVE_SUIL #ifdef HAVE_SUIL
@ -644,13 +632,6 @@ LV2Plugin::~LV2Plugin ()
free(_make_path_feature.data); free(_make_path_feature.data);
free(_work_schedule_feature.data); free(_work_schedule_feature.data);
for (uint32_t pidx = 0; pidx < _patch_count; ++pidx) {
free(_patch_value_uri[pidx]);
free(_patch_value_key[pidx]);
}
free(_patch_value_cur);
free(_patch_value_set);
delete _to_ui; delete _to_ui;
delete _from_ui; delete _from_ui;
delete _worker; delete _worker;
@ -1239,6 +1220,166 @@ LV2Plugin::write_to_ui(uint32_t index,
return true; return true;
} }
static void
forge_variant(LV2_Atom_Forge* forge, const Variant& value)
{
switch (value.type()) {
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 << "LV2: set_property called with unset patch_port_in_index" << 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, 1, LV2Plugin::urids.patch_Set);
lv2_atom_forge_key(forge, LV2Plugin::urids.patch_property);
lv2_atom_forge_urid(forge, key);
lv2_atom_forge_key(forge, LV2Plugin::urids.patch_value);
#else
lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.patch_Set);
lv2_atom_forge_property_head(forge, LV2Plugin::urids.patch_property, 0);
lv2_atom_forge_urid(forge, key);
lv2_atom_forge_property_head(forge, LV2Plugin::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,
LV2Plugin::urids.atom_eventTransfer,
lv2_atom_total_size(atom),
(const uint8_t*)atom);
}
void
LV2Plugin::get_supported_properties(std::vector<ParameterDescriptor>& 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* label = lilv_world_get(lworld, prop, _world.rdfs_label, NULL);
LilvNode* range = lilv_world_get(lworld, prop, _world.rdfs_range, NULL);
// 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: unknown variant datatype \"%1\""),
lilv_node_as_uri(range));
continue;
}
// Add description to result
ParameterDescriptor desc;
desc.key = _uri_map.uri_to_id(lilv_node_as_uri(prop));
desc.label = lilv_node_as_string(label);
desc.datatype = datatype;
desc.toggled = datatype == Variant::BOOL;
desc.integer_step = datatype == Variant::INT || datatype == Variant::LONG;
descs.push_back(desc);
lilv_node_free(label);
lilv_node_free(range);
}
lilv_nodes_free(properties);
}
void
LV2Plugin::announce_property_values()
{
if (_patch_port_in_index == (uint32_t)-1) {
error << "LV2: set_property called with unset patch_port_in_index" << 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:Get message with no subject (implicitly plugin instance)
lv2_atom_forge_object(forge, &frame, 1, LV2Plugin::urids.patch_Get);
// Write message to UI=>Plugin ring
const LV2_Atom* const atom = (const LV2_Atom*)buf;
write_from_ui(_patch_port_in_index,
LV2Plugin::urids.atom_eventTransfer,
lv2_atom_total_size(atom),
(const uint8_t*)atom);
}
void void
LV2Plugin::enable_ui_emission() LV2Plugin::enable_ui_emission()
{ {
@ -1587,6 +1728,24 @@ write_position(LV2_Atom_Forge* forge,
uint8_t pos_buf[256]; uint8_t pos_buf[256];
lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf)); lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf));
LV2_Atom_Forge_Frame frame; LV2_Atom_Forge_Frame frame;
#ifdef HAVE_LV2_1_10_0
lv2_atom_forge_object(forge, &frame, 1, LV2Plugin::urids.time_Position);
lv2_atom_forge_key(forge, LV2Plugin::urids.time_frame);
lv2_atom_forge_long(forge, position);
lv2_atom_forge_key(forge, LV2Plugin::urids.time_speed);
lv2_atom_forge_float(forge, speed);
lv2_atom_forge_key(forge, LV2Plugin::urids.time_barBeat);
lv2_atom_forge_float(forge, bbt.beats - 1 +
(bbt.ticks / Timecode::BBT_Time::ticks_per_beat));
lv2_atom_forge_key(forge, LV2Plugin::urids.time_bar);
lv2_atom_forge_long(forge, bbt.bars - 1);
lv2_atom_forge_key(forge, LV2Plugin::urids.time_beatUnit);
lv2_atom_forge_int(forge, t.meter().note_divisor());
lv2_atom_forge_key(forge, LV2Plugin::urids.time_beatsPerBar);
lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
lv2_atom_forge_key(forge, LV2Plugin::urids.time_beatsPerMinute);
lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
#else
lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.time_Position); lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.time_Position);
lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_frame, 0); lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_frame, 0);
lv2_atom_forge_long(forge, position); lv2_atom_forge_long(forge, position);
@ -1603,6 +1762,7 @@ write_position(LV2_Atom_Forge* forge,
lv2_atom_forge_float(forge, t.meter().divisions_per_bar()); lv2_atom_forge_float(forge, t.meter().divisions_per_bar());
lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerMinute, 0); lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerMinute, 0);
lv2_atom_forge_float(forge, t.tempo().beats_per_minute()); lv2_atom_forge_float(forge, t.tempo().beats_per_minute());
#endif
LV2_Evbuf_Iterator end = lv2_evbuf_end(buf); LV2_Evbuf_Iterator end = lv2_evbuf_end(buf);
const LV2_Atom* const atom = (const LV2_Atom*)pos_buf; const LV2_Atom* const atom = (const LV2_Atom*)pos_buf;
@ -1610,48 +1770,6 @@ write_position(LV2_Atom_Forge* forge,
(const uint8_t*)(atom + 1)); (const uint8_t*)(atom + 1));
} }
static bool
write_patch_change(
LV2_Atom_Forge* forge,
LV2_Evbuf* buf,
const char* uri,
const char* filename
)
{
LV2_Atom_Forge_Frame frame;
uint8_t patch_buf[PATH_MAX];
lv2_atom_forge_set_buffer(forge, patch_buf, sizeof(patch_buf));
#if 0 // new LV2
lv2_atom_forge_object(forge, &frame, 0, LV2Plugin::urids.patch_Set);
lv2_atom_forge_key(forge, LV2Plugin::urids.patch_property);
lv2_atom_forge_urid(forge, uri_map.uri_to_id(uri));
lv2_atom_forge_key(forge, LV2Plugin::urids.patch_value);
lv2_atom_forge_path(forge, filename, strlen(filename));
#else
lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.patch_Set);
lv2_atom_forge_property_head(forge, LV2Plugin::urids.patch_property, 0);
lv2_atom_forge_urid(forge, LV2Plugin::_uri_map.uri_to_id(uri));
lv2_atom_forge_property_head(forge, LV2Plugin::urids.patch_value, 0);
lv2_atom_forge_path(forge, filename, strlen(filename));
#endif
LV2_Evbuf_Iterator end = lv2_evbuf_end(buf);
const LV2_Atom* const atom = (const LV2_Atom*)patch_buf;
return lv2_evbuf_write(&end, 0, 0, atom->type, atom->size,
(const uint8_t*)(atom + 1));
}
bool
LV2Plugin::patch_set (const uint32_t p, const char * val) {
if (p >= _patch_count) return false;
_patch_set_lock.lock();
strncpy(_patch_value_set[p], val, PATH_MAX);
_patch_value_set[p][PATH_MAX - 1] = 0;
_patch_set_lock.unlock();
return true;
}
int int
LV2Plugin::connect_and_run(BufferSet& bufs, LV2Plugin::connect_and_run(BufferSet& bufs,
ChanMapping in_map, ChanMapping out_map, ChanMapping in_map, ChanMapping out_map,
@ -1784,23 +1902,6 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
(flags & PORT_INPUT), 0, (flags & PORT_EVENT)); (flags & PORT_INPUT), 0, (flags & PORT_EVENT));
} }
/* queue patch messages */
if (flags & PORT_PATCHMSG) {
if (_patch_set_lock.trylock()) {
for (uint32_t pidx = 0; pidx < _patch_count; ++ pidx) {
if (strlen(_patch_value_set[pidx]) > 0
&& 0 != strcmp(_patch_value_cur[pidx], _patch_value_set[pidx])
)
{
write_patch_change(&_impl->forge, _ev_buffers[port_index],
_patch_value_uri[pidx], _patch_value_set[pidx]);
strncpy(_patch_value_cur[pidx], _patch_value_set[pidx], PATH_MAX);
}
}
_patch_set_lock.unlock();
}
}
buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]); buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]);
} else { } else {
continue; // Control port, leave buffer alone continue; // Control port, leave buffer alone
@ -1875,7 +1976,8 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
// Write messages to UI // Write messages to UI
if ((_to_ui || _patch_count > 0) && (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) { if ((_to_ui || _patch_port_out_index != (uint32_t)-1) &&
(flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
LV2_Evbuf* buf = _ev_buffers[port_index]; LV2_Evbuf* buf = _ev_buffers[port_index];
for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf); for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf);
lv2_evbuf_is_valid(i); lv2_evbuf_is_valid(i);
@ -1884,42 +1986,32 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
uint8_t* data; uint8_t* data;
lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data); lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
// intercept patch change messages // Intercept patch change messages to emit PropertyChanged signal
/* TODO this should eventually be done in the UI-thread if ((flags & PORT_PATCHMSG)) {
* using a ringbuffer and no locks.
* The current UI ringbuffer is unsuitable. It only exists
* if a UI enabled and misses initial patch-set or changes.
*/
if (_patch_count > 0 && (flags & PORT_OUTPUT) && (flags & PORT_PATCHMSG)) {
// TODO reduce if() nesting below:
LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom)); LV2_Atom* atom = (LV2_Atom*)(data - sizeof(LV2_Atom));
if (atom->type == LV2Plugin::urids.atom_Blank) { if (atom->type == LV2Plugin::urids.atom_Blank ||
atom->type == LV2Plugin::urids.atom_Object) {
LV2_Atom_Object* obj = (LV2_Atom_Object*)atom; LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
if (obj->body.otype == LV2Plugin::urids.patch_Set) { if (obj->body.otype == LV2Plugin::urids.patch_Set) {
const LV2_Atom* property = NULL; const LV2_Atom* property = NULL;
lv2_atom_object_get (obj, LV2Plugin::urids.patch_property, &property, 0); const LV2_Atom* value = NULL;
if (property->type == LV2Plugin::urids.atom_URID) { lv2_atom_object_get(obj,
for (uint32_t pidx = 0; pidx < _patch_count; ++ pidx) { LV2Plugin::urids.patch_property, &property,
if (((const LV2_Atom_URID*)property)->body != _uri_map.uri_to_id(_patch_value_uri[pidx])) { LV2Plugin::urids.patch_value, &value,
continue; 0);
}
/* Get value. */ if (!property || !value ||
const LV2_Atom* file_path = NULL; property->type != LV2Plugin::urids.atom_URID ||
lv2_atom_object_get(obj, LV2Plugin::urids.patch_value, &file_path, 0); value->type != LV2Plugin::urids.atom_Path) {
if (!file_path || file_path->type != LV2Plugin::urids.atom_Path) { std::cerr << "warning: patch:Set for unknown property" << std::endl;
continue; continue;
}
// LV2_ATOM_BODY() casts away qualifiers, so do it explicitly:
const char* uri = (const char*)((uint8_t const*)(file_path) + sizeof(LV2_Atom));
_patch_set_lock.lock();
strncpy(_patch_value_cur[pidx], uri, PATH_MAX);
strncpy(_patch_value_set[pidx], uri, PATH_MAX);
_patch_value_cur[pidx][PATH_MAX - 1] = 0;
_patch_value_set[pidx][PATH_MAX - 1] = 0;
_patch_set_lock.unlock();
PatchChanged(pidx); // emit signal
}
} }
const uint32_t prop_id = ((const LV2_Atom_URID*)property)->body;
const char* path = (const char*)LV2_ATOM_BODY_CONST(value);
// Emit PropertyChanged signal for UI
PropertyChanged(prop_id, Variant(Variant::PATH, path));
} }
} }
} }
@ -2133,6 +2225,8 @@ LV2World::LV2World()
lv2_freewheeling = lilv_new_uri(world, LV2_CORE__freeWheeling); lv2_freewheeling = lilv_new_uri(world, LV2_CORE__freeWheeling);
midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT); midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT);
rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); 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); rsz_minimumSize = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize);
time_Position = lilv_new_uri(world, LV2_TIME__Position); time_Position = lilv_new_uri(world, LV2_TIME__Position);
ui_GtkUI = lilv_new_uri(world, LV2_UI__GtkUI); ui_GtkUI = lilv_new_uri(world, LV2_UI__GtkUI);
@ -2156,6 +2250,8 @@ LV2World::~LV2World()
lilv_node_free(time_Position); lilv_node_free(time_Position);
lilv_node_free(rsz_minimumSize); lilv_node_free(rsz_minimumSize);
lilv_node_free(rdfs_comment); lilv_node_free(rdfs_comment);
lilv_node_free(rdfs_label);
lilv_node_free(rdfs_range);
lilv_node_free(midi_MidiEvent); lilv_node_free(midi_MidiEvent);
lilv_node_free(lv2_enumeration); lilv_node_free(lv2_enumeration);
lilv_node_free(lv2_freewheeling); lilv_node_free(lv2_freewheeling);

View file

@ -267,6 +267,8 @@ def configure(conf):
atleast_version='1.0.0', mandatory=True) atleast_version='1.0.0', mandatory=True)
autowaf.check_pkg(conf, 'lv2', uselib_store='LV2_1_2_0', autowaf.check_pkg(conf, 'lv2', uselib_store='LV2_1_2_0',
atleast_version='1.2.0', mandatory=False) atleast_version='1.2.0', mandatory=False)
autowaf.check_pkg(conf, 'lv2', uselib_store='LV2_1_10_0',
atleast_version='1.10.0', mandatory=False)
autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD', autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD',
atleast_version='0.14.0', mandatory=True) atleast_version='0.14.0', mandatory=True)
autowaf.check_pkg(conf, 'sord-0', uselib_store='SORD', autowaf.check_pkg(conf, 'sord-0', uselib_store='SORD',