2 Copyright (C) 2018-2019 Len Ovens
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.
23 #include <sigc++/bind.h>
25 #include <gtkmm/messagedialog.h>
27 #include "pbd/convert.h"
28 #include "pbd/enumwriter.h"
29 #include "pbd/replace_all.h"
30 #include "pbd/stacktrace.h"
32 #include "ardour/amp.h"
33 #include "ardour/audioengine.h"
34 #include "ardour/internal_send.h"
35 #include "ardour/internal_return.h"
36 #include "ardour/io.h"
37 #include "ardour/io_processor.h"
38 #include "ardour/pannable.h"
39 #include "ardour/panner.h"
40 #include "ardour/panner_shell.h"
41 #include "ardour/panner_manager.h"
42 #include "ardour/port.h"
43 #include "ardour/profile.h"
44 #include "ardour/route.h"
45 #include "ardour/send.h"
46 #include "ardour/session.h"
47 #include "ardour/types.h"
48 #include "ardour/user_bundle.h"
49 #include "ardour/value_as_string.h"
51 #include "gtkmm2ext/gtk_ui.h"
52 #include "gtkmm2ext/menu_elems.h"
53 #include "gtkmm2ext/utils.h"
54 #include "gtkmm2ext/doi.h"
56 #include "widgets/tooltips.h"
58 #include "ardour_window.h"
59 #include "enums_convert.h"
60 #include "foldback_strip.h"
63 #include "public_editor.h"
65 #include "io_selector.h"
67 #include "gui_thread.h"
68 #include "ui_config.h"
72 using namespace ARDOUR;
73 using namespace ArdourWidgets;
76 using namespace Gtkmm2ext;
79 #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
81 FoldbackSend::FoldbackSend (boost::shared_ptr<Send> snd, \
82 boost::shared_ptr<ARDOUR::Route> sr, boost::shared_ptr<ARDOUR::Route> fr)
83 : _button (ArdourButton::led_default_elements)
86 , _foldback_route (fr)
89 , pan_control (ArdourKnob::default_elements, ArdourKnob::Flags (ArdourKnob::Detent | ArdourKnob::ArcToZero))
90 , _adjustment (gain_to_slider_position_with_max (1.0, Config->get_max_gain()), 0, 1, 0.01, 0.1)
91 , _slider (&_adjustment, boost::shared_ptr<PBD::Controllable>(), 0, max(13.f, rintf(13.f * UIConfiguration::instance().get_ui_scale())))
92 , _ignore_ui_adjustment (true)
93 , _slider_persistant_tooltip (&_slider)
97 HBox * snd_but_pan = new HBox ();
99 _button.set_distinct_led_click (true);
100 _button.set_fallthrough_to_parent(true);
101 _button.set_led_left (true);
102 _button.signal_led_clicked.connect (sigc::mem_fun (*this, &FoldbackSend::led_clicked));
103 _button.set_name ("processor prefader");
104 _button.set_text (_send_route->name());
105 snd_but_pan->pack_start (_button, true, true);
106 _button.set_active (_send_proc->enabled ());
109 if (_foldback_route->input()->n_ports().n_audio() == 2) {
110 boost::shared_ptr<Pannable> pannable = _send_del->panner()->pannable();
111 boost::shared_ptr<AutomationControl> ac;
112 ac = pannable->pan_azimuth_control;
113 pan_control.set_size_request (PX_SCALE(19), PX_SCALE(19));
114 pan_control.set_tooltip_prefix (_("Pan: "));
115 pan_control.set_name ("trim knob");
116 pan_control.set_no_show_all (true);
117 snd_but_pan->pack_start (pan_control, false, false);
119 pan_control.set_controllable (ac);
121 boost::shared_ptr<AutomationControl> lc;
122 lc = _send->gain_control();
123 _slider.set_controllable (lc);
124 _slider.set_name ("ProcessorControlSlider");
125 _slider.set_text (_("Level"));
129 pack_start (*snd_but_pan, Gtk::PACK_SHRINK);
131 pack_start (_slider, true, true);
134 _adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &FoldbackSend::level_adjusted));
135 lc->Changed.connect (_connections, invalidator (*this), boost::bind (&FoldbackSend::level_changed, this), gui_context ());
136 _send_proc->ActiveChanged.connect (_connections, invalidator (*this), boost::bind (&FoldbackSend::send_state_changed, this), gui_context ());
143 FoldbackSend::~FoldbackSend ()
145 _send = boost::shared_ptr<Send> ();
146 _send_route = boost::shared_ptr<Route> ();
147 _foldback_route = boost::shared_ptr<Route> ();
148 _send_proc = boost::shared_ptr<Processor> ();
149 _send_del = boost::shared_ptr<Delivery> ();
150 _connections.drop_connections();
151 pan_control.set_controllable (boost::shared_ptr<AutomationControl> ());
152 _slider.set_controllable (boost::shared_ptr<AutomationControl> ());
157 FoldbackSend::led_clicked(GdkEventButton *ev)
160 if (_button.get_active ()) {
161 _send_proc->enable (false);
164 _send_proc->enable (true);
170 FoldbackSend::send_state_changed ()
172 _button.set_active (_send_proc->enabled ());
177 FoldbackSend::level_adjusted ()
179 if (_ignore_ui_adjustment) {
182 boost::shared_ptr<AutomationControl> lc = _send->gain_control();
188 lc->set_value ( lc->interface_to_internal(_adjustment.get_value ()) , Controllable::NoGroup);
193 FoldbackSend::level_changed ()
195 boost::shared_ptr<AutomationControl> lc = _send->gain_control();
200 _ignore_ui_adjustment = true;
202 const double nval = lc->internal_to_interface (lc->get_value ());
203 if (_adjustment.get_value() != nval) {
204 _adjustment.set_value (nval);
208 _ignore_ui_adjustment = false;
212 FoldbackSend::set_tooltip ()
214 boost::shared_ptr<AutomationControl> lc = _send->gain_control();
219 std::string tt = ARDOUR::value_as_string (lc->desc(), lc->get_value ());
220 string sm = Gtkmm2ext::markup_escape_text (tt);
221 _slider_persistant_tooltip.set_tip (sm);
222 ArdourWidgets::set_tooltip (_button, Gtkmm2ext::markup_escape_text (sm));
226 FoldbackStrip* FoldbackStrip::_entered_foldback_strip;
227 PBD::Signal1<void,FoldbackStrip*> FoldbackStrip::CatchDeletion;
229 FoldbackStrip::FoldbackStrip (Mixer_UI& mx, Session* sess, boost::shared_ptr<Route> rt)
230 : SessionHandlePtr (sess)
233 , _mixer_owned (true)
236 , mute_solo_table (1, 2)
237 , bottom_button_table (1, 1)
238 , _plugin_insert_cnt (0)
239 , _comment_button (_("Comments"))
240 , fb_level_control (0)
248 FoldbackStrip::init ()
250 _entered_foldback_strip= 0;
252 route_select_menu = 0;
253 ignore_comment_edit = false;
254 ignore_toggle = false;
258 /* the length of this string determines the width of the mixer strip when it is set to `wide' */
259 longest_label = "longest label";
261 output_button.set_text (_("Output"));
262 output_button.set_name ("mixer strip button");
264 send_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
265 send_scroller.add (send_display);
267 send_display.set_flags (CAN_FOCUS);
268 //send_display.set_name ("ProcessorList");
269 send_display.set_name ("AudioBusStripBase");
270 send_display.set_spacing (4);
272 insert_box = new ProcessorBox (0, boost::bind (&FoldbackStrip::plugin_selector, this), _pr_selection, 0);
273 insert_box->set_no_show_all ();
275 insert_box->set_session (_session);
277 send_scroller.show ();
278 send_display.show ();
280 fb_level_control = new ArdourKnob (ArdourKnob::default_elements, ArdourKnob::Detent);
281 fb_level_control->set_size_request (PX_SCALE(65), PX_SCALE(65));
282 fb_level_control->set_tooltip_prefix (_("Level: "));
283 fb_level_control->set_name ("monitor section knob");
284 fb_level_control->set_no_show_all (true);
286 bottom_button_table.attach (*fb_level_control, 0, 1, 0, 1,FILL,FILL,20,20); //EXPAND
287 bottom_button_table.set_spacings (20);
288 bottom_button_table.set_row_spacings (20);
289 bottom_button_table.set_homogeneous (true);
291 mute_solo_table.set_homogeneous (true);
292 mute_solo_table.set_spacings (2);
294 show_sends_button->set_text (_("Show Sends"));
296 show_sends_box.pack_start (*show_sends_button, true, true);
297 show_sends_button->show();
300 name_button.set_name ("mixer strip button");
301 name_button.set_text_ellipsize (Pango::ELLIPSIZE_END);
303 _select_button.set_name ("mixer strip button");
304 _select_button.set_text (_("Select Foldback Bus"));
306 _comment_button.set_name (X_("mixer strip button"));
307 _comment_button.set_text_ellipsize (Pango::ELLIPSIZE_END);
308 _comment_button.signal_clicked.connect (sigc::mem_fun (*this, &RouteUI::toggle_comment_editor));
310 global_vpacker.set_border_width (1);
311 global_vpacker.set_spacing (4);
313 global_vpacker.pack_start (_select_button, Gtk::PACK_SHRINK);
314 global_vpacker.pack_start (name_button, Gtk::PACK_SHRINK);
315 global_vpacker.pack_start (_invert_button_box, Gtk::PACK_SHRINK);
316 global_vpacker.pack_start (show_sends_box, Gtk::PACK_SHRINK);
317 global_vpacker.pack_start (send_scroller, true, true);
319 //add a spacer underneath the foldback bus;
320 //this fills the area that is taken up by the scrollbar on the tracks;
321 //and therefore keeps the strip boxes "even" across the bottom
322 int scrollbar_height = 0;
324 Gtk::Window window (WINDOW_TOPLEVEL);
325 HScrollbar scrollbar;
326 window.add (scrollbar);
327 scrollbar.set_name ("MixerWindow");
328 scrollbar.ensure_style();
329 Gtk::Requisition requisition(scrollbar.size_request ());
330 scrollbar_height = requisition.height;
332 spacer.set_size_request (-1, scrollbar_height);
333 global_vpacker.pack_end (spacer, false, false);
335 global_vpacker.pack_end (_comment_button, Gtk::PACK_SHRINK);
336 global_vpacker.pack_end (output_button, Gtk::PACK_SHRINK);
337 global_vpacker.pack_end (*insert_box, Gtk::PACK_SHRINK);
338 global_vpacker.pack_end (bottom_button_table, Gtk::PACK_SHRINK);
339 global_vpacker.pack_end (mute_solo_table, Gtk::PACK_SHRINK);
340 global_vpacker.pack_end (panners, Gtk::PACK_SHRINK);
342 global_frame.add (global_vpacker);
343 global_frame.set_shadow_type (Gtk::SHADOW_IN);
344 global_frame.set_name ("BaseFrame");
348 /* force setting of visible selected status */
351 set_selected (false);
355 _session->engine().Stopped.connect (*this, invalidator (*this), boost::bind (&FoldbackStrip::engine_stopped, this), gui_context());
356 _session->engine().Running.connect (*this, invalidator (*this), boost::bind (&FoldbackStrip::engine_running, this), gui_context());
358 output_button.set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE);
359 output_button.signal_button_press_event().connect (sigc::mem_fun(*this, &FoldbackStrip::output_press), false);
360 output_button.signal_button_release_event().connect (sigc::mem_fun(*this, &FoldbackStrip::output_release), false);
362 name_button.signal_button_press_event().connect (sigc::mem_fun(*this, &FoldbackStrip::name_button_button_press), false);
363 _select_button.signal_button_press_event().connect (sigc::mem_fun (*this, &FoldbackStrip::select_button_button_press), false);
367 add_events (Gdk::BUTTON_RELEASE_MASK|
368 Gdk::ENTER_NOTIFY_MASK|
369 Gdk::LEAVE_NOTIFY_MASK|
371 Gdk::KEY_RELEASE_MASK);
373 set_flags (get_flags() | Gtk::CAN_FOCUS);
375 AudioEngine::instance()->PortConnectedOrDisconnected.connect (
376 *this, invalidator (*this), boost::bind (&FoldbackStrip::port_connected_or_disconnected, this, _1, _3), gui_context ()
379 //watch for mouse enter/exit so we can do some stuff
380 signal_enter_notify_event().connect (sigc::mem_fun(*this, &FoldbackStrip::mixer_strip_enter_event ));
381 signal_leave_notify_event().connect (sigc::mem_fun(*this, &FoldbackStrip::mixer_strip_leave_event ));
385 FoldbackStrip::~FoldbackStrip ()
387 CatchDeletion (this);
388 delete fb_level_control;
389 fb_level_control = 0;
390 _connections.drop_connections();
393 if (this ==_entered_foldback_strip)
394 _entered_foldback_strip = NULL;
398 FoldbackStrip::mixer_strip_enter_event (GdkEventCrossing* /*ev*/)
400 _entered_foldback_strip = this;
402 //although we are triggering on the "enter", to the user it will appear that it is happenin on the "leave"
403 //because the FoldbackStrip control is a parent that encompasses the strip
404 deselect_all_processors();
410 FoldbackStrip::mixer_strip_leave_event (GdkEventCrossing *ev)
412 //if we have moved outside our strip, but not into a child view, then deselect ourselves
413 if ( !(ev->detail == GDK_NOTIFY_INFERIOR) ) {
414 _entered_foldback_strip= 0;
422 FoldbackStrip::name() const
425 return _route->name();
431 FoldbackStrip::update_fb_level_control ()
433 fb_level_control->show ();
434 fb_level_control->set_controllable (_route->gain_control());
438 FoldbackStrip::set_route (boost::shared_ptr<Route> rt)
443 RouteUI::self_delete ();
448 RouteUI::set_route (rt);
450 insert_box->set_route (_route);
451 revert_to_default_display ();
452 if (solo_button->get_parent()) {
453 mute_solo_table.remove (*solo_button);
456 if (mute_button->get_parent()) {
457 mute_solo_table.remove (*mute_button);
460 mute_solo_table.attach (*mute_button, 0, 1, 0, 1);
461 mute_solo_table.attach (*solo_button, 1, 2, 0, 1);
462 mute_button->show ();
463 solo_button->show ();
464 show_sends_box.show ();
466 update_fb_level_control();
468 BusSendDisplayChanged (boost::shared_ptr<Route> ());
469 show_sends_button->show();
471 delete route_ops_menu;
473 delete route_select_menu;
474 route_select_menu = 0;
476 _route->output()->changed.connect (*this, invalidator (*this), boost::bind (&FoldbackStrip::update_output_display, this), gui_context());
477 _route->io_changed.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::io_changed_proxy, this), gui_context ());
479 if (_route->panner_shell()) {
480 update_panner_choices();
481 _route->panner_shell()->Changed.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::connect_to_pan, this), gui_context());
484 _route->comment_changed.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::setup_comment_button, this), gui_context());
486 set_stuff_from_route ();
488 /* now force an update of all the various elements */
490 update_mute_display ();
491 update_solo_display ();
495 panners.setup_pan ();
497 update_output_display ();
499 add_events (Gdk::BUTTON_RELEASE_MASK);
503 _session->FBSendsChanged.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::update_send_box, this), gui_context());
506 global_vpacker.show();
507 mute_solo_table.show();
508 bottom_button_table.show();
509 show_sends_box.show_all();
510 //send_scroller.show ();
511 send_display.show ();
512 output_button.show();
514 _select_button.show();
515 _comment_button.show();
523 FoldbackStrip::update_send_box ()
529 Route::FedBy fed_by = _route->fed_by();
530 for (Route::FedBy::iterator i = fed_by.begin(); i != fed_by.end(); ++i) {
532 boost::shared_ptr<Route> s_rt (i->r.lock());
533 boost::shared_ptr<Send> snd = s_rt->internal_send_for (_route);
535 FoldbackSend * fb_s = new FoldbackSend (snd, s_rt, _route);
536 send_display.pack_start (*fb_s, Gtk::PACK_SHRINK);
538 s_rt->processors_changed.connect (_connections, invalidator (*this), boost::bind (&FoldbackStrip::processors_changed, this, _1), gui_context ());
545 FoldbackStrip::clear_send_box ()
547 std::vector< Widget* > snd_list = send_display.get_children ();
548 _connections.drop_connections ();
549 for (uint32_t i = 0; i < snd_list.size(); i++) {
550 send_display.remove (*(snd_list[i]));
557 FoldbackStrip::processors_changed (RouteProcessorChange)
563 FoldbackStrip::set_stuff_from_route ()
565 /* if width is not set, it will be set by the MixerUI or editor */
568 if (get_gui_property ("strip-width", width)) {
569 // set_width_enum (width, this);
574 FoldbackStrip::set_packed (bool yn)
577 set_gui_property ("visible", _packed);
581 struct RouteCompareByName {
582 bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
583 return a->name().compare (b->name()) < 0;
588 FoldbackStrip::output_release (GdkEventButton *ev)
590 switch (ev->button) {
592 edit_output_configuration ();
600 FoldbackStrip::output_press (GdkEventButton *ev)
602 using namespace Menu_Helpers;
603 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
607 MenuList& citems = output_menu.items();
608 switch (ev->button) {
611 return false; //wait for the mouse-up to pop the dialog
615 output_menu.set_name ("ArdourContextMenu");
617 output_menu_bundles.clear ();
619 citems.push_back (MenuElem (_("Disconnect"), sigc::mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_output)));
621 citems.push_back (SeparatorElem());
622 uint32_t const n_with_separator = citems.size ();
624 ARDOUR::BundleList current = _route->output()->bundles_connected ();
626 boost::shared_ptr<ARDOUR::BundleList> b = _session->bundles ();
628 /* guess the user-intended main type of the route output */
629 DataType intended_type = guess_main_type(false);
631 /* try adding the master bus first */
632 boost::shared_ptr<Route> master = _session->master_out();
634 maybe_add_bundle_to_output_menu (master->input()->bundle(), current, intended_type);
637 /* then other routes inputs */
638 RouteList copy = _session->get_routelist ();
639 copy.sort (RouteCompareByName ());
640 for (ARDOUR::RouteList::const_iterator i = copy.begin(); i != copy.end(); ++i) {
641 maybe_add_bundle_to_output_menu ((*i)->input()->bundle(), current, intended_type);
644 /* then try adding user bundles, often labeled/grouped physical inputs */
645 for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) {
646 if (boost::dynamic_pointer_cast<UserBundle> (*i)) {
647 maybe_add_bundle_to_output_menu (*i, current, intended_type);
651 /* then all other bundles, including physical outs or other sofware */
652 for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) {
653 if (boost::dynamic_pointer_cast<UserBundle> (*i) == 0) {
654 maybe_add_bundle_to_output_menu (*i, current, intended_type);
658 if (citems.size() == n_with_separator) {
659 /* no routes added; remove the separator */
663 if (!ARDOUR::Profile->get_mixbus()) {
664 citems.push_back (SeparatorElem());
666 for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
669 string_compose (_("Add %1 port"), (*i).to_i18n_string()),
670 sigc::bind (sigc::mem_fun (*this, &FoldbackStrip::add_output_port), *i)
676 citems.push_back (SeparatorElem());
677 citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::edit_output_configuration)));
679 Gtkmm2ext::anchored_menu_popup(&output_menu, &output_button, "",
692 FoldbackStrip::bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle> c)
698 _route->output()->connect_ports_to_bundle (c, true, true, this);
702 FoldbackStrip::maybe_add_bundle_to_output_menu (boost::shared_ptr<Bundle> b, ARDOUR::BundleList const& /*current*/,
705 using namespace Menu_Helpers;
707 /* The bundle should be an input one, but not ours */
708 if (b->ports_are_inputs() == false || *b == *_route->input()->bundle()) {
712 /* Don't add the monitor input unless we are Master */
713 boost::shared_ptr<Route> monitor = _session->monitor_out();
714 if ((!_route->is_master()) && monitor && b->has_same_ports (monitor->input()->bundle()))
717 /* It should either match exactly our outputs (if |type| is DataType::NIL)
718 * or have the same number of |type| channels than our outputs. */
719 if (type == DataType::NIL) {
720 if(b->nchannels() != _route->n_outputs())
723 if (b->nchannels().n(type) != _route->n_outputs().n(type))
727 /* Avoid adding duplicates */
728 list<boost::shared_ptr<Bundle> >::iterator i = output_menu_bundles.begin ();
729 while (i != output_menu_bundles.end() && b->has_same_ports (*i) == false) {
732 if (i != output_menu_bundles.end()) {
736 /* Now add the bundle to the menu */
737 output_menu_bundles.push_back (b);
739 MenuList& citems = output_menu.items();
740 citems.push_back (MenuElemNoMnemonic (b->name (), sigc::bind (sigc::mem_fun(*this, &FoldbackStrip::bundle_output_chosen), b)));
744 FoldbackStrip::connect_to_pan ()
746 ENSURE_GUI_THREAD (*this, &FoldbackStrip::connect_to_pan)
748 panstate_connection.disconnect ();
749 panstyle_connection.disconnect ();
751 if (!_route->panner()) {
755 boost::shared_ptr<Pannable> p = _route->pannable ();
757 //p->automation_state_changed.connect (panstate_connection, invalidator (*this), boost::bind (&PannerUI::pan_automation_state_changed, &panners), gui_context());
759 /* This call reduncant, PannerUI::set_panner() connects to _panshell->Changed itself
760 * However, that only works a panner was previously set.
762 * PannerUI must remain subscribed to _panshell->Changed() in case
763 * we switch the panner eg. AUX-Send and back
764 * _route->panner_shell()->Changed() vs _panshell->Changed
766 /*if (panners._panner == 0) {
767 panners.panshell_changed ();
769 update_panner_choices();
773 FoldbackStrip::update_panner_choices ()
775 ENSURE_GUI_THREAD (*this, &FoldbackStrip::update_panner_choices)
776 if (!_route->panner_shell()) { return; }
778 uint32_t in = _route->output()->n_ports().n_audio();
780 if (_route->panner()) {
781 in = _route->panner()->in().n_audio();
784 panners.set_available_panners(PannerManager::instance().PannerManager::get_available_panners(in, out));
788 FoldbackStrip::guess_main_type(bool for_input, bool favor_connected) const
790 /* The heuristic follows these principles:
791 * A) If all ports that the user connected are of the same type, then he
792 * very probably intends to use the IO with that type. A common subcase
793 * is when the IO has only ports of the same type (connected or not).
794 * B) If several types of ports are connected, then we should guess based
795 * on the likeliness of the user wanting to use a given type.
796 * We assume that the DataTypes are ordered from the most likely to the
797 * least likely when iterating or comparing them with "<".
798 * C) If no port is connected, the same logic can be applied with all ports
799 * instead of connected ones. TODO: Try other ideas, for instance look at
800 * the last plugin output when |for_input| is false (note: when StrictIO
801 * the outs of the last plugin should be the same as the outs of the route
802 * modulo the panner which forwards non-audio anyway).
803 * All of these constraints are respected by the following algorithm that
804 * just returns the most likely datatype found in connected ports if any, or
805 * available ports if any (since if all ports are of the same type, the most
806 * likely found will be that one obviously). */
808 boost::shared_ptr<IO> io = for_input ? _route->input() : _route->output();
810 /* Find most likely type among connected ports */
811 if (favor_connected) {
812 DataType type = DataType::NIL; /* NIL is always last so least likely */
813 for (PortSet::iterator p = io->ports().begin(); p != io->ports().end(); ++p) {
814 if (p->connected() && p->type() < type)
817 if (type != DataType::NIL) {
818 /* There has been a connected port (necessarily non-NIL) */
823 /* Find most likely type among available ports.
824 * The iterator stops before NIL. */
825 for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
826 if (io->n_ports().n(*t) > 0)
830 /* No port at all, return the most likely datatype by default */
831 return DataType::front();
835 * Output port labelling
837 * Case 1: Each output has one connection, all connections are to system:playback_%i
838 * out 1 -> system:playback_1
839 * out 2 -> system:playback_2
840 * out 3 -> system:playback_3
843 * Case 2: Each output has one connection, all connections are to ardour:track_x/in 1
844 * out 1 -> ardour:track_x/in 1
845 * out 2 -> ardour:track_x/in 2
846 * Display as: track_x
848 * Case 3: Each output has one connection, all connections are to Jack client "program x"
849 * out 1 -> program x:foo
850 * out 2 -> program x:foo
851 * Display as: program x
853 * Case 4: No connections (Disconnected)
856 * Default case (unusual routing):
857 * Display as: *number of connections*
862 * .-----------------------------------------------.
864 * | out 1 -> ardour:master/in 1, jamin:input/in 1 |
865 * | out 2 -> ardour:master/in 2, jamin:input/in 2 |
866 * '-----------------------------------------------'
867 * .-----------------------------------------------.
870 * '-----------------------------------------------'
874 FoldbackStrip::update_io_button ()
876 ostringstream tooltip;
878 bool have_label = false;
880 uint32_t total_connection_count = 0;
881 uint32_t typed_connection_count = 0;
882 bool each_typed_port_has_one_connection = true;
884 DataType dt = guess_main_type(false);
885 boost::shared_ptr<IO> io = _route->output();
887 /* Fill in the tooltip. Also count:
888 * - The total number of connections.
889 * - The number of main-typed connections.
890 * - Whether each main-typed port has exactly one connection. */
892 tooltip << string_compose (_("<b>OUTPUT</b> from %1"),
893 Gtkmm2ext::markup_escape_text (_route->name()));
895 string arrow = Gtkmm2ext::markup_escape_text(" -> ");
896 vector<string> port_connections;
897 for (PortSet::iterator port = io->ports().begin();
898 port != io->ports().end();
900 port_connections.clear();
901 port->get_connections(port_connections);
903 uint32_t port_connection_count = 0;
905 for (vector<string>::iterator i = port_connections.begin();
906 i != port_connections.end();
908 ++port_connection_count;
910 if (port_connection_count == 1) {
911 tooltip << endl << Gtkmm2ext::markup_escape_text (
912 port->name().substr(port->name().find("/") + 1));
918 tooltip << Gtkmm2ext::markup_escape_text(*i);
921 total_connection_count += port_connection_count;
922 if (port->type() == dt) {
923 typed_connection_count += port_connection_count;
924 each_typed_port_has_one_connection &= (port_connection_count == 1);
929 if (total_connection_count == 0) {
930 tooltip << endl << _("Disconnected");
933 if (typed_connection_count == 0) {
938 /* Are all main-typed channels connected to the same route ? */
940 boost::shared_ptr<ARDOUR::RouteList> routes = _session->get_routes ();
941 for (ARDOUR::RouteList::const_iterator route = routes->begin();
942 route != routes->end();
944 boost::shared_ptr<IO> dest_io = (*route)->output();
945 if (io->bundle()->connected_to(dest_io->bundle(),
948 label << Gtkmm2ext::markup_escape_text ((*route)->name());
955 /* Are all main-typed channels connected to the same (user) bundle ? */
957 boost::shared_ptr<ARDOUR::BundleList> bundles = _session->bundles ();
958 for (ARDOUR::BundleList::iterator bundle = bundles->begin();
959 bundle != bundles->end();
961 if (boost::dynamic_pointer_cast<UserBundle> (*bundle) == 0)
963 if (io->bundle()->connected_to(*bundle, _session->engine(),
965 label << Gtkmm2ext::markup_escape_text ((*bundle)->name());
972 /* Is each main-typed channel only connected to a physical output ? */
973 if (!have_label && each_typed_port_has_one_connection) {
974 ostringstream temp_label;
976 string playorcapture;
978 _session->engine().get_physical_outputs(dt, phys);
979 playorcapture = "playback_";
980 for (PortSet::iterator port = io->ports().begin(dt);
981 port != io->ports().end(dt);
984 for (vector<string>::iterator s = phys.begin();
987 if (!port->connected_to(*s))
989 pn = AudioEngine::instance()->get_pretty_name_by_name(*s);
991 string::size_type start = (*s).find(playorcapture);
992 if (start != string::npos) {
993 pn = (*s).substr(start + playorcapture.size());
999 temp_label.str(""); /* erase the failed attempt */
1002 if (port != io->ports().begin(dt))
1007 if (!temp_label.str().empty()) {
1008 label << temp_label.str();
1013 /* Is each main-typed channel connected to a single and different port with
1014 * the same client name (e.g. another JACK client) ? */
1015 if (!have_label && each_typed_port_has_one_connection) {
1016 string maybe_client = "";
1017 vector<string> connections;
1018 for (PortSet::iterator port = io->ports().begin(dt);
1019 port != io->ports().end(dt);
1021 port_connections.clear();
1022 port->get_connections(port_connections);
1023 string connection = port_connections.front();
1025 vector<string>::iterator i = connections.begin();
1026 while (i != connections.end() && *i != connection) {
1029 if (i != connections.end())
1030 break; /* duplicate connection */
1031 connections.push_back(connection);
1033 connection = connection.substr(0, connection.find(":"));
1034 if (maybe_client.empty())
1035 maybe_client = connection;
1036 if (maybe_client != connection)
1039 if (connections.size() == io->n_ports().n(dt)) {
1040 label << maybe_client;
1045 /* Odd configuration */
1047 label << "*" << total_connection_count << "*";
1050 if (total_connection_count > typed_connection_count) {
1051 label << "\u2295"; /* circled plus */
1054 /* Actually set the properties of the button */
1055 char * cstr = new char[tooltip.str().size() + 1];
1056 strcpy(cstr, tooltip.str().c_str());
1058 output_button.set_text (label.str());
1059 set_tooltip (&output_button, cstr);
1065 FoldbackStrip::update_output_display ()
1067 update_io_button ();
1068 panners.setup_pan ();
1070 if (has_audio_outputs ()) {
1071 panners.show_all ();
1073 panners.hide_all ();
1078 FoldbackStrip::io_changed_proxy ()
1080 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &FoldbackStrip::update_panner_choices));
1084 FoldbackStrip::port_connected_or_disconnected (boost::weak_ptr<Port> wa, boost::weak_ptr<Port> wb)
1086 boost::shared_ptr<Port> a = wa.lock ();
1087 boost::shared_ptr<Port> b = wb.lock ();
1089 if ((a && _route->output()->has_port (a)) || (b && _route->output()->has_port (b))) {
1090 update_output_display ();
1095 FoldbackStrip::setup_comment_button ()
1097 std::string comment = _route->comment();
1099 set_tooltip (_comment_button, comment.empty() ? _("Click to add/edit comments") : _route->comment());
1101 if (comment.empty ()) {
1102 _comment_button.set_name ("generic button");
1103 _comment_button.set_text (_("Comments"));
1107 _comment_button.set_name ("comment button");
1109 string::size_type pos = comment.find_first_of (" \t\n");
1110 if (pos != string::npos) {
1111 comment = comment.substr (0, pos);
1113 if (comment.empty()) {
1114 _comment_button.set_text (_("Comments"));
1116 _comment_button.set_text (comment);
1121 FoldbackStrip::show_passthru_color ()
1123 //reset_strip_style ();
1128 FoldbackStrip::help_count_plugins (boost::weak_ptr<Processor> p)
1130 boost::shared_ptr<Processor> processor (p.lock ());
1131 if (!processor || !processor->display_to_user()) {
1134 boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (processor);
1136 if (pi && pi->is_channelstrip ()) {
1141 ++_plugin_insert_cnt;
1145 FoldbackStrip::build_route_ops_menu ()
1147 using namespace Menu_Helpers;
1148 route_ops_menu = new Menu;
1149 route_ops_menu->set_name ("ArdourContextMenu");
1151 MenuList& items = route_ops_menu->items();
1153 items.push_back (MenuElem (_("Comments..."), sigc::mem_fun (*this, &RouteUI::open_comment_editor)));
1155 items.push_back (MenuElem (_("Outputs..."), sigc::mem_fun (*this, &RouteUI::edit_output_configuration)));
1157 items.push_back (SeparatorElem());
1159 items.push_back (MenuElem (_("Save As Template..."), sigc::mem_fun(*this, &RouteUI::save_as_template)));
1161 items.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteUI::route_rename)));
1163 items.push_back (SeparatorElem());
1164 items.push_back (CheckMenuElem (_("Active")));
1165 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem *> (&items.back());
1166 i->set_active (_route->active());
1167 i->set_sensitive(! _session->transport_rolling());
1168 i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteUI::set_route_active), !_route->active(), false));
1170 items.push_back (SeparatorElem());
1171 items.push_back (CheckMenuElem (_("Protect Against Denormals"), sigc::mem_fun (*this, &RouteUI::toggle_denormal_protection)));
1172 denormal_menu_item = dynamic_cast<Gtk::CheckMenuItem *> (&items.back());
1173 denormal_menu_item->set_active (_route->denormal_protection());
1175 items.push_back (SeparatorElem());
1176 items.push_back (MenuElem (_("Remove"), sigc::mem_fun(*this, &FoldbackStrip::remove_current_fb)));
1180 FoldbackStrip::build_route_select_menu ()
1182 using namespace Menu_Helpers;
1183 route_select_menu = new Menu;
1184 route_select_menu->set_name ("ArdourContextMenu");
1186 MenuList& items = route_select_menu->items();
1187 StripableList fb_list;
1188 _session->get_stripables (fb_list, PresentationInfo::FoldbackBus);
1189 for (StripableList::iterator s = fb_list.begin(); s != fb_list.end(); ++s) {
1191 boost::shared_ptr<Route> route = boost::dynamic_pointer_cast<Route> ((*s));
1192 if (route == _route) {
1195 items.push_back (MenuElem (route->name (), sigc::bind (sigc::mem_fun (*this, &FoldbackStrip::set_route), route)));
1202 FoldbackStrip::name_button_button_press (GdkEventButton* ev)
1204 if (ev->button == 1 || ev->button == 3) {
1205 list_route_operations ();
1207 if (ev->button == 1) {
1208 Gtkmm2ext::anchored_menu_popup(route_ops_menu, &name_button, "",
1211 route_ops_menu->popup (3, ev->time);
1221 FoldbackStrip::select_button_button_press (GdkEventButton* ev)
1223 if (ev->button == 1 || ev->button == 3) {
1226 if (ev->button == 1) {
1227 Gtkmm2ext::anchored_menu_popup(route_select_menu, &_select_button, "",
1230 route_select_menu->popup (3, ev->time);
1240 FoldbackStrip::list_route_operations ()
1242 delete route_ops_menu;
1243 build_route_ops_menu ();
1247 FoldbackStrip::list_fb_routes ()
1249 delete route_select_menu;
1250 build_route_select_menu ();
1254 FoldbackStrip::set_selected (bool yn)
1256 AxisView::set_selected (yn);
1259 global_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
1260 global_frame.set_name ("MixerStripSelectedFrame");
1262 global_frame.set_shadow_type (Gtk::SHADOW_IN);
1263 global_frame.set_name ("MixerStripFrame");
1266 global_frame.queue_draw ();
1269 // processor_box.deselect_all_processors();
1273 FoldbackStrip::route_property_changed (const PropertyChange& what_changed)
1275 if (what_changed.contains (ARDOUR::Properties::name)) {
1281 FoldbackStrip::name_changed ()
1284 name_button.set_text_ellipsize (Pango::ELLIPSIZE_END);
1285 name_button.set_text (_route->name());
1287 set_tooltip (name_button, Gtkmm2ext::markup_escape_text(_route->name()));
1292 FoldbackStrip::set_embedded (bool yn)
1298 FoldbackStrip::map_frozen ()
1300 ENSURE_GUI_THREAD (*this, &FoldbackStrip::map_frozen)
1303 RouteUI::map_frozen ();
1307 FoldbackStrip::hide_redirect_editors ()
1309 _route->foreach_processor (sigc::mem_fun (*this, &FoldbackStrip::hide_processor_editor));
1313 FoldbackStrip::hide_processor_editor (boost::weak_ptr<Processor> p)
1315 boost::shared_ptr<Processor> processor (p.lock ());
1320 Gtk::Window* w = insert_box->get_processor_ui (processor);
1328 FoldbackStrip::reset_strip_style ()
1330 if (_route->active()) {
1331 set_name ("AudioBusStripBase");
1333 set_name ("AudioBusStripBaseInactive");
1339 FoldbackStrip::engine_stopped ()
1344 FoldbackStrip::engine_running ()
1349 FoldbackStrip::drop_send ()
1351 boost::shared_ptr<Send> current_send;
1353 if (_current_delivery && ((current_send = boost::dynamic_pointer_cast<Send>(_current_delivery)) != 0)) {
1354 current_send->set_metering (false);
1357 send_gone_connection.disconnect ();
1358 output_button.set_sensitive (true);
1359 set_invert_sensitive (true);
1360 mute_button->set_sensitive (true);
1361 solo_button->set_sensitive (true);
1362 _comment_button.set_sensitive (true);
1363 fb_level_control->set_sensitive (true);
1364 set_button_names (); // update solo button visual state
1368 FoldbackStrip::set_current_delivery (boost::shared_ptr<Delivery> d)
1370 _current_delivery = d;
1371 DeliveryChanged (_current_delivery);
1375 FoldbackStrip::revert_to_default_display ()
1379 set_current_delivery (_route->main_outs ());
1381 panner_ui().set_panner (_route->main_outs()->panner_shell(), _route->main_outs()->panner());
1382 update_panner_choices();
1383 panner_ui().setup_pan ();
1384 panner_ui().set_send_drawing_mode (false);
1386 if (has_audio_outputs ()) {
1387 panners.show_all ();
1389 panners.hide_all ();
1392 reset_strip_style ();
1396 FoldbackStrip::set_button_names ()
1399 mute_button->set_text (_("Mute"));
1401 if (_route && _route->solo_safe_control()->solo_safe()) {
1402 solo_button->set_visual_state (Gtkmm2ext::VisualState (solo_button->visual_state() | Gtkmm2ext::Insensitive));
1404 solo_button->set_visual_state (Gtkmm2ext::VisualState (solo_button->visual_state() & ~Gtkmm2ext::Insensitive));
1406 if (!Config->get_solo_control_is_listen_control()) {
1407 solo_button->set_text (_("Solo"));
1409 switch (Config->get_listen_position()) {
1410 case AfterFaderListen:
1411 solo_button->set_text (_("AFL"));
1413 case PreFaderListen:
1414 solo_button->set_text (_("PFL"));
1421 FoldbackStrip::plugin_selector()
1423 return _mixer.plugin_selector();
1427 FoldbackStrip::state_id () const
1429 return string_compose ("strip %1", _route->id().to_s());
1433 FoldbackStrip::add_output_port (DataType t)
1435 _route->output()->add_port ("", this, t);
1439 FoldbackStrip::route_active_changed ()
1441 reset_strip_style ();
1445 FoldbackStrip::copy_processors ()
1447 insert_box->processor_operation (ProcessorBox::ProcessorsCopy);
1451 FoldbackStrip::cut_processors ()
1453 insert_box->processor_operation (ProcessorBox::ProcessorsCut);
1457 FoldbackStrip::paste_processors ()
1459 insert_box->processor_operation (ProcessorBox::ProcessorsPaste);
1463 FoldbackStrip::select_all_processors ()
1465 insert_box->processor_operation (ProcessorBox::ProcessorsSelectAll);
1469 FoldbackStrip::deselect_all_processors ()
1471 insert_box->processor_operation (ProcessorBox::ProcessorsSelectNone);
1475 FoldbackStrip::delete_processors ()
1477 return insert_box->processor_operation (ProcessorBox::ProcessorsDelete);
1481 FoldbackStrip::toggle_processors ()
1483 insert_box->processor_operation (ProcessorBox::ProcessorsToggleActive);
1487 FoldbackStrip::ab_plugins ()
1489 insert_box->processor_operation (ProcessorBox::ProcessorsAB);
1493 FoldbackStrip::build_sends_menu ()
1495 using namespace Menu_Helpers;
1497 sends_menu = new Menu;
1498 sends_menu->set_name ("ArdourContextMenu");
1499 MenuList& items = sends_menu->items();
1502 MenuElem(_("Assign all tracks"), sigc::bind (sigc::mem_fun (*this, &RouteUI::create_sends), PreFader, false))
1506 MenuElem(_("Assign all tracks and buses (prefader)"), sigc::bind (sigc::mem_fun (*this, &RouteUI::create_sends), PreFader, true))
1510 MenuElem(_("Assign selected tracks (prefader)"), sigc::bind (sigc::mem_fun (*this, &RouteUI::create_selected_sends), PreFader, false))
1514 MenuElem(_("Assign selected tracks and buses (prefader)"), sigc::bind (sigc::mem_fun (*this, &RouteUI::create_selected_sends), PreFader, true)));
1516 items.push_back (MenuElem(_("Copy track/bus gains to sends"), sigc::mem_fun (*this, &RouteUI::set_sends_gain_from_track)));
1517 items.push_back (MenuElem(_("Set sends gain to -inf"), sigc::mem_fun (*this, &RouteUI::set_sends_gain_to_zero)));
1518 items.push_back (MenuElem(_("Set sends gain to 0dB"), sigc::mem_fun (*this, &RouteUI::set_sends_gain_to_unity)));
1523 FoldbackStrip::color () const
1525 return route_color ();
1529 FoldbackStrip::remove_current_fb ()
1532 StripableList slist;
1533 boost::shared_ptr<Route> next = boost::shared_ptr<Route> ();
1534 boost::shared_ptr<Route> old_route = _route;
1535 _session->get_stripables (slist, PresentationInfo::FoldbackBus);
1536 if (slist.size ()) {
1537 for (StripableList::iterator s = slist.begin(); s != slist.end(); ++s) {
1538 if ((*s) != _route) {
1539 next = boost::dynamic_pointer_cast<Route> (*s);
1546 _session->remove_route (old_route);
1549 RouteUI::self_delete ();
1550 _session->remove_route (old_route);