13
0

prototype [LV2]patch-change support for generic plugin UIs.

This commit is contained in:
Robin Gareus 2014-10-31 03:26:47 +01:00
parent 1648d9cbc6
commit b8cea19b95
4 changed files with 234 additions and 3 deletions

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);