Compare commits

...

4 Commits

Author SHA1 Message Date
Robin Gareus 72e5ec2c5e
Prototype LV2 dialog-message feature extension 2021-10-07 04:47:47 +02:00
Robin Gareus 532fbc41cb
Implement LV2 request-value UI
* Handle cross-thread request from DSP context
* Allow calls without plugin GUI
* Implement boolean value requests, show dialog
2021-10-07 03:48:40 +02:00
Robin Gareus eadd98efec
Handle LV2 requestValue calls from DSP context 2021-10-07 03:48:19 +02:00
Robin Gareus 652f99260b
Consolidate plugin API to access parent insert
Previously this was special cased only for VST for callbacks
from the plugin (e.g. check pin connections), but it is generally
useful.
2021-10-07 03:47:38 +02:00
14 changed files with 238 additions and 63 deletions

View File

@ -89,6 +89,7 @@
#include "gtkmm2ext/application.h"
#include "gtkmm2ext/bindings.h"
#include "gtkmm2ext/doi.h"
#include "gtkmm2ext/gtk_ui.h"
#include "gtkmm2ext/utils.h"
#include "gtkmm2ext/window_title.h"
@ -108,6 +109,7 @@
#include "ardour/filename_extensions.h"
#include "ardour/filesystem_paths.h"
#include "ardour/ltc_file_reader.h"
#include "ardour/lv2_plugin.h"
#include "ardour/monitor_control.h"
#include "ardour/midi_track.h"
#include "ardour/port.h"
@ -174,6 +176,7 @@
#include "location_ui.h"
#include "lua_script_manager.h"
#include "luawindow.h"
#include "lv2_plugin_ui.h"
#include "main_clock.h"
#include "missing_file_dialog.h"
#include "missing_plugin_dialog.h"
@ -437,6 +440,11 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir)
ARDOUR::Session::Dialog.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::session_dialog, this, _1), gui_context());
#ifdef HAVE_LV2_1_17_2
ARDOUR::LV2Plugin::Request.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::lv2_plugin_request, this, _1, _2, _3, _4), gui_context());
LV2PluginUI::CatchDeletion.connect_same_thread (forever_connections, boost::bind (&delete_when_idle<LV2PluginUI>, _1));
#endif
/* handle pending state with a dialog (PROBLEM: needs to return a value and thus cannot be x-thread) */
ARDOUR::Session::AskAboutPendingState.connect_same_thread (forever_connections, boost::bind (&ARDOUR_UI::pending_state_dialog, this));

View File

@ -72,6 +72,9 @@
#include "gtkmm2ext/bindings.h"
#include "gtkmm2ext/visibility_tracker.h"
#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#include "ardour/ardour.h"
#include "ardour/types.h"
#include "ardour/utils.h"
@ -177,6 +180,7 @@ namespace ARDOUR {
class Route;
class RouteGroup;
class Location;
class PluginInsert;
class ProcessThread;
}
@ -236,6 +240,8 @@ public:
bool ask_about_loading_existing_session (const std::string& session_path);
int load_session_from_startup_fsm ();
void lv2_plugin_request (boost::weak_ptr<ARDOUR::PluginInsert>, LV2_URID, LV2_URID, LV2_Feature const* const*);
/// @return true if session was successfully unloaded.
int unload_session (bool hide_stuff = false);
void close_session();

View File

@ -40,6 +40,8 @@
#include "ardour/audioengine.h"
#include "ardour/automation_watch.h"
#include "ardour/control_protocol_manager.h"
#include "ardour/lv2_plugin.h"
#include "ardour/plugin_insert.h"
#include "ardour/profile.h"
#include "ardour/session.h"
@ -63,6 +65,7 @@
#include "location_ui.h"
#include "lua_script_manager.h"
#include "luawindow.h"
#include "lv2_plugin_ui.h"
#include "main_clock.h"
#include "meterbridge.h"
#include "meter_patterns.h"
@ -1034,6 +1037,25 @@ ARDOUR_UI::show_plugin_manager ()
tact->set_active();
}
void
ARDOUR_UI::lv2_plugin_request (boost::weak_ptr<PluginInsert> wi, LV2_URID key, LV2_URID type, LV2_Feature const* const* features)
{
#ifdef HAVE_LV2_1_17_2
boost::shared_ptr<PluginInsert> insert = wi.lock ();
if (!insert) {
return;
}
boost::shared_ptr<LV2Plugin> vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin());
if (!vp) {
return;
}
LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
if (LV2PluginUI::request_value (lpu, key, type, features) != LV2UI_REQUEST_VALUE_SUCCESS) {
delete lpu;
}
#endif
}
bool
ARDOUR_UI::timecode_button_press (GdkEventButton* ev)
{

View File

@ -25,17 +25,18 @@
#include <gtkmm/stock.h>
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "ardour/lv2_plugin.h"
#include "ardour/session.h"
#include "pbd/error.h"
#include "gtkmm2ext/utils.h"
#include "gui_thread.h"
#include "lv2_plugin_ui.h"
#include "timers.h"
#include "gtkmm2ext/utils.h"
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "ardour_message.h"
#include <lilv/lilv.h>
#include <suil/suil.h>
@ -48,6 +49,10 @@ using namespace PBD;
#define NS_UI "http://lv2plug.in/ns/extensions/ui#"
#ifdef HAVE_LV2_1_17_2
PBD::Signal1<void, LV2PluginUI*> LV2PluginUI::CatchDeletion;
#endif
static SuilHost* ui_host = NULL;
void
@ -121,24 +126,33 @@ LV2PluginUI::touch(void* controller,
}
}
#ifdef HAVE_LV2_1_17_2
void
LV2PluginUI::set_path_property (int response,
const ParameterDescriptor& desc,
Gtk::FileChooserDialog* widget)
LV2PluginUI::request_response (int response,
const ParameterDescriptor& desc,
Gtk::Widget* widget)
{
if (response == Gtk::RESPONSE_ACCEPT) {
plugin->set_property (desc.key, Variant (Variant::PATH, widget->get_filename()));
switch (desc.datatype) {
case Variant::PATH:
if (response == Gtk::RESPONSE_ACCEPT) {
set_path_property (desc.key, Variant (Variant::PATH, dynamic_cast<Gtk::FileChooserDialog*> (widget)->get_filename ()));
}
break;
case Variant::BOOL:
if (response != Gtk::BUTTONS_OK) {
set_path_property (desc.key, Variant (Variant::BOOL, response == Gtk::RESPONSE_YES));
}
break;
default:
break;
}
#if 0
widget->hide ();
delete_when_idle (widget);
#else
delete widget;
#endif
active_parameter_requests.erase (desc.key);
_active_parameter_requests.erase (desc.key);
CatchDeletion (this); /* EMIT SIGNAL */
}
#ifdef HAVE_LV2_1_17_2
LV2UI_Request_Value_Status
LV2PluginUI::request_value(void* handle,
LV2_URID key,
@ -150,39 +164,82 @@ LV2PluginUI::request_value(void* handle,
const ParameterDescriptor& desc (me->_lv2->get_property_descriptor(key));
if (desc.key == (uint32_t)-1) {
return LV2UI_REQUEST_VALUE_ERR_UNKNOWN;
} else if (desc.datatype != Variant::PATH) {
return LV2UI_REQUEST_VALUE_ERR_UNSUPPORTED;
} else if (me->active_parameter_requests.count (key)) {
} else if (me->_active_parameter_requests.count (key)) {
return LV2UI_REQUEST_VALUE_BUSY;
}
me->active_parameter_requests.insert (key);
Gtk::FileChooserDialog* lv2ui_file_dialog = new Gtk::FileChooserDialog(desc.label, FILE_CHOOSER_ACTION_OPEN);
Gtkmm2ext::add_volume_shortcuts (*lv2ui_file_dialog);
lv2ui_file_dialog->add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
lv2ui_file_dialog->add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);
lv2ui_file_dialog->set_default_response(Gtk::RESPONSE_ACCEPT);
me->_active_parameter_requests.insert (key);
/* this assumes announce_property_values() was called, or
* the plugin has previously sent a patch:Set */
const Variant& value = me->_lv2->get_property_value (desc.key);
if (value.type() == Variant::PATH) {
lv2ui_file_dialog->set_filename (value.get_path());
}
switch (desc.datatype) {
case Variant::PATH:
{
Gtk::FileChooserDialog* lv2ui_file_dialog = new Gtk::FileChooserDialog(desc.label, FILE_CHOOSER_ACTION_OPEN);
Gtkmm2ext::add_volume_shortcuts (*lv2ui_file_dialog);
lv2ui_file_dialog->add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
lv2ui_file_dialog->add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);
lv2ui_file_dialog->set_default_response(Gtk::RESPONSE_ACCEPT);
/* this assumes announce_property_values() was called, or
* the plugin has previously sent a patch:Set */
const Variant& value = me->_lv2->get_property_value (desc.key);
if (value.type() == Variant::PATH) {
lv2ui_file_dialog->set_filename (value.get_path());
}
#if 0 // TODO mime-type, file-extension filter, get from LV2 Parameter Property
FileFilter file_ext_filter;
file_ext_filter.add_pattern ("*.foo");
file_ext_filter.set_name ("Foo File");
lv2ui_file_dialog.add_filter (file_ext_filter);
FileFilter file_ext_filter;
file_ext_filter.add_pattern ("*.foo");
file_ext_filter.set_name ("Foo File");
lv2ui_file_dialog.add_filter (file_ext_filter);
#endif
lv2ui_file_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*me, &LV2PluginUI::request_response), desc, lv2ui_file_dialog));
lv2ui_file_dialog->present();
}
break;
case Variant::BOOL:
{
std::string message = desc.label;
Gtk::MessageType type = Gtk::MESSAGE_QUESTION;
Gtk::ButtonsType buttons = Gtk::BUTTONS_YES_NO;
#ifdef LV2_EXTENDED
for (int i = 0; features && features[i]; ++i) {
if (!strcmp (features[i]->URI, LV2_DIALOGMESSAGE_URI)) {
LV2_Dialog_Message* mt = NULL;
mt = (LV2_Dialog_Message*) features[i]->data;
if (mt->msg) {
message = mt->msg;
mt->free_msg (mt->msg);
}
if (!mt->requires_return) {
type = Gtk::MESSAGE_INFO;
buttons = Gtk::BUTTONS_OK;
}
}
}
#endif
ArdourMessageDialog* msg = new ArdourMessageDialog (message, false, type, buttons, false);
msg->signal_response().connect (sigc::bind (sigc::mem_fun (*me, &LV2PluginUI::request_response), desc, msg));
msg->present();
}
break;
default:
me->_active_parameter_requests.erase (key);
return LV2UI_REQUEST_VALUE_ERR_UNSUPPORTED;
}
lv2ui_file_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*me, &LV2PluginUI::set_path_property), desc, lv2ui_file_dialog));
lv2ui_file_dialog->present();
return LV2UI_REQUEST_VALUE_SUCCESS;
}
#endif
void
LV2PluginUI::set_path_property (const uint32_t key, ARDOUR::Variant const& val)
{
plugin->set_property (key, val);
}
void
LV2PluginUI::update_timeout()
{

View File

@ -66,6 +66,16 @@ public:
int package (Gtk::Window&);
void grab_focus ();
#ifdef HAVE_LV2_1_17_2
static LV2UI_Request_Value_Status
request_value(void* handle,
LV2_URID key,
LV2_URID type,
const LV2_Feature* const* features);
static PBD::Signal1<void, LV2PluginUI*> CatchDeletion;
#endif
private:
void control_changed (uint32_t);
@ -88,6 +98,7 @@ private:
#ifdef HAVE_LV2_1_17_2
LV2UI_Request_Value _lv2ui_request_value;
LV2_Feature _lv2ui_request_feature;
std::set<uint32_t> _active_parameter_requests;
#endif
struct lv2_external_ui* _external_ui_ptr;
LV2_Feature _parent_feature;
@ -115,18 +126,14 @@ private:
uint32_t port_index,
bool grabbed);
void set_path_property (const uint32_t key, ARDOUR::Variant const&);
#ifdef HAVE_LV2_1_17_2
static LV2UI_Request_Value_Status
request_value(void* handle,
LV2_URID key,
LV2_URID type,
const LV2_Feature* const* features);
void request_response (int,
ARDOUR::ParameterDescriptor const&,
Gtk::Widget*);
#endif
void set_path_property (int,
const ARDOUR::ParameterDescriptor&,
Gtk::FileChooserDialog*);
std::set<uint32_t> active_parameter_requests;
void update_timeout();

View File

@ -276,6 +276,31 @@ typedef struct {
void (*notify)(LV2_BankPatch_Handle handle, uint8_t channel, uint32_t bank, uint8_t pgm);
} LV2_BankPatch;
/**
@}
*/
/**
@defgroup LV2_Dialog_Message
LV2 extension to allow a plugin to ask a host to display
a dedicated dialog message when requesting a value
@{
*/
#define LV2_DIALOGMESSAGE_URI "http://ardour.org/lv2/dialog_message"
/** a LV2 Feature provided by the Plugin, passed as optional feature to LV2_UI__requestValue */
typedef struct {
void (*free_msg)(char const* msg);
char const* msg;
bool requires_return;
} LV2_Dialog_Message;
/**
@}
*/

View File

@ -30,6 +30,8 @@
#include <vector>
#include <boost/enable_shared_from_this.hpp>
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "ardour/plugin.h"
#include "ardour/plugin_scan_result.h"
#include "ardour/uri_map.h"
@ -172,6 +174,8 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
Variant get_property_value (uint32_t) const;
void announce_property_values();
static PBD::Signal4<void, boost::weak_ptr<PluginInsert>, LV2_URID, LV2_URID, LV2_Feature const* const*> Request;
/* LV2 Option Options */
static void set_global_ui_background_color (uint32_t c) {
_ui_background_color = c;
@ -339,6 +343,17 @@ class LIBARDOUR_API LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee
LV2_Feature _bankpatch_feature;
#endif
#ifdef HAVE_LV2_1_17_2
LV2UI_Request_Value _lv2ui_request_value;
LV2_Feature _lv2ui_request_feature;
static LV2UI_Request_Value_Status
request_value(void* handle,
LV2_URID key,
LV2_URID type,
const LV2_Feature* const* features);
#endif
// Options passed to plugin
int32_t _seq_size;
static int32_t _ui_style_flat;

View File

@ -24,6 +24,7 @@
#define __ardour_plugin_h__
#include <boost/shared_ptr.hpp>
#include <set>
#include <string>
@ -83,6 +84,14 @@ public:
virtual void set_insert_id (PBD::ID id) {}
virtual void set_state_dir (const std::string& d = "") {}
void set_insert (PluginInsert* pi, uint32_t num) {
_pi = pi;
_num = num;
}
PluginInsert* plugin_insert () const { return _pi; }
uint32_t plugin_number () const { return _num; }
virtual std::string unique_id () const = 0;
virtual const char* label () const = 0;
virtual const char* name () const = 0;
@ -410,6 +419,9 @@ private:
void invalidate_preset_cache (std::string const&, Plugin*, bool);
void resolve_midi ();
PluginInsert* _pi;
uint32_t _num;
};
struct PluginPreset {

View File

@ -28,6 +28,7 @@
#include <string>
#include <boost/weak_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "pbd/stack_allocator.h"
#include "pbd/timing.h"
@ -56,7 +57,7 @@ class Plugin;
/** Plugin inserts: send data through a plugin
*/
class LIBARDOUR_API PluginInsert : public Processor
class LIBARDOUR_API PluginInsert : public Processor, public boost::enable_shared_from_this <PluginInsert>
{
public:
PluginInsert (Session&, Temporal::TimeDomain td, boost::shared_ptr<Plugin> = boost::shared_ptr<Plugin>());
@ -64,6 +65,10 @@ public:
void drop_references ();
boost::weak_ptr<PluginInsert> weak_ptr () {
return shared_from_this();
}
static const std::string port_automation_node_name;
int set_state(const XMLNode&, int version);

View File

@ -98,9 +98,6 @@ public:
int first_user_preset_index () const;
void set_insert (PluginInsert* pi, uint32_t num) { _pi = pi; _num = num; }
PluginInsert* plugin_insert () const { return _pi; }
uint32_t plugin_number () const { return _num; }
VstTimeInfo* timeinfo () { return &_timeInfo; }
samplepos_t transport_sample () const { return _transport_sample; }
float transport_speed () const { return _transport_speed; }
@ -125,8 +122,6 @@ protected:
VSTHandle* _handle;
VSTState* _state;
AEffect* _plugin;
PluginInsert* _pi;
uint32_t _num;
MidiBuffer* _midi_out_buf;
VstTimeInfo _timeInfo;

View File

@ -64,6 +64,7 @@
#include "ardour/debug.h"
#include "ardour/lv2_plugin.h"
#include "ardour/midi_patch_manager.h"
#include "ardour/plugin_insert.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/types.h"
@ -138,6 +139,8 @@ uint32_t LV2Plugin::_ui_contrasting_color = 0x33ff33ff; // RGBA
float LV2Plugin::_ui_scale_factor = 1.0;
unsigned long LV2Plugin::_ui_transient_win_id = 0;
PBD::Signal4<void, boost::weak_ptr<PluginInsert>, LV2_URID, LV2_URID, LV2_Feature const* const*> LV2Plugin::Request;
class LV2World : boost::noncopyable {
public:
LV2World ();
@ -271,6 +274,19 @@ set_port_value(const char* port_symbol,
}
}
#ifdef HAVE_LV2_1_17_2
LV2UI_Request_Value_Status
LV2Plugin::request_value(void* handle,
LV2_URID key,
LV2_URID type,
const LV2_Feature* const* features)
{
LV2Plugin* plugin = (LV2Plugin*)handle;
Request (plugin->plugin_insert ()->weak_ptr (), key, type, features); /* EMIT SIGNAL */
return LV2UI_REQUEST_VALUE_SUCCESS;
}
#endif
#ifdef LV2_EXTENDED
/* inline display extension */
void
@ -495,7 +511,7 @@ LV2Plugin::init(const void* c_plugin, samplecnt_t rate)
lilv_node_free(state_uri);
lilv_node_free(state_iface_uri);
_features = (LV2_Feature**)calloc(14, sizeof(LV2_Feature*));
_features = (LV2_Feature**)calloc(15, sizeof(LV2_Feature*));
_features[0] = &_instance_access_feature;
_features[1] = &_data_access_feature;
_features[2] = &_make_path_feature;
@ -509,6 +525,14 @@ LV2Plugin::init(const void* c_plugin, samplecnt_t rate)
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_17_2
_lv2ui_request_value.handle = this;
_lv2ui_request_value.request = LV2Plugin::request_value;
_lv2ui_request_feature.URI = LV2_UI__requestValue;
_lv2ui_request_feature.data = &_lv2ui_request_value;
_features[n_features++] = &_lv2ui_request_feature;
#endif
#ifdef LV2_EXTENDED
_impl->queue_draw = (LV2_Inline_Display*)
malloc (sizeof(LV2_Inline_Display));
@ -3623,6 +3647,9 @@ LV2PluginInfo::discover (boost::function <void (std::string const&, PluginScanLo
if (!strcmp (rf, LV2_BUF_SIZE__boundedBlockLength)) { ok = true; }
if (!strcmp (rf, "http://lv2plug.in/ns/ext/buf-size#coarseBlockLength" /*LV2_BUF_SIZE__coarseBlockLength*/)) { ok = true; }
if (!strcmp (rf, LV2_OPTIONS__options)) { ok = true; }
#ifdef HAVE_LV2_1_17_2
if (!strcmp (rf, LV2_UI__requestValue)) { ok = true; }
#endif
#ifdef LV2_EXTENDED
if (!strcmp (rf, LV2_INLINEDISPLAY__queue_draw)) { ok = true; }
if (!strcmp (rf, LV2_MIDNAM__update)) { ok = true; }

View File

@ -95,6 +95,8 @@ Plugin::Plugin (AudioEngine& e, Session& s)
, _have_pending_stop_events (false)
, _parameter_changed_since_last_preset (false)
, _immediate_events(6096) // FIXME: size?
, _pi (0)
, _num (0)
{
_pending_stop_events.ensure_buffers (DataType::MIDI, 1, 4096);
PresetsChanged.connect_same_thread(_preset_connection, boost::bind (&Plugin::invalidate_preset_cache, this, _1, _2, _3));
@ -114,6 +116,8 @@ Plugin::Plugin (const Plugin& other)
, _last_preset (other._last_preset)
, _parameter_changed_since_last_preset (false)
, _immediate_events(6096) // FIXME: size?
, _pi (other._pi)
, _num (other._num)
{
_pending_stop_events.ensure_buffers (DataType::MIDI, 1, 4096);

View File

@ -3243,12 +3243,8 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
}
}
}
#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT)
boost::shared_ptr<VSTPlugin> vst = boost::dynamic_pointer_cast<VSTPlugin> (plugin);
if (vst) {
vst->set_insert (this, _plugins.size ());
}
#endif
plugin->set_insert (this, _plugins.size ());
_plugins.push_back (plugin);

View File

@ -52,8 +52,6 @@ VSTPlugin::VSTPlugin (AudioEngine& engine, Session& session, VSTHandle* handle)
, _handle (handle)
, _state (0)
, _plugin (0)
, _pi (0)
, _num (0)
, _transport_sample (0)
, _transport_speed (0.f)
, _eff_bypassed (false)
@ -66,8 +64,6 @@ VSTPlugin::VSTPlugin (const VSTPlugin& other)
, _handle (other._handle)
, _state (other._state)
, _plugin (other._plugin)
, _pi (other._pi)
, _num (other._num)
, _midi_out_buf (other._midi_out_buf)
, _transport_sample (0)
, _transport_speed (0.f)