2 Copyright (C) 2000 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include <pbd/stl_delete.h>
26 #include <pbd/xml++.h>
27 #include <pbd/failed_constructor.h>
29 #include <gtkmm2ext/click_box.h>
30 #include <gtkmm2ext/fastmeter.h>
31 #include <gtkmm2ext/barcontroller.h>
32 #include <gtkmm2ext/utils.h>
33 #include <gtkmm2ext/doi.h>
34 #include <gtkmm2ext/slider_controller.h>
36 #include <midi++/manager.h>
38 #include <ardour/plugin.h>
39 #include <ardour/plugin_insert.h>
40 #include <ardour/ladspa_plugin.h>
41 #include <ardour/lv2_plugin.h>
45 #include "ardour_ui.h"
47 #include "plugin_ui.h"
49 #include "gui_thread.h"
50 #include "automation_controller.h"
55 using namespace ARDOUR;
57 using namespace Gtkmm2ext;
61 GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrollable)
63 button_table (initial_button_rows, initial_button_cols),
64 output_table (initial_output_rows, initial_output_cols),
65 hAdjustment(0.0, 0.0, 0.0),
66 vAdjustment(0.0, 0.0, 0.0),
67 scroller_view(hAdjustment, vAdjustment),
69 is_scrollable(scrollable)
71 set_name ("PluginEditor");
72 set_border_width (10);
73 set_homogeneous (false);
75 settings_box.set_homogeneous (false);
77 HBox* constraint_hbox = manage (new HBox);
78 HBox* smaller_hbox = manage (new HBox);
79 Label* combo_label = manage (new Label (_("<span size=\"large\">Presets</span>")));
80 combo_label->set_use_markup (true);
82 Label* latency_label = manage (new Label (_("<span size=\"large\">Latency</span>")));
83 latency_label->set_use_markup (true);
85 smaller_hbox->pack_start (*latency_label, false, false, 10);
86 smaller_hbox->pack_start (latency_gui, false, false, 10);
87 smaller_hbox->pack_start (combo, false, false);
88 smaller_hbox->pack_start (save_button, false, false);
90 constraint_hbox->set_spacing (5);
91 constraint_hbox->pack_start (*smaller_hbox, true, false);
92 constraint_hbox->pack_end (bypass_button, false, false);
94 settings_box.pack_end (*constraint_hbox, false, false);
96 pack_start (settings_box, false, false);
98 if ( is_scrollable ) {
99 scroller.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
100 scroller.set_name ("PluginEditor");
101 scroller_view.set_name("PluginEditor");
102 scroller_view.add (hpacker);
103 scroller.add (scroller_view);
105 pack_start (scroller, true, true);
109 pack_start (hpacker, false, false);
112 pi->ActiveChanged.connect (bind(mem_fun(*this, &GenericPluginUI::processor_active_changed),
113 boost::weak_ptr<Processor>(pi)));
114 bypass_button.set_active (!pi->active());
119 GenericPluginUI::~GenericPluginUI ()
121 if (output_controls.size() > 0) {
122 screen_update_connection.disconnect();
127 GenericPluginUI::build ()
135 int output_row, output_col;
136 int button_row, button_col;
137 int output_rows, output_cols;
138 int button_rows, button_cols;
141 hpacker.set_spacing (10);
143 output_rows = initial_output_rows;
144 output_cols = initial_output_cols;
145 button_rows = initial_button_rows;
146 button_cols = initial_button_cols;
152 button_table.set_homogeneous (false);
153 button_table.set_row_spacings (2);
154 button_table.set_col_spacings (2);
155 output_table.set_homogeneous (true);
156 output_table.set_row_spacings (2);
157 output_table.set_col_spacings (2);
158 button_table.set_border_width (5);
159 output_table.set_border_width (5);
161 hpacker.set_border_width (10);
163 bt_frame = manage (new Frame);
164 bt_frame->set_name ("BaseFrame");
165 bt_frame->add (button_table);
166 hpacker.pack_start(*bt_frame, true, true);
168 box = manage (new VBox);
169 box->set_border_width (5);
170 box->set_spacing (1);
172 frame = manage (new Frame);
173 frame->set_name ("BaseFrame");
174 frame->set_label (_("Controls"));
176 hpacker.pack_start(*frame, true, true);
178 /* find all ports. build control elements for all appropriate control ports */
180 for (i = 0; i < plugin->parameter_count(); ++i) {
182 if (plugin->parameter_is_control (i)) {
184 /* Don't show latency control ports */
186 if (plugin->describe_parameter (Parameter(PluginAutomation, i)) == X_("latency")) {
192 /* if we are scrollable, just use one long column */
194 if (!is_scrollable) {
196 frame = manage (new Frame);
197 frame->set_name ("BaseFrame");
198 box = manage (new VBox);
200 box->set_border_width (5);
201 box->set_spacing (1);
204 hpacker.pack_start(*frame,true,true);
210 if ((cui = build_control_ui (i, insert->control(Parameter(PluginAutomation, i)))) == 0) {
211 error << string_compose(_("Plugin Editor: could not build control element for port %1"), i) << endmsg;
215 if (cui->control || cui->clickbox || cui->combo) {
217 box->pack_start (*cui, false, false);
219 } else if (cui->button) {
221 if (button_row == button_rows) {
223 if (++button_col == button_cols) {
225 button_table.resize (button_rows, button_cols);
229 button_table.attach (*cui, button_col, button_col + 1, button_row, button_row+1,
233 } else if (cui->display) {
235 output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
238 // TODO: The meters should be divided into multiple rows
240 if (++output_col == output_cols) {
242 output_table.resize (output_rows, output_cols);
245 /* old code, which divides meters into
246 * columns first, rows later. New code divides into one row
248 if (output_row == output_rows) {
250 if (++output_col == output_cols) {
252 output_table.resize (output_rows, output_cols);
256 output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
263 /* HACK: ideally the preferred height would be queried from
264 the complete hpacker, but I can't seem to get that
265 information in time, so this is an estimation
273 if (box->children().empty()) {
274 hpacker.remove (*frame);
277 if (button_table.children().empty()) {
278 hpacker.remove (*bt_frame);
281 if (!output_table.children().empty()) {
282 frame = manage (new Frame);
283 frame->set_name ("BaseFrame");
284 frame->add (output_table);
285 hpacker.pack_end (*frame, true, true);
290 output_table.show_all ();
291 button_table.show_all ();
294 GenericPluginUI::ControlUI::ControlUI ()
295 : automate_button (X_("")) // force creation of a label
297 automate_button.set_name ("PluginAutomateButton");
298 ARDOUR_UI::instance()->tooltips().set_tip (automate_button, _("Automation control"));
300 /* XXX translators: use a string here that will be at least as long
301 as the longest automation label (see ::automation_state_changed()
302 below). be sure to include a descender.
305 set_size_request_to_display_given_text (*automate_button.get_child(), _("Mgnual"), 5, 5);
314 GenericPluginUI::ControlUI::~ControlUI()
317 delete meterinfo->meter;
323 GenericPluginUI::automation_state_changed (ControlUI* cui)
325 /* update button label */
327 // don't lock to avoid deadlock because we're triggered by
328 // AutomationControl::Changed() while the automation lock is taken
329 switch (insert->get_parameter_automation_state (cui->parameter(), false)
330 & (Off|Play|Touch|Write)) {
332 cui->automate_button.set_label (_("Manual"));
335 cui->automate_button.set_label (_("Play"));
338 cui->automate_button.set_label (_("Write"));
341 cui->automate_button.set_label (_("Touch"));
344 cui->automate_button.set_label (_("???"));
350 static void integer_printer (char buf[32], Adjustment &adj, void *arg)
352 snprintf (buf, 32, "%.0f", adj.get_value());
356 GenericPluginUI::print_parameter (char *buf, uint32_t len, uint32_t param)
358 plugin->print_parameter (param, buf, len);
361 GenericPluginUI::ControlUI*
362 GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<AutomationControl> mcontrol)
364 ControlUI* control_ui = NULL;
368 Plugin::ParameterDescriptor desc;
370 plugin->get_parameter_descriptor (port_index, desc);
372 control_ui = manage (new ControlUI ());
373 control_ui->combo = 0;
374 control_ui->combo_map = 0;
375 control_ui->control = mcontrol;
376 control_ui->update_pending = false;
377 control_ui->label.set_text (desc.label);
378 control_ui->label.set_alignment (0.0, 0.5);
379 control_ui->label.set_name ("PluginParameterLabel");
381 control_ui->set_spacing (5);
383 Gtk::Requisition req (control_ui->automate_button.size_request());
385 if (plugin->parameter_is_input (port_index)) {
387 boost::shared_ptr<LadspaPlugin> lp;
388 boost::shared_ptr<LV2Plugin> lv2p;
390 if ((lp = boost::dynamic_pointer_cast<LadspaPlugin>(plugin)) != 0) {
392 // FIXME: not all plugins have a numeric unique ID
393 uint32_t id = atol (lp->unique_id().c_str());
394 lrdf_defaults* defaults = lrdf_get_scale_values(id, port_index);
396 if (defaults && defaults->count > 0) {
398 control_ui->combo = new Gtk::ComboBoxText;
399 //control_ui->combo->set_value_in_list(true, false);
400 set_popdown_strings (*control_ui->combo, setup_scale_values(port_index, control_ui));
401 control_ui->combo->signal_changed().connect (bind (mem_fun(*this, &GenericPluginUI::control_combo_changed), control_ui));
402 mcontrol->Changed.connect (bind (mem_fun (*this, &GenericPluginUI::parameter_changed), control_ui));
403 control_ui->pack_start(control_ui->label, true, true);
404 control_ui->pack_start(*control_ui->combo, false, true);
406 update_control_display(control_ui);
408 lrdf_free_setting_values(defaults);
412 } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin>(plugin)) != 0) {
414 SLV2Port port = lv2p->slv2_port(port_index);
415 SLV2ScalePoints points = slv2_port_get_scale_points(lv2p->slv2_plugin(), port);
418 control_ui->combo = new Gtk::ComboBoxText;
419 //control_ui->combo->set_value_in_list(true, false);
420 set_popdown_strings (*control_ui->combo, setup_scale_values(port_index, control_ui));
421 control_ui->combo->signal_changed().connect (bind (mem_fun(*this, &GenericPluginUI::control_combo_changed), control_ui));
422 mcontrol->Changed.connect (bind (mem_fun (*this, &GenericPluginUI::parameter_changed), control_ui));
423 control_ui->pack_start(control_ui->label, true, true);
424 control_ui->pack_start(*control_ui->combo, false, true);
426 update_control_display(control_ui);
428 slv2_scale_points_free(points);
437 control_ui->button = manage (new ToggleButton ());
438 control_ui->button->set_name ("PluginEditorButton");
439 control_ui->button->set_size_request (20, 20);
441 control_ui->pack_start (control_ui->label, true, true);
442 control_ui->pack_start (*control_ui->button, false, true);
443 control_ui->pack_start (control_ui->automate_button, false, false);
445 control_ui->button->signal_clicked().connect (bind (mem_fun(*this, &GenericPluginUI::control_port_toggled), control_ui));
447 if(plugin->get_parameter (port_index) == 1){
448 control_ui->button->set_active(true);
454 /* create the controller */
456 control_ui->controller = AutomationController::create(insert, mcontrol->list(), mcontrol);
458 /* XXX this code is not right yet, because it doesn't handle
459 the absence of bounds in any sensible fashion.
463 control_ui->controller->adjustment()->set_lower (desc.lower);
464 control_ui->controller->adjustment()->set_upper (desc.upper);
466 control_ui->logarithmic = desc.logarithmic;
467 if (control_ui->logarithmic) {
468 if (control_ui->controller->adjustment()->get_lower() == 0.0) {
469 control_ui->controller->adjustment()->set_lower (control_ui->controller->adjustment()->get_upper()/10000);
471 control_ui->controller->adjustment()->set_upper (log(control_ui->controller->adjustment()->get_upper()));
472 control_ui->controller->adjustment()->set_lower (log(control_ui->controller->adjustment()->get_lower()));
476 float delta = desc.upper - desc.lower;
478 control_ui->controller->adjustment()->set_page_size (delta/100.0);
479 control_ui->controller->adjustment()->set_step_increment (desc.step);
480 control_ui->controller->adjustment()->set_page_increment (desc.largestep);
483 if (desc.integer_step) {
484 control_ui->clickbox = new ClickBox (control_ui->controller->adjustment(), "PluginUIClickBox");
485 Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->clickbox, "g9999999", 2, 2);
486 control_ui->clickbox->set_print_func (integer_printer, 0);
488 //sigc::slot<void,char*,uint32_t> pslot = sigc::bind (mem_fun(*this, &GenericPluginUI::print_parameter), (uint32_t) port_index);
490 control_ui->controller->set_size_request (200, req.height);
491 control_ui->controller->set_name (X_("PluginSlider"));
492 control_ui->controller->set_style (BarController::LeftToRight);
493 control_ui->controller->set_use_parent (true);
495 control_ui->controller->StartGesture.connect (bind (mem_fun(*this, &GenericPluginUI::start_touch), control_ui));
496 control_ui->controller->StopGesture.connect (bind (mem_fun(*this, &GenericPluginUI::stop_touch), control_ui));
500 if (control_ui->logarithmic) {
501 control_ui->controller->adjustment()->set_value(log(plugin->get_parameter(port_index)));
503 control_ui->controller->adjustment()->set_value(plugin->get_parameter(port_index));
506 /* XXX memory leak: SliderController not destroyed by ControlUI
507 destructor, and manage() reports object hierarchy
511 control_ui->pack_start (control_ui->label, true, true);
512 if (desc.integer_step) {
513 control_ui->pack_start (*control_ui->clickbox, false, false);
515 control_ui->pack_start (*control_ui->controller, false, false);
518 control_ui->pack_start (control_ui->automate_button, false, false);
519 control_ui->automate_button.signal_clicked().connect (bind (mem_fun(*this, &GenericPluginUI::astate_clicked), control_ui, (uint32_t) port_index));
521 automation_state_changed (control_ui);
523 mcontrol->Changed.connect (bind (mem_fun (*this, &GenericPluginUI::parameter_changed), control_ui));
524 mcontrol->list()->automation_state_changed.connect
525 (bind (mem_fun(*this, &GenericPluginUI::automation_state_changed), control_ui));
527 } else if (plugin->parameter_is_output (port_index)) {
529 control_ui->display = manage (new EventBox);
530 control_ui->display->set_name ("ParameterValueDisplay");
532 control_ui->display_label = manage (new Label);
534 control_ui->display_label->set_name ("ParameterValueDisplay");
536 control_ui->display->add (*control_ui->display_label);
537 Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->display, "-99,99", 2, 2);
539 control_ui->display->show_all ();
542 /* TODO: only make a meter if the port is Hinted for it */
544 MeterInfo * info = new MeterInfo(port_index);
545 control_ui->meterinfo = info;
547 info->meter = new FastMeter (5, 5, FastMeter::Vertical);
549 info->min_unbound = desc.min_unbound;
550 info->max_unbound = desc.max_unbound;
552 info->min = desc.lower;
553 info->max = desc.upper;
555 control_ui->vbox = manage (new VBox);
556 control_ui->hbox = manage (new HBox);
558 control_ui->label.set_angle(90);
559 control_ui->hbox->pack_start (control_ui->label, false, false);
560 control_ui->hbox->pack_start (*info->meter, false, false);
562 control_ui->vbox->pack_start (*control_ui->hbox, false, false);
564 control_ui->vbox->pack_start (*control_ui->display, false, false);
566 control_ui->pack_start (*control_ui->vbox);
568 control_ui->meterinfo->meter->show_all();
569 control_ui->meterinfo->packed = true;
571 output_controls.push_back (control_ui);
574 mcontrol->Changed.connect (bind (mem_fun (*this, &GenericPluginUI::parameter_changed), control_ui));
580 GenericPluginUI::start_touch (GenericPluginUI::ControlUI* cui)
582 cui->control->list()->start_touch ();
586 GenericPluginUI::stop_touch (GenericPluginUI::ControlUI* cui)
588 cui->control->list()->stop_touch ();
592 GenericPluginUI::astate_clicked (ControlUI* cui, uint32_t port)
594 using namespace Menu_Helpers;
596 if (automation_menu == 0) {
597 automation_menu = manage (new Menu);
598 automation_menu->set_name ("ArdourContextMenu");
601 MenuList& items (automation_menu->items());
604 items.push_back (MenuElem (_("Manual"),
605 bind (mem_fun(*this, &GenericPluginUI::set_automation_state), (AutoState) Off, cui)));
606 items.push_back (MenuElem (_("Play"),
607 bind (mem_fun(*this, &GenericPluginUI::set_automation_state), (AutoState) Play, cui)));
608 items.push_back (MenuElem (_("Write"),
609 bind (mem_fun(*this, &GenericPluginUI::set_automation_state), (AutoState) Write, cui)));
610 items.push_back (MenuElem (_("Touch"),
611 bind (mem_fun(*this, &GenericPluginUI::set_automation_state), (AutoState) Touch, cui)));
613 automation_menu->popup (1, gtk_get_current_event_time());
617 GenericPluginUI::set_automation_state (AutoState state, ControlUI* cui)
619 insert->set_parameter_automation_state (cui->parameter(), state);
623 GenericPluginUI::parameter_changed (ControlUI* cui)
625 if (!cui->update_pending) {
626 cui->update_pending = true;
627 Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &GenericPluginUI::update_control_display), cui));
632 GenericPluginUI::update_control_display (ControlUI* cui)
634 /* XXX how do we handle logarithmic stuff here ? */
636 cui->update_pending = false;
638 float val = cui->control->get_value();
640 cui->ignore_change++;
642 std::map<string,float>::iterator it;
643 for (it = cui->combo_map->begin(); it != cui->combo_map->end(); ++it) {
644 if (it->second == val) {
645 cui->combo->set_active_text(it->first);
649 } else if (cui->button) {
652 cui->button->set_active (true);
654 cui->button->set_active (false);
658 cui->controller->display_effective_value();
662 if (cui->logarithmic) {
665 if (val != cui->adjustment->get_value()) {
666 cui->adjustment->set_value (val);
669 cui->ignore_change--;
673 GenericPluginUI::control_port_toggled (ControlUI* cui)
675 if (!cui->ignore_change) {
676 insert->set_parameter (cui->parameter(), cui->button->get_active());
681 GenericPluginUI::control_combo_changed (ControlUI* cui)
683 if (!cui->ignore_change) {
684 string value = cui->combo->get_active_text();
685 std::map<string,float> mapping = *cui->combo_map;
686 insert->set_parameter (cui->parameter(), mapping[value]);
692 GenericPluginUI::processor_active_changed (boost::weak_ptr<Processor> weak_processor)
694 ENSURE_GUI_THREAD(bind (mem_fun(*this, &GenericPluginUI::processor_active_changed), weak_processor));
696 boost::shared_ptr<Processor> processor = weak_processor.lock();
698 bypass_button.set_active (!processor || !processor->active());
702 GenericPluginUI::start_updating (GdkEventAny* ignored)
704 if (output_controls.size() > 0 ) {
705 screen_update_connection.disconnect();
706 screen_update_connection = ARDOUR_UI::instance()->RapidScreenUpdate.connect
707 (mem_fun(*this, &GenericPluginUI::output_update));
713 GenericPluginUI::stop_updating (GdkEventAny* ignored)
715 if (output_controls.size() > 0 ) {
716 screen_update_connection.disconnect();
722 GenericPluginUI::output_update ()
724 for (vector<ControlUI*>::iterator i = output_controls.begin(); i != output_controls.end(); ++i) {
725 float val = plugin->get_parameter ((*i)->parameter().id());
727 snprintf (buf, sizeof(buf), "%.2f", val);
728 (*i)->display_label->set_text (buf);
730 /* autoscaling for the meter */
731 if ((*i)->meterinfo && (*i)->meterinfo->packed) {
733 if (val < (*i)->meterinfo->min) {
734 if ((*i)->meterinfo->min_unbound)
735 (*i)->meterinfo->min = val;
737 val = (*i)->meterinfo->min;
740 if (val > (*i)->meterinfo->max) {
741 if ((*i)->meterinfo->max_unbound)
742 (*i)->meterinfo->max = val;
744 val = (*i)->meterinfo->max;
747 if ((*i)->meterinfo->max > (*i)->meterinfo->min ) {
748 float lval = (val - (*i)->meterinfo->min) / ((*i)->meterinfo->max - (*i)->meterinfo->min) ;
749 (*i)->meterinfo->meter->set (lval );
756 GenericPluginUI::setup_scale_values(guint32 port_index, ControlUI* cui)
758 vector<string> enums;
759 boost::shared_ptr<LadspaPlugin> lp;
760 boost::shared_ptr<LV2Plugin> lv2p;
762 if ((lp = boost::dynamic_pointer_cast<LadspaPlugin>(plugin)) != 0) {
763 // all LADPSA plugins have a numeric unique ID
764 uint32_t id = atol (lp->unique_id().c_str());
766 cui->combo_map = new std::map<string, float>;
767 lrdf_defaults* defaults = lrdf_get_scale_values(id, port_index);
769 for (uint32_t i = 0; i < defaults->count; ++i) {
770 enums.push_back(defaults->items[i].label);
771 pair<string, float> newpair;
772 newpair.first = defaults->items[i].label;
773 newpair.second = defaults->items[i].value;
774 cui->combo_map->insert(newpair);
777 lrdf_free_setting_values(defaults);
780 } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin>(plugin)) != 0) {
782 SLV2Port port = lv2p->slv2_port(port_index);
783 SLV2ScalePoints points = slv2_port_get_scale_points(lv2p->slv2_plugin(), port);
784 cui->combo_map = new std::map<string, float>;
786 for (unsigned i=0; i < slv2_scale_points_size(points); ++i) {
787 SLV2ScalePoint p = slv2_scale_points_get_at(points, i);
788 SLV2Value label = slv2_scale_point_get_label(p);
789 SLV2Value value = slv2_scale_point_get_value(p);
790 if (label && (slv2_value_is_float(value) || slv2_value_is_int(value))) {
791 enums.push_back(slv2_value_as_string(label));
792 pair<string, float> newpair;
793 newpair.first = slv2_value_as_string(label);
794 newpair.second = slv2_value_as_float(value);
795 cui->combo_map->insert(newpair);
799 slv2_scale_points_free(points);