ardour/gtk2_ardour/ladspa_pluginui.cc
David Robillard 24ccaac67e Further automation refactoring - bring in the concept of Controllable, work towards
making automation + GUI + play/write/touch generic and easily reusable.
Added bar controller to automation track controls (mostly relevant for MIDI CC, but added for gain and pan too Just Because).
Fixed glaring "redirect" list errors.
Fix plugin controls/automation loading.


git-svn-id: svn://localhost/ardour2/trunk@2080 d708f5d6-7413-0410-9779-e7cbd77b26cf
2007-06-29 04:02:58 +00:00

765 lines
21 KiB
C++

/*
Copyright (C) 2000 Paul Davis
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 <climits>
#include <cerrno>
#include <cmath>
#include <string>
#include <pbd/stl_delete.h>
#include <pbd/xml++.h>
#include <pbd/failed_constructor.h>
#include <gtkmm2ext/click_box.h>
#include <gtkmm2ext/fastmeter.h>
#include <gtkmm2ext/barcontroller.h>
#include <gtkmm2ext/utils.h>
#include <gtkmm2ext/doi.h>
#include <gtkmm2ext/slider_controller.h>
#include <midi++/manager.h>
#include <ardour/plugin.h>
#include <ardour/plugin_insert.h>
#include <ardour/ladspa_plugin.h>
#include <lrdf.h>
#include "ardour_ui.h"
#include "prompter.h"
#include "plugin_ui.h"
#include "utils.h"
#include "gui_thread.h"
#include "i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace PBD;
using namespace Gtkmm2ext;
using namespace Gtk;
using namespace sigc;
LadspaPluginUI::LadspaPluginUI (boost::shared_ptr<PluginInsert> pi, nframes64_t sample_rate, nframes64_t period_size, bool scrollable)
: PlugUIBase (pi, sample_rate, period_size),
button_table (initial_button_rows, initial_button_cols),
output_table (initial_output_rows, initial_output_cols),
hAdjustment(0.0, 0.0, 0.0),
vAdjustment(0.0, 0.0, 0.0),
scroller_view(hAdjustment, vAdjustment),
automation_menu (0),
is_scrollable(scrollable)
{
set_name ("PluginEditor");
set_border_width (10);
set_homogeneous (false);
settings_box.set_homogeneous (false);
HBox* constraint_hbox = manage (new HBox);
HBox* smaller_hbox = manage (new HBox);
Label* combo_label = manage (new Label (_("<span size=\"large\">Presets</span>")));
combo_label->set_use_markup (true);
Label* latency_label = manage (new Label (_("<span size=\"large\">Latency</span>")));
latency_label->set_use_markup (true);
smaller_hbox->pack_start (*latency_label, false, false, 10);
smaller_hbox->pack_start (latency_gui, false, false, 10);
smaller_hbox->pack_start (combo, false, false);
smaller_hbox->pack_start (save_button, false, false);
constraint_hbox->set_spacing (5);
constraint_hbox->pack_start (*smaller_hbox, true, false);
constraint_hbox->pack_end (bypass_button, false, false);
settings_box.pack_end (*constraint_hbox, false, false);
pack_start (settings_box, false, false);
if ( is_scrollable ) {
scroller.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
scroller.set_name ("PluginEditor");
scroller_view.set_name("PluginEditor");
scroller_view.add (hpacker);
scroller.add (scroller_view);
pack_start (scroller, true, true);
}
else {
pack_start (hpacker, false, false);
}
pi->ActiveChanged.connect (bind(mem_fun(*this, &LadspaPluginUI::processor_active_changed),
boost::weak_ptr<Processor>(pi)));
bypass_button.set_active (!pi->active());
build ();
}
LadspaPluginUI::~LadspaPluginUI ()
{
if (output_controls.size() > 0) {
screen_update_connection.disconnect();
}
}
void
LadspaPluginUI::build ()
{
guint32 i = 0;
guint32 x = 0;
Frame* frame;
Frame* bt_frame;
VBox* box;
int output_row, output_col;
int button_row, button_col;
int output_rows, output_cols;
int button_rows, button_cols;
prefheight = 30;
hpacker.set_spacing (10);
output_rows = initial_output_rows;
output_cols = initial_output_cols;
button_rows = initial_button_rows;
button_cols = initial_button_cols;
output_row = 0;
button_row = 0;
output_col = 0;
button_col = 0;
button_table.set_homogeneous (false);
button_table.set_row_spacings (2);
button_table.set_col_spacings (2);
output_table.set_homogeneous (true);
output_table.set_row_spacings (2);
output_table.set_col_spacings (2);
button_table.set_border_width (5);
output_table.set_border_width (5);
hpacker.set_border_width (10);
bt_frame = manage (new Frame);
bt_frame->set_name ("BaseFrame");
bt_frame->add (button_table);
hpacker.pack_start(*bt_frame, true, true);
box = manage (new VBox);
box->set_border_width (5);
box->set_spacing (1);
frame = manage (new Frame);
frame->set_name ("BaseFrame");
frame->set_label (_("Controls"));
frame->add (*box);
hpacker.pack_start(*frame, true, true);
/* find all ports. build control elements for all appropriate control ports */
for (i = 0; i < plugin->parameter_count(); ++i) {
if (plugin->parameter_is_control (i)) {
/* Don't show latency control ports */
if (plugin->describe_parameter (ParamID(PluginAutomation, i)) == X_("latency")) {
continue;
}
ControlUI* cui;
/* if we are scrollable, just use one long column */
if (!is_scrollable) {
if (x++ > 7){
frame = manage (new Frame);
frame->set_name ("BaseFrame");
box = manage (new VBox);
box->set_border_width (5);
box->set_spacing (1);
frame->add (*box);
hpacker.pack_start(*frame,true,true);
x = 1;
}
}
if ((cui = build_control_ui (i, plugin->get_nth_control (i))) == 0) {
error << string_compose(_("Plugin Editor: could not build control element for port %1"), i) << endmsg;
continue;
}
if (cui->control || cui->clickbox || cui->combo) {
box->pack_start (*cui, false, false);
} else if (cui->button) {
if (button_row == button_rows) {
button_row = 0;
if (++button_col == button_cols) {
button_cols += 2;
button_table.resize (button_rows, button_cols);
}
}
button_table.attach (*cui, button_col, button_col + 1, button_row, button_row+1,
FILL|EXPAND, FILL);
button_row++;
} else if (cui->display) {
output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
FILL|EXPAND, FILL);
// TODO: The meters should be divided into multiple rows
if (++output_col == output_cols) {
output_cols ++;
output_table.resize (output_rows, output_cols);
}
/* old code, which divides meters into
* columns first, rows later. New code divides into one row
if (output_row == output_rows) {
output_row = 0;
if (++output_col == output_cols) {
output_cols += 2;
output_table.resize (output_rows, output_cols);
}
}
output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
FILL|EXPAND, FILL);
output_row++;
*/
}
/* HACK: ideally the preferred height would be queried from
the complete hpacker, but I can't seem to get that
information in time, so this is an estimation
*/
prefheight += 30;
}
}
if (box->children().empty()) {
hpacker.remove (*frame);
}
if (button_table.children().empty()) {
hpacker.remove (*bt_frame);
}
if (!output_table.children().empty()) {
frame = manage (new Frame);
frame->set_name ("BaseFrame");
frame->add (output_table);
hpacker.pack_end (*frame, true, true);
}
output_update ();
output_table.show_all ();
button_table.show_all ();
}
LadspaPluginUI::ControlUI::ControlUI ()
: automate_button (X_("")) // force creation of a label
{
automate_button.set_name ("PluginAutomateButton");
ARDOUR_UI::instance()->tooltips().set_tip (automate_button, _("Automation control"));
/* XXX translators: use a string here that will be at least as long
as the longest automation label (see ::automation_state_changed()
below). be sure to include a descender.
*/
set_size_request_to_display_given_text (*automate_button.get_child(), _("Mgnual"), 5, 5);
ignore_change = 0;
display = 0;
button = 0;
control = 0;
clickbox = 0;
adjustment = 0;
meterinfo = 0;
}
LadspaPluginUI::ControlUI::~ControlUI()
{
if (adjustment) {
delete adjustment;
}
if (meterinfo) {
delete meterinfo->meter;
delete meterinfo;
}
}
void
LadspaPluginUI::automation_state_changed (ControlUI* cui)
{
/* update button label */
switch (insert->get_parameter_automation_state (ParamID(PluginAutomation, cui->port_index))
& (Off|Play|Touch|Write)) {
case Off:
cui->automate_button.set_label (_("Manual"));
break;
case Play:
cui->automate_button.set_label (_("Play"));
break;
case Write:
cui->automate_button.set_label (_("Write"));
break;
case Touch:
cui->automate_button.set_label (_("Touch"));
break;
default:
cui->automate_button.set_label (_("???"));
break;
}
}
static void integer_printer (char buf[32], Adjustment &adj, void *arg)
{
snprintf (buf, 32, "%.0f", adj.get_value());
}
void
LadspaPluginUI::print_parameter (char *buf, uint32_t len, uint32_t param)
{
plugin->print_parameter (param, buf, len);
}
LadspaPluginUI::ControlUI*
LadspaPluginUI::build_control_ui (guint32 port_index, PBD::Controllable* mcontrol)
{
ControlUI* control_ui;
Plugin::ParameterDescriptor desc;
plugin->get_parameter_descriptor (port_index, desc);
control_ui = manage (new ControlUI ());
control_ui->adjustment = 0;
control_ui->combo = 0;
control_ui->combo_map = 0;
control_ui->port_index = port_index;
control_ui->update_pending = false;
control_ui->label.set_text (desc.label);
control_ui->label.set_alignment (0.0, 0.5);
control_ui->label.set_name ("PluginParameterLabel");
control_ui->set_spacing (5);
Gtk::Requisition req (control_ui->automate_button.size_request());
if (plugin->parameter_is_input (port_index)) {
boost::shared_ptr<LadspaPlugin> lp;
if ((lp = boost::dynamic_pointer_cast<LadspaPlugin>(plugin)) != 0) {
lrdf_defaults* defaults = lrdf_get_scale_values(lp->unique_id(), port_index);
if (defaults && defaults->count > 0) {
control_ui->combo = new Gtk::ComboBoxText;
//control_ui->combo->set_value_in_list(true, false);
set_popdown_strings (*control_ui->combo, setup_scale_values(port_index, control_ui));
control_ui->combo->signal_changed().connect (bind (mem_fun(*this, &LadspaPluginUI::control_combo_changed), control_ui));
plugin->ParameterChanged.connect (bind (mem_fun (*this, &LadspaPluginUI::parameter_changed), control_ui));
control_ui->pack_start(control_ui->label, true, true);
control_ui->pack_start(*control_ui->combo, false, true);
update_control_display(control_ui);
lrdf_free_setting_values(defaults);
return control_ui;
}
}
if (desc.toggled) {
/* Build a button */
control_ui->button = manage (new ToggleButton ());
control_ui->button->set_name ("PluginEditorButton");
control_ui->button->set_size_request (20, 20);
control_ui->pack_start (control_ui->label, true, true);
control_ui->pack_start (*control_ui->button, false, true);
control_ui->pack_start (control_ui->automate_button, false, false);
control_ui->button->signal_clicked().connect (bind (mem_fun(*this, &LadspaPluginUI::control_port_toggled), control_ui));
if(plugin->get_parameter (port_index) == 1){
control_ui->button->set_active(true);
}
return control_ui;
}
control_ui->adjustment = new Adjustment (0, 0, 0, 0, 0);
/* XXX this code is not right yet, because it doesn't handle
the absence of bounds in any sensible fashion.
*/
control_ui->adjustment->set_lower (desc.lower);
control_ui->adjustment->set_upper (desc.upper);
control_ui->logarithmic = desc.logarithmic;
if (control_ui->logarithmic) {
if (control_ui->adjustment->get_lower() == 0.0) {
control_ui->adjustment->set_lower (control_ui->adjustment->get_upper()/10000);
}
control_ui->adjustment->set_upper (log(control_ui->adjustment->get_upper()));
control_ui->adjustment->set_lower (log(control_ui->adjustment->get_lower()));
}
float delta = desc.upper - desc.lower;
control_ui->adjustment->set_page_size (delta/100.0);
control_ui->adjustment->set_step_increment (desc.step);
control_ui->adjustment->set_page_increment (desc.largestep);
if (desc.integer_step) {
control_ui->clickbox = new ClickBox (control_ui->adjustment, "PluginUIClickBox");
Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->clickbox, "g9999999", 2, 2);
control_ui->clickbox->set_print_func (integer_printer, 0);
} else {
sigc::slot<void,char*,uint32_t> pslot = sigc::bind (mem_fun(*this, &LadspaPluginUI::print_parameter), (uint32_t) port_index);
control_ui->control = new BarController (*control_ui->adjustment, *mcontrol, pslot);
control_ui->control->set_size_request (200, req.height);
control_ui->control->set_name (X_("PluginSlider"));
control_ui->control->set_style (BarController::LeftToRight);
control_ui->control->set_use_parent (true);
control_ui->control->StartGesture.connect (bind (mem_fun(*this, &LadspaPluginUI::start_touch), control_ui));
control_ui->control->StopGesture.connect (bind (mem_fun(*this, &LadspaPluginUI::stop_touch), control_ui));
}
if (control_ui->logarithmic) {
control_ui->adjustment->set_value(log(plugin->get_parameter(port_index)));
} else{
control_ui->adjustment->set_value(plugin->get_parameter(port_index));
}
/* XXX memory leak: SliderController not destroyed by ControlUI
destructor, and manage() reports object hierarchy
ambiguity.
*/
control_ui->pack_start (control_ui->label, true, true);
if (desc.integer_step) {
control_ui->pack_start (*control_ui->clickbox, false, false);
} else {
control_ui->pack_start (*control_ui->control, false, false);
}
control_ui->pack_start (control_ui->automate_button, false, false);
control_ui->adjustment->signal_value_changed().connect (bind (mem_fun(*this, &LadspaPluginUI::control_adjustment_changed), control_ui));
control_ui->automate_button.signal_clicked().connect (bind (mem_fun(*this, &LadspaPluginUI::astate_clicked), control_ui, (uint32_t) port_index));
automation_state_changed (control_ui);
plugin->ParameterChanged.connect (bind (mem_fun(*this, &LadspaPluginUI::parameter_changed), control_ui));
insert->control (ParamID(PluginAutomation, port_index))->list()->automation_state_changed.connect
(bind (mem_fun(*this, &LadspaPluginUI::automation_state_changed), control_ui));
} else if (plugin->parameter_is_output (port_index)) {
control_ui->display = manage (new EventBox);
control_ui->display->set_name ("ParameterValueDisplay");
control_ui->display_label = manage (new Label);
control_ui->display_label->set_name ("ParameterValueDisplay");
control_ui->display->add (*control_ui->display_label);
Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->display, "-99,99", 2, 2);
control_ui->display->show_all ();
/* set up a meter */
/* TODO: only make a meter if the port is Hinted for it */
MeterInfo * info = new MeterInfo(port_index);
control_ui->meterinfo = info;
info->meter = new FastMeter (5, 100, FastMeter::Vertical);
info->min_unbound = desc.min_unbound;
info->max_unbound = desc.max_unbound;
info->min = desc.lower;
info->max = desc.upper;
control_ui->vbox = manage (new VBox);
control_ui->hbox = manage (new HBox);
control_ui->label.set_angle(90);
control_ui->hbox->pack_start (control_ui->label, false, false);
control_ui->hbox->pack_start (*info->meter, false, false);
control_ui->vbox->pack_start (*control_ui->hbox, false, false);
control_ui->vbox->pack_start (*control_ui->display, false, false);
control_ui->pack_start (*control_ui->vbox);
control_ui->meterinfo->meter->show_all();
control_ui->meterinfo->packed = true;
output_controls.push_back (control_ui);
}
plugin->ParameterChanged.connect (bind (mem_fun(*this, &LadspaPluginUI::parameter_changed), control_ui));
return control_ui;
}
void
LadspaPluginUI::start_touch (LadspaPluginUI::ControlUI* cui)
{
insert->control (ParamID(PluginAutomation, cui->port_index))->list()->start_touch ();
}
void
LadspaPluginUI::stop_touch (LadspaPluginUI::ControlUI* cui)
{
insert->control (ParamID(PluginAutomation, cui->port_index))->list()->stop_touch ();
}
void
LadspaPluginUI::astate_clicked (ControlUI* cui, uint32_t port)
{
using namespace Menu_Helpers;
if (automation_menu == 0) {
automation_menu = manage (new Menu);
automation_menu->set_name ("ArdourContextMenu");
}
MenuList& items (automation_menu->items());
items.clear ();
items.push_back (MenuElem (_("Manual"),
bind (mem_fun(*this, &LadspaPluginUI::set_automation_state), (AutoState) Off, cui)));
items.push_back (MenuElem (_("Play"),
bind (mem_fun(*this, &LadspaPluginUI::set_automation_state), (AutoState) Play, cui)));
items.push_back (MenuElem (_("Write"),
bind (mem_fun(*this, &LadspaPluginUI::set_automation_state), (AutoState) Write, cui)));
items.push_back (MenuElem (_("Touch"),
bind (mem_fun(*this, &LadspaPluginUI::set_automation_state), (AutoState) Touch, cui)));
automation_menu->popup (1, gtk_get_current_event_time());
}
void
LadspaPluginUI::set_automation_state (AutoState state, ControlUI* cui)
{
insert->set_parameter_automation_state (ParamID(PluginAutomation, cui->port_index), state);
}
void
LadspaPluginUI::control_adjustment_changed (ControlUI* cui)
{
if (cui->ignore_change) {
return;
}
double value = cui->adjustment->get_value();
if (cui->logarithmic) {
value = exp(value);
}
insert->set_parameter (ParamID(PluginAutomation, cui->port_index), (float) value);
}
void
LadspaPluginUI::parameter_changed (uint32_t abs_port_id, float val, ControlUI* cui)
{
if (cui->port_index == abs_port_id) {
if (!cui->update_pending) {
cui->update_pending = true;
Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &LadspaPluginUI::update_control_display), cui));
}
}
}
void
LadspaPluginUI::update_control_display (ControlUI* cui)
{
/* XXX how do we handle logarithmic stuff here ? */
cui->update_pending = false;
float val = plugin->get_parameter (cui->port_index);
cui->ignore_change++;
if (cui->combo) {
std::map<string,float>::iterator it;
for (it = cui->combo_map->begin(); it != cui->combo_map->end(); ++it) {
if (it->second == val) {
cui->combo->set_active_text(it->first);
break;
}
}
} else if (cui->adjustment == 0) {
if (val > 0.5) {
cui->button->set_active (true);
} else {
cui->button->set_active (false);
}
} else {
if (cui->logarithmic) {
val = log(val);
}
if (val != cui->adjustment->get_value()) {
cui->adjustment->set_value (val);
}
}
cui->ignore_change--;
}
void
LadspaPluginUI::control_port_toggled (ControlUI* cui)
{
if (!cui->ignore_change) {
insert->set_parameter (ParamID(PluginAutomation, cui->port_index), cui->button->get_active());
}
}
void
LadspaPluginUI::control_combo_changed (ControlUI* cui)
{
if (!cui->ignore_change) {
string value = cui->combo->get_active_text();
std::map<string,float> mapping = *cui->combo_map;
insert->set_parameter (ParamID(PluginAutomation, cui->port_index), mapping[value]);
}
}
void
LadspaPluginUI::processor_active_changed (boost::weak_ptr<Processor> weak_processor)
{
ENSURE_GUI_THREAD(bind (mem_fun(*this, &LadspaPluginUI::processor_active_changed), weak_processor));
boost::shared_ptr<Processor> processor = weak_processor.lock();
bypass_button.set_active (!processor || !processor->active());
}
bool
LadspaPluginUI::start_updating (GdkEventAny* ignored)
{
if (output_controls.size() > 0 ) {
screen_update_connection.disconnect();
screen_update_connection = ARDOUR_UI::instance()->RapidScreenUpdate.connect
(mem_fun(*this, &LadspaPluginUI::output_update));
}
return false;
}
bool
LadspaPluginUI::stop_updating (GdkEventAny* ignored)
{
if (output_controls.size() > 0 ) {
screen_update_connection.disconnect();
}
return false;
}
void
LadspaPluginUI::output_update ()
{
for (vector<ControlUI*>::iterator i = output_controls.begin(); i != output_controls.end(); ++i) {
float val = plugin->get_parameter ((*i)->port_index);
char buf[32];
snprintf (buf, sizeof(buf), "%.2f", val);
(*i)->display_label->set_text (buf);
/* autoscaling for the meter */
if ((*i)->meterinfo && (*i)->meterinfo->packed) {
if (val < (*i)->meterinfo->min) {
if ((*i)->meterinfo->min_unbound)
(*i)->meterinfo->min = val;
else
val = (*i)->meterinfo->min;
}
if (val > (*i)->meterinfo->max) {
if ((*i)->meterinfo->max_unbound)
(*i)->meterinfo->max = val;
else
val = (*i)->meterinfo->max;
}
if ((*i)->meterinfo->max > (*i)->meterinfo->min ) {
float lval = (val - (*i)->meterinfo->min) / ((*i)->meterinfo->max - (*i)->meterinfo->min) ;
(*i)->meterinfo->meter->set (lval );
}
}
}
}
vector<string>
LadspaPluginUI::setup_scale_values(guint32 port_index, ControlUI* cui)
{
vector<string> enums;
boost::shared_ptr<LadspaPlugin> lp = boost::dynamic_pointer_cast<LadspaPlugin> (plugin);
cui->combo_map = new std::map<string, float>;
lrdf_defaults* defaults = lrdf_get_scale_values(lp->unique_id(), port_index);
if (defaults) {
for (uint32_t i = 0; i < defaults->count; ++i) {
enums.push_back(defaults->items[i].label);
pair<string, float> newpair;
newpair.first = defaults->items[i].label;
newpair.second = defaults->items[i].value;
cui->combo_map->insert(newpair);
}
lrdf_free_setting_values(defaults);
}
return enums;
}