/* * Copyright (C) 2010-2019 Paul Davis * Copyright (C) 2011-2012 David Robillard * Copyright (C) 2011 Carl Hetherington * Copyright (C) 2014-2015 Tim Mayberry * Copyright (C) 2014-2018 Ben Loftis * Copyright (C) 2014-2019 Robin Gareus * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "pbd/compose.h" #include "pbd/error.h" #include "pbd/replace_all.h" #include "gtkmm2ext/actions.h" #include "gtkmm2ext/utils.h" #include #include #include "widgets/tearoff.h" #include "widgets/tooltips.h" #include "ardour/amp.h" #include "ardour/audioengine.h" #include "ardour/monitor_processor.h" #include "ardour/port.h" #include "ardour/route.h" #include "ardour/solo_isolate_control.h" #include "ardour/user_bundle.h" #include "ardour/plugin_manager.h" #include "ardour_ui.h" #include "gui_thread.h" #include "mixer_ui.h" #include "monitor_section.h" #include "public_editor.h" #include "timers.h" #include "ui_config.h" #include "utils.h" #include "pbd/i18n.h" using namespace ARDOUR; using namespace ArdourWidgets; using namespace ARDOUR_UI_UTILS; using namespace Gtk; using namespace Gtkmm2ext; using namespace PBD; using namespace std; #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale())) #define SYNCHRONIZE_TOGGLE_ACTION(action, value) \ if (action && action->get_active() != value) { \ action->set_active(value); \ } MonitorSection::MonitorSection () : RouteUI ((Session*) 0) , _tearoff (0) , channel_table (0) , channel_table_viewport (*channel_table_scroller.get_hadjustment() , *channel_table_scroller.get_vadjustment ()) , gain_control (0) , dim_control (0) , solo_boost_control (0) , solo_cut_control (0) , gain_display (0) , dim_display (0) , solo_boost_display (0) , solo_cut_display (0) , _output_button (false) , solo_in_place_button (_("SiP"), ArdourButton::led_default_elements) , afl_button (_("AFL"), ArdourButton::led_default_elements) , pfl_button (_("PFL"), ArdourButton::led_default_elements) , exclusive_solo_button (ArdourButton::led_default_elements) , solo_mute_override_button (ArdourButton::led_default_elements) , toggle_processorbox_button (ArdourButton::default_elements) , _inhibit_solo_model_update (false) , _rr_selection () , _ui_initialized (false) { /* note that although this a RouteUI, we never called ::set_route() so * we do not need to worry about self-destructing when the Route (the * monitor out) is destroyed. */ using namespace Menu_Helpers; Glib::RefPtr act; load_bindings (); register_actions (); set_data ("ardour-bindings", bindings); channel_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL); insert_box = new ProcessorBox (0, std::bind (&MonitorSection::plugin_selector, this), _rr_selection, 0); insert_box->set_no_show_all (); insert_box->show (); // TODO allow keyboard shortcuts in ProcessorBox /* Rude Solo & Solo Isolated */ rude_solo_button.set_text (_("Soloing")); rude_solo_button.set_name ("rude solo"); rude_solo_button.show (); rude_iso_button.set_text (_("Isolated")); rude_iso_button.set_name ("rude isolate"); rude_iso_button.show (); rude_audition_button.set_text (_("Auditioning")); rude_audition_button.set_name ("rude audition"); rude_audition_button.show (); Timers::blink_connect (sigc::mem_fun (*this, &MonitorSection::do_blink)); UI::instance()->set_tip (rude_solo_button, _("When active, something is soloed.\nClick to de-solo everything")); rude_iso_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MonitorSection::cancel_isolate), false); UI::instance()->set_tip (rude_iso_button, _("When active, something is solo-isolated.\nClick to de-isolate everything")); rude_audition_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MonitorSection::cancel_audition), false); UI::instance()->set_tip (rude_audition_button, _("When active, auditioning is active.\nClick to stop the audition")); /* SIP, AFL, PFL radio */ solo_in_place_button.set_name ("monitor section solo model"); afl_button.set_name ("monitor section solo model"); pfl_button.set_name ("monitor section solo model"); solo_in_place_button.set_led_left (true); afl_button.set_led_left (true); pfl_button.set_led_left (true); solo_in_place_button.show (); afl_button.show (); pfl_button.show (); act = ActionManager::get_action (X_("Solo"), X_("solo-use-in-place")); set_tooltip (solo_in_place_button, _("Solo controls affect solo-in-place")); if (act) { solo_in_place_button.set_related_action (act); } act = ActionManager::get_action (X_("Solo"), X_("solo-use-afl")); set_tooltip (afl_button, _("Solo controls toggle after-fader-listen")); if (act) { afl_button.set_related_action (act); } act = ActionManager::get_action (X_("Solo"), X_("solo-use-pfl")); set_tooltip (pfl_button, _("Solo controls toggle pre-fader-listen")); if (act) { pfl_button.set_related_action (act); } /* Solo option buttons */ exclusive_solo_button.set_text (_("Excl. Solo")); exclusive_solo_button.set_name (X_("monitor section solo option")); set_tooltip (&exclusive_solo_button, _("Exclusive solo means that only 1 solo is active at a time")); act = ActionManager::get_action (X_("Solo"), X_("toggle-exclusive-solo")); if (act) { exclusive_solo_button.set_related_action (act); } solo_mute_override_button.set_text (_("Solo ยป Mute")); solo_mute_override_button.set_name (X_("monitor section solo option")); set_tooltip (&solo_mute_override_button, _("If enabled, solo will override mute\n(a soloed & muted track or bus will be audible)")); solo_mute_override_button.set_related_action (ActionManager::get_action (X_("Solo"), X_("toggle-mute-overrides-solo"))); /* Processor Box hide/shos */ toggle_processorbox_button.set_text (_("Processors")); toggle_processorbox_button.set_name (X_("monitor section processors toggle")); set_tooltip (&toggle_processorbox_button, _("Allow one to add monitor effect processors")); proctoggle = ActionManager::get_toggle_action (X_("Monitor"), X_("toggle-monitor-processor-box")); toggle_processorbox_button.set_related_action (proctoggle); /* Knobs */ Label* solo_boost_label; Label* solo_cut_label; Label* dim_label; /* Solo Boost Knob */ solo_boost_control = new ArdourKnob (); solo_boost_control->set_name("monitor section knob"); solo_boost_control->set_size_request (PX_SCALE(36), PX_SCALE(36)); set_tooltip (*solo_boost_control, _("Gain increase for soloed signals (0dB is normal)")); solo_boost_display = new ArdourDisplay (); set_tooltip (*solo_boost_display, _("Gain increase for soloed signals (0dB is normal)")); solo_boost_display->set_name("monitor section button"); solo_boost_display->set_size_request (PX_SCALE(68), PX_SCALE(20)); solo_boost_display->add_controllable_preset(_("0 dB"), 0.0); solo_boost_display->add_controllable_preset(_("3 dB"), 3.0); solo_boost_display->add_controllable_preset(_("6 dB"), 6.0); solo_boost_display->add_controllable_preset(_("10 dB"), 10.0); solo_boost_label = manage (new Label (_("Solo Boost"))); /* Solo (SiP) cut */ solo_cut_control = new ArdourKnob (); solo_cut_control->set_name ("monitor section knob"); solo_cut_control->set_size_request (PX_SCALE(36), PX_SCALE(36)); set_tooltip (*solo_cut_control, _("Gain reduction non-soloed signals\nA value above -inf dB causes \"solo-in-front\"")); solo_cut_display = new ArdourDisplay (); set_tooltip (*solo_cut_display, _("Gain reduction non-soloed signals\nA value above -inf dB causes \"solo-in-front\"")); solo_cut_display->set_name("monitor section button"); solo_cut_display->set_size_request (PX_SCALE(68), PX_SCALE(20)); solo_cut_display->add_controllable_preset(_("0 dB"), 0.0); solo_cut_display->add_controllable_preset(_("-6 dB"), -6.0); solo_cut_display->add_controllable_preset(_("-12 dB"), -12.0); solo_cut_display->add_controllable_preset(_("-20 dB"), -20.0); solo_cut_display->add_controllable_preset(_("OFF"), -1200.0); solo_cut_label = manage (new Label (_("SiP Cut"))); /* Dim */ dim_control = new ArdourKnob (ArdourKnob::default_elements, ArdourKnob::Detent); dim_control->set_name ("monitor section knob"); dim_control->set_size_request (PX_SCALE(36), PX_SCALE(36)); set_tooltip (*dim_control, _("Gain reduction to use when dimming monitor outputs")); dim_display = new ArdourDisplay (); set_tooltip (*dim_display, _("Gain reduction to use when dimming monitor outputs")); dim_display->set_name ("monitor section button"); dim_display->set_size_request (PX_SCALE(68), PX_SCALE(20)); dim_display->add_controllable_preset(_("0 dB"), 0.0); dim_display->add_controllable_preset(_("-3 dB"), -3.0); dim_display->add_controllable_preset(_("-6 dB"), -6.0); dim_display->add_controllable_preset(_("-12 dB"), -12.0); dim_display->add_controllable_preset(_("-20 dB"), -20.0); dim_label = manage (new Label (_("Dim"))); // mute button cut_all_button.set_text (_("Mute")); cut_all_button.set_name ("mute button"); cut_all_button.set_size_request (-1, PX_SCALE(30)); cut_all_button.show (); act = ActionManager::get_action (X_("Monitor Section"), X_("monitor-cut-all")); if (act) { cut_all_button.set_related_action (act); } // dim button dim_all_button.set_text (_("Dim")); dim_all_button.set_name ("monitor section dim"); dim_all_button.set_size_request (-1, PX_SCALE(25)); act = ActionManager::get_action (X_("Monitor Section"), X_("monitor-dim-all")); if (act) { dim_all_button.set_related_action (act); } // mono button mono_button.set_text (_("Mono")); mono_button.set_name ("monitor section mono"); mono_button.set_size_request (-1, PX_SCALE(25)); act = ActionManager::get_action (X_("Monitor Section"), X_("monitor-mono")); if (act) { mono_button.set_related_action (act); } /* Gain */ gain_control = new ArdourKnob (ArdourKnob::default_elements, ArdourKnob::Detent); gain_control->set_name("monitor section knob"); gain_control->set_size_request (PX_SCALE(60), PX_SCALE(60)); gain_display = new ArdourDisplay (); gain_display->set_name("monitor section button"); gain_display->set_size_request (PX_SCALE(68), PX_SCALE(20)); gain_display->add_controllable_preset(_("0 dB"), 0.0); gain_display->add_controllable_preset(_("-3 dB"), -3.0); gain_display->add_controllable_preset(_("-6 dB"), -6.0); gain_display->add_controllable_preset(_("-12 dB"), -12.0); gain_display->add_controllable_preset(_("-20 dB"), -20.0); gain_display->add_controllable_preset(_("-30 dB"), -30.0); Label* output_label = manage (new Label (_("Output"))); output_label->set_name (X_("MonitorSectionLabel")); channel_table_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); channel_table_scroller.set_size_request (-1, PX_SCALE(150)); channel_table_scroller.set_shadow_type (Gtk::SHADOW_NONE); channel_table_scroller.show (); channel_table_scroller.add (channel_table_viewport); channel_size_group->add_widget (channel_table_header); channel_table_header.resize (1, 5); Label* l1 = manage (new Label (X_(" "))); l1->set_name (X_("MonitorSectionLabel")); channel_table_header.attach (*l1, 0, 1, 0, 1, EXPAND|FILL); l1 = manage (new Label (_("Mute"))); l1->set_name (X_("MonitorSectionLabel")); channel_table_header.attach (*l1, 1, 2, 0, 1, EXPAND|FILL); l1 = manage (new Label (_("Dim"))); l1->set_name (X_("MonitorSectionLabel")); channel_table_header.attach (*l1, 2, 3, 0, 1, EXPAND|FILL); l1 = manage (new Label (_("Solo"))); l1->set_name (X_("MonitorSectionLabel")); channel_table_header.attach (*l1, 3, 4, 0, 1, EXPAND|FILL); l1 = manage (new Label (_("Inv"))); l1->set_name (X_("MonitorSectionLabel")); channel_table_header.attach (*l1, 4, 5, 0, 1, EXPAND|FILL); channel_table_header.show (); /**************************************************************************** * LAYOUT top to bottom */ Gtk::Label *top_spacer = manage (new Gtk::Label); // solo, iso information HBox* rude_box = manage (new HBox); rude_box->set_spacing (PX_SCALE(4)); rude_box->set_homogeneous (true); rude_box->pack_start (rude_solo_button, true, true); rude_box->pack_start (rude_iso_button, true, true); // solo options (right align) HBox* tbx1 = manage (new HBox); tbx1->pack_end (exclusive_solo_button, false, false); HBox* tbx2 = manage (new HBox); tbx2->pack_end (solo_mute_override_button, false, false); HBox* tbx3 = manage (new HBox); tbx3->pack_end (toggle_processorbox_button, false, false); HBox* tbx0 = manage (new HBox); // space // combined solo mode (Sip, AFL, PFL) & solo options Table *solo_tbl = manage (new Table); solo_tbl->attach (solo_in_place_button, 0, 1, 0, 1, EXPAND|FILL, SHRINK, 0, 2); solo_tbl->attach (pfl_button, 0, 1, 1, 2, EXPAND|FILL, SHRINK, 0, 2); solo_tbl->attach (afl_button, 0, 1, 2, 3, EXPAND|FILL, SHRINK, 0, 2); solo_tbl->attach (*tbx0, 1, 2, 0, 3, EXPAND|FILL, SHRINK, 2, 2); solo_tbl->attach (*tbx1, 2, 3, 0, 1, EXPAND|FILL, SHRINK, 0, 2); solo_tbl->attach (*tbx2, 2, 3, 1, 2, EXPAND|FILL, SHRINK, 0, 2); solo_tbl->attach (*tbx3, 2, 3, 2, 3, EXPAND|FILL, SHRINK, 0, 2); // boost, cut, dim volume control Table *level_tbl = manage (new Table); level_tbl->attach (*solo_boost_label, 0, 2, 0, 1, EXPAND|FILL, SHRINK, 1, 2); level_tbl->attach (*solo_boost_control, 0, 2, 1, 2, EXPAND|FILL, SHRINK, 1, 2); level_tbl->attach (*solo_boost_display, 0, 2, 2, 3, EXPAND , SHRINK, 1, 2); level_tbl->attach (*solo_cut_label, 2, 4, 0, 1, EXPAND|FILL, SHRINK, 1, 2); level_tbl->attach (*solo_cut_control, 2, 4, 1, 2, EXPAND|FILL, SHRINK, 1, 2); level_tbl->attach (*solo_cut_display, 2, 4, 2, 3, EXPAND , SHRINK, 1, 2); level_tbl->attach (*dim_label, 1, 3, 3, 4, EXPAND|FILL, SHRINK, 1, 2); level_tbl->attach (*dim_control, 1, 3, 4, 5, EXPAND|FILL, SHRINK, 1, 2); level_tbl->attach (*dim_display, 1, 3, 5, 6, EXPAND , SHRINK, 1, 2); // mono, dim HBox* mono_dim_box = manage (new HBox); mono_dim_box->set_spacing (PX_SCALE(4)); mono_dim_box->set_homogeneous (true); mono_dim_box->pack_start (mono_button, true, true); mono_dim_box->pack_end (dim_all_button, true, true); // master gain Label* spin_label = manage (new Label (_("Monitor"))); VBox* spin_packer = manage (new VBox); spin_packer->set_spacing (PX_SCALE(2)); spin_packer->pack_start (*spin_label, false, false); spin_packer->pack_start (*gain_control, false, false); spin_packer->pack_start (*gain_display, false, false); master_packer.pack_start (*spin_packer, true, false); // combined gain section (channels, mute, dim) VBox* lower_packer = manage (new VBox); lower_packer->pack_start (channel_table_header, false, false, PX_SCALE(0)); lower_packer->pack_start (channel_table_packer, false, false, PX_SCALE(8)); lower_packer->pack_start (*mono_dim_box, false, false, PX_SCALE(2)); lower_packer->pack_start (cut_all_button, false, false, PX_SCALE(2)); // calc height of mixer scrollbar int scrollbar_height = 0; { Gtk::Window window (WINDOW_TOPLEVEL); HScrollbar scrollbar; window.add (scrollbar); scrollbar.set_name ("MixerWindow"); scrollbar.ensure_style(); Gtk::Requisition requisition(scrollbar.size_request ()); scrollbar_height = requisition.height; scrollbar_height += 3; // track_display_frame border/shadow } // output port select VBox* out_packer = manage (new VBox); out_packer->set_spacing (PX_SCALE(2)); out_packer->pack_start (*output_label, false, false); out_packer->pack_start (_output_button, false, false); /**************************************************************************** * TOP LEVEL LAYOUT */ vpacker.set_border_width (PX_SCALE(3)); vpacker.pack_start (*top_spacer, false, false, PX_SCALE(3)); vpacker.pack_start (*rude_box, false, false, PX_SCALE(3)); vpacker.pack_start (rude_audition_button, false, false, 0); vpacker.pack_start (*solo_tbl, false, false, PX_SCALE(8)); vpacker.pack_start (*insert_box, true, true, PX_SCALE(8)); vpacker.pack_start (*level_tbl, false, false, PX_SCALE(8)); vpacker.pack_start (*lower_packer, false, false, PX_SCALE(8)); vpacker.pack_start (master_packer, false, false, PX_SCALE(10)); vpacker.pack_end (*out_packer, false, false, #ifdef MIXBUS scrollbar_height /* no outer frame */ #else scrollbar_height + 2 /* frame borders */ #endif ); hpacker.set_spacing (0); hpacker.pack_start (vpacker, true, true); add (hpacker); gain_control->show_all (); gain_display->show_all (); dim_control->show_all (); dim_display->show_all(); solo_boost_control->show_all (); solo_boost_display->show_all(); mono_dim_box->show (); spin_packer->show (); master_packer.show (); rude_box->show(); solo_tbl->show_all(); level_tbl->show(); lower_packer->show (); out_packer->show (); vpacker.show (); hpacker.show (); map_state (); signal_enter_notify_event().connect (sigc::mem_fun (*this, &MonitorSection::enter_handler)); signal_leave_notify_event().connect (sigc::mem_fun (*this, &MonitorSection::leave_handler)); set_can_focus (); _tearoff = new TearOff (*this); if (!UIConfiguration::instance().get_floating_monitor_section()) { /* if torn off, make this a normal window * (default is WINDOW_TYPE_HINT_UTILITY in libs/gtkmm2ext/tearoff.cc) */ _tearoff->tearoff_window().set_type_hint (Gdk::WINDOW_TYPE_HINT_NORMAL); } _tearoff->tearoff_window().set_title (X_("Monitor")); _tearoff->tearoff_window().signal_key_press_event().connect (sigc::bind (sigc::ptr_fun (relay_key_press), (Gtk::Window*) &_tearoff->tearoff_window()), false); update_processor_box (); _ui_initialized = true; /* catch changes that affect us */ Config->ParameterChanged.connect (config_connection, invalidator (*this), std::bind (&MonitorSection::parameter_changed, this, _1), gui_context()); } MonitorSection::~MonitorSection () { for (ChannelButtons::iterator i = _channel_buttons.begin(); i != _channel_buttons.end(); ++i) { delete *i; } _channel_buttons.clear (); route_connections.drop_connections (); delete insert_box; insert_box = 0; delete gain_control; gain_control = 0; delete gain_display; gain_display = 0; delete dim_control; dim_control = 0; delete dim_display; dim_display = 0; delete solo_boost_control; solo_boost_control = 0; delete solo_boost_display; solo_boost_display = 0; delete solo_cut_control; solo_cut_control = 0; delete solo_cut_display; solo_cut_display = 0; delete _tearoff; _tearoff = 0; delete channel_table; channel_table = 0; } bool MonitorSection::enter_handler (GdkEventCrossing* ev) { grab_focus (); return false; } bool MonitorSection::leave_handler (GdkEventCrossing* ev) { switch (ev->detail) { case GDK_NOTIFY_INFERIOR: return false; default: break; } /* cancel focus if we're not torn off. With X11 WM's that do * focus-follows-mouse, focus will be taken from us anyway. */ Widget* top = get_toplevel(); if (top->get_is_toplevel() && top != &_tearoff->tearoff_window()) { Window* win = dynamic_cast (top); gtk_window_set_focus (win->gobj(), 0); } return false; } void MonitorSection::update_processor_box () { bool show_processor_box = proctoggle->get_active (); if (count_processors () > 0 && !show_processor_box) { toggle_processorbox_button.set_name (X_("monitor section processors present")); } else { toggle_processorbox_button.set_name (X_("monitor section processors toggle")); } if (insert_box->get_visible() == show_processor_box) { return; } if (show_processor_box) { if (master_packer.get_parent()) { master_packer.get_parent()->remove (master_packer); } insert_box->show(); vpacker.pack_start (master_packer, false, false, PX_SCALE(10)); } else { if (master_packer.get_parent()) { master_packer.get_parent()->remove (master_packer); } insert_box->hide(); vpacker.pack_start (master_packer, true, false, PX_SCALE(10)); } } void MonitorSection::set_session (Session* s) { RouteUI::set_session (s); Glib::RefPtr global_monitor_actions = ActionManager::get_action_group (X_("Monitor Section")); if (_session) { insert_box->set_session (_session); /* These are not actually dependent on the Session, but they * need to be set after construction, not during, and * this is as good a place as any. */ ActionManager::get_toggle_action (X_("Solo"), X_("toggle-exclusive-solo"))->set_active (Config->get_exclusive_solo()); ActionManager::get_toggle_action (X_("Solo"), X_("toggle-mute-overrides-solo"))->set_active (Config->get_solo_mute_override()); _route = _session->monitor_out (); _output_button.set_route (_route, this); if (_route) { /* session with monitor section */ _monitor = _route->monitor_control (); assign_controllables (); insert_box->set_route (_route); _route->processors_changed.connect (route_connections, invalidator (*this), std::bind (&MonitorSection::processors_changed, this, _1), gui_context()); _route->output()->PortCountChanged.connect (route_connections, invalidator (*this), std::bind (&MonitorSection::populate_buttons, this), gui_context()); _route->DropReferences.connect (route_connections, invalidator (*this), std::bind (&MonitorSection::drop_route, this), gui_context()); if (_ui_initialized) { update_processor_box (); } SYNCHRONIZE_TOGGLE_ACTION (ActionManager::get_toggle_action (X_("Monitor"), "UseMonitorSection"), true); ActionManager::set_sensitive (global_monitor_actions, true); ActionManager::set_sensitive (monitor_actions, true); ActionManager::set_sensitive (solo_actions, true); } else { /* session with no monitor section */ route_connections.drop_connections(); _monitor.reset (); _route.reset (); SYNCHRONIZE_TOGGLE_ACTION (ActionManager::get_toggle_action (X_("Monitor"), "UseMonitorSection"), false); ActionManager::set_sensitive (global_monitor_actions, false); ActionManager::set_sensitive (monitor_actions, false); ActionManager::set_sensitive (solo_actions, true); /* this action needs to always be true in this, so that we can turn it back on */ ActionManager::get_toggle_action (X_("Monitor"), X_("UseMonitorSection"))->set_sensitive (true); } populate_buttons (); map_state (); } else { /* no session */ if (_route) { drop_route (); unassign_controllables (); } ActionManager::set_sensitive (monitor_actions, false); ActionManager::set_sensitive (solo_actions, false); ActionManager::set_sensitive (global_monitor_actions, false); } } void MonitorSection::drop_route () { _output_button.set_route (std::shared_ptr(), 0); route_connections.drop_connections(); _monitor.reset (); _route.reset (); unassign_controllables (); rude_iso_button.unset_active_state (); rude_solo_button.unset_active_state (); } MonitorSection::ChannelButtonSet::ChannelButtonSet () { cut.set_name (X_("mute button")); dim.set_name (X_("monitor section dim")); solo.set_name (X_("solo button")); invert.set_name (X_("invert button")); cut.set_can_focus (false); dim.set_can_focus (false); solo.set_can_focus (false); invert.set_can_focus (false); } void MonitorSection::populate_buttons () { if (!_monitor) { return; } if (channel_table) { channel_size_group->remove_widget (*channel_table); delete channel_table; } channel_table = new Gtk::Table(); channel_table->set_col_spacings (6); channel_table->set_row_spacings (6); channel_table->set_homogeneous (true); channel_size_group->add_widget (*channel_table); channel_table->show (); table_hpacker.pack_start (*channel_table, true, true); for (ChannelButtons::iterator i = _channel_buttons.begin(); i != _channel_buttons.end(); ++i) { delete *i; } _channel_buttons.clear (); Glib::RefPtr act; uint32_t nchans = _monitor->output_streams().n_audio(); channel_table->resize (nchans, 5); const uint32_t row_offset = 0; for (uint32_t i = 0; i < nchans; ++i) { string l; char buf[64]; if (nchans == 2) { if (i == 0) { l = "L"; } else { l = "R"; } } else { char buf[32]; snprintf (buf, sizeof (buf), "%d", i+1); l = buf; } Label* label = manage (new Label (l)); channel_table->attach (*label, 0, 1, i+row_offset, i+row_offset+1, EXPAND|FILL); ChannelButtonSet* cbs = new ChannelButtonSet; _channel_buttons.push_back (cbs); channel_table->attach (cbs->cut, 1, 2, i+row_offset, i+row_offset+1, EXPAND|FILL); channel_table->attach (cbs->dim, 2, 3, i+row_offset, i+row_offset+1, EXPAND|FILL); channel_table->attach (cbs->solo, 3, 4, i+row_offset, i+row_offset+1, EXPAND|FILL); channel_table->attach (cbs->invert, 4, 5, i+row_offset, i+row_offset+1, EXPAND|FILL); snprintf (buf, sizeof (buf), "monitor-cut-%u", i); act = ActionManager::get_action (X_("Monitor"), buf); if (act) { cbs->cut.set_related_action (act); } snprintf (buf, sizeof (buf), "monitor-dim-%u", i); act = ActionManager::get_action (X_("Monitor"), buf); if (act) { cbs->dim.set_related_action (act); } snprintf (buf, sizeof (buf), "monitor-solo-%u", i); act = ActionManager::get_action (X_("Monitor"), buf); if (act) { cbs->solo.set_related_action (act); } snprintf (buf, sizeof (buf), "monitor-invert-%u", i); act = ActionManager::get_action (X_("Monitor"), buf); if (act) { cbs->invert.set_related_action (act); } } channel_table->show_all (); if (channel_table_scroller.get_parent()) { /* scroller is packed, so remove it */ channel_table_packer.remove (channel_table_scroller); } if (table_hpacker.get_parent () == &channel_table_packer) { /* this occurs when the table hpacker is directly packed, so remove it. */ channel_table_packer.remove (table_hpacker); } else if (table_hpacker.get_parent()) { channel_table_viewport.remove (); } if (nchans > 7) { /* put the table into a scrolled window, and then put * that into the channel vpacker, after the table header */ channel_table_viewport.add (table_hpacker); channel_table_packer.pack_start (channel_table_scroller, true, true); channel_table_viewport.show (); channel_table_scroller.show (); } else { /* just put the channel table itself into the channel * vpacker, after the table header */ channel_table_packer.pack_start (table_hpacker, true, true); channel_table_scroller.hide (); } table_hpacker.show (); channel_table->show (); } void MonitorSection::toggle_exclusive_solo () { if (!_monitor) { return; } Config->set_exclusive_solo (ActionManager::get_toggle_action (X_("Solo"), "toggle-exclusive-solo")->get_active()); } void MonitorSection::toggle_mute_overrides_solo () { if (!_monitor) { return; } Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Solo"), "toggle-mute-overrides-solo"); Config->set_solo_mute_override (tact->get_active()); } void MonitorSection::dim_all () { if (!_monitor) { return; } Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Monitor Section"), "monitor-dim-all"); _monitor->set_dim_all (tact->get_active()); } void MonitorSection::cut_all () { if (!_monitor) { return; } Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Monitor Section"), "monitor-cut-all"); _monitor->set_cut_all (tact->get_active()); } void MonitorSection::mono () { if (!_monitor) { return; } Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Monitor Section"), "monitor-mono"); _monitor->set_mono (tact->get_active()); } void MonitorSection::cut_channel (uint32_t chn) { if (!_monitor) { return; } char buf[64]; snprintf (buf, sizeof (buf), "monitor-cut-%u", chn); Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Monitor"), buf); _monitor->set_cut (chn, tact->get_active()); } void MonitorSection::dim_channel (uint32_t chn) { if (!_monitor) { return; } char buf[64]; snprintf (buf, sizeof (buf), "monitor-dim-%u", chn); Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Monitor"), buf); _monitor->set_dim (chn, tact->get_active()); } void MonitorSection::solo_channel (uint32_t chn) { if (!_monitor) { return; } char buf[64]; snprintf (buf, sizeof (buf), "monitor-solo-%u", chn); Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Monitor"), buf); _monitor->set_solo (chn, tact->get_active()); } void MonitorSection::invert_channel (uint32_t chn) { if (!_monitor) { return; } char buf[64]; snprintf (buf, sizeof (buf), "monitor-invert-%u", chn); Glib::RefPtr tact = ActionManager::get_toggle_action (X_("Monitor"), buf); _monitor->set_polarity (chn, tact->get_active()); } void MonitorSection::register_actions () { string action_name; string action_descr; Glib::RefPtr act; /* ...will get sensitized if a mon-session is added */ monitor_actions = ActionManager::create_action_group (bindings, X_("Monitor")); solo_actions = ActionManager::create_action_group (bindings, X_("Monitor")); ActionManager::register_toggle_action (monitor_actions, X_("UseMonitorSection"), _("Use Monitor Section"), sigc::mem_fun(*this, &MonitorSection::toggle_use_monitor_section)); /* these are global monitor actions that invoke MonitorSectioncode. Do * not create local versions (i.e. as part of "monitor_actions") * because then we can end up with two different bindings (one global, * one local to the monitor section) for the same action. */ Glib::RefPtr global_monitor_actions = ActionManager::get_action_group (X_("Monitor Section")); ActionManager::register_toggle_action (global_monitor_actions, "monitor-mono", _("Mono"), sigc::mem_fun (*this, &MonitorSection::mono)); ActionManager::register_toggle_action (global_monitor_actions, "monitor-cut-all", _("Mute"), sigc::mem_fun (*this, &MonitorSection::cut_all)); ActionManager::register_toggle_action (global_monitor_actions, "monitor-dim-all", _("Dim"), sigc::mem_fun (*this, &MonitorSection::dim_all)); ActionManager::register_toggle_action (monitor_actions, "toggle-monitor-processor-box", _("Toggle Monitor Section Processor Box"), sigc::mem_fun (*this, &MonitorSection::update_processor_box)); for (uint32_t chn = 0; chn < 16; ++chn) { action_name = string_compose (X_("monitor-cut-%1"), chn); action_descr = string_compose (_("Cut monitor channel %1"), chn); ActionManager::register_toggle_action (monitor_actions, action_name.c_str(), action_descr.c_str(), sigc::bind (sigc::mem_fun (*this, &MonitorSection::cut_channel), chn)); action_name = string_compose (X_("monitor-dim-%1"), chn); action_descr = string_compose (_("Dim monitor channel %1"), chn); ActionManager::register_toggle_action (monitor_actions, action_name.c_str(), action_descr.c_str(), sigc::bind (sigc::mem_fun (*this, &MonitorSection::dim_channel), chn)); action_name = string_compose (X_("monitor-solo-%1"), chn); action_descr = string_compose (_("Solo monitor channel %1"), chn); ActionManager::register_toggle_action (monitor_actions, action_name.c_str(), action_descr.c_str(), sigc::bind (sigc::mem_fun (*this, &MonitorSection::solo_channel), chn)); action_name = string_compose (X_("monitor-invert-%1"), chn); action_descr = string_compose (_("Invert monitor channel %1"), chn); ActionManager::register_toggle_action (monitor_actions, action_name.c_str(), action_descr.c_str(), sigc::bind (sigc::mem_fun (*this, &MonitorSection::invert_channel), chn)); } solo_actions = ActionManager::create_action_group (bindings, X_("Solo")); RadioAction::Group solo_group; ActionManager::register_radio_action (solo_actions, solo_group, "solo-use-in-place", _("In-place solo"), sigc::mem_fun (*this, &MonitorSection::solo_use_in_place)); ActionManager::register_radio_action (solo_actions, solo_group, "solo-use-afl", _("After Fade Listen (AFL) solo"), sigc::mem_fun (*this, &MonitorSection::solo_use_afl)); ActionManager::register_radio_action (solo_actions, solo_group, "solo-use-pfl", _("Pre Fade Listen (PFL) solo"), sigc::mem_fun (*this, &MonitorSection::solo_use_pfl)); ActionManager::register_toggle_action (solo_actions, "toggle-exclusive-solo", _("Toggle exclusive solo mode"), sigc::mem_fun (*this, &MonitorSection::toggle_exclusive_solo)); ActionManager::register_toggle_action (solo_actions, "toggle-mute-overrides-solo", _("Toggle mute overrides solo mode"), sigc::mem_fun (*this, &MonitorSection::toggle_mute_overrides_solo)); } void MonitorSection::solo_use_in_place () { /* this is driven by a toggle on a radio group, and so is invoked twice, once for the item that became inactive and once for the one that became active. */ Glib::RefPtr ract = ActionManager::get_radio_action (X_("Solo"), X_("solo-use-in-place")); if (!ract->get_active ()) { /* We are turning SiP off, which means that AFL or PFL will be turned on shortly; don't update the solo model in the mean time, as if the currently configured listen position is not the one that is about to be turned on, things will go wrong. */ _inhibit_solo_model_update = true; } Config->set_solo_control_is_listen_control (!ract->get_active()); _inhibit_solo_model_update = false; } void MonitorSection::solo_use_afl () { /* this is driven by a toggle on a radio group, and so is invoked twice, once for the item that became inactive and once for the one that became active. */ Glib::RefPtr ract = ActionManager::get_radio_action (X_("Solo"), X_("solo-use-afl")); if (ract->get_active()) { Config->set_solo_control_is_listen_control (true); Config->set_listen_position (AfterFaderListen); } } void MonitorSection::solo_use_pfl () { /* this is driven by a toggle on a radio group, and so is invoked twice, once for the item that became inactive and once for the one that became active. */ Glib::RefPtr ract = ActionManager::get_radio_action (X_("Solo"), X_("solo-use-pfl")); if (ract->get_active()) { Config->set_solo_control_is_listen_control (true); Config->set_listen_position (PreFaderListen); } } void MonitorSection::update_solo_model () { if (_inhibit_solo_model_update) { return; } const char* action_name = 0; Glib::RefPtr ract; if (Config->get_solo_control_is_listen_control()) { switch (Config->get_listen_position()) { case AfterFaderListen: action_name = X_("solo-use-afl"); break; case PreFaderListen: action_name = X_("solo-use-pfl"); break; } } else { action_name = X_("solo-use-in-place"); } ract = ActionManager::get_radio_action (X_("Solo"), action_name); /* because these are radio buttons, one of them will be active no matter what. to trigger a change in the action so that the view picks it up, toggle it. */ if (ract->get_active()) { ract->set_active (false); } ract->set_active (true); } void MonitorSection::map_state () { if (!_route || !_monitor) { return; } update_solo_model (); Glib::RefPtr act; Glib::RefPtr tact; tact = ActionManager::get_toggle_action (X_("Monitor Section"), "monitor-cut-all"); tact->set_active (_monitor->cut_all()); tact = ActionManager::get_toggle_action (X_("Monitor Section"), "monitor-dim-all"); tact->set_active (_monitor->dim_all()); tact = ActionManager::get_toggle_action (X_("Monitor Section"), "monitor-mono"); tact->set_active (_monitor->mono()); uint32_t nchans = _monitor->output_streams().n_audio(); assert (nchans == _channel_buttons.size ()); for (uint32_t n = 0; n < nchans; ++n) { char action_name[32]; snprintf (action_name, sizeof (action_name), "monitor-cut-%u", n); tact = ActionManager::get_toggle_action (X_("Monitor"), action_name); tact->set_active (_monitor->cut (n)); snprintf (action_name, sizeof (action_name), "monitor-dim-%u", n); tact = ActionManager::get_toggle_action (X_("Monitor"), action_name); tact->set_active (_monitor->dimmed (n)); snprintf (action_name, sizeof (action_name), "monitor-solo-%u", n); tact = ActionManager::get_toggle_action (X_("Monitor"), action_name); tact->set_active (_monitor->soloed (n)); snprintf (action_name, sizeof (action_name), "monitor-invert-%u", n); tact = ActionManager::get_toggle_action (X_("Monitor"), action_name); tact->set_active (_monitor->inverted (n)); } } void MonitorSection::do_blink (bool onoff) { if (!UIConfiguration::instance().get_blink_alert_indicators ()) { onoff = true; } solo_blink (onoff); audition_blink (onoff); } void MonitorSection::audition_blink (bool onoff) { if (_session == 0) { return; } if (_session->is_auditioning()) { rude_audition_button.set_active (onoff); } else { rude_audition_button.set_active (false); } } void MonitorSection::solo_blink (bool onoff) { if (_session == 0) { return; } if (_session->soloing() || _session->listening()) { rude_solo_button.set_active (onoff); if (_session->soloing()) { if (_session->solo_isolated()) { rude_iso_button.set_active (onoff); } else { rude_iso_button.set_active (false); } } } else { rude_solo_button.set_active (false); rude_iso_button.set_active (false); } } bool MonitorSection::cancel_isolate (GdkEventButton*) { if (_session) { std::shared_ptr rl (_session->get_routes ()); _session->set_controls (route_list_to_control_list (rl, &Stripable::solo_isolate_control), 0.0, Controllable::NoGroup); } return true; } bool MonitorSection::cancel_audition (GdkEventButton*) { if (_session) { _session->cancel_audition(); } return true; } void MonitorSection::parameter_changed (std::string name) { if (name == "solo-control-is-listen-control") { update_solo_model (); } else if (name == "listen-position") { update_solo_model (); } else if (name == "solo-mute-override") { SYNCHRONIZE_TOGGLE_ACTION (ActionManager::get_toggle_action (X_("Solo"), "toggle-mute-overrides-solo"), Config->get_solo_mute_override ()); } else if (name == "exclusive-solo") { SYNCHRONIZE_TOGGLE_ACTION (ActionManager::get_toggle_action (X_("Solo"), "toggle-exclusive-solo"), Config->get_exclusive_solo ()); } } void MonitorSection::unassign_controllables () { std::shared_ptr none; solo_cut_control->set_controllable (none); solo_cut_display->set_controllable (none); gain_control->set_controllable (none); gain_display->set_controllable (none); cut_all_button.set_controllable (none); dim_all_button.set_controllable (none); mono_button.set_controllable (none); dim_control->set_controllable (none); dim_display->set_controllable (none); solo_boost_control->set_controllable (none); solo_boost_display->set_controllable (none); } void MonitorSection::assign_controllables () { assert (_session); assert (_route); assert (_monitor); solo_cut_control->set_controllable (_session->solo_cut_control()); solo_cut_display->set_controllable (_session->solo_cut_control()); gain_control->set_controllable (_route->gain_control()); gain_display->set_controllable (_route->gain_control()); cut_all_button.set_controllable (_monitor->cut_control()); cut_all_button.watch (); dim_all_button.set_controllable (_monitor->dim_control()); dim_all_button.watch (); mono_button.set_controllable (_monitor->mono_control()); mono_button.watch (); dim_control->set_controllable (_monitor->dim_level_control ()); dim_display->set_controllable (_monitor->dim_level_control ()); solo_boost_control->set_controllable (_monitor->solo_boost_control ()); solo_boost_display->set_controllable (_monitor->solo_boost_control ()); } string MonitorSection::state_id() const { return "monitor-section"; } void MonitorSection::load_bindings () { bindings = Bindings::get_bindings (X_("Monitor Section")); } void MonitorSection::help_count_processors (std::weak_ptr p, uint32_t* cnt) const { std::shared_ptr processor (p.lock ()); if (!processor || !processor->display_to_user()) { return; } if (std::dynamic_pointer_cast(processor)) { return; } ++(*cnt); } uint32_t MonitorSection::count_processors () { uint32_t processor_count = 0; if (_route) { _route->foreach_processor (sigc::bind (sigc::mem_fun (*this, &MonitorSection::help_count_processors), &processor_count)); } return processor_count; } void MonitorSection::processors_changed (ARDOUR::RouteProcessorChange) { update_processor_box (); } PluginSelector* MonitorSection::plugin_selector () { return Mixer_UI::instance()->plugin_selector (); } void MonitorSection::use_others_actions () { rude_solo_button.set_related_action (ActionManager::get_action (X_("Main"), X_("cancel-solo"))); } void MonitorSection::toggle_use_monitor_section () { if (!_session) { return; } bool want_ms = ActionManager::get_toggle_action (X_("Monitor"), "UseMonitorSection")->get_active(); bool have_ms = Config->get_use_monitor_bus (); if (want_ms == have_ms) { return; } if (want_ms) { Config->set_use_monitor_bus (true); ActionManager::get_toggle_action (X_("Mixer"), X_("ToggleMonitorSection"))->set_active (true); } else { Config->set_use_monitor_bus (false); } }