Robin Gareus
c05ad01b2d
Don't ever clean up external UI plugins that use the LV2plug.in URI. only free the memory of the last instance when the plugin is removed or ardour closes.
498 lines
13 KiB
C++
498 lines
13 KiB
C++
/*
|
|
Copyright (C) 2008-2012 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.
|
|
*/
|
|
|
|
#include "ardour/lv2_plugin.h"
|
|
#include "ardour/session.h"
|
|
#include "pbd/error.h"
|
|
|
|
#include "ardour_ui.h"
|
|
#include "lv2_plugin_ui.h"
|
|
|
|
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
|
|
|
|
#include <lilv/lilv.h>
|
|
#include <suil/suil.h>
|
|
|
|
#include "i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace Gtk;
|
|
using namespace PBD;
|
|
|
|
#define NS_UI "http://lv2plug.in/ns/extensions/ui#"
|
|
|
|
static SuilHost* ui_host = NULL;
|
|
|
|
void
|
|
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;
|
|
}
|
|
|
|
boost::shared_ptr<AutomationControl> ac = me->_controllables[port_index];
|
|
if (ac) {
|
|
ac->set_value(*(const float*)buffer);
|
|
}
|
|
} else if (format == me->_lv2->urids.atom_eventTransfer) {
|
|
|
|
const int cnt = me->_pi->get_count();
|
|
for (int i=0; i < cnt; i++ ) {
|
|
boost::shared_ptr<LV2Plugin> lv2i = boost::dynamic_pointer_cast<LV2Plugin> (me->_pi->plugin(i));
|
|
lv2i->write_from_ui(port_index, format, buffer_size, (const uint8_t*)buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (me->_inst) {
|
|
suil_instance_port_event((SuilInstance*)me->_inst,
|
|
port_index, buffer_size, format, buffer);
|
|
}
|
|
}
|
|
|
|
uint32_t
|
|
LV2PluginUI::port_index(void* controller, const char* symbol)
|
|
{
|
|
return ((LV2PluginUI*)controller)->_lv2->port_index(symbol);
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::touch(void* controller,
|
|
uint32_t port_index,
|
|
bool grabbed)
|
|
{
|
|
LV2PluginUI* me = (LV2PluginUI*)controller;
|
|
if (port_index >= me->_controllables.size()) {
|
|
return;
|
|
}
|
|
|
|
ControllableRef control = me->_controllables[port_index];
|
|
if (grabbed) {
|
|
control->start_touch(control->session().transport_frame());
|
|
} else {
|
|
control->stop_touch(false, control->session().transport_frame());
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::update_timeout()
|
|
{
|
|
_lv2->emit_to_ui(this, &LV2PluginUI::write_to_ui);
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::on_external_ui_closed(void* controller)
|
|
{
|
|
//printf("LV2PluginUI::on_external_ui_closed\n");
|
|
LV2PluginUI* me = (LV2PluginUI*)controller;
|
|
if (me->_lv2->is_external_kx() /* called from plugin's UI_RUN() */) {
|
|
me->_screen_update_connection.disconnect();
|
|
// plugin is free()d in parent function - LV2PluginUI::output_update()
|
|
me->_external_ui_ptr = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::parameter_changed(uint32_t port_index, float val)
|
|
{
|
|
PlugUIBase::parameter_changed(port_index, val);
|
|
|
|
if (val != _values[port_index]) {
|
|
parameter_update(port_index, val);
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::parameter_update(uint32_t port_index, float val)
|
|
{
|
|
if (!_inst) {
|
|
return;
|
|
}
|
|
|
|
suil_instance_port_event((SuilInstance*)_inst, port_index, 4, 0, &val);
|
|
_values[port_index] = val;
|
|
}
|
|
|
|
bool
|
|
LV2PluginUI::start_updating(GdkEventAny*)
|
|
{
|
|
if (!_output_ports.empty()) {
|
|
_screen_update_connection.disconnect();
|
|
_screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect
|
|
(sigc::mem_fun(*this, &LV2PluginUI::output_update));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
LV2PluginUI::stop_updating(GdkEventAny*)
|
|
{
|
|
//cout << "stop_updating" << endl;
|
|
|
|
if (!_output_ports.empty()) {
|
|
_screen_update_connection.disconnect();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::output_update()
|
|
{
|
|
//cout << "output_update" << endl;
|
|
if (_external_ui_ptr) {
|
|
LV2_EXTERNAL_UI_RUN(_external_ui_ptr);
|
|
if (_lv2->is_external_kx() && !_external_ui_ptr) {
|
|
// clean up external UI if it closes itself via
|
|
// on_external_ui_closed() during run()
|
|
//printf("LV2PluginUI::output_update -- UI was closed\n");
|
|
_screen_update_connection.disconnect();
|
|
_message_update_connection.disconnect();
|
|
if (_inst) {
|
|
suil_instance_free((SuilInstance*)_inst);
|
|
}
|
|
_inst = NULL;
|
|
_external_ui_ptr = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* FIXME only works with control output ports (which is all we support now anyway) */
|
|
uint32_t nports = _output_ports.size();
|
|
for (uint32_t i = 0; i < nports; ++i) {
|
|
uint32_t index = _output_ports[i];
|
|
parameter_changed(index, _lv2->get_parameter(index));
|
|
}
|
|
|
|
}
|
|
|
|
LV2PluginUI::LV2PluginUI(boost::shared_ptr<PluginInsert> pi,
|
|
boost::shared_ptr<LV2Plugin> lv2p)
|
|
: PlugUIBase(pi)
|
|
, _pi(pi)
|
|
, _lv2(lv2p)
|
|
, _gui_widget(NULL)
|
|
, _values(NULL)
|
|
, _external_ui_ptr(NULL)
|
|
, _inst(NULL)
|
|
{
|
|
_ardour_buttons_box.set_spacing (6);
|
|
_ardour_buttons_box.set_border_width (6);
|
|
_ardour_buttons_box.pack_end (focus_button, false, false);
|
|
_ardour_buttons_box.pack_end (bypass_button, false, false, 10);
|
|
_ardour_buttons_box.pack_end (delete_button, false, false);
|
|
_ardour_buttons_box.pack_end (save_button, false, false);
|
|
_ardour_buttons_box.pack_end (add_button, false, false);
|
|
_ardour_buttons_box.pack_end (_preset_combo, false, false);
|
|
_ardour_buttons_box.pack_end (_preset_modified, false, false);
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::lv2ui_instantiate(const std::string& title)
|
|
{
|
|
bool is_external_ui = _lv2->is_external_ui();
|
|
LV2_Feature** features_src = const_cast<LV2_Feature**>(_lv2->features());
|
|
LV2_Feature** features = const_cast<LV2_Feature**>(_lv2->features());
|
|
size_t features_count = 0;
|
|
while (*features++) {
|
|
features_count++;
|
|
}
|
|
|
|
Gtk::Alignment* container = NULL;
|
|
if (is_external_ui) {
|
|
_external_ui_host.ui_closed = LV2PluginUI::on_external_ui_closed;
|
|
_external_ui_host.plugin_human_id = strdup(title.c_str());
|
|
|
|
_external_ui_feature.URI = LV2_EXTERNAL_UI_URI;
|
|
_external_ui_feature.data = &_external_ui_host;
|
|
|
|
_external_kxui_feature.URI = LV2_EXTERNAL_UI_KX__Host;
|
|
_external_kxui_feature.data = &_external_ui_host;
|
|
|
|
++features_count;
|
|
features = (LV2_Feature**)malloc(
|
|
sizeof(LV2_Feature*) * (features_count + 2));
|
|
for (size_t i = 0; i < features_count - 2; ++i) {
|
|
features[i] = features_src[i];
|
|
}
|
|
features[features_count - 2] = &_external_kxui_feature;
|
|
features[features_count - 1] = &_external_ui_feature;
|
|
features[features_count] = NULL;
|
|
} else {
|
|
if (_ardour_buttons_box.get_parent()) {
|
|
_ardour_buttons_box.get_parent()->remove(_ardour_buttons_box);
|
|
}
|
|
pack_start(_ardour_buttons_box, false, false);
|
|
_ardour_buttons_box.show_all();
|
|
|
|
_gui_widget = Gtk::manage((container = new Gtk::Alignment()));
|
|
pack_start(*_gui_widget, true, true);
|
|
_gui_widget->show();
|
|
|
|
_parent_feature.URI = LV2_UI__parent;
|
|
_parent_feature.data = _gui_widget->gobj();
|
|
|
|
++features_count;
|
|
features = (LV2_Feature**)malloc(
|
|
sizeof(LV2_Feature*) * (features_count + 1));
|
|
for (size_t i = 0; i < features_count - 1; ++i) {
|
|
features[i] = features_src[i];
|
|
}
|
|
features[features_count - 1] = &_parent_feature;
|
|
features[features_count] = NULL;
|
|
}
|
|
|
|
if (!ui_host) {
|
|
ui_host = suil_host_new(LV2PluginUI::write_from_ui,
|
|
LV2PluginUI::port_index,
|
|
NULL, NULL);
|
|
suil_host_set_touch_func(ui_host, LV2PluginUI::touch);
|
|
}
|
|
const char* container_type = (is_external_ui)
|
|
? NS_UI "external"
|
|
: NS_UI "GtkUI";
|
|
|
|
if (_lv2->has_message_output()) {
|
|
_lv2->enable_ui_emmission();
|
|
}
|
|
|
|
const LilvUI* ui = (const LilvUI*)_lv2->c_ui();
|
|
_inst = suil_instance_new(
|
|
ui_host,
|
|
this,
|
|
container_type,
|
|
_lv2->uri(),
|
|
lilv_node_as_uri(lilv_ui_get_uri(ui)),
|
|
lilv_node_as_uri((const LilvNode*)_lv2->c_ui_type()),
|
|
lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_bundle_uri(ui))),
|
|
lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_binary_uri(ui))),
|
|
features);
|
|
|
|
free(features);
|
|
|
|
#define GET_WIDGET(inst) suil_instance_get_widget((SuilInstance*)inst);
|
|
|
|
const uint32_t num_ports = _lv2->num_ports();
|
|
for (uint32_t i = 0; i < num_ports; ++i) {
|
|
if (_lv2->parameter_is_output(i)
|
|
&& _lv2->parameter_is_control(i)
|
|
&& is_update_wanted(i)) {
|
|
_output_ports.push_back(i);
|
|
}
|
|
}
|
|
|
|
_external_ui_ptr = NULL;
|
|
if (_inst) {
|
|
if (!is_external_ui) {
|
|
GtkWidget* c_widget = (GtkWidget*)GET_WIDGET(_inst);
|
|
if (!c_widget) {
|
|
error << _("failed to get LV2 UI widget") << endmsg;
|
|
suil_instance_free((SuilInstance*)_inst);
|
|
_inst = NULL;
|
|
return;
|
|
}
|
|
if (!container->get_child()) {
|
|
// Suil didn't add the UI to the container for us, so do it now
|
|
container->add(*Gtk::manage(Glib::wrap(c_widget)));
|
|
}
|
|
container->show_all();
|
|
} else {
|
|
_external_ui_ptr = (struct lv2_external_ui*)GET_WIDGET(_inst);
|
|
}
|
|
}
|
|
|
|
_values = new float[num_ports];
|
|
_controllables.resize(num_ports);
|
|
for (uint32_t i = 0; i < num_ports; ++i) {
|
|
bool ok;
|
|
uint32_t port = _lv2->nth_parameter(i, ok);
|
|
if (ok) {
|
|
_values[port] = _lv2->get_parameter(port);
|
|
_controllables[port] = boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (
|
|
insert->control(Evoral::Parameter(PluginAutomation, 0, port)));
|
|
|
|
if (_lv2->parameter_is_control(port) && _lv2->parameter_is_input(port)) {
|
|
parameter_update(port, _values[port]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_lv2->has_message_output()) {
|
|
_message_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect(
|
|
sigc::mem_fun(*this, &LV2PluginUI::update_timeout));
|
|
}
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::lv2ui_free()
|
|
{
|
|
stop_updating (0);
|
|
|
|
if (_gui_widget) {
|
|
remove (*_gui_widget);
|
|
_gui_widget = NULL;
|
|
}
|
|
|
|
if (_inst) {
|
|
suil_instance_free((SuilInstance*)_inst);
|
|
_inst = NULL;
|
|
}
|
|
}
|
|
|
|
LV2PluginUI::~LV2PluginUI ()
|
|
{
|
|
if (_values) {
|
|
delete[] _values;
|
|
}
|
|
|
|
_message_update_connection.disconnect();
|
|
_screen_update_connection.disconnect();
|
|
|
|
if (_external_ui_ptr && _lv2->is_external_kx()) {
|
|
LV2_EXTERNAL_UI_HIDE(_external_ui_ptr);
|
|
}
|
|
lv2ui_free();
|
|
_external_ui_ptr = NULL;
|
|
}
|
|
|
|
int
|
|
LV2PluginUI::get_preferred_height()
|
|
{
|
|
Gtk::Requisition r = size_request();
|
|
return r.height;
|
|
}
|
|
|
|
int
|
|
LV2PluginUI::get_preferred_width()
|
|
{
|
|
Gtk::Requisition r = size_request();
|
|
return r.width;
|
|
}
|
|
|
|
bool
|
|
LV2PluginUI::resizable()
|
|
{
|
|
return _lv2->ui_is_resizable();
|
|
}
|
|
|
|
int
|
|
LV2PluginUI::package(Gtk::Window& win)
|
|
{
|
|
if (_external_ui_ptr) {
|
|
_win_ptr = &win;
|
|
} else {
|
|
/* forward configure events to plugin window */
|
|
win.signal_configure_event().connect(
|
|
sigc::mem_fun(*this, &LV2PluginUI::configure_handler));
|
|
win.signal_map_event().connect(
|
|
sigc::mem_fun(*this, &LV2PluginUI::start_updating));
|
|
win.signal_unmap_event().connect(
|
|
sigc::mem_fun(*this, &LV2PluginUI::stop_updating));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
LV2PluginUI::configure_handler(GdkEventConfigure*)
|
|
{
|
|
std::cout << "CONFIGURE" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
LV2PluginUI::is_update_wanted(uint32_t /*index*/)
|
|
{
|
|
/* FIXME: use port notification properties
|
|
and/or new UI extension subscription methods
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
LV2PluginUI::on_window_show(const std::string& title)
|
|
{
|
|
//cout << "on_window_show - " << title << endl; flush(cout);
|
|
|
|
if (_lv2->is_external_ui()) {
|
|
if (_external_ui_ptr) {
|
|
_screen_update_connection.disconnect();
|
|
_message_update_connection.disconnect();
|
|
LV2_EXTERNAL_UI_SHOW(_external_ui_ptr);
|
|
_screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect
|
|
(sigc::mem_fun(*this, &LV2PluginUI::output_update));
|
|
if (_lv2->has_message_output()) {
|
|
_message_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect(
|
|
sigc::mem_fun(*this, &LV2PluginUI::update_timeout));
|
|
}
|
|
return false;
|
|
}
|
|
lv2ui_instantiate(title);
|
|
if (!_external_ui_ptr) {
|
|
return false;
|
|
}
|
|
|
|
_screen_update_connection.disconnect();
|
|
_message_update_connection.disconnect();
|
|
LV2_EXTERNAL_UI_SHOW(_external_ui_ptr);
|
|
_screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect
|
|
(sigc::mem_fun(*this, &LV2PluginUI::output_update));
|
|
if (_lv2->has_message_output()) {
|
|
_message_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect(
|
|
sigc::mem_fun(*this, &LV2PluginUI::update_timeout));
|
|
}
|
|
return false;
|
|
} else {
|
|
lv2ui_instantiate("gtk2gui");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
LV2PluginUI::on_window_hide()
|
|
{
|
|
//printf("LV2PluginUI::on_window_hide\n");
|
|
_message_update_connection.disconnect();
|
|
|
|
if (_lv2->is_external_ui()) {
|
|
if (!_external_ui_ptr) { return; }
|
|
if (!_lv2->is_external_kx()) { return ; }
|
|
_screen_update_connection.disconnect();
|
|
LV2_EXTERNAL_UI_HIDE(_external_ui_ptr);
|
|
_external_ui_ptr = NULL;
|
|
suil_instance_free((SuilInstance*)_inst);
|
|
_inst = NULL;
|
|
} else {
|
|
lv2ui_free();
|
|
}
|
|
}
|