From 4d7810dee89b36107b61f4124fd5ce3908abd705 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 25 Feb 2012 08:43:23 +0000 Subject: [PATCH] Full round-trip message communication between LV2 UIs and plugins. Still a little bit rough around the edges, but it works. This can be tested with the eg-sampler plugin from LV2 svn (whose UI can load different samples). git-svn-id: svn://localhost/ardour2/branches/3.0@11519 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/lv2_plugin_ui.cc | 49 ++++++--- gtk2_ardour/lv2_plugin_ui.h | 19 ++-- libs/ardour/ardour/lv2_plugin.h | 40 ++++++- libs/ardour/lv2_plugin.cc | 186 ++++++++++++++++++++++++++++---- 4 files changed, 252 insertions(+), 42 deletions(-) diff --git a/gtk2_ardour/lv2_plugin_ui.cc b/gtk2_ardour/lv2_plugin_ui.cc index 751985871d..70fbf448c6 100644 --- a/gtk2_ardour/lv2_plugin_ui.cc +++ b/gtk2_ardour/lv2_plugin_ui.cc @@ -40,23 +40,45 @@ using namespace PBD; static SuilHost* ui_host = NULL; void -LV2PluginUI::lv2_ui_write(void* controller, - uint32_t port_index, - uint32_t /*buffer_size*/, - uint32_t /*format*/, - const void* buffer) +LV2PluginUI::write_from_ui(void* controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) { LV2PluginUI* me = (LV2PluginUI*)controller; + if (format == 0) { + if (port_index >= me->_controllables.size()) { + return; + } - if (port_index >= me->_controllables.size()) { - return; + boost::shared_ptr ac = me->_controllables[port_index]; + if (ac) { + ac->set_value(*(float*)buffer); + } + } else if (format == me->_lv2->atom_eventTransfer()) { + me->_lv2->write_from_ui(port_index, format, buffer_size, (uint8_t*)buffer); } +} - boost::shared_ptr ac = me->_controllables[port_index]; +void +LV2PluginUI::write_to_ui(void* controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) +{ + LV2PluginUI* me = (LV2PluginUI*)controller; + fprintf(stderr, "MESSAGE FROM PLUGIN %u BYTES\n", buffer_size); + suil_instance_port_event((SuilInstance*)me->_inst, + port_index, buffer_size, format, buffer); +} - if (ac) { - ac->set_value(*(float*)buffer); - } +bool +LV2PluginUI::update_timeout() +{ + _lv2->emit_to_ui(this, &LV2PluginUI::write_to_ui); + return true; } void @@ -173,7 +195,7 @@ LV2PluginUI::lv2ui_instantiate(const std::string& title) } if (!ui_host) { - ui_host = suil_host_new(LV2PluginUI::lv2_ui_write, NULL, NULL, NULL); + ui_host = suil_host_new(LV2PluginUI::write_from_ui, NULL, NULL, NULL); } const char* container_type = (is_external_ui) ? NS_UI "external" @@ -245,6 +267,9 @@ LV2PluginUI::lv2ui_instantiate(const std::string& title) } } } + + Glib::signal_timeout().connect( + sigc::mem_fun(*this, &LV2PluginUI::update_timeout), 500); } void diff --git a/gtk2_ardour/lv2_plugin_ui.h b/gtk2_ardour/lv2_plugin_ui.h index b9d644081c..8500312856 100644 --- a/gtk2_ardour/lv2_plugin_ui.h +++ b/gtk2_ardour/lv2_plugin_ui.h @@ -79,12 +79,19 @@ class LV2PluginUI : public PlugUIBase, public Gtk::VBox void* _inst; - static void lv2_ui_write( - void* controller, - uint32_t port_index, - uint32_t buffer_size, - uint32_t format, - const void* buffer); + static void write_from_ui(void* controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer); + + static void write_to_ui(void* controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer); + + bool update_timeout(); void lv2ui_instantiate(const std::string& title); void lv2ui_free(); diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h index 77ad8ead24..65b63269e1 100644 --- a/libs/ardour/ardour/lv2_plugin.h +++ b/libs/ardour/ardour/lv2_plugin.h @@ -26,6 +26,7 @@ #include "ardour/plugin.h" #include "ardour/uri_map.h" +#include "pbd/ringbuffer.h" namespace ARDOUR { @@ -113,6 +114,20 @@ class LV2Plugin : public ARDOUR::Plugin bool has_editor () const; + uint32_t atom_eventTransfer() const; + + void write_from_ui(uint32_t index, uint32_t protocol, uint32_t size, uint8_t* body); + + typedef void UIMessageSink(void* controller, + uint32_t index, + uint32_t size, + uint32_t format, + const void* buffer); + + void emit_to_ui(void* controller, UIMessageSink sink); + + static URIMap _uri_map; + private: struct Impl; Impl* _impl; @@ -122,6 +137,7 @@ class LV2Plugin : public ARDOUR::Plugin float* _control_data; float* _shadow_data; float* _defaults; + LV2_Evbuf** _ev_buffers; float* _latency_control_port; PBD::ID _insert_id; @@ -139,6 +155,28 @@ class LV2Plugin : public ARDOUR::Plugin std::vector _port_flags; std::map _port_indices; + /// Message send to/from UI via ports + struct UIMessage { + uint32_t index; + uint32_t protocol; + uint32_t size; + }; + + void write_to_ui(uint32_t index, + uint32_t protocol, + uint32_t size, + uint8_t* body); + + void write_to(RingBuffer* dest, + uint32_t index, + uint32_t protocol, + uint32_t size, + uint8_t* body); + + // Created on demand so the space is only consumed if necessary + RingBuffer* _to_ui; + RingBuffer* _from_ui; + typedef struct { const void* (*extension_data) (const char* uri); } LV2_DataAccess; @@ -153,10 +191,10 @@ class LV2Plugin : public ARDOUR::Plugin bool _was_activated; bool _has_state_interface; - static URIMap _uri_map; static uint32_t _midi_event_type_ev; static uint32_t _midi_event_type; static uint32_t _sequence_type; + static uint32_t _event_transfer_type; static uint32_t _state_path_type; const std::string plugin_dir () const; diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 3ff3d06337..2907c2f904 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -76,6 +76,8 @@ uint32_t LV2Plugin::_midi_event_type = _uri_map.uri_to_id( "http://lv2plug.in/ns/ext/midi#MidiEvent"); uint32_t LV2Plugin::_sequence_type = _uri_map.uri_to_id( NULL, LV2_ATOM__Sequence); +uint32_t LV2Plugin::_event_transfer_type = _uri_map.uri_to_id( + NULL, LV2_ATOM__eventTransfer); uint32_t LV2Plugin::_state_path_type = _uri_map.uri_to_id( NULL, LV2_STATE_PATH_URI); @@ -89,6 +91,7 @@ public: LilvNode* atom_MessagePort; LilvNode* atom_Sequence; LilvNode* atom_bufferType; + LilvNode* atom_eventTransfer; LilvNode* ev_EventPort; LilvNode* ext_logarithmic; LilvNode* lv2_AudioPort; @@ -157,8 +160,11 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) _impl->plugin = (LilvPlugin*)c_plugin; _impl->ui = NULL; _impl->ui_type = NULL; + _to_ui = NULL; + _from_ui = NULL; _control_data = 0; _shadow_data = 0; + _ev_buffers = 0; _latency_control_port = 0; _state_version = 0; _was_activated = false; @@ -265,6 +271,8 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) _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); for (uint32_t i = 0; i < num_ports; ++i) { const LilvPort* port = lilv_plugin_get_port_by_index(plugin, i); @@ -354,8 +362,12 @@ LV2Plugin::~LV2Plugin () lilv_node_free(_impl->name); lilv_node_free(_impl->author); + delete _to_ui; + delete _from_ui; + delete [] _control_data; delete [] _shadow_data; + delete [] _ev_buffers; } bool @@ -751,6 +763,85 @@ LV2Plugin::has_editor() const return _impl->ui != NULL; } +uint32_t +LV2Plugin::atom_eventTransfer() const +{ + return _event_transfer_type; +} + +void +LV2Plugin::write_to(RingBuffer* dest, + uint32_t index, + uint32_t protocol, + uint32_t size, + uint8_t* body) +{ + const uint32_t buf_size = sizeof(UIMessage) + size; + uint8_t buf[buf_size]; + + UIMessage* msg = (UIMessage*)buf; + msg->index = index; + msg->protocol = protocol; + msg->size = size; + memcpy(msg + 1, body, size); + + if (dest->write(buf, buf_size) != buf_size) { + error << "Error writing to UI=>Plugin RingBuffer" << endmsg; + } +} + +void +LV2Plugin::write_from_ui(uint32_t index, + uint32_t protocol, + uint32_t size, + uint8_t* body) +{ + if (!_from_ui) { + _from_ui = new RingBuffer(4096); + } + + write_to(_from_ui, index, protocol, size, body); +} + +void +LV2Plugin::write_to_ui(uint32_t index, + uint32_t protocol, + uint32_t size, + uint8_t* body) +{ + if (!_to_ui) { + _to_ui = new RingBuffer(4096); + } + + write_to(_to_ui, index, protocol, size, body); +} + +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 << "Error reading from Plugin=>UI RingBuffer" << endmsg; + break; + } + uint8_t body[msg.size]; + if (_to_ui->read(body, msg.size) != msg.size) { + error << "Error reading from Plugin=>UI RingBuffer" << endmsg; + break; + } + + sink(controller, msg.index, msg.size, msg.protocol, body); + + read_space -= sizeof(msg) + msg.size; + } +} + void LV2Plugin::set_insert_info(const PluginInsert* insert) { @@ -994,14 +1085,16 @@ LV2Plugin::connect_and_run(BufferSet& bufs, const uint32_t atom_type = (flags & PORT_MESSAGE) ? _sequence_type : 0; if (flags & PORT_INPUT) { index = in_map.get(DataType::MIDI, midi_in_index++, &valid); - buf = (valid && bufs.count().n_midi() > index) - ? lv2_evbuf_get_buffer(bufs.get_lv2_midi(true, index, atom_type)) - : lv2_evbuf_get_buffer(silent_bufs.get_lv2_midi(true, 0, atom_type)); + _ev_buffers[port_index] = (valid && bufs.count().n_midi() > index) + ? bufs.get_lv2_midi(true, index, atom_type) + : silent_bufs.get_lv2_midi(true, 0, atom_type); + buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]); } else { index = out_map.get(DataType::MIDI, midi_out_index++, &valid); - buf = (valid && bufs.count().n_midi() > index) - ? lv2_evbuf_get_buffer(bufs.get_lv2_midi(false, index, atom_type)) - : lv2_evbuf_get_buffer(scratch_bufs.get_lv2_midi(false, 0, atom_type)); + _ev_buffers[port_index] = (valid && bufs.count().n_midi() > index) + ? bufs.get_lv2_midi(false, index, atom_type) + : scratch_bufs.get_lv2_midi(false, 0, atom_type); + buf = lv2_evbuf_get_buffer(_ev_buffers[port_index]); } } else { continue; // Control port, leave buffer alone @@ -1009,17 +1102,62 @@ LV2Plugin::connect_and_run(BufferSet& bufs, 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 << "Error reading from UI=>Plugin RingBuffer" << endmsg; + break; + } + uint8_t body[msg.size]; + if (_from_ui->read(body, msg.size) != msg.size) { + error << "Error reading from UI=>Plugin RingBuffer" << endmsg; + break; + } + if (msg.protocol == _event_transfer_type) { + LV2_Evbuf* buf = _ev_buffers[msg.index]; + LV2_Evbuf_Iterator i = lv2_evbuf_end(buf); + const LV2_Atom* const atom = (const LV2_Atom*)body; + lv2_evbuf_write(&i, nframes, 0, atom->type, atom->size, + (const uint8_t*)LV2_ATOM_BODY(atom)); + } else { + error << "Received unknown message type from UI" << 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) { - if (parameter_is_event(port_index) && parameter_is_output(port_index)) { + PortFlags flags = _port_flags[port_index]; + + // Flush MIDI (write back to Ardour MIDI buffers) + if ((flags & PORT_OUTPUT) && (flags & (PORT_EVENT|PORT_MESSAGE))) { 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 ((flags & PORT_OUTPUT) && (flags & PORT_MESSAGE)) { + 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 frames, subframes, type, size; + uint8_t* data; + lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data); + write_to_ui(port_index, _event_transfer_type, + size + sizeof(LV2_Atom), + data - sizeof(LV2_Atom)); + } + } } cycles_t now = get_cycles(); @@ -1160,22 +1298,23 @@ LV2World::LV2World() : world(lilv_world_new()) { lilv_world_load_all(world); - atom_MessagePort = lilv_new_uri(world, LV2_ATOM__MessagePort); - atom_Sequence = lilv_new_uri(world, LV2_ATOM__Sequence); - atom_bufferType = lilv_new_uri(world, LV2_ATOM__bufferType); - ev_EventPort = lilv_new_uri(world, LILV_URI_EVENT_PORT); - ext_logarithmic = lilv_new_uri(world, "http://lv2plug.in/ns/dev/extportinfo#logarithmic"); - 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, LILV_NS_LV2 "inPlaceBroken"); - lv2_integer = lilv_new_uri(world, LILV_NS_LV2 "integer"); - lv2_sampleRate = lilv_new_uri(world, LILV_NS_LV2 "sampleRate"); - lv2_toggled = lilv_new_uri(world, LILV_NS_LV2 "toggled"); - midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT); - ui_GtkUI = lilv_new_uri(world, NS_UI "GtkUI"); - ui_external = lilv_new_uri(world, NS_UI "external"); + atom_MessagePort = lilv_new_uri(world, LV2_ATOM__MessagePort); + atom_Sequence = lilv_new_uri(world, LV2_ATOM__Sequence); + atom_bufferType = lilv_new_uri(world, LV2_ATOM__bufferType); + 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, "http://lv2plug.in/ns/dev/extportinfo#logarithmic"); + 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, LILV_NS_LV2 "inPlaceBroken"); + lv2_integer = lilv_new_uri(world, LILV_NS_LV2 "integer"); + lv2_sampleRate = lilv_new_uri(world, LILV_NS_LV2 "sampleRate"); + lv2_toggled = lilv_new_uri(world, LILV_NS_LV2 "toggled"); + midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT); + ui_GtkUI = lilv_new_uri(world, NS_UI "GtkUI"); + ui_external = lilv_new_uri(world, NS_UI "external"); } LV2World::~LV2World() @@ -1193,6 +1332,7 @@ LV2World::~LV2World() lilv_node_free(lv2_AudioPort); lilv_node_free(ext_logarithmic); lilv_node_free(ev_EventPort); + lilv_node_free(atom_eventTransfer); lilv_node_free(atom_bufferType); lilv_node_free(atom_Sequence); lilv_node_free(atom_MessagePort); -- 2.30.2