summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2014-10-31 03:26:47 +0100
committerRobin Gareus <robin@gareus.org>2014-10-31 03:26:47 +0100
commitb8cea19b957b3af6ce0dd72f6441cb333e38ab94 (patch)
treecc5dec3dd316a1aab895b4ee3a631dad39e17b9f
parent1648d9cbc6321a15230db6965b9fe8afa1f8b421 (diff)
prototype [LV2]patch-change support for generic plugin UIs.
-rw-r--r--gtk2_ardour/generic_pluginui.cc47
-rw-r--r--gtk2_ardour/plugin_ui.h6
-rw-r--r--libs/ardour/ardour/lv2_plugin.h26
-rw-r--r--libs/ardour/lv2_plugin.cc158
4 files changed, 234 insertions, 3 deletions
diff --git a/gtk2_ardour/generic_pluginui.cc b/gtk2_ardour/generic_pluginui.cc
index 21a21b1751..e1d4609f6a 100644
--- a/gtk2_ardour/generic_pluginui.cc
+++ b/gtk2_ardour/generic_pluginui.cc
@@ -41,6 +41,9 @@
#include "ardour/plugin.h"
#include "ardour/plugin_insert.h"
#include "ardour/session.h"
+#ifdef LV2_SUPPORT
+#include "ardour/lv2_plugin.h"
+#endif
#include "ardour_ui.h"
#include "prompter.h"
@@ -305,6 +308,34 @@ GenericPluginUI::build ()
}
}
+#ifdef LV2_SUPPORT
+ boost::shared_ptr<ARDOUR::LV2Plugin> lv2p = boost::dynamic_pointer_cast<LV2Plugin> (plugin);
+ if (lv2p) {
+ _fcb = (Gtk::FileChooserButton**) malloc(lv2p->patch_count() * sizeof(Gtk::FileChooserButton*));
+ for (uint32_t p = 0; p < lv2p->patch_count(); ++p) {
+ _fcb[p] = manage (new Gtk::FileChooserButton (Gtk::FILE_CHOOSER_ACTION_OPEN));
+ _fcb[p]->signal_file_set().connect (sigc::bind(sigc::mem_fun (*this, &GenericPluginUI::patch_set_file), p));
+ lv2p->PatchChanged.connect (*this, invalidator (*this), boost::bind (&GenericPluginUI::patch_changed, this, _1), gui_context());
+ // when user cancels file selection the FileChooserButton will display "None"
+ // TODO hack away around this..
+ if (lv2p->patch_val(p)) {
+ _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;
+ }
+ }
+#endif
+
// Iterate over the list of controls to find which adjacent controls
// are similar enough to be grouped together.
@@ -934,4 +965,20 @@ GenericPluginUI::output_update ()
}
}
+#ifdef LV2_SUPPORT
+void
+GenericPluginUI::patch_set_file (uint32_t p)
+{
+ boost::shared_ptr<ARDOUR::LV2Plugin> lv2p = boost::dynamic_pointer_cast<LV2Plugin> (plugin);
+ lv2p->patch_set(p, _fcb[p]->get_filename ().c_str());
+}
+
+void
+GenericPluginUI::patch_changed (uint32_t p)
+{
+ boost::shared_ptr<ARDOUR::LV2Plugin> lv2p = boost::dynamic_pointer_cast<LV2Plugin> (plugin);
+ _fcb[p]->set_filename(lv2p->patch_val(p));
+}
+
+#endif
diff --git a/gtk2_ardour/plugin_ui.h b/gtk2_ardour/plugin_ui.h
index fefa999618..e5a5b59930 100644
--- a/gtk2_ardour/plugin_ui.h
+++ b/gtk2_ardour/plugin_ui.h
@@ -278,6 +278,12 @@ class GenericPluginUI : public PlugUIBase, public Gtk::VBox
void print_parameter (char *buf, uint32_t len, uint32_t param);
bool integer_printer (char* buf, Gtk::Adjustment &, ControlUI *);
bool midinote_printer(char* buf, Gtk::Adjustment &, ControlUI *);
+
+#ifdef LV2_SUPPORT
+ void patch_set_file (uint32_t patch_idx);
+ void patch_changed (uint32_t patch_idx);
+ Gtk::FileChooserButton **_fcb;
+#endif
};
class PluginUIWindow : public ArdourWindow
diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h
index 7d080fa8e2..85fec0acb6 100644
--- a/libs/ardour/ardour/lv2_plugin.h
+++ b/libs/ardour/ardour/lv2_plugin.h
@@ -30,6 +30,10 @@
#include "ardour/worker.h"
#include "pbd/ringbuffer.h"
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif
+
typedef struct LV2_Evbuf_Impl LV2_Evbuf;
namespace ARDOUR {
@@ -142,6 +146,13 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
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_response(uint32_t size, const void* data);
@@ -152,6 +163,8 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
uint32_t atom_Path;
uint32_t atom_Sequence;
uint32_t atom_eventTransfer;
+ uint32_t atom_URID;
+ uint32_t atom_Blank;
uint32_t log_Error;
uint32_t log_Note;
uint32_t log_Warning;
@@ -164,6 +177,9 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
uint32_t time_beatsPerMinute;
uint32_t time_frame;
uint32_t time_speed;
+ uint32_t patch_Set;
+ uint32_t patch_property;
+ uint32_t patch_value;
};
static URIDs urids;
@@ -187,6 +203,13 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
double _next_cycle_speed; ///< Expected start frame of next run cycle
PBD::ID _insert_id;
+ uint32_t _patch_count;
+ 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,
void* user_data,
uint32_t* size,
@@ -200,7 +223,8 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
PORT_EVENT = 1 << 4, ///< Old event API event port
PORT_SEQUENCE = 1 << 5, ///< New atom API event port
PORT_MIDI = 1 << 6, ///< Event port understands MIDI
- PORT_POSITION = 1 << 7 ///< Event port understands position
+ PORT_POSITION = 1 << 7, ///< Event port understands position
+ PORT_PATCHMSG = 1 << 8 ///< Event port supports patch:Message
} PortFlag;
typedef unsigned PortFlags;
diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc
index fe214e9659..b32c4db087 100644
--- a/libs/ardour/lv2_plugin.cc
+++ b/libs/ardour/lv2_plugin.cc
@@ -66,6 +66,7 @@
#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/ext/patch/patch.h"
#ifdef HAVE_NEW_LV2
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
#include "lv2/lv2plug.in/ns/ext/options/options.h"
@@ -94,6 +95,8 @@ LV2Plugin::URIDs LV2Plugin::urids = {
_uri_map.uri_to_id(LV2_ATOM__Path),
_uri_map.uri_to_id(LV2_ATOM__Sequence),
_uri_map.uri_to_id(LV2_ATOM__eventTransfer),
+ _uri_map.uri_to_id(LV2_ATOM__URID),
+ _uri_map.uri_to_id(LV2_ATOM__Blank),
_uri_map.uri_to_id(LV2_LOG__Error),
_uri_map.uri_to_id(LV2_LOG__Note),
_uri_map.uri_to_id(LV2_LOG__Warning),
@@ -105,7 +108,10 @@ LV2Plugin::URIDs LV2Plugin::urids = {
_uri_map.uri_to_id(LV2_TIME__beatsPerBar),
_uri_map.uri_to_id(LV2_TIME__beatsPerMinute),
_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__Set),
+ _uri_map.uri_to_id(LV2_PATCH__property),
+ _uri_map.uri_to_id(LV2_PATCH__value)
};
class LV2World : boost::noncopyable {
@@ -146,6 +152,8 @@ public:
LilvNode* ui_externalkx;
LilvNode* units_unit;
LilvNode* units_midiNote;
+ LilvNode* patch_writable;
+ LilvNode* patch_Message;
private:
bool _bundle_checked;
@@ -254,6 +262,11 @@ LV2Plugin::LV2Plugin (AudioEngine& engine,
, _features(NULL)
, _worker(NULL)
, _insert_id("0")
+ , _patch_count(0)
+ , _patch_value_uri(NULL)
+ , _patch_value_key(NULL)
+ , _patch_value_cur(NULL)
+ , _patch_value_set(NULL)
{
init(c_plugin, rate);
}
@@ -265,6 +278,11 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other)
, _features(NULL)
, _worker(NULL)
, _insert_id(other._insert_id)
+ , _patch_count(0)
+ , _patch_value_uri(NULL)
+ , _patch_value_key(NULL)
+ , _patch_value_cur(NULL)
+ , _patch_value_set(NULL)
{
init(other._impl->plugin, other._sample_rate);
@@ -456,6 +474,9 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
if (lilv_nodes_contains(atom_supports, _world.time_Position)) {
flags |= PORT_POSITION;
}
+ if (lilv_nodes_contains(atom_supports, _world.patch_Message)) {
+ flags |= PORT_PATCHMSG;
+ }
}
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;
@@ -532,6 +553,25 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate)
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");
+ 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));
+
+ _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(property));
+ ++_patch_count;
+ }
+ 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);
if (lilv_uis_size(uis) > 0) {
#ifdef HAVE_SUIL
@@ -597,6 +637,13 @@ LV2Plugin::~LV2Plugin ()
free(_make_path_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 _from_ui;
delete _worker;
@@ -1556,6 +1603,48 @@ write_position(LV2_Atom_Forge* forge,
(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
LV2Plugin::connect_and_run(BufferSet& bufs,
ChanMapping in_map, ChanMapping out_map,
@@ -1687,6 +1776,24 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
_ev_buffers[port_index] = scratch_bufs.get_lv2_midi(
(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]);
} else {
continue; // Control port, leave buffer alone
@@ -1759,8 +1866,9 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
}
}
+
// Write messages to UI
- if (_to_ui && (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
+ if ((_to_ui || _patch_count > 0) && (flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_SEQUENCE))) {
LV2_Evbuf* buf = _ev_buffers[port_index];
for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(buf);
lv2_evbuf_is_valid(i);
@@ -1768,6 +1876,48 @@ LV2Plugin::connect_and_run(BufferSet& bufs,
uint32_t frames, subframes, type, size;
uint8_t* data;
lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data);
+
+ // intercept patch change messages
+ /* TODO this should eventually be done in the UI-thread
+ * 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));
+ if (atom->type == LV2Plugin::urids.atom_Blank) {
+ LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
+ if (obj->body.otype == LV2Plugin::urids.patch_Set) {
+ const LV2_Atom* property = NULL;
+ lv2_atom_object_get (obj, LV2Plugin::urids.patch_property, &property, 0);
+ if (property->type == LV2Plugin::urids.atom_URID) {
+ for (uint32_t pidx = 0; pidx < _patch_count; ++ pidx) {
+ if (((const LV2_Atom_URID*)property)->body != _uri_map.uri_to_id(_patch_value_uri[pidx])) {
+ continue;
+ }
+ /* Get value. */
+ const LV2_Atom* file_path = NULL;
+ lv2_atom_object_get(obj, LV2Plugin::urids.patch_value, &file_path, 0);
+ if (!file_path || file_path->type != LV2Plugin::urids.atom_Path) {
+ 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
+ }
+ }
+ }
+ }
+ }
+
+ if (!_to_ui) continue;
write_to_ui(port_index, urids.atom_eventTransfer,
size + sizeof(LV2_Atom),
data - sizeof(LV2_Atom));
@@ -1983,10 +2133,14 @@ LV2World::LV2World()
ui_externalkx = lilv_new_uri(world, "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget");
units_unit = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#unit");
units_midiNote = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#midiNote");
+ patch_writable = lilv_new_uri(world, LV2_PATCH__writable);
+ patch_Message = lilv_new_uri(world, LV2_PATCH__Message);
}
LV2World::~LV2World()
{
+ lilv_node_free(patch_Message);
+ lilv_node_free(patch_writable);
lilv_node_free(units_midiNote);
lilv_node_free(units_unit);
lilv_node_free(ui_externalkx);