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.
21 #include "gtk2ardour-config.h"
29 #include "pbd/stl_delete.h"
30 #include "pbd/xml++.h"
31 #include "pbd/failed_constructor.h"
33 #include <gtkmm2ext/click_box.h>
34 #include <gtkmm2ext/fastmeter.h>
35 #include <gtkmm2ext/barcontroller.h>
36 #include <gtkmm2ext/utils.h>
37 #include <gtkmm2ext/doi.h>
38 #include <gtkmm2ext/slider_controller.h>
40 #include "midi++/manager.h"
42 #include "ardour/plugin.h"
43 #include "ardour/plugin_insert.h"
44 #include "ardour/session.h"
48 #include "ardour_ui.h"
50 #include "plugin_ui.h"
52 #include "gui_thread.h"
53 #include "automation_controller.h"
58 using namespace ARDOUR
;
60 using namespace Gtkmm2ext
;
63 GenericPluginUI::GenericPluginUI (boost::shared_ptr
<PluginInsert
> pi
, bool scrollable
)
65 button_table (initial_button_rows
, initial_button_cols
),
66 output_table (initial_output_rows
, initial_output_cols
),
67 hAdjustment(0.0, 0.0, 0.0),
68 vAdjustment(0.0, 0.0, 0.0),
69 scroller_view(hAdjustment
, vAdjustment
),
71 is_scrollable(scrollable
)
73 set_name ("PluginEditor");
74 set_border_width (10);
75 //set_homogeneous (false);
77 pack_start (main_contents
, true, true);
78 settings_box
.set_homogeneous (false);
80 HBox
* constraint_hbox
= manage (new HBox
);
81 HBox
* smaller_hbox
= manage (new HBox
);
82 smaller_hbox
->set_spacing (4);
83 Label
* combo_label
= manage (new Label (_("<span size=\"large\">Presets</span>")));
84 combo_label
->set_use_markup (true);
86 latency_button
.add (latency_label
);
87 latency_button
.signal_clicked().connect (sigc::mem_fun (*this, &PlugUIBase::latency_button_clicked
));
90 smaller_hbox
->pack_start (latency_button
, false, false, 10);
91 smaller_hbox
->pack_start (_preset_box
, false, false);
92 smaller_hbox
->pack_start (add_button
, false, false);
93 smaller_hbox
->pack_start (save_button
, false, false);
94 smaller_hbox
->pack_start (delete_button
, false, false);
95 smaller_hbox
->pack_start (bypass_button
, false, true);
97 constraint_hbox
->set_spacing (5);
98 constraint_hbox
->set_homogeneous (false);
100 VBox
* v1_box
= manage (new VBox
);
101 VBox
* v2_box
= manage (new VBox
);
102 pack_end (plugin_analysis_expander
, false, false);
104 v1_box
->pack_start (*smaller_hbox
, false, true);
105 v2_box
->pack_start (focus_button
, false, true);
107 main_contents
.pack_start (settings_box
, false, false);
109 constraint_hbox
->pack_end (*v2_box
, false, false);
110 constraint_hbox
->pack_end (*v1_box
, false, false);
112 main_contents
.pack_start (*constraint_hbox
, false, false);
114 if (is_scrollable
) {
115 scroller
.set_policy (Gtk::POLICY_AUTOMATIC
, Gtk::POLICY_AUTOMATIC
);
116 scroller
.set_name ("PluginEditor");
117 scroller_view
.set_name("PluginEditor");
118 scroller_view
.add (hpacker
);
119 scroller
.add (scroller_view
);
121 main_contents
.pack_start (scroller
, true, true);
124 main_contents
.pack_start (hpacker
, false, false);
127 pi
->ActiveChanged
.connect (active_connection
, invalidator (*this), boost::bind (&GenericPluginUI::processor_active_changed
, this, boost::weak_ptr
<Processor
>(pi
)), gui_context());
129 bypass_button
.set_active (!pi
->active());
134 GenericPluginUI::~GenericPluginUI ()
136 if (output_controls
.size() > 0) {
137 screen_update_connection
.disconnect();
142 GenericPluginUI::build ()
149 int output_row
, output_col
;
150 int button_row
, button_col
;
151 int output_rows
, output_cols
;
152 int button_rows
, button_cols
;
155 hpacker
.set_spacing (10);
157 output_rows
= initial_output_rows
;
158 output_cols
= initial_output_cols
;
159 button_rows
= initial_button_rows
;
160 button_cols
= initial_button_cols
;
166 button_table
.set_homogeneous (false);
167 button_table
.set_row_spacings (2);
168 button_table
.set_col_spacings (2);
169 output_table
.set_homogeneous (true);
170 output_table
.set_row_spacings (2);
171 output_table
.set_col_spacings (2);
172 button_table
.set_border_width (5);
173 output_table
.set_border_width (5);
175 hpacker
.set_border_width (10);
177 bt_frame
= manage (new Frame
);
178 bt_frame
->set_name ("BaseFrame");
179 bt_frame
->add (button_table
);
180 hpacker
.pack_start(*bt_frame
, true, true);
182 box
= manage (new VBox
);
183 box
->set_border_width (5);
184 box
->set_spacing (1);
186 frame
= manage (new Frame
);
187 frame
->set_name ("BaseFrame");
188 frame
->set_label (_("Controls"));
190 hpacker
.pack_start(*frame
, true, true);
192 /* find all ports. build control elements for all appropriate control ports */
194 for (i
= 0; i
< plugin
->parameter_count(); ++i
) {
196 if (plugin
->parameter_is_control (i
)) {
198 /* Don't show latency control ports */
200 if (plugin
->describe_parameter (Evoral::Parameter(PluginAutomation
, 0, i
)) == X_("latency")) {
206 /* if we are scrollable, just use one long column */
208 if (!is_scrollable
) {
210 frame
= manage (new Frame
);
211 frame
->set_name ("BaseFrame");
212 box
= manage (new VBox
);
214 box
->set_border_width (5);
215 box
->set_spacing (1);
218 hpacker
.pack_start(*frame
, true, true);
224 boost::shared_ptr
<ARDOUR::AutomationControl
> c
225 = boost::dynamic_pointer_cast
<ARDOUR::AutomationControl
>(
226 insert
->control(Evoral::Parameter(PluginAutomation
, 0, i
)));
228 if ((cui
= build_control_ui (i
, c
)) == 0) {
229 error
<< string_compose(_("Plugin Editor: could not build control element for port %1"), i
) << endmsg
;
233 if (cui
->controller
|| cui
->clickbox
|| cui
->combo
) {
235 box
->pack_start (*cui
, false, false);
237 } else if (cui
->button
) {
239 if (button_row
== button_rows
) {
241 if (++button_col
== button_cols
) {
243 button_table
.resize (button_rows
, button_cols
);
247 button_table
.attach (*cui
, button_col
, button_col
+ 1, button_row
, button_row
+1,
251 } else if (cui
->display
) {
253 output_table
.attach (*cui
, output_col
, output_col
+ 1, output_row
, output_row
+1,
256 // TODO: The meters should be divided into multiple rows
258 if (++output_col
== output_cols
) {
260 output_table
.resize (output_rows
, output_cols
);
263 /* old code, which divides meters into
264 * columns first, rows later. New code divides into one row
266 if (output_row == output_rows) {
268 if (++output_col == output_cols) {
270 output_table.resize (output_rows, output_cols);
274 output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
281 /* HACK: ideally the preferred height would be queried from
282 the complete hpacker, but I can't seem to get that
283 information in time, so this is an estimation
291 if (box
->children().empty()) {
292 hpacker
.remove (*frame
);
295 if (button_table
.children().empty()) {
296 hpacker
.remove (*bt_frame
);
299 if (!output_table
.children().empty()) {
300 frame
= manage (new Frame
);
301 frame
->set_name ("BaseFrame");
302 frame
->add (output_table
);
303 hpacker
.pack_end (*frame
, true, true);
308 output_table
.show_all ();
309 button_table
.show_all ();
312 GenericPluginUI::ControlUI::ControlUI ()
313 : automate_button (X_("")) // force creation of a label
315 automate_button
.set_name ("PluginAutomateButton");
316 ARDOUR_UI::instance()->set_tip (automate_button
, _("Automation control"));
318 /* XXX translators: use a string here that will be at least as long
319 as the longest automation label (see ::automation_state_changed()
320 below). be sure to include a descender.
323 set_size_request_to_display_given_text (automate_button
, _("Mgnual"), 15, 10);
332 GenericPluginUI::ControlUI::~ControlUI()
335 delete meterinfo
->meter
;
341 GenericPluginUI::automation_state_changed (ControlUI
* cui
)
343 /* update button label */
345 // don't lock to avoid deadlock because we're triggered by
346 // AutomationControl::Changed() while the automation lock is taken
347 switch (insert
->get_parameter_automation_state (cui
->parameter())
348 & (Off
|Play
|Touch
|Write
)) {
350 cui
->automate_button
.set_label (_("Manual"));
353 cui
->automate_button
.set_label (_("Play"));
356 cui
->automate_button
.set_label (_("Write"));
359 cui
->automate_button
.set_label (_("Touch"));
362 cui
->automate_button
.set_label (_("???"));
368 static void integer_printer (char buf
[32], Adjustment
&adj
, void */
*arg*/
)
370 snprintf (buf
, 32, "%.0f", adj
.get_value());
374 GenericPluginUI::print_parameter (char *buf
, uint32_t len
, uint32_t param
)
376 plugin
->print_parameter (param
, buf
, len
);
379 GenericPluginUI::ControlUI
*
380 GenericPluginUI::build_control_ui (guint32 port_index
, boost::shared_ptr
<AutomationControl
> mcontrol
)
382 ControlUI
* control_ui
= 0;
384 Plugin::ParameterDescriptor desc
;
386 plugin
->get_parameter_descriptor (port_index
, desc
);
388 control_ui
= manage (new ControlUI ());
389 control_ui
->combo
= 0;
390 control_ui
->combo_map
= 0;
391 control_ui
->control
= mcontrol
;
392 control_ui
->update_pending
= false;
393 control_ui
->label
.set_text (desc
.label
);
394 control_ui
->label
.set_alignment (0.0, 0.5);
395 control_ui
->label
.set_name ("PluginParameterLabel");
396 control_ui
->port_index
= port_index
;
398 control_ui
->set_spacing (5);
400 Gtk::Requisition
req (control_ui
->automate_button
.size_request());
402 if (plugin
->parameter_is_input(port_index
)) {
404 /* Build a combo box */
406 boost::shared_ptr
<ARDOUR::Plugin::ScalePoints
> points
407 = plugin
->get_scale_points(port_index
);
410 std::vector
<std::string
> labels
;
411 for (ARDOUR::Plugin::ScalePoints::const_iterator i
= points
->begin();
412 i
!= points
->end(); ++i
) {
413 labels
.push_back(i
->first
);
416 control_ui
->combo
= new Gtk::ComboBoxText();
417 set_popdown_strings(*control_ui
->combo
, labels
);
418 control_ui
->combo
->signal_changed().connect(
419 sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::control_combo_changed
),
421 mcontrol
->Changed
.connect(control_connections
, invalidator(*this),
422 boost::bind(&GenericPluginUI::ui_parameter_changed
,
425 control_ui
->pack_start(control_ui
->label
, true, true);
426 control_ui
->pack_start(*control_ui
->combo
, false, true);
428 update_control_display(control_ui
);
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 (sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::control_port_toggled
), control_ui
));
446 control_ui
->automate_button
.signal_clicked().connect (bind (mem_fun(*this, &GenericPluginUI::astate_clicked
), control_ui
, (uint32_t) port_index
));
448 mcontrol
->Changed
.connect (control_connections
, invalidator (*this), boost::bind (&GenericPluginUI::toggle_parameter_changed
, this, control_ui
), gui_context());
449 mcontrol
->alist()->automation_state_changed
.connect (control_connections
, invalidator (*this), boost::bind (&GenericPluginUI::automation_state_changed
, this, control_ui
), gui_context());
451 if (plugin
->get_parameter (port_index
) > 0.5){
452 control_ui
->button
->set_active(true);
455 automation_state_changed (control_ui
);
460 /* create the controller */
463 control_ui
->controller
= AutomationController::create(insert
, mcontrol
->parameter(), mcontrol
);
466 /* XXX this code is not right yet, because it doesn't handle
467 the absence of bounds in any sensible fashion.
470 Adjustment
* adj
= control_ui
->controller
->adjustment();
471 boost::shared_ptr
<PluginInsert::PluginControl
> pc
= boost::dynamic_pointer_cast
<PluginInsert::PluginControl
> (control_ui
->control
);
473 adj
->set_lower (pc
->user_to_ui (desc
.lower
));
474 adj
->set_upper (pc
->user_to_ui (desc
.upper
));
476 adj
->set_step_increment (desc
.step
);
477 adj
->set_page_increment (desc
.largestep
);
479 if (desc
.integer_step
) {
480 control_ui
->clickbox
= new ClickBox (adj
, "PluginUIClickBox");
481 Gtkmm2ext::set_size_request_to_display_given_text (*control_ui
->clickbox
, "g9999999", 2, 2);
482 control_ui
->clickbox
->set_print_func (integer_printer
, 0);
484 //sigc::slot<void,char*,uint32_t> pslot = sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::print_parameter), (uint32_t) port_index);
486 control_ui
->controller
->set_size_request (200, req
.height
);
487 control_ui
->controller
->set_name (X_("PluginSlider"));
488 control_ui
->controller
->set_style (BarController::LeftToRight
);
489 control_ui
->controller
->set_use_parent (true);
490 control_ui
->controller
->set_logarithmic (desc
.logarithmic
);
492 control_ui
->controller
->StartGesture
.connect (sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::start_touch
), control_ui
));
493 control_ui
->controller
->StopGesture
.connect (sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::stop_touch
), control_ui
));
497 adj
->set_value (pc
->plugin_to_ui (plugin
->get_parameter (port_index
)));
499 /* XXX memory leak: SliderController not destroyed by ControlUI
500 destructor, and manage() reports object hierarchy
504 control_ui
->pack_start (control_ui
->label
, true, true);
505 if (desc
.integer_step
) {
506 control_ui
->pack_start (*control_ui
->clickbox
, false, false);
508 control_ui
->pack_start (*control_ui
->controller
, false, false);
511 control_ui
->pack_start (control_ui
->automate_button
, false, false);
512 control_ui
->automate_button
.signal_clicked().connect (sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::astate_clicked
), control_ui
, (uint32_t) port_index
));
514 automation_state_changed (control_ui
);
516 mcontrol
->Changed
.connect (control_connections
, invalidator (*this), boost::bind (&GenericPluginUI::ui_parameter_changed
, this, control_ui
), gui_context());
517 mcontrol
->alist()->automation_state_changed
.connect (control_connections
, invalidator (*this), boost::bind (&GenericPluginUI::automation_state_changed
, this, control_ui
), gui_context());
519 input_controls
.push_back (control_ui
);
521 } else if (plugin
->parameter_is_output (port_index
)) {
523 control_ui
->display
= manage (new EventBox
);
524 control_ui
->display
->set_name ("ParameterValueDisplay");
526 control_ui
->display_label
= manage (new Label
);
528 control_ui
->display_label
->set_name ("ParameterValueDisplay");
530 control_ui
->display
->add (*control_ui
->display_label
);
531 Gtkmm2ext::set_size_request_to_display_given_text (*control_ui
->display
, "-99,99", 2, 2);
533 control_ui
->display
->show_all ();
536 /* TODO: only make a meter if the port is Hinted for it */
538 MeterInfo
* info
= new MeterInfo(port_index
);
539 control_ui
->meterinfo
= info
;
541 info
->meter
= new FastMeter (5, 5, FastMeter::Vertical
);
543 info
->min_unbound
= desc
.min_unbound
;
544 info
->max_unbound
= desc
.max_unbound
;
546 info
->min
= desc
.lower
;
547 info
->max
= desc
.upper
;
549 control_ui
->vbox
= manage (new VBox
);
550 control_ui
->hbox
= manage (new HBox
);
552 control_ui
->label
.set_angle(90);
553 control_ui
->hbox
->pack_start (control_ui
->label
, false, false);
554 control_ui
->hbox
->pack_start (*info
->meter
, false, false);
556 control_ui
->vbox
->pack_start (*control_ui
->hbox
, false, false);
558 control_ui
->vbox
->pack_start (*control_ui
->display
, false, false);
560 control_ui
->pack_start (*control_ui
->vbox
);
562 control_ui
->meterinfo
->meter
->show_all();
563 control_ui
->meterinfo
->packed
= true;
565 output_controls
.push_back (control_ui
);
569 mcontrol
->Changed
.connect (control_connections
, invalidator (*this), boost::bind (&GenericPluginUI::ui_parameter_changed
, this, control_ui
), gui_context());
576 GenericPluginUI::start_touch (GenericPluginUI::ControlUI
* cui
)
578 cui
->control
->start_touch (cui
->control
->session().transport_frame());
582 GenericPluginUI::stop_touch (GenericPluginUI::ControlUI
* cui
)
584 cui
->control
->stop_touch (false, cui
->control
->session().transport_frame());
588 GenericPluginUI::astate_clicked (ControlUI
* cui
, uint32_t /*port*/)
590 using namespace Menu_Helpers
;
592 if (automation_menu
== 0) {
593 automation_menu
= manage (new Menu
);
594 automation_menu
->set_name ("ArdourContextMenu");
597 MenuList
& items (automation_menu
->items());
600 items
.push_back (MenuElem (_("Manual"),
601 sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::set_automation_state
), (AutoState
) Off
, cui
)));
602 items
.push_back (MenuElem (_("Play"),
603 sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::set_automation_state
), (AutoState
) Play
, cui
)));
604 items
.push_back (MenuElem (_("Write"),
605 sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::set_automation_state
), (AutoState
) Write
, cui
)));
606 items
.push_back (MenuElem (_("Touch"),
607 sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::set_automation_state
), (AutoState
) Touch
, cui
)));
609 automation_menu
->popup (1, gtk_get_current_event_time());
613 GenericPluginUI::set_automation_state (AutoState state
, ControlUI
* cui
)
615 insert
->set_parameter_automation_state (cui
->parameter(), state
);
619 GenericPluginUI::toggle_parameter_changed (ControlUI
* cui
)
621 float val
= cui
->control
->get_value();
623 if (!cui
->ignore_change
) {
625 cui
->button
->set_active (true);
627 cui
->button
->set_active (false);
633 GenericPluginUI::ui_parameter_changed (ControlUI
* cui
)
635 if (!cui
->update_pending
) {
636 cui
->update_pending
= true;
637 Gtkmm2ext::UI::instance()->call_slot (MISSING_INVALIDATOR
, boost::bind (&GenericPluginUI::update_control_display
, this, cui
));
642 GenericPluginUI::update_control_display (ControlUI
* cui
)
644 /* XXX how do we handle logarithmic stuff here ? */
646 cui
->update_pending
= false;
648 float val
= cui
->control
->get_value();
650 cui
->ignore_change
++;
652 if (cui
->combo
&& cui
->combo_map
) {
653 std::map
<string
,float>::iterator it
;
654 for (it
= cui
->combo_map
->begin(); it
!= cui
->combo_map
->end(); ++it
) {
655 if (it
->second
== val
) {
656 cui
->combo
->set_active_text(it
->first
);
660 } else if (cui
->button
) {
663 cui
->button
->set_active (true);
665 cui
->button
->set_active (false);
669 if( cui
->controller
) {
670 cui
->controller
->display_effective_value();
675 if (cui->logarithmic) {
678 if (val != cui->adjustment->get_value()) {
679 cui->adjustment->set_value (val);
682 cui
->ignore_change
--;
686 GenericPluginUI::control_port_toggled (ControlUI
* cui
)
688 cui
->ignore_change
++;
689 insert
->automation_control (cui
->parameter())->set_value (cui
->button
->get_active());
690 cui
->ignore_change
--;
694 GenericPluginUI::control_combo_changed (ControlUI
* cui
)
696 if (!cui
->ignore_change
&& cui
->combo_map
) {
697 string value
= cui
->combo
->get_active_text();
698 std::map
<string
,float> mapping
= *cui
->combo_map
;
699 insert
->automation_control(cui
->parameter())->set_value(mapping
[value
]);
704 GenericPluginUI::processor_active_changed (boost::weak_ptr
<Processor
> weak_processor
)
706 ENSURE_GUI_THREAD (*this, &GenericPluginUI::processor_active_changed
, weak_processor
)
708 boost::shared_ptr
<Processor
> processor
= weak_processor
.lock();
710 bypass_button
.set_active (!processor
|| !processor
->active());
714 GenericPluginUI::start_updating (GdkEventAny
*)
716 if (output_controls
.size() > 0 ) {
717 screen_update_connection
.disconnect();
718 screen_update_connection
= ARDOUR_UI::instance()->RapidScreenUpdate
.connect
719 (sigc::mem_fun(*this, &GenericPluginUI::output_update
));
725 GenericPluginUI::stop_updating (GdkEventAny
*)
727 for (vector
<ControlUI
*>::iterator i
= input_controls
.begin(); i
!= input_controls
.end(); ++i
) {
728 (*i
)->controller
->stop_updating ();
731 if (output_controls
.size() > 0 ) {
732 screen_update_connection
.disconnect();
739 GenericPluginUI::output_update ()
741 for (vector
<ControlUI
*>::iterator i
= output_controls
.begin(); i
!= output_controls
.end(); ++i
) {
742 float val
= plugin
->get_parameter ((*i
)->port_index
);
744 snprintf (buf
, sizeof(buf
), "%.2f", val
);
745 (*i
)->display_label
->set_text (buf
);
747 /* autoscaling for the meter */
748 if ((*i
)->meterinfo
&& (*i
)->meterinfo
->packed
) {
750 if (val
< (*i
)->meterinfo
->min
) {
751 if ((*i
)->meterinfo
->min_unbound
)
752 (*i
)->meterinfo
->min
= val
;
754 val
= (*i
)->meterinfo
->min
;
757 if (val
> (*i
)->meterinfo
->max
) {
758 if ((*i
)->meterinfo
->max_unbound
)
759 (*i
)->meterinfo
->max
= val
;
761 val
= (*i
)->meterinfo
->max
;
764 if ((*i
)->meterinfo
->max
> (*i
)->meterinfo
->min
) {
765 float lval
= (val
- (*i
)->meterinfo
->min
) / ((*i
)->meterinfo
->max
- (*i
)->meterinfo
->min
) ;
766 (*i
)->meterinfo
->meter
->set (lval
);