/* * Copyright (C) 2007-2012 Carl Hetherington * Copyright (C) 2007-2017 Paul Davis * Copyright (C) 2008-2012 David Robillard * Copyright (C) 2013-2014 Colin Fletcher * Copyright (C) 2013-2022 Robin Gareus * Copyright (C) 2015-2016 Tim Mayberry * Copyright (C) 2015 Ben Loftis * * 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 #include #include #include "pbd/error.h" #include "pbd/failed_constructor.h" #include "pbd/locale_guard.h" #include "pbd/unwind.h" #include "pbd/xml++.h" #include #include #include #include #include "widgets/tooltips.h" #include "ardour/audio_backend.h" #include "ardour/audioengine.h" #include "ardour/mididm.h" #include "ardour/mtdm.h" #include "ardour/profile.h" #include "ardour/rc_configuration.h" #include "ardour/types.h" #include "pbd/convert.h" #include "pbd/error.h" #include "ardour_message.h" #include "ardour_ui.h" #include "debug.h" #include "engine_dialog.h" #include "gui_thread.h" #include "opts.h" #include "pbd/i18n.h" #include "public_editor.h" #include "splash.h" #include "ui_config.h" #include "utils.h" using namespace std; using namespace Gtk; using namespace Gtkmm2ext; using namespace PBD; using namespace Glib; using namespace ArdourWidgets; using namespace ARDOUR_UI_UTILS; #define DEBUG_ECONTROL(msg) DEBUG_TRACE (PBD::DEBUG::EngineControl, string_compose ("%1: %2\n", __LINE__, msg)); static const unsigned int midi_tab = 2; static const unsigned int latency_tab = 1; /* zero-based, page zero is the main setup page */ static const char* results_markup = X_("%1"); EngineControl::EngineControl () : ArdourDialog (_("Audio/MIDI Setup")) , engine_status ("") , settings_table (4, 4) , latency_expander (_("Advanced Settings")) , monitor_expander (_("Hardware Monitoring")) , input_latency_adjustment (0, 0, 99999, 1) , input_latency (input_latency_adjustment) , output_latency_adjustment (0, 0, 99999, 1) , output_latency (output_latency_adjustment) , lbl_audio_system (_("Audio System:"), Gtk::ALIGN_START) , lbl_midi_system (_("MIDI System:"), Gtk::ALIGN_START) , lbl_driver (_("Driver:"), Gtk::ALIGN_START) , lbl_device (_("Device:"), Gtk::ALIGN_START) , lbl_input_device (_("Input Device:"), Gtk::ALIGN_START) , lbl_output_device (_("Output Device:"), Gtk::ALIGN_START) , lbl_sample_rate (_("Sample Rate:"), Gtk::ALIGN_START) , lbl_buffer_size (_("Buffer Size:"), Gtk::ALIGN_START) , lbl_nperiods (_("Periods:"), Gtk::ALIGN_START) , lbl_input_latency (_("Hardware Input Latency:"), Gtk::ALIGN_START) , lbl_output_latency (_("Hardware Output Latency:"), Gtk::ALIGN_START) , lbl_monitor_model (_("Record Monitoring Handled by:"), Gtk::ALIGN_START) , lbl_jack_msg ("", Gtk::ALIGN_START) , unit_samples_txt1 (_("samples"), Gtk::ALIGN_START) , unit_samples_txt2 (_("samples"), Gtk::ALIGN_START) , control_app_button (_("Show Device's\nControl Panel")) , midi_devices_button (_("Setup & Calibration")) , start_stop_button (_("Stop")) , update_devices_button (_("Refresh Devices")) , use_buffered_io_button (_("Use Buffered I/O"), ArdourButton::led_default_elements) , try_autostart_button (_("Autostart"), ArdourButton::led_default_elements) , lm_measure_label (_("Measure")) , lm_use_button (_("Use Results")) , lm_back_button (_("Back to Settings (Ignore Results)")) , lm_button_audio (_("Calibrate Audio")) , lm_table (12, 3) , have_lm_results (false) , lm_running (false) , midi_back_button (_("Back to Settings")) , ignore_changes (0) , ignore_device_changes (0) , _desired_sample_rate (0) , started_at_least_once (false) , queue_device_changed (false) , _have_control (true) , block_signals (0) { using namespace Notebook_Helpers; vector backend_names; Label* label; AttachOptions xopt = AttachOptions (FILL | EXPAND); int row; if (UIConfiguration::instance().get_allow_to_resize_engine_dialog ()) { set_resizable (false); } set_name (X_("AudioMIDISetup")); /* the backend combo is the one thing that is ALWAYS visible */ vector backends = ARDOUR::AudioEngine::instance ()->available_backends (); if (backends.empty ()) { ArdourMessageDialog msg (string_compose (_("No audio/MIDI backends detected. %1 cannot run\n\n(This is a build/packaging/system error. It should never happen.)"), PROGRAM_NAME)); msg.run (); throw failed_constructor (); } for (vector::const_iterator b = backends.begin (); b != backends.end (); ++b) { backend_names.push_back ((*b)->name); } set_popdown_strings (backend_combo, backend_names); /* setup HW monitoring */ monitor_expander.set_expanded (true); monitor_expander.property_expanded ().signal_changed ().connect (sigc::mem_fun (*this, &EngineControl::on_monitor_expand)); monitor_model_combo.append_text (PROGRAM_NAME); monitor_model_combo.append_text (_("Audio Hardware")); if (ARDOUR::HardwareMonitoring == ARDOUR::Config->get_monitoring_model ()) { /* this really depends on running backend, check * AudioEngine::instance()->port_engine().can_monitor_input() */ monitor_model_combo.append_text (_("Audio Driver")); } /* setup latency spinbox behavior */ input_latency.set_can_focus (); input_latency.set_digits (0); input_latency.set_wrap (false); input_latency.set_editable (true); output_latency.set_can_focus (); output_latency.set_digits (0); output_latency.set_wrap (false); output_latency.set_editable (true); /* setup basic packing characteristics for the table used on the main * tab of the notebook */ settings_table.set_spacings (6); settings_table.set_border_width (12); buffer_size_duration_label.set_alignment (0.0); /* left-align */ /* pack it in */ main_hbox.pack_start (settings_table, false, false); main_vbox.pack_start (main_hbox, false, false); midi_vbox.set_border_width (12); midi_device_table.set_border_width (12); midi_vbox.pack_start (midi_device_table, true, true); latency_expander.property_expanded ().signal_changed ().connect (sigc::mem_fun (*this, &EngineControl::on_latency_expand)); latency_expander.set_expanded (false); /* latency measurement tab */ row = 0; lm_table.set_row_spacings (12); lm_table.set_col_spacings (6); lm_table.set_homogeneous (false); lm_title.set_markup (string_compose ("%1", _("Latency Measurement Tool"))); lm_table.attach (lm_title, 0, 4, row, row + 1, xopt, SHRINK); row++; lm_preamble.set_alignment (ALIGN_CENTER); lm_preamble.set_markup (_("Turn down the volume on your audio equipment to a very low level.")); lm_table.attach (lm_preamble, 0, 4, row, row + 1, xopt, SHRINK); row++; Gtk::Label* preamble; preamble = manage (new Label); preamble->set_alignment (ALIGN_CENTER); preamble->set_markup (_("Select two channels below and connect them using a cable.")); lm_table.attach (*preamble, 0, 4, row, row + 1, xopt, SHRINK); row++; label = manage (new Label (_("Playback channel:"), ALIGN_START)); lm_table.attach (*label, 1, 2, row, row + 1, FILL, SHRINK); lm_output_channel_list = Gtk::ListStore::create (lm_output_channel_cols); lm_output_channel_combo.set_model (lm_output_channel_list); lm_output_channel_combo.pack_start (lm_output_channel_cols.pretty_name); lm_table.attach (lm_output_channel_combo, 2, 3, row, row + 1, FILL, SHRINK); ++row; label = manage (new Label (_("Capture channel:"), ALIGN_START)); lm_table.attach (*label, 1, 2, row, row + 1, FILL, SHRINK); lm_input_channel_list = Gtk::ListStore::create (lm_input_channel_cols); lm_input_channel_combo.set_model (lm_input_channel_list); lm_input_channel_combo.pack_start (lm_input_channel_cols.pretty_name); lm_table.attach (lm_input_channel_combo, 2, 3, row, row + 1, FILL, SHRINK); ++row; lm_measure_button.add (lm_measure_label); lm_measure_button.signal_clicked ().connect (sigc::mem_fun (*this, &EngineControl::latency_button_clicked)); lm_use_button.signal_clicked ().connect (sigc::mem_fun (*this, &EngineControl::use_latency_button_clicked)); lm_back_button_signal = lm_back_button.signal_clicked ().connect (sigc::mem_fun (*this, &EngineControl::latency_back_button_clicked)); lm_use_button.set_sensitive (false); preamble = manage (new Label); preamble->set_alignment (ALIGN_CENTER); preamble->set_markup (_("Once the channels are connected, click the \"Measure\" button.\nWhen satisfied with the results, click the \"Use results\" button.")); lm_table.attach (*preamble, 0, 4, row, row + 1, xopt, SHRINK); row++; lm_table.attach (lm_results, 0, 4, row, row + 1, xopt, SHRINK); lm_results.set_markup (string_compose (results_markup, _("No measurement results yet"))); lm_vbox.set_border_width (12); lm_vbox.pack_start (lm_table, false, false); midi_back_button.signal_clicked ().connect (sigc::bind (sigc::mem_fun (notebook, &Gtk::Notebook::set_current_page), 0)); /* pack it all up */ notebook.pages ().push_back (TabElem (main_vbox, _("Audio"))); notebook.pages ().push_back (TabElem (lm_vbox, _("Latency"))); notebook.pages ().push_back (TabElem (midi_vbox, _("MIDI"))); notebook.set_border_width (12); notebook.set_show_tabs (false); notebook.show_all (); notebook.set_name ("SettingsNotebook"); /* packup the notebook */ get_vbox ()->set_border_width (12); get_vbox ()->pack_start (notebook); /* Setup buttons and signals */ lm_button_audio.signal_clicked.connect (sigc::mem_fun (*this, &EngineControl::calibrate_audio_latency)); lm_button_audio.set_name ("generic button"); lm_button_audio.set_can_focus (true); midi_devices_button.signal_clicked.connect (mem_fun (*this, &EngineControl::configure_midi_devices)); midi_devices_button.set_name ("generic button"); midi_devices_button.set_can_focus (true); control_app_button.signal_clicked.connect (mem_fun (*this, &EngineControl::control_app_button_clicked)); control_app_button.set_name ("generic button"); control_app_button.set_can_focus (true); manage_control_app_sensitivity (); start_stop_button.signal_clicked.connect (mem_fun (*this, &EngineControl::start_stop_button_clicked)); start_stop_button.set_sensitive (false); start_stop_button.set_name ("generic button"); start_stop_button.set_can_focus (true); start_stop_button.set_can_default (true); start_stop_button.set_act_on_release (false); update_devices_button.signal_clicked.connect (mem_fun (*this, &EngineControl::update_devices_button_clicked)); update_devices_button.set_sensitive (false); update_devices_button.set_name ("generic button"); update_devices_button.set_can_focus (true); use_buffered_io_button.signal_clicked.connect (mem_fun (*this, &EngineControl::use_buffered_io_button_clicked)); use_buffered_io_button.set_sensitive (false); use_buffered_io_button.set_name ("generic button"); use_buffered_io_button.set_can_focus (true); try_autostart_button.signal_clicked.connect (mem_fun (*this, &EngineControl::try_autostart_button_clicked)); try_autostart_button.set_name ("generic button"); try_autostart_button.set_can_focus (true); set_tooltip (try_autostart_button, string_compose (_("Always try these settings when starting %1, if the same device is available"), PROGRAM_NAME)); ARDOUR::Config->ParameterChanged.connect (*this, invalidator (*this), std::bind (&EngineControl::config_parameter_changed, this, _1), gui_context ()); /* Pick up any existing audio setup configuration, if appropriate */ XMLNode* audio_setup = ARDOUR::Config->extra_xml ("AudioMIDISetup"); ARDOUR::AudioEngine::instance ()->Running.connect (running_connection, MISSING_INVALIDATOR, std::bind (&EngineControl::engine_running, this), gui_context ()); ARDOUR::AudioEngine::instance ()->Stopped.connect (stopped_connection, MISSING_INVALIDATOR, std::bind (&EngineControl::engine_stopped, this), gui_context ()); ARDOUR::AudioEngine::instance ()->Halted.connect (stopped_connection, MISSING_INVALIDATOR, std::bind (&EngineControl::engine_stopped, this), gui_context ()); ARDOUR::AudioEngine::instance ()->DeviceListChanged.connect (devicelist_connection, MISSING_INVALIDATOR, std::bind (&EngineControl::device_list_changed, this), gui_context ()); config_parameter_changed ("try-autostart-engine"); config_parameter_changed ("monitoring-model"); if (audio_setup) { if (!set_state (*audio_setup)) { set_default_state (); } } else { set_default_state (); } update_sensitivity (); connect_changed_signals (); notebook.signal_switch_page ().connect (sigc::mem_fun (*this, &EngineControl::on_switch_page)); connect_disconnect_button.signal_clicked ().connect (sigc::mem_fun (*this, &EngineControl::connect_disconnect_click)); connect_disconnect_button.set_no_show_all (); start_stop_button.set_no_show_all (); lbl_monitor_model.set_no_show_all (); monitor_model_combo.set_no_show_all (); lbl_input_latency.set_no_show_all (); lbl_output_latency.set_no_show_all (); input_latency.set_no_show_all (); output_latency.set_no_show_all (); unit_samples_txt1.set_no_show_all (); unit_samples_txt2.set_no_show_all (); lm_button_audio.set_no_show_all (); midi_devices_button.set_no_show_all (); lbl_midi_system.set_no_show_all (); midi_option_combo.set_no_show_all (); } void EngineControl::connect_changed_signals () { backend_combo_connection = backend_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::backend_changed)); driver_combo_connection = driver_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::driver_changed)); sample_rate_combo_connection = sample_rate_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::sample_rate_changed)); buffer_size_combo_connection = buffer_size_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::buffer_size_changed)); nperiods_combo_connection = nperiods_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::nperiods_changed)); device_combo_connection = device_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::device_changed)); midi_option_combo_connection = midi_option_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::midi_option_changed)); input_device_combo_connection = input_device_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::input_device_changed)); output_device_combo_connection = output_device_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::output_device_changed)); input_latency_connection = input_latency.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::latency_changed)); output_latency_connection = output_latency.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::latency_changed)); monitor_model_connection = monitor_model_combo.signal_changed ().connect ( sigc::mem_fun (*this, &EngineControl::monitor_model_changed)); } void EngineControl::block_changed_signals () { if (block_signals++ == 0) { DEBUG_ECONTROL ("Blocking changed signals"); backend_combo_connection.block (); driver_combo_connection.block (); sample_rate_combo_connection.block (); buffer_size_combo_connection.block (); nperiods_combo_connection.block (); device_combo_connection.block (); input_device_combo_connection.block (); output_device_combo_connection.block (); midi_option_combo_connection.block (); input_latency_connection.block (); output_latency_connection.block (); } } void EngineControl::unblock_changed_signals () { if (--block_signals == 0) { DEBUG_ECONTROL ("Unblocking changed signals"); backend_combo_connection.unblock (); driver_combo_connection.unblock (); sample_rate_combo_connection.unblock (); buffer_size_combo_connection.unblock (); nperiods_combo_connection.unblock (); device_combo_connection.unblock (); input_device_combo_connection.unblock (); output_device_combo_connection.unblock (); midi_option_combo_connection.unblock (); input_latency_connection.unblock (); output_latency_connection.unblock (); } } EngineControl::SignalBlocker::SignalBlocker (EngineControl& engine_control, const std::string& reason) : ec (engine_control) , m_reason (reason) { DEBUG_ECONTROL (string_compose ("SignalBlocker: %1", m_reason)); ec.block_changed_signals (); } EngineControl::SignalBlocker::~SignalBlocker () { DEBUG_ECONTROL (string_compose ("~SignalBlocker: %1", m_reason)); ec.unblock_changed_signals (); } void EngineControl::on_show () { ArdourDialog::on_show (); if (!ARDOUR::AudioEngine::instance ()->current_backend () || !ARDOUR::AudioEngine::instance ()->running ()) { // re-check _have_control (jackd running) see #6041 backend_changed (); } device_changed (); start_stop_button.grab_focus (); } void EngineControl::on_map () { if (!ARDOUR_UI::instance ()->the_session () && !PublicEditor::_instance) { set_type_hint (Gdk::WINDOW_TYPE_HINT_NORMAL); } else if (UIConfiguration::instance ().get_all_floating_windows_are_dialogs ()) { set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG); } else { set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY); } ArdourDialog::on_map (); } void EngineControl::config_parameter_changed (std::string const& p) { if (p == "try-autostart-engine") { try_autostart_button.set_active (ARDOUR::Config->get_try_autostart_engine ()); } else if (p == "monitoring-model") { switch (ARDOUR::Config->get_monitoring_model ()) { case ARDOUR::SoftwareMonitoring: monitor_model_combo.set_active (0); break; case ARDOUR::ExternalMonitoring: monitor_model_combo.set_active (1); break; case ARDOUR::HardwareMonitoring: // TODO check if "via Audio Driver" is present, add it monitor_model_combo.set_active (2); break; } } } bool EngineControl::start_engine () { int rv = push_state_to_backend (true); if (rv < 0) { /* error message from backend */ ArdourMessageDialog msg (*this, ARDOUR::AudioEngine::instance ()->get_last_backend_error ()); msg.run (); } else if (rv > 0) { /* error from push_state_to_backend() */ // TODO: get error message from push_state_to_backend ArdourMessageDialog msg (*this, _("Could not configure Audio/MIDI engine with given settings.")); msg.run (); } return rv == 0; } bool EngineControl::stop_engine (bool for_latency) { if (ARDOUR::AudioEngine::instance ()->stop (for_latency)) { return false; } return true; } void EngineControl::build_notebook () { AttachOptions xopt = AttachOptions (FILL | EXPAND); /* clear the table */ Gtkmm2ext::container_clear (settings_table); settings_table.attach (lbl_audio_system, 0, 1, 0, 1, xopt, SHRINK); settings_table.attach (backend_combo, 1, 2, 0, 1, xopt, SHRINK); settings_table.attach (engine_status, 2, 3, 0, 1, xopt, SHRINK); engine_status.show (); if (_have_control) { build_full_control_notebook (); } else { build_no_control_notebook (); } { PBD::Unwinder protect_ignore_changes (ignore_changes, ignore_changes + 1); main_vbox.show_all (); } notebook.set_current_page (0); populate_action_area (0); on_latency_expand (); on_monitor_expand (); } void EngineControl::build_full_control_notebook () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); using namespace Notebook_Helpers; vector strings; AttachOptions xopt = AttachOptions (FILL | EXPAND); int row = 1; // row zero == backend combo int btn = 0; settings_table.attach (try_autostart_button, 3, 4, btn, btn + 1, xopt, xopt); ++btn; /* Interface */ if (backend->can_use_buffered_io ()) { /* same line as driver */ settings_table.attach (use_buffered_io_button, 3, 4, btn, btn + 1, xopt, xopt); ++btn; } else if (backend->requires_driver_selection ()) { /* push `update_devices_button` down */ ++btn; } if (backend->can_request_update_devices ()) { /* same line and height as Device(s) */ int ht = backend->use_separate_input_and_output_devices () ? 2 : 1; settings_table.attach (update_devices_button, 3, 4, btn, btn + ht, xopt, xopt); } if (backend->requires_driver_selection ()) { settings_table.attach (lbl_driver, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (driver_combo, 1, 2, row, row + 1, xopt, SHRINK); ++row; } settings_table.attach (*manage (new ArdourHSpacer(1.0)), 0, 4, row, row + 1, xopt, SHRINK, 4, 12); row++; if (backend->use_separate_input_and_output_devices ()) { settings_table.attach (lbl_input_device, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (input_device_combo, 1, 2, row, row + 1, xopt, SHRINK); ++row; settings_table.attach (lbl_output_device, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (output_device_combo, 1, 2, row, row + 1, xopt, SHRINK); /* reset so it isn't used in state comparisons */ device_combo.set_active_text (""); ++row; btn += 2; } else { settings_table.attach (lbl_device, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (device_combo, 1, 2, row, row + 1, xopt, SHRINK); /* reset these so they don't get used in state comparisons */ input_device_combo.set_active_text (""); output_device_combo.set_active_text (""); ++row; ++btn; } /* HW settings */ settings_table.attach (lbl_sample_rate, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (sample_rate_combo, 1, 2, row, row + 1, xopt, SHRINK); row++; settings_table.attach (lbl_buffer_size, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (buffer_size_combo, 1, 2, row, row + 1, xopt, SHRINK); settings_table.attach (buffer_size_duration_label, 2, 3, row, row + 1, SHRINK, SHRINK); int ctrl_btn_span = 1; if (backend->can_set_period_size ()) { ++row; settings_table.attach (lbl_nperiods, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (nperiods_combo, 1, 2, row, row + 1, xopt, SHRINK); ++ctrl_btn_span; } /* button spans 2 or 3 rows: Sample rate, Buffer size, Periods */ settings_table.attach (control_app_button, 3, 4, row - ctrl_btn_span, row + 1, xopt, xopt); ++row; settings_table.attach (*manage (new ArdourHSpacer(1.0)), 0, 4, row, row + 1, xopt, SHRINK, 4, 12); ++row; /* Monitor settings */ #ifndef LIVETRAX settings_table.attach (monitor_expander, 0, 4, row, row + 1, xopt, SHRINK); ++row; settings_table.attach (lbl_monitor_model, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (monitor_model_combo, 1, 2, row, row + 1, xopt, SHRINK); ++row; #endif settings_table.attach (latency_expander, 0, 4, row, row + 1, xopt, SHRINK); ++row; /* Advanced: Systemic Latency, MIDI */ settings_table.attach (lbl_midi_system, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (midi_option_combo, 1, 2, row, row + 1, xopt, SHRINK); settings_table.attach (midi_devices_button, 3, 4, row, row + 1, xopt, SHRINK); ++row; settings_table.attach (lbl_input_latency, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (input_latency, 1, 2, row, row + 1, xopt, SHRINK); settings_table.attach (unit_samples_txt1, 2, 3, row, row + 1, SHRINK, SHRINK); settings_table.attach (lm_button_audio, 3, 4, row, row + 2, xopt, xopt); ++row; settings_table.attach (lbl_output_latency, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (output_latency, 1, 2, row, row + 1, xopt, SHRINK); settings_table.attach (unit_samples_txt2, 2, 3, row, row + 1, SHRINK, SHRINK); } void EngineControl::build_no_control_notebook () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); using namespace Notebook_Helpers; vector strings; AttachOptions xopt = AttachOptions (FILL | EXPAND); int row = 1; // row zero == backend combo const string msg = string_compose (_("%1 is already running. %2 will connect to it and use the existing settings."), backend->name (), PROGRAM_NAME); lbl_jack_msg.set_markup (string_compose ("%1", msg)); settings_table.attach (lbl_jack_msg, 0, 2, row, row + 1, xopt, SHRINK); ++row; if (backend->can_change_sample_rate_when_running ()) { settings_table.attach (lbl_sample_rate, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (sample_rate_combo, 1, 2, row, row + 1, xopt, SHRINK); ++row; } if (backend->can_change_buffer_size_when_running ()) { settings_table.attach (lbl_buffer_size, 0, 1, row, row + 1, xopt, SHRINK); settings_table.attach (buffer_size_combo, 1, 2, row, row + 1, xopt, SHRINK); settings_table.attach (buffer_size_duration_label, 2, 3, row, row + 1, xopt, SHRINK); } } EngineControl::~EngineControl () { ignore_changes = true; } void EngineControl::disable_latency_tab () { lm_input_channel_list->clear (); lm_output_channel_list->clear (); lm_measure_button.set_sensitive (false); lm_use_button.set_sensitive (false); } void EngineControl::enable_latency_tab () { State state = get_saved_state_for_currently_displayed_backend_and_device (); vector outputs; vector inputs; ARDOUR::DataType const type = _measure_midi ? ARDOUR::DataType::MIDI : ARDOUR::DataType::AUDIO; ARDOUR::AudioEngine::instance ()->get_physical_outputs (type, outputs); ARDOUR::AudioEngine::instance ()->get_physical_inputs (type, inputs); if (!ARDOUR::AudioEngine::instance ()->running ()) { ArdourMessageDialog msg (_("Failed to start or connect to audio-engine.\n\nLatency calibration requires a working audio interface.")); notebook.set_current_page (0); msg.run (); return; } else if (inputs.empty () || outputs.empty ()) { ArdourMessageDialog msg (_("Your selected audio configuration is playback- or capture-only.\n\nLatency calibration requires playback and capture")); notebook.set_current_page (0); msg.run (); return; } lm_back_button_signal.disconnect (); if (_measure_midi) { lm_back_button_signal = lm_back_button.signal_clicked ().connect (sigc::bind (sigc::mem_fun (notebook, &Gtk::Notebook::set_current_page), midi_tab)); lm_preamble.hide (); } else { lm_back_button_signal = lm_back_button.signal_clicked ().connect (sigc::mem_fun (*this, &EngineControl::latency_back_button_clicked)); lm_preamble.show (); } unsigned int j = 0; unsigned int n = 0; lm_output_channel_list->clear (); for (vector::const_iterator i = outputs.begin (); i != outputs.end (); ++i, ++j) { Gtk::TreeModel::iterator iter = lm_output_channel_list->append (); Gtk::TreeModel::Row row = *iter; row[lm_output_channel_cols.port_name] = *i; std::string pn = ARDOUR::AudioEngine::instance ()->get_pretty_name_by_name (*i); if (pn.empty ()) { pn = (*i).substr ((*i).find (':') + 1); } row[lm_output_channel_cols.pretty_name] = pn; if (state && state->lm_output == *i) { n = j; } } lm_output_channel_combo.set_active (n); lm_output_channel_combo.set_sensitive (true); j = n = 0; lm_input_channel_list->clear (); for (vector::const_iterator i = inputs.begin (); i != inputs.end (); ++i, ++j) { Gtk::TreeModel::iterator iter = lm_input_channel_list->append (); Gtk::TreeModel::Row row = *iter; row[lm_input_channel_cols.port_name] = *i; std::string pn = ARDOUR::AudioEngine::instance ()->get_pretty_name_by_name (*i); if (pn.empty ()) { pn = (*i).substr ((*i).find (':') + 1); } row[lm_input_channel_cols.pretty_name] = pn; if (state && state->lm_input == *i) { n = j; } } lm_input_channel_combo.set_active (n); lm_input_channel_combo.set_sensitive (true); lm_measure_button.set_sensitive (true); } void EngineControl::update_sensitivity () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (!backend) { start_stop_button.set_sensitive (false); return; } bool valid = true; size_t devices_available = 0; bool engine_running = ARDOUR::AudioEngine::instance ()->running (); if (backend->use_separate_input_and_output_devices ()) { devices_available += get_popdown_string_count (input_device_combo); devices_available += get_popdown_string_count (output_device_combo); } else { devices_available += get_popdown_string_count (device_combo); } if (devices_available == 0) { valid = false; input_latency.set_sensitive (false); output_latency.set_sensitive (false); } else { input_latency.set_sensitive (true); output_latency.set_sensitive (true); } if (get_popdown_string_count (buffer_size_combo) > 0) { if (!engine_running) { buffer_size_combo.set_sensitive (valid); } else if (backend->can_change_buffer_size_when_running ()) { buffer_size_combo.set_sensitive (valid || !_have_control); } else { buffer_size_combo.set_sensitive (false); } } else { buffer_size_combo.set_sensitive (false); valid = false; } if (!engine_running || backend->can_change_systemic_latency_when_running ()) { input_latency.set_sensitive (true); output_latency.set_sensitive (true); } else { input_latency.set_sensitive (false); output_latency.set_sensitive (false); } if (get_popdown_string_count (sample_rate_combo) > 0) { bool allow_to_set_rate = false; if (!engine_running) { if (!ARDOUR_UI::instance ()->the_session ()) { // engine is not running, no session loaded -> anything goes. allow_to_set_rate = true; } else if (_desired_sample_rate > 0 && get_rate () != _desired_sample_rate) { // only allow to change if the current setting is not the native session rate. allow_to_set_rate = true; } } sample_rate_combo.set_sensitive (allow_to_set_rate); } else { sample_rate_combo.set_sensitive (false); valid = false; } if (get_popdown_string_count (nperiods_combo) > 0) { if (!engine_running) { nperiods_combo.set_sensitive (true); } else { nperiods_combo.set_sensitive (false); } } else { nperiods_combo.set_sensitive (false); } if (_have_control) { start_stop_button.set_sensitive (true); start_stop_button.show (); if (engine_running) { start_stop_button.set_text (S_("Engine|Stop")); update_devices_button.set_sensitive (false); use_buffered_io_button.set_sensitive (false); } else { start_stop_button.set_text (S_("Engine|Start")); update_devices_button.set_sensitive (backend->can_request_update_devices ()); use_buffered_io_button.set_sensitive (backend->can_use_buffered_io ()); } } else { start_stop_button.set_sensitive (false); start_stop_button.hide (); } if (engine_running && _have_control) { input_device_combo.set_sensitive (false); output_device_combo.set_sensitive (false); device_combo.set_sensitive (false); driver_combo.set_sensitive (false); } else { input_device_combo.set_sensitive (true); output_device_combo.set_sensitive (true); device_combo.set_sensitive (true); if (backend->requires_driver_selection () && get_popdown_string_count (driver_combo) > 0) { driver_combo.set_sensitive (true); } else { driver_combo.set_sensitive (false); } } midi_option_combo.set_sensitive (!engine_running); } void EngineControl::midi_latency_adjustment_changed (Gtk::Adjustment* a, MidiDeviceSettings device, bool for_input) { if (for_input) { device->input_latency = a->get_value (); } else { device->output_latency = a->get_value (); } if (ARDOUR::AudioEngine::instance ()->running () && !_measure_midi) { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); if (device->enabled) { if (for_input) { backend->set_systemic_midi_input_latency (device->name, device->input_latency); } else { backend->set_systemic_midi_output_latency (device->name, device->output_latency); } } } } void EngineControl::midi_device_enabled_toggled (ArdourButton* b, MidiDeviceSettings device) { b->set_active (!b->get_active ()); device->enabled = b->get_active (); refresh_midi_display (device->name); if (ARDOUR::AudioEngine::instance ()->running ()) { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); backend->set_midi_device_enabled (device->name, device->enabled); if (device->enabled) { backend->set_systemic_midi_input_latency (device->name, device->input_latency); backend->set_systemic_midi_output_latency (device->name, device->output_latency); } } } void EngineControl::refresh_midi_display (std::string focus) { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); int row = 0; AttachOptions xopt = AttachOptions (FILL | EXPAND); Gtk::Label* l; Gtkmm2ext::container_clear (midi_device_table); midi_device_table.set_spacings (6); l = manage (new Label); l->set_markup (string_compose ("%1", _("MIDI Devices"))); midi_device_table.attach (*l, 0, 4, row, row + 1, xopt, AttachOptions (0)); l->set_alignment (0.5, 0.5); row++; l->show (); l = manage (new Label (_("Device"))); l->show (); l->set_alignment (0.5, 0.5); midi_device_table.attach (*l, 0, 1, row, row + 2, xopt, AttachOptions (0)); l = manage (new Label (_("Systemic Latency [samples]"))); l->show (); l->set_alignment (0.5, 0.5); midi_device_table.attach (*l, 1, 3, row, row + 1, xopt, AttachOptions (0)); row++; l = manage (new Label (_("Input"))); l->show (); l->set_alignment (0.5, 0.5); midi_device_table.attach (*l, 1, 2, row, row + 1, xopt, AttachOptions (0)); l = manage (new Label (_("Output"))); l->show (); l->set_alignment (0.5, 0.5); midi_device_table.attach (*l, 2, 3, row, row + 1, xopt, AttachOptions (0)); row++; /* Don't autostart engine for MIDI latency compensation, only allow to configure when running * or when the engine is stopped after calibration (otherwise ardour proceeds to load session). */ bool allow_calibration = ARDOUR::AudioEngine::instance ()->running () || !backend->can_change_systemic_latency_when_running (); for (vector::const_iterator p = _midi_devices.begin (); p != _midi_devices.end (); ++p) { ArdourButton* m; Gtk::Button* b; Gtk::Adjustment* a; Gtk::SpinButton* s; bool enabled = (*p)->enabled; m = manage (new ArdourButton ((*p)->name, ArdourButton::led_default_elements)); m->set_name ("midi device"); m->set_can_focus (true); m->add_events (Gdk::BUTTON_RELEASE_MASK); m->set_active (enabled); m->signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &EngineControl::midi_device_enabled_toggled), m, *p)); midi_device_table.attach (*m, 0, 1, row, row + 1, xopt, AttachOptions (0)); m->show (); if ((*p)->name == focus) { m->grab_focus (); } a = manage (new Gtk::Adjustment (0, 0, 99999, 1)); s = manage (new Gtk::SpinButton (*a)); a->set_value ((*p)->input_latency); s->signal_value_changed ().connect (sigc::bind (sigc::mem_fun (*this, &EngineControl::midi_latency_adjustment_changed), a, *p, true)); s->set_sensitive (_can_set_midi_latencies && enabled); midi_device_table.attach (*s, 1, 2, row, row + 1, xopt, AttachOptions (0)); s->show (); a = manage (new Gtk::Adjustment (0, 0, 99999, 1)); s = manage (new Gtk::SpinButton (*a)); a->set_value ((*p)->output_latency); s->signal_value_changed ().connect (sigc::bind (sigc::mem_fun (*this, &EngineControl::midi_latency_adjustment_changed), a, *p, false)); s->set_sensitive (_can_set_midi_latencies && enabled); midi_device_table.attach (*s, 2, 3, row, row + 1, xopt, AttachOptions (0)); s->show (); b = manage (new Button (_("Calibrate"))); b->signal_clicked ().connect (sigc::bind (sigc::mem_fun (*this, &EngineControl::calibrate_midi_latency), *p)); b->set_sensitive (_can_set_midi_latencies && enabled && allow_calibration); midi_device_table.attach (*b, 3, 4, row, row + 1, xopt, AttachOptions (0)); b->show (); row++; } } void EngineControl::backend_changed () { SignalBlocker blocker (*this, "backend_changed"); string backend_name = backend_combo.get_active_text (); std::shared_ptr backend; if (!(backend = ARDOUR::AudioEngine::instance ()->set_backend (backend_name, ARDOUR_COMMAND_LINE::backend_client_name, ""))) { /* eh? setting the backend failed... how ? */ /* A: stale config contains a backend that does not exist in current build */ return; } DEBUG_ECONTROL (string_compose ("Backend name: %1", backend_name)); _have_control = ARDOUR::AudioEngine::instance ()->setup_required (); build_notebook (); _midi_devices.clear (); if (backend->requires_driver_selection ()) { if (set_driver_popdown_strings ()) { driver_changed (); } } else { /* this will change the device text which will cause a call to * device changed which will set up parameters */ list_devices (); } update_midi_options (); connect_disconnect_button.hide (); midi_option_changed (); started_at_least_once = false; /* changing the backend implies stopping the engine * ARDOUR::AudioEngine() may or may not emit this signal * depending on previous engine state */ engine_stopped (); // set "active/inactive" if (!_have_control) { // set settings from backend that we do have control over set_buffersize_popdown_strings (); set_active_text_if_present (buffer_size_combo, bufsize_as_string (backend->buffer_size ())); } if (_have_control && !ignore_changes) { if (!set_state_for_backend (backend_combo.get_active_text ())) { DEBUG_ECONTROL ("backend-changed(): no prior state for backend"); } } else { DEBUG_ECONTROL (string_compose ("backend-changed(): _have_control=%1 ignore_changes=%2", _have_control, ignore_changes)); } if (!ignore_changes) { maybe_display_saved_state (); } if (!UIConfiguration::instance().get_allow_to_resize_engine_dialog ()) { resize (1, 1); // shrink window } } void EngineControl::update_midi_options () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector midi_options = backend->enumerate_midi_options (); if (midi_options.size () == 1 || _have_control) { set_popdown_strings (midi_option_combo, midi_options); if (backend->midi_option ().empty ()) { midi_option_combo.set_active_text (midi_options.front ()); } else { midi_option_combo.set_active_text (backend->midi_option ()); } } } // @return true if there are drivers available bool EngineControl::set_driver_popdown_strings () { DEBUG_ECONTROL ("set_driver_popdown_strings"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector drivers = backend->enumerate_drivers (); if (drivers.empty ()) { // This is an error...? return false; } string current_driver = backend->driver_name (); DEBUG_ECONTROL (string_compose ("backend->driver_name: %1", current_driver)); if (std::find (drivers.begin (), drivers.end (), current_driver) == drivers.end ()) { current_driver = drivers.front (); } set_popdown_strings (driver_combo, drivers); DEBUG_ECONTROL ( string_compose ("driver_combo.set_active_text: %1", current_driver)); driver_combo.set_active_text (current_driver); return true; } std::string EngineControl::get_default_device (const string& current_device_name, const vector& available_devices) { // If the current device is available, use it as default if (std::find (available_devices.begin (), available_devices.end (), current_device_name) != available_devices.end ()) { return current_device_name; } using namespace ARDOUR; string default_device_name = AudioBackend::get_standard_device_name (AudioBackend::DeviceDefault); vector::const_iterator i; // If there is a "Default" device available, use it for (i = available_devices.begin (); i != available_devices.end (); ++i) { if (*i == default_device_name) { return *i; } } string none_device_name = AudioBackend::get_standard_device_name (AudioBackend::DeviceNone); // Use the first device that isn't "None" for (i = available_devices.begin (); i != available_devices.end (); ++i) { if (*i != none_device_name) { return *i; } } // Use "None" if there are no other available return available_devices.front (); } // @return true if there are devices available bool EngineControl::set_device_popdown_strings () { DEBUG_ECONTROL ("set_device_popdown_strings"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector all_devices = backend->enumerate_devices (); /* NOTE: Ardour currently does not display the "available" field of the * returned devices. * * Doing so would require a different GUI widget than the combo * box/popdown that we currently use, since it has no way to list * items that are not selectable. Something more like a popup menu, * which could have unselectable items, would be appropriate. */ vector available_devices; for (vector::const_iterator i = all_devices.begin (); i != all_devices.end (); ++i) { available_devices.push_back (i->name); } if (available_devices.empty ()) { return false; } set_popdown_strings (device_combo, available_devices); std::string default_device = get_default_device (backend->device_name (), available_devices); DEBUG_ECONTROL ( string_compose ("set device_combo active text: %1", default_device)); device_combo.set_active_text (default_device); return true; } // @return true if there are input devices available bool EngineControl::set_input_device_popdown_strings () { DEBUG_ECONTROL ("set_input_device_popdown_strings"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector all_devices = backend->enumerate_input_devices (); vector available_devices; for (vector::const_iterator i = all_devices.begin (); i != all_devices.end (); ++i) { available_devices.push_back (i->name); } if (available_devices.empty ()) { return false; } set_popdown_strings (input_device_combo, available_devices); std::string default_device = get_default_device (backend->input_device_name (), available_devices); DEBUG_ECONTROL ( string_compose ("set input_device_combo active text: %1", default_device)); input_device_combo.set_active_text (default_device); return true; } // @return true if there are output devices available bool EngineControl::set_output_device_popdown_strings () { DEBUG_ECONTROL ("set_output_device_popdown_strings"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector all_devices = backend->enumerate_output_devices (); vector available_devices; for (vector::const_iterator i = all_devices.begin (); i != all_devices.end (); ++i) { available_devices.push_back (i->name); } if (available_devices.empty ()) { return false; } set_popdown_strings (output_device_combo, available_devices); std::string default_device = get_default_device (backend->output_device_name (), available_devices); DEBUG_ECONTROL ( string_compose ("set output_device_combo active text: %1", default_device)); output_device_combo.set_active_text (default_device); return true; } void EngineControl::list_devices () { DEBUG_ECONTROL ("list_devices"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); /* now fill out devices, mark sample rates, buffer sizes insensitive */ bool devices_available = false; block_changed_signals (); if (backend->use_separate_input_and_output_devices ()) { bool input_devices_available = set_input_device_popdown_strings (); bool output_devices_available = set_output_device_popdown_strings (); devices_available = input_devices_available || output_devices_available; } else { devices_available = set_device_popdown_strings (); } unblock_changed_signals (); if (devices_available) { device_changed (); } else { device_combo.clear (); input_device_combo.clear (); output_device_combo.clear (); } update_sensitivity (); } void EngineControl::driver_changed () { SignalBlocker blocker (*this, "driver_changed"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); backend->set_driver (driver_combo.get_active_text ()); list_devices (); // TODO load LRU device(s) for backend + driver combo if (!ignore_changes) { maybe_display_saved_state (); } } vector EngineControl::get_sample_rates_for_all_devices () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector all_rates; if (backend->use_separate_input_and_output_devices ()) { all_rates = backend->available_sample_rates2 (get_input_device_name (), get_output_device_name ()); } else { all_rates = backend->available_sample_rates (get_device_name ()); } return all_rates; } vector EngineControl::get_default_sample_rates () { vector rates; rates.push_back (8000.0f); rates.push_back (16000.0f); rates.push_back (32000.0f); rates.push_back (44100.0f); rates.push_back (48000.0f); rates.push_back (88200.0f); rates.push_back (96000.0f); rates.push_back (192000.0f); rates.push_back (384000.0f); return rates; } void EngineControl::set_samplerate_popdown_strings () { DEBUG_ECONTROL ("set_samplerate_popdown_strings"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); string desired; vector sr; vector s; if (_have_control) { sr = get_sample_rates_for_all_devices (); } else { sr = get_default_sample_rates (); } for (vector::const_iterator x = sr.begin (); x != sr.end (); ++x) { s.push_back (rate_as_string (*x)); if (*x == _desired_sample_rate) { desired = s.back (); } } set_popdown_strings (sample_rate_combo, s); if (!s.empty ()) { if (ARDOUR::AudioEngine::instance ()->running ()) { sample_rate_combo.set_active_text (rate_as_string (backend->sample_rate ())); } else if (ARDOUR_UI::instance ()->the_session ()) { float active_sr = ARDOUR_UI::instance ()->the_session ()->nominal_sample_rate (); if (std::find (sr.begin (), sr.end (), active_sr) == sr.end ()) { active_sr = sr.front (); } sample_rate_combo.set_active_text (rate_as_string (active_sr)); } else if (desired.empty ()) { float new_active_sr = backend->default_sample_rate (); if (std::find (sr.begin (), sr.end (), new_active_sr) == sr.end ()) { new_active_sr = sr.front (); } sample_rate_combo.set_active_text (rate_as_string (new_active_sr)); } else { sample_rate_combo.set_active_text (desired); } } update_sensitivity (); } vector EngineControl::get_buffer_sizes_for_all_devices () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector all_sizes; if (backend->use_separate_input_and_output_devices ()) { all_sizes = backend->available_buffer_sizes2 (get_input_device_name (), get_output_device_name ()); } else { all_sizes = backend->available_buffer_sizes (get_device_name ()); } return all_sizes; } vector EngineControl::get_default_buffer_sizes () { vector sizes; sizes.push_back (8); sizes.push_back (16); sizes.push_back (32); sizes.push_back (64); sizes.push_back (128); sizes.push_back (256); sizes.push_back (512); sizes.push_back (1024); sizes.push_back (2048); sizes.push_back (4096); sizes.push_back (8192); return sizes; } void EngineControl::set_buffersize_popdown_strings () { DEBUG_ECONTROL ("set_buffersize_popdown_strings"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector bs; vector s; if (_have_control) { bs = get_buffer_sizes_for_all_devices (); } else if (backend->can_change_buffer_size_when_running ()) { bs = get_default_buffer_sizes (); } for (vector::const_iterator x = bs.begin (); x != bs.end (); ++x) { s.push_back (bufsize_as_string (*x)); } uint32_t previous_size = backend->buffer_size (); if (!buffer_size_combo.get_active_text ().empty ()) { previous_size = get_buffer_size (); } set_popdown_strings (buffer_size_combo, s); if (!s.empty ()) { if (std::find (bs.begin (), bs.end (), previous_size) != bs.end ()) { buffer_size_combo.set_active_text (bufsize_as_string (previous_size)); } else { buffer_size_combo.set_active_text (s.front ()); uint32_t period = backend->buffer_size (); if (0 == period && backend->use_separate_input_and_output_devices ()) { period = backend->default_buffer_size (get_input_device_name ()); } if (0 == period && backend->use_separate_input_and_output_devices ()) { period = backend->default_buffer_size (get_output_device_name ()); } if (0 == period && !backend->use_separate_input_and_output_devices ()) { period = backend->default_buffer_size (get_device_name ()); } set_active_text_if_present (buffer_size_combo, bufsize_as_string (period)); } show_buffer_duration (); } update_sensitivity (); } void EngineControl::set_nperiods_popdown_strings () { DEBUG_ECONTROL ("set_nperiods_popdown_strings"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); vector np; vector s; if (backend->can_set_period_size ()) { if (backend->use_separate_input_and_output_devices ()) { np = backend->available_period_sizes (get_driver (), get_output_device_name ()); } else { np = backend->available_period_sizes (get_driver (), get_device_name ()); } } for (vector::const_iterator x = np.begin (); x != np.end (); ++x) { s.push_back (to_string (*x)); } set_popdown_strings (nperiods_combo, s); if (!s.empty ()) { set_active_text_if_present (nperiods_combo, to_string (backend->period_size ())); // XXX } update_sensitivity (); } void EngineControl::device_changed () { SignalBlocker blocker (*this, "device_changed"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); string device_name_in; string device_name_out; // only used if backend support separate I/O devices if (backend->use_separate_input_and_output_devices ()) { device_name_in = get_input_device_name (); device_name_out = get_output_device_name (); } else { device_name_in = get_device_name (); } /* we set the backend-device to query various device related intormation. * This has the side effect that backend->device_name() will match * the device_name and 'change_device' will never be true. * so work around this by setting... */ if (!_have_control) { queue_device_changed = false; } else if (backend->use_separate_input_and_output_devices ()) { if (device_name_in != backend->input_device_name () || device_name_out != backend->output_device_name ()) { queue_device_changed = true; } } else { if (device_name_in != backend->device_name ()) { queue_device_changed = true; } } //the device name must be set FIRST so ASIO can populate buffersizes and the control panel button if (ARDOUR::AudioEngine::instance ()->running ()) { assert (!queue_device_changed); } else if (backend->use_separate_input_and_output_devices ()) { backend->set_input_device_name (device_name_in); backend->set_output_device_name (device_name_out); } else { backend->set_device_name (device_name_in); } { /* don't allow programmatic change to combos to cause a recursive call to this method. */ PBD::Unwinder protect_ignore_changes (ignore_changes, ignore_changes + 1); set_samplerate_popdown_strings (); set_buffersize_popdown_strings (); set_nperiods_popdown_strings (); /* TODO set min + max channel counts here */ manage_control_app_sensitivity (); } /* pick up any saved state for this device */ if (!ignore_changes) { maybe_display_saved_state (); } } void EngineControl::input_device_changed () { DEBUG_ECONTROL ("input_device_changed"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (backend && backend->match_input_output_devices_or_none ()) { const std::string& dev_none = ARDOUR::AudioBackend::get_standard_device_name (ARDOUR::AudioBackend::DeviceNone); if (get_output_device_name () != dev_none && get_input_device_name () != dev_none && get_input_device_name () != get_output_device_name ()) { block_changed_signals (); if (contains_value (output_device_combo, get_input_device_name ())) { output_device_combo.set_active_text (get_input_device_name ()); } else { assert (contains_value (output_device_combo, dev_none)); output_device_combo.set_active_text (dev_none); } unblock_changed_signals (); } } device_changed (); } void EngineControl::output_device_changed () { DEBUG_ECONTROL ("output_device_changed"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (backend && backend->match_input_output_devices_or_none ()) { const std::string& dev_none = ARDOUR::AudioBackend::get_standard_device_name (ARDOUR::AudioBackend::DeviceNone); if (get_input_device_name () != dev_none && get_input_device_name () != dev_none && get_input_device_name () != get_output_device_name ()) { block_changed_signals (); if (contains_value (input_device_combo, get_output_device_name ())) { input_device_combo.set_active_text (get_output_device_name ()); } else { assert (contains_value (input_device_combo, dev_none)); input_device_combo.set_active_text (dev_none); } unblock_changed_signals (); } } device_changed (); } string EngineControl::bufsize_as_string (uint32_t sz) { return string_compose (P_("%1 sample", "%1 samples", sz), to_string (sz)); } void EngineControl::sample_rate_changed () { DEBUG_ECONTROL ("sample_rate_changed"); /* reset the strings for buffer size to show the correct msec value (reflecting the new sample rate). */ show_buffer_duration (); } void EngineControl::buffer_size_changed () { DEBUG_ECONTROL ("buffer_size_changed"); if (ARDOUR::AudioEngine::instance ()->running ()) { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (backend && backend->can_change_buffer_size_when_running ()) { backend->set_buffer_size (get_buffer_size ()); } } show_buffer_duration (); } void EngineControl::nperiods_changed () { DEBUG_ECONTROL ("nperiods_changed"); show_buffer_duration (); } void EngineControl::show_buffer_duration () { DEBUG_ECONTROL ("show_buffer_duration"); /* buffer sizes - convert from just samples to samples + msecs for * the displayed string */ string bs_text = buffer_size_combo.get_active_text (); uint32_t samples = atoi (bs_text); /* will ignore trailing text */ uint32_t rate = get_rate (); /* Except for ALSA and Dummy backends, we don't know the number of periods * per cycle and settings. * * jack1 vs jack2 have different default latencies since jack2 start * in async-mode unless --sync is given which adds an extra cycle * of latency. The value is not known if jackd is started externally.. * * So just display the period size, that's also what * ARDOUR_UI::update_sample_rate() does for the status bar. * (the statusbar calls AudioEngine::instance()->usecs_per_cycle() * but still, that's the buffer period, not [round-trip] latency) */ char buf[32]; snprintf (buf, sizeof (buf), _("(%.1f ms)"), (samples / (rate / 1000.0f))); buffer_size_duration_label.set_text (buf); } void EngineControl::midi_option_changed () { DEBUG_ECONTROL ("midi_option_changed"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); backend->set_midi_option (get_midi_option ()); vector midi_devices = backend->enumerate_midi_devices (); _can_set_midi_latencies = backend->can_set_systemic_midi_latencies (); std::vector new_devices; for (vector::const_iterator i = midi_devices.begin (); i != midi_devices.end (); ++i) { MidiDeviceSettings mds = find_midi_device (i->name); if (i->available && !mds) { uint32_t input_latency = 0; uint32_t output_latency = 0; if (_can_set_midi_latencies) { input_latency = backend->systemic_midi_input_latency (i->name); output_latency = backend->systemic_midi_output_latency (i->name); } bool enabled = backend->midi_device_enabled (i->name); MidiDeviceSettings ptr (new MidiDeviceSetting (i->name, enabled, input_latency, output_latency)); new_devices.push_back (ptr); } else if (i->available) { new_devices.push_back (mds); } } _midi_devices = new_devices; if (_midi_devices.empty ()) { midi_devices_button.set_sensitive (false); } else { midi_devices_button.set_sensitive (true); } } void EngineControl::latency_changed () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (!backend || !_have_control || !ARDOUR::AudioEngine::instance ()->running ()) { return; } if (!backend->can_change_systemic_latency_when_running ()) { return; } backend->set_systemic_input_latency (get_input_latency ()); backend->set_systemic_output_latency (get_output_latency ()); post_push (); } void EngineControl::monitor_model_changed () { ARDOUR::MonitorModel mm = ARDOUR::Config->get_monitoring_model (); switch (monitor_model_combo.get_active_row_number ()) { case 0: mm = ARDOUR::SoftwareMonitoring; break; case 1: mm = ARDOUR::ExternalMonitoring; break; case 2: mm = ARDOUR::HardwareMonitoring; break; } if (mm == ARDOUR::Config->get_monitoring_model ()) { return; } ARDOUR::Config->set_monitoring_model (mm); ARDOUR::Config->save_state (); } bool EngineControl::set_state_for_backend (const string& backend) { for (StateList::const_iterator i = states.begin (); i != states.end (); ++i) { if ((*i)->backend != backend) { continue; } PBD::Unwinder protect_ignore_changes (ignore_changes, ignore_changes + 1); if (set_current_state (*i)) { //push_state_to_backend (false); return true; } } return false; } EngineControl::State EngineControl::get_matching_state ( const string& backend, const string& driver, const string& device, const float sample_rate) { for (auto const& i : states) { if (i->backend != backend) { continue; } if (!_have_control) { return i; } if (i->driver == driver && i->device == device) { if (sample_rate == 0 || i->sample_rate == sample_rate) { return i; } } } return State (); } EngineControl::State EngineControl::get_matching_state ( const string& backend, const string& driver, const string& input_device, const string& output_device, const float sample_rate) { for (auto const& i : states) { if (i->backend != backend) { continue; } if (!_have_control) { return i; } if (i->driver == driver && i->input_device == input_device && i->output_device == output_device) { if (sample_rate == 0 || i->sample_rate == sample_rate) { return i; } } } return State (); } EngineControl::State EngineControl::get_saved_state_for_currently_displayed_backend_and_device () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (backend) { if (backend->use_separate_input_and_output_devices ()) { return get_matching_state (backend_combo.get_active_text (), (backend->requires_driver_selection () ? (std::string)driver_combo.get_active_text () : string ()), input_device_combo.get_active_text (), output_device_combo.get_active_text (), get_rate ()); } else { return get_matching_state (backend_combo.get_active_text (), (backend->requires_driver_selection () ? (std::string)driver_combo.get_active_text () : string ()), device_combo.get_active_text (), get_rate ()); } } return get_matching_state (backend_combo.get_active_text (), string (), device_combo.get_active_text (), get_rate ()); } bool EngineControl::equivalent_states (const EngineControl::State& state1, const EngineControl::State& state2) { if (state1->backend == state2->backend && state1->driver == state2->driver && state1->device == state2->device && state1->input_device == state2->input_device && state1->output_device == state2->output_device && state1->sample_rate == state2->sample_rate) { return true; } return false; } // sort active first, then most recently used to the beginning of the list bool EngineControl::state_sort_cmp (const State& a, const State& b) { if (a->active) { return true; } else if (b->active) { return false; } else { return a->lru > b->lru; } } EngineControl::State EngineControl::save_state () { State state; if (!_have_control) { state = get_matching_state (backend_combo.get_active_text (), string (), string ()); if (state) { state->lru = time (NULL); return state; } state.reset (new StateStruct); state->backend = get_backend (); } else { state.reset (new StateStruct); store_state (state); } for (StateList::iterator i = states.begin (); i != states.end ();) { if (equivalent_states (*i, state)) { i = states.erase (i); } else { ++i; } } states.push_back (state); states.sort (state_sort_cmp); return state; } void EngineControl::store_state (State state) { state->backend = get_backend (); state->driver = get_driver (); state->device = get_device_name (); state->input_device = get_input_device_name (); state->output_device = get_output_device_name (); state->sample_rate = get_rate (); state->buffer_size = get_buffer_size (); state->n_periods = get_nperiods (); state->input_latency = get_input_latency (); state->output_latency = get_output_latency (); state->midi_option = get_midi_option (); state->midi_devices = _midi_devices; state->use_buffered_io = get_use_buffered_io (); } void EngineControl::maybe_display_saved_state () { if (!_have_control || ARDOUR::AudioEngine::instance ()->running ()) { return; } State state = get_saved_state_for_currently_displayed_backend_and_device (); if (state) { DEBUG_ECONTROL ("Restoring saved state"); PBD::Unwinder protect_ignore_changes (ignore_changes, ignore_changes + 1); if (0 == _desired_sample_rate && sample_rate_combo.get_sensitive ()) { sample_rate_combo.set_active_text (rate_as_string (state->sample_rate)); } set_active_text_if_present (buffer_size_combo, bufsize_as_string (state->buffer_size)); set_active_text_if_present (nperiods_combo, to_string (state->n_periods)); /* call this explicitly because we're ignoring changes to the controls at this point. */ show_buffer_duration (); input_latency.set_value (state->input_latency); output_latency.set_value (state->output_latency); use_buffered_io_button.set_active (state->use_buffered_io); if (!state->midi_option.empty ()) { midi_option_combo.set_active_text (state->midi_option); _midi_devices = state->midi_devices; midi_option_changed (); } } else { DEBUG_ECONTROL ("Unable to find saved state for backend and devices"); input_latency.set_value (0); output_latency.set_value (0); } } XMLNode& EngineControl::get_state () const { LocaleGuard lg; XMLNode* root = new XMLNode ("AudioMIDISetup"); std::string path; if (!states.empty ()) { XMLNode* state_nodes = new XMLNode ("EngineStates"); for (StateList::const_iterator i = states.begin (); i != states.end (); ++i) { XMLNode* node = new XMLNode ("State"); node->set_property ("backend", (*i)->backend); node->set_property ("driver", (*i)->driver); node->set_property ("device", (*i)->device); node->set_property ("input-device", (*i)->input_device); node->set_property ("output-device", (*i)->output_device); node->set_property ("sample-rate", (*i)->sample_rate); node->set_property ("buffer-size", (*i)->buffer_size); node->set_property ("n-periods", (*i)->n_periods); node->set_property ("input-latency", (*i)->input_latency); node->set_property ("output-latency", (*i)->output_latency); node->set_property ("lm-input", (*i)->lm_input); node->set_property ("lm-output", (*i)->lm_output); node->set_property ("active", (*i)->active); node->set_property ("use-buffered-io", (*i)->use_buffered_io); node->set_property ("midi-option", (*i)->midi_option); int32_t lru_val = (*i)->active ? time (NULL) : (*i)->lru; node->set_property ("lru", lru_val); XMLNode* midi_devices = new XMLNode ("MIDIDevices"); for (std::vector::const_iterator p = (*i)->midi_devices.begin (); p != (*i)->midi_devices.end (); ++p) { XMLNode* midi_device_stuff = new XMLNode ("MIDIDevice"); midi_device_stuff->set_property (X_("name"), (*p)->name); midi_device_stuff->set_property (X_("enabled"), (*p)->enabled); midi_device_stuff->set_property (X_("input-latency"), (*p)->input_latency); midi_device_stuff->set_property (X_("output-latency"), (*p)->output_latency); midi_devices->add_child_nocopy (*midi_device_stuff); } node->add_child_nocopy (*midi_devices); state_nodes->add_child_nocopy (*node); } root->add_child_nocopy (*state_nodes); } return *root; } void EngineControl::set_default_state () { vector backend_names; vector backends = ARDOUR::AudioEngine::instance ()->available_backends (); for (vector::const_iterator b = backends.begin (); b != backends.end (); ++b) { backend_names.push_back ((*b)->name); } backend_combo.set_active_text (backend_names.front ()); // We could set default backends per platform etc here backend_changed (); } bool EngineControl::set_state (const XMLNode& root) { XMLNodeList clist, cclist; XMLNodeConstIterator citer, cciter; XMLNode const* child; XMLNode const* grandchild; if (root.name () != "AudioMIDISetup") { return false; } clist = root.children (); states.clear (); for (citer = clist.begin (); citer != clist.end (); ++citer) { child = *citer; if (child->name () != "EngineStates") { continue; } cclist = child->children (); for (cciter = cclist.begin (); cciter != cclist.end (); ++cciter) { State state (new StateStruct); grandchild = *cciter; if (grandchild->name () != "State") { continue; } if (!grandchild->get_property ("backend", state->backend)) { continue; } // If any of the required properties are not found in the state node // then continue/skip to the next engine state if (!grandchild->get_property ("driver", state->driver) || !grandchild->get_property ("device", state->device) || !grandchild->get_property ("input-device", state->input_device) || !grandchild->get_property ("output-device", state->output_device) || !grandchild->get_property ("sample-rate", state->sample_rate) || !grandchild->get_property ("buffer-size", state->buffer_size) || !grandchild->get_property ("input-latency", state->input_latency) || !grandchild->get_property ("output-latency", state->output_latency) || !grandchild->get_property ("active", state->active) || !grandchild->get_property ("use-buffered-io", state->use_buffered_io) || !grandchild->get_property ("midi-option", state->midi_option)) { continue; } if (!grandchild->get_property ("n-periods", state->n_periods)) { // optional (new value in 4.5) state->n_periods = 0; } if (!grandchild->get_property ("lm-input", state->lm_input) || !grandchild->get_property ("lm-output", state->lm_output)) { state->lm_input = ""; state->lm_output = ""; } state->midi_devices.clear (); XMLNode* midinode; if ((midinode = ARDOUR::find_named_node (*grandchild, "MIDIDevices")) != 0) { const XMLNodeList mnc = midinode->children (); for (XMLNodeList::const_iterator n = mnc.begin (); n != mnc.end (); ++n) { std::string name; bool enabled; uint32_t input_latency; uint32_t output_latency; if (!(*n)->get_property (X_("name"), name) || !(*n)->get_property (X_("enabled"), enabled) || !(*n)->get_property (X_("input-latency"), input_latency) || !(*n)->get_property (X_("output-latency"), output_latency)) { continue; } MidiDeviceSettings ptr ( new MidiDeviceSetting (name, enabled, input_latency, output_latency)); state->midi_devices.push_back (ptr); } } int32_t lru_val; if (grandchild->get_property ("lru", lru_val)) { state->lru = lru_val; } states.push_back (state); } } /* now see if there was an active state and switch the setup to it */ /* purge states of backend that are not available in this built */ vector backends = ARDOUR::AudioEngine::instance ()->available_backends (); vector backend_names; for (vector::const_iterator i = backends.begin (); i != backends.end (); ++i) { backend_names.push_back ((*i)->name); } for (StateList::iterator i = states.begin (); i != states.end ();) { if (std::find (backend_names.begin (), backend_names.end (), (*i)->backend) == backend_names.end ()) { i = states.erase (i); } else { ++i; } } states.sort (state_sort_cmp); /* purge old states referring to the same backend */ const time_t now = time (NULL); for (vector::const_iterator bi = backend_names.begin (); bi != backend_names.end (); ++bi) { bool first = true; for (StateList::iterator i = states.begin (); i != states.end ();) { if ((*i)->backend != *bi) { ++i; continue; } /* keep at latest one for every audio-system */ if (first) { first = false; ++i; continue; } /* keep states used in the last 2 weeks */ if ((now - (*i)->lru) < 86400 * 14) { ++i; continue; } /* also keep state if it was used in the last 90 days * and latency was calibrated */ if ((now - (*i)->lru) < 86400 * 90) { if ((*i)->input_latency != 0 || (*i)->output_latency != 0) { ++i; continue; } } assert (!(*i)->active); i = states.erase (i); } } /* active was sorted first */ for (StateList::const_iterator i = states.begin (); i != states.end (); ++i) { /* test if the backend & device is available */ if (set_current_state (*i)) { return 0 == push_state_to_backend (false); } } return false; } bool EngineControl::set_current_state (const State& state) { DEBUG_ECONTROL ("set_current_state"); std::shared_ptr backend; if (!(backend = ARDOUR::AudioEngine::instance ()->set_backend (state->backend, ARDOUR_COMMAND_LINE::backend_client_name, ""))) { DEBUG_ECONTROL (string_compose ("Unable to set backend to %1", state->backend)); // this shouldn't happen as the invalid backend names should have been // removed from the list of states. return false; } // now reflect the change in the backend in the GUI so backend_changed will // do the right thing backend_combo.set_active_text (state->backend); if (!ARDOUR::AudioEngine::instance ()->setup_required ()) { backend_changed (); // we don't have control don't restore state return true; } if (!state->driver.empty ()) { if (!backend->requires_driver_selection ()) { DEBUG_ECONTROL ("Backend should require driver selection"); // A backend has changed from having driver selection to not having // it or someone has been manually editing a config file and messed // it up return false; } if (backend->set_driver (state->driver) != 0) { DEBUG_ECONTROL (string_compose ("Unable to set driver %1", state->driver)); // Driver names for a backend have changed and the name in the // config file is now invalid or support for driver is no longer // included in the backend return false; } // no need to set the driver_combo as backend_changed will use // backend->driver_name to set the active driver } if (!state->device.empty ()) { if (backend->set_device_name (state->device) != 0) { DEBUG_ECONTROL ( string_compose ("Unable to set device name %1", state->device)); // device is no longer available on the system return false; } // no need to set active device as it will be picked up in // via backend_changed ()/set_device_popdown_strings } else { // backend supports separate input/output devices if (backend->set_input_device_name (state->input_device) != 0) { DEBUG_ECONTROL (string_compose ("Unable to set input device name %1", state->input_device)); // input device is no longer available on the system return false; } if (backend->set_output_device_name (state->output_device) != 0) { DEBUG_ECONTROL (string_compose ("Unable to set output device name %1", state->input_device)); // output device is no longer available on the system return false; } // no need to set active devices as it will be picked up in via // backend_changed ()/set_*_device_popdown_strings } backend_changed (); // Now restore the state of the rest of the controls // We don't use a SignalBlocker as set_current_state is currently only // called from set_state before any signals are connected. If at some point // a more general named state mechanism is implemented and // set_current_state is called while signals are connected then a // SignalBlocker will need to be instantiated before setting these. device_combo.set_active_text (state->device); input_device_combo.set_active_text (state->input_device); output_device_combo.set_active_text (state->output_device); if (0 == _desired_sample_rate && sample_rate_combo.get_sensitive ()) { sample_rate_combo.set_active_text (rate_as_string (state->sample_rate)); } set_active_text_if_present (buffer_size_combo, bufsize_as_string (state->buffer_size)); set_active_text_if_present (nperiods_combo, to_string (state->n_periods)); set_active_text_if_present (midi_option_combo, state->midi_option); input_latency.set_value (state->input_latency); output_latency.set_value (state->output_latency); use_buffered_io_button.set_active (state->use_buffered_io); return true; } int EngineControl::push_state_to_backend (bool start) { DEBUG_ECONTROL ("push_state_to_backend"); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); PBD::Unwinder protect_ignore_device_changes (ignore_device_changes, ignore_device_changes + 1); if (!backend) { return 0; } /* figure out what is going to change */ bool restart_required = false; bool was_running = ARDOUR::AudioEngine::instance ()->running (); bool change_driver = false; bool change_device = false; bool change_rate = false; bool change_bufsize = false; bool change_nperiods = false; bool change_latency = false; bool change_midi = false; bool change_buffered_io = false; if (_have_control) { if (started_at_least_once) { /* we can control the backend */ if (backend->requires_driver_selection ()) { if (get_driver () != backend->driver_name ()) { change_driver = true; } } if (backend->use_separate_input_and_output_devices ()) { if (get_input_device_name () != backend->input_device_name ()) { change_device = true; } if (get_output_device_name () != backend->output_device_name ()) { change_device = true; } } else { if (get_device_name () != backend->device_name ()) { change_device = true; } } if (queue_device_changed) { change_device = true; } if (get_rate () != backend->sample_rate ()) { change_rate = true; } if (get_buffer_size () != backend->buffer_size ()) { change_bufsize = true; } if (backend->can_set_period_size () && get_popdown_string_count (nperiods_combo) > 0 && get_nperiods () != backend->period_size ()) { change_nperiods = true; } if (get_midi_option () != backend->midi_option ()) { change_midi = true; } if (backend->can_use_buffered_io ()) { if (get_use_buffered_io () != backend->get_use_buffered_io ()) { change_buffered_io = true; } } if (get_input_latency () != backend->systemic_input_latency () || get_output_latency () != backend->systemic_output_latency ()) { change_latency = true; } } else { /* backend never started, so we have to force a group of settings. */ change_device = true; if (backend->requires_driver_selection ()) { change_driver = true; } change_rate = true; change_bufsize = true; change_latency = true; change_midi = true; change_buffered_io = backend->can_use_buffered_io (); change_nperiods = backend->can_set_period_size () && get_popdown_string_count (nperiods_combo) > 0; } } else { /* we have no control over the backend, meaning that we can * only possibly change sample rate and buffer size. */ if (get_rate () != backend->sample_rate ()) { change_bufsize = true; } if (get_buffer_size () != backend->buffer_size ()) { change_bufsize = true; } } queue_device_changed = false; if (!_have_control) { /* We do not have control over the backend, so the best we can * do is try to change the sample rate and/or bufsize and get * out of here. */ if (change_rate && !backend->can_change_sample_rate_when_running ()) { return 1; } if (change_bufsize && !backend->can_change_buffer_size_when_running ()) { return 1; } if (change_rate) { backend->set_sample_rate (get_rate ()); } if (change_bufsize) { backend->set_buffer_size (get_buffer_size ()); } if (start) { if (ARDOUR::AudioEngine::instance ()->start ()) { error << string_compose (_("Could not start backend engine %1"), backend->name ()) << endmsg; return -1; } } post_push (); return 0; } /* determine if we need to stop the backend before changing parameters */ if (change_driver || change_device || change_nperiods || (change_latency && !backend->can_change_systemic_latency_when_running ()) || (change_rate && !backend->can_change_sample_rate_when_running ()) || change_midi || change_buffered_io || (change_bufsize && !backend->can_change_buffer_size_when_running ())) { restart_required = true; } else { restart_required = false; } if (was_running) { if (restart_required) { if (ARDOUR::AudioEngine::instance ()->stop ()) { return 1; } } } if (change_driver && backend->set_driver (get_driver ())) { error << string_compose (_("Cannot set driver to %1"), get_driver ()) << endmsg; return 1; } if (backend->use_separate_input_and_output_devices ()) { if (change_device && backend->set_input_device_name (get_input_device_name ())) { error << string_compose (_("Cannot set input device name to %1"), get_input_device_name ()) << endmsg; return 1; } if (change_device && backend->set_output_device_name (get_output_device_name ())) { error << string_compose (_("Cannot set output device name to %1"), get_output_device_name ()) << endmsg; return 1; } } else { if (change_device && backend->set_device_name (get_device_name ())) { error << string_compose (_("Cannot set device name to %1"), get_device_name ()) << endmsg; return 1; } } if (change_rate && backend->set_sample_rate (get_rate ())) { error << string_compose (_("Cannot set sample rate to %1"), get_rate ()) << endmsg; return 1; } if (change_bufsize && backend->set_buffer_size (get_buffer_size ())) { error << string_compose (_("Cannot set buffer size to %1"), get_buffer_size ()) << endmsg; return 1; } if (change_nperiods && backend->set_peridod_size (get_nperiods ())) { error << string_compose (_("Cannot set periods to %1"), get_nperiods ()) << endmsg; return 1; } if (change_latency) { if (backend->set_systemic_input_latency (get_input_latency ())) { error << string_compose (_("Cannot set input latency to %1"), get_input_latency ()) << endmsg; return 1; } if (backend->set_systemic_output_latency (get_output_latency ())) { error << string_compose (_("Cannot set output latency to %1"), get_output_latency ()) << endmsg; return 1; } } if (change_midi) { backend->set_midi_option (get_midi_option ()); } if (change_buffered_io) { backend->set_use_buffered_io (use_buffered_io_button.get_active ()); } if (1 /* TODO */) { for (vector::const_iterator p = _midi_devices.begin (); p != _midi_devices.end (); ++p) { if (_measure_midi) { /* Disable other MIDI devices while measuring. * This is a hack to only show ports from the selected device */ if (*p == _measure_midi) { backend->set_midi_device_enabled ((*p)->name, true); } else { backend->set_midi_device_enabled ((*p)->name, false); } continue; } backend->set_midi_device_enabled ((*p)->name, (*p)->enabled); if (backend->can_set_systemic_midi_latencies ()) { backend->set_systemic_midi_input_latency ((*p)->name, (*p)->input_latency); backend->set_systemic_midi_output_latency ((*p)->name, (*p)->output_latency); } } } if (start || (was_running && restart_required)) { if (ARDOUR::AudioEngine::instance ()->start ()) { return -1; } } post_push (); return 0; } void EngineControl::post_push () { /* get a pointer to the current state object, creating one if * necessary */ State state = get_saved_state_for_currently_displayed_backend_and_device (); if (!state) { state = save_state (); assert (state); } else { store_state (state); } if (ARDOUR::AudioEngine::instance ()->running ()) { /* all off */ for (StateList::iterator i = states.begin (); i != states.end (); ++i) { (*i)->active = false; } /* mark this one active (to be used next time the dialog is shown) */ state->active = true; state->lru = time (NULL); } states.sort (state_sort_cmp); if (_have_control) { // XXX manage_control_app_sensitivity (); } /* schedule a redisplay of MIDI ports */ //Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (*this, &EngineControl::refresh_midi_display), false), 1000); } float EngineControl::get_rate () const { float r = atof (sample_rate_combo.get_active_text ()); /* the string may have been translated with an abbreviation for * thousands, so use a crude heuristic to fix this. */ if (r < 1000.0) { r *= 1000.0; } return r; } uint32_t EngineControl::get_buffer_size () const { string txt = buffer_size_combo.get_active_text (); uint32_t samples; if (sscanf (txt.c_str (), "%d", &samples) != 1) { fprintf (stderr, "Find a trout and repeatedly slap the nearest C++ developer who throws exceptions without catching them.\n"); fprintf (stderr, "Ardour will likely crash now, giving you time to get the trout.\n"); fprintf (stderr, "the buffer size combo said [%s]\n", txt.c_str()); throw exception (); } return samples; } uint32_t EngineControl::get_nperiods () const { string txt = nperiods_combo.get_active_text (); return atoi (txt.c_str ()); } string EngineControl::get_midi_option () const { return midi_option_combo.get_active_text (); } bool EngineControl::get_use_buffered_io () const { return use_buffered_io_button.get_active (); } uint32_t EngineControl::get_input_latency () const { return (uint32_t)input_latency_adjustment.get_value (); } uint32_t EngineControl::get_output_latency () const { return (uint32_t)output_latency_adjustment.get_value (); } string EngineControl::get_backend () const { return backend_combo.get_active_text (); } string EngineControl::get_driver () const { if (driver_combo.get_parent ()) { return driver_combo.get_active_text (); } else { return ""; } } string EngineControl::get_device_name () const { return device_combo.get_active_text (); } string EngineControl::get_input_device_name () const { return input_device_combo.get_active_text (); } string EngineControl::get_output_device_name () const { return output_device_combo.get_active_text (); } void EngineControl::control_app_button_clicked () { if (!_sensitive) { return; } std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (!backend) { return; } backend->launch_control_app (); } void EngineControl::on_response (int r) { /* Do not run ArdourDialog::on_response() which will hide us. Leave * that to whoever invoked us, if they wish to hide us after "start". * * StartupFSM does hide us after response(); Window > Audio/MIDI Setup * does not. */ if (r == RESPONSE_OK) { pop_splash (); } Gtk::Dialog::on_response (r); } void EngineControl::start_stop_button_clicked () { if (!_sensitive) { return; } std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (!backend) { return; } int rv = RESPONSE_OK; if (ARDOUR::AudioEngine::instance ()->running ()) { ARDOUR::AudioEngine::instance ()->stop (); } else { /* whoever displayed this dialog is expected to do its own check on whether or not the engine is running. */ rv = start_engine () ? RESPONSE_OK : RESPONSE_ACCEPT; } response (rv); } void EngineControl::update_devices_button_clicked () { if (!_sensitive) { return; } std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (!backend) { return; } assert (!ARDOUR::AudioEngine::instance ()->running ()); if (backend->update_devices ()) { device_list_changed (); if (set_state_for_backend (backend_combo.get_active_text ())) { maybe_display_saved_state (); } } } void EngineControl::try_autostart_button_clicked () { if (!_sensitive) { return; } ARDOUR::Config->set_try_autostart_engine (!try_autostart_button.get_active ()); try_autostart_button.set_active (ARDOUR::Config->get_try_autostart_engine ()); } void EngineControl::use_buffered_io_button_clicked () { if (!_sensitive) { return; } std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (!backend) { return; } bool set_buffered_io = !use_buffered_io_button.get_active (); use_buffered_io_button.set_active (set_buffered_io); backend->set_use_buffered_io (set_buffered_io); } void EngineControl::manage_control_app_sensitivity () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (!backend) { return; } string appname = backend->control_app_name (); if (appname.empty ()) { control_app_button.set_sensitive (false); control_app_button.hide (); } else { control_app_button.set_sensitive (true); control_app_button.show (); } lm_button_audio.set_sensitive (backend->can_measure_systemic_latency ()); } void EngineControl::set_desired_sample_rate (uint32_t sr) { _desired_sample_rate = sr; if (ARDOUR::AudioEngine::instance ()->running () && ARDOUR::AudioEngine::instance ()->sample_rate () != sr) { stop_engine (); } device_changed (); } void EngineControl::on_monitor_expand () { if (monitor_expander.get_expanded ()) { lbl_monitor_model.show (); monitor_model_combo.show (); } else { #if 1 // always keep expanded /* This helps overall layout. The text "Record monitoring handled by:" * is the longest label. Hiding it changes the layout significantly. */ monitor_expander.set_expanded (true); #else lbl_monitor_model.hide (); monitor_model_combo.hide (); #endif } } void EngineControl::on_latency_expand () { if (latency_expander.get_expanded ()) { lbl_input_latency.show (); lbl_output_latency.show (); input_latency.show (); output_latency.show (); unit_samples_txt1.show (); unit_samples_txt2.show (); lm_button_audio.show (); lbl_midi_system.show (); midi_option_combo.show (); midi_devices_button.show (); } else { lbl_input_latency.hide (); lbl_output_latency.hide (); input_latency.hide (); output_latency.hide (); unit_samples_txt1.hide (); unit_samples_txt2.hide (); lm_button_audio.hide (); lbl_midi_system.hide (); midi_option_combo.hide (); midi_devices_button.hide (); if (!UIConfiguration::instance().get_allow_to_resize_engine_dialog ()) { resize (1, 1); // shrink window } } } void EngineControl::on_switch_page (GtkNotebookPage*, guint page_num) { if (ignore_changes) { return; } if (page_num == 0) { _measure_midi.reset (); update_sensitivity (); } if (page_num == midi_tab) { /* MIDI tab */ refresh_midi_display (); /* undo special case from push_state_to_backend() when measuring midi latency */ if (_measure_midi && ARDOUR::AudioEngine::instance ()->running ()) { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); for (vector::const_iterator p = _midi_devices.begin (); p != _midi_devices.end (); ++p) { backend->set_midi_device_enabled ((*p)->name, (*p)->enabled); } } _measure_midi.reset (); midi_vbox.show_all (); } if (page_num == latency_tab) { /* latency tab */ was_running_before_lm = ARDOUR::AudioEngine::instance ()->running (); if (ARDOUR::AudioEngine::instance ()->running ()) { stop_engine (true); } { PBD::Unwinder protect_ignore_changes (ignore_changes, ignore_changes + 1); /* save any existing latency values */ uint32_t il = (uint32_t)input_latency.get_value (); uint32_t ol = (uint32_t)output_latency.get_value (); /* reset to zero so that our new test instance will be clean of any existing latency measures. NB. this should really be done by the backend when stated for latency measurement. */ input_latency.set_value (0); output_latency.set_value (0); push_state_to_backend (false); /* reset control */ input_latency.set_value (il); output_latency.set_value (ol); } // This should be done in push_state_to_backend() if (ARDOUR::AudioEngine::instance ()->prepare_for_latency_measurement ()) { disable_latency_tab (); } enable_latency_tab (); } else { if (lm_running) { end_latency_detection (); } } populate_action_area (page_num); } static void unparent_widget (Gtk::Widget& w) { if (w.get_parent ()) { w.get_parent ()->remove (w); } } void EngineControl::populate_action_area (int page_num) { /* re-populate action area */ unparent_widget (start_stop_button); unparent_widget (connect_disconnect_button); unparent_widget (lm_measure_button); unparent_widget (lm_use_button); unparent_widget (lm_back_button); unparent_widget (midi_back_button); if (page_num == 0) { if (_have_control) { get_action_area ()->add (start_stop_button); } else { get_action_area ()->add (connect_disconnect_button); } } else if (page_num == latency_tab) { get_action_area ()->add (lm_measure_button); get_action_area ()->add (lm_use_button); get_action_area ()->add (lm_back_button); get_action_area ()->show_all (); } else if (page_num == midi_tab) { get_action_area ()->add (midi_back_button); midi_back_button.show (); } } /* latency measurement */ bool EngineControl::check_audio_latency_measurement () { MTDM* mtdm = ARDOUR::AudioEngine::instance ()->mtdm (); if (mtdm->resolve () < 0) { lm_results.set_markup (string_compose (results_markup, _("No signal detected "))); return true; } if (mtdm->get_peak () > 0.707f) { // get_peak() resets the peak-hold in the detector. // this GUI callback is at 10Hz and so will be fine (test-signal is at higher freq) lm_results.set_markup (string_compose (results_markup, _("Input signal is > -3dBFS. Lower the signal level (output gain, input gain) on the audio-interface."))); return true; } if (mtdm->err () > 0.3) { mtdm->invert (); mtdm->resolve (); } char buf[256]; ARDOUR::samplecnt_t const sample_rate = ARDOUR::AudioEngine::instance ()->sample_rate (); if (sample_rate == 0) { lm_results.set_markup (string_compose (results_markup, _("Disconnected from audio engine"))); ARDOUR::AudioEngine::instance ()->stop_latency_detection (); return false; } int samples_total = mtdm->del (); int extra = samples_total - ARDOUR::AudioEngine::instance ()->latency_signal_delay (); snprintf (buf, sizeof (buf), "%s%d samples (%.3lf ms)\n%s%d samples (%.3lf ms)", _("Detected roundtrip latency: "), samples_total, samples_total * 1000.0f / sample_rate, _("Systemic latency: "), extra, extra * 1000.0f / sample_rate); bool solid = true; if (mtdm->err () > 0.2) { strcat (buf, "\n"); strcat (buf, _("Large measurement deviation. Invalid result.")); solid = false; } if (mtdm->inv ()) { /* only warn user, in some cases the measured value is correct, * regardless of the warning - https://github.com/Ardour/ardour/pull/656 */ strcat (buf, "\n"); strcat (buf, _("Signal polarity inverted (bad wiring).")); } lm_results.set_markup (string_compose (results_markup, buf)); if (solid) { have_lm_results = true; end_latency_detection (); lm_use_button.set_sensitive (true); return false; } return true; } bool EngineControl::check_midi_latency_measurement () { ARDOUR::MIDIDM* mididm = ARDOUR::AudioEngine::instance ()->mididm (); if (!mididm->have_signal () || mididm->latency () == 0) { lm_results.set_markup (string_compose (results_markup, _("No signal detected "))); return true; } char buf[256]; ARDOUR::samplecnt_t const sample_rate = ARDOUR::AudioEngine::instance ()->sample_rate (); if (sample_rate == 0) { lm_results.set_markup (string_compose (results_markup, _("Disconnected from audio engine"))); ARDOUR::AudioEngine::instance ()->stop_latency_detection (); return false; } ARDOUR::samplecnt_t samples_total = mididm->latency (); ARDOUR::samplecnt_t extra = samples_total - ARDOUR::AudioEngine::instance ()->latency_signal_delay (); snprintf (buf, sizeof (buf), "%s%" PRId64 " samples (%.1lf ms) dev: %.2f[spl]\n%s%" PRId64 " samples (%.1lf ms)", _("Detected roundtrip latency: "), samples_total, samples_total * 1000.0f / sample_rate, mididm->deviation (), _("Systemic latency: "), extra, extra * 1000.0f / sample_rate); bool solid = true; if (!mididm->ok ()) { strcat (buf, " "); strcat (buf, _("(averaging)")); solid = false; } if (mididm->deviation () > 50.0) { strcat (buf, " "); strcat (buf, _("(too large jitter)")); solid = false; } else if (mididm->deviation () > 10.0) { strcat (buf, " "); strcat (buf, _("(large jitter)")); } if (solid) { have_lm_results = true; end_latency_detection (); lm_use_button.set_sensitive (true); lm_results.set_markup (string_compose (results_markup, buf)); return false; } else if (mididm->processed () > 400) { have_lm_results = false; end_latency_detection (); lm_results.set_markup (string_compose (results_markup, _("Timeout - large MIDI jitter."))); return false; } lm_results.set_markup (string_compose (results_markup, buf)); return true; } void EngineControl::start_latency_detection () { ARDOUR::AudioEngine::instance ()->set_latency_input_port (lm_input_channel_combo.get_active ()->get_value (lm_input_channel_cols.port_name)); ARDOUR::AudioEngine::instance ()->set_latency_output_port (lm_output_channel_combo.get_active ()->get_value (lm_output_channel_cols.port_name)); if (ARDOUR::AudioEngine::instance ()->start_latency_detection (_measure_midi ? true : false) == 0) { lm_results.set_markup (string_compose (results_markup, _("Detecting ..."))); if (_measure_midi) { latency_timeout = Glib::signal_timeout ().connect (mem_fun (*this, &EngineControl::check_midi_latency_measurement), 100); } else { latency_timeout = Glib::signal_timeout ().connect (mem_fun (*this, &EngineControl::check_audio_latency_measurement), 100); } lm_measure_label.set_text (_("Cancel")); have_lm_results = false; lm_use_button.set_sensitive (false); lm_input_channel_combo.set_sensitive (false); lm_output_channel_combo.set_sensitive (false); lm_running = true; } } void EngineControl::end_latency_detection () { latency_timeout.disconnect (); ARDOUR::AudioEngine::instance ()->stop_latency_detection (); lm_measure_label.set_text (_("Measure")); if (!have_lm_results) { lm_use_button.set_sensitive (false); } lm_input_channel_combo.set_sensitive (true); lm_output_channel_combo.set_sensitive (true); lm_running = false; } void EngineControl::latency_button_clicked () { if (!_sensitive) { return; } if (!lm_running) { start_latency_detection (); } else { end_latency_detection (); } } void EngineControl::latency_back_button_clicked () { if (!_sensitive) { return; } ARDOUR::AudioEngine::instance ()->stop_latency_detection (); notebook.set_current_page (0); std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (backend && backend->can_change_systemic_latency_when_running ()) { /* IFF engine was not running before latency detection, stop it */ if (!was_running_before_lm && ARDOUR::AudioEngine::instance ()->running ()) { stop_engine (); } } } void EngineControl::use_latency_button_clicked () { if (!_sensitive) { return; } std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); if (_measure_midi) { ARDOUR::MIDIDM* mididm = ARDOUR::AudioEngine::instance ()->mididm (); if (!mididm) { return; } ARDOUR::samplecnt_t samples_total = mididm->latency (); ARDOUR::samplecnt_t extra = samples_total - ARDOUR::AudioEngine::instance ()->latency_signal_delay (); uint32_t one_way = max ((ARDOUR::samplecnt_t)0, extra / 2); _measure_midi->input_latency = one_way; _measure_midi->output_latency = one_way; backend->set_systemic_midi_input_latency (_measure_midi->name, one_way); backend->set_systemic_midi_output_latency (_measure_midi->name, one_way); notebook.set_current_page (midi_tab); } else { MTDM* mtdm = ARDOUR::AudioEngine::instance ()->mtdm (); if (!mtdm) { return; } double one_way = rint ((mtdm->del () - ARDOUR::AudioEngine::instance ()->latency_signal_delay ()) / 2.0); one_way = std::max (0., one_way); State state = get_saved_state_for_currently_displayed_backend_and_device (); if (state) { state->lm_input = lm_input_channel_combo.get_active ()->get_value (lm_input_channel_cols.port_name); state->lm_output = lm_output_channel_combo.get_active ()->get_value (lm_output_channel_cols.port_name); post_push (); /* save */ } /* these trigger EngineControl::latency_changed, and a post_push() * when the latency can be changed while running */ input_latency_adjustment.set_value (one_way); output_latency_adjustment.set_value (one_way); if (backend->can_change_systemic_latency_when_running ()) { /* engine is running, continue to load session. * RESPONSE_OK is a NO-OP when the dialog is displayed as Window * from a running instance. */ notebook.set_current_page (0); response (RESPONSE_OK); return; } /* back to settings page */ notebook.set_current_page (0); } } bool EngineControl::on_delete_event (GdkEventAny* ev) { if (lm_running || notebook.get_current_page () == 2) { /* currently measuring latency - be sure to clean up */ end_latency_detection (); } return ArdourDialog::on_delete_event (ev); } void EngineControl::engine_running () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); set_active_text_if_present (buffer_size_combo, bufsize_as_string (backend->buffer_size ())); sample_rate_combo.set_active_text (rate_as_string (backend->sample_rate ())); if (backend->can_set_period_size ()) { set_active_text_if_present (nperiods_combo, to_string (backend->period_size ())); } connect_disconnect_button.set_label (string_compose (_("Disconnect from %1"), backend->name ())); connect_disconnect_button.show (); started_at_least_once = true; if (_have_control) { engine_status.set_markup (string_compose ("%1", _("Running"))); } else { engine_status.set_markup (string_compose ("%1", _("Connected"))); } update_sensitivity (); } void EngineControl::engine_stopped () { std::shared_ptr backend = ARDOUR::AudioEngine::instance ()->current_backend (); assert (backend); connect_disconnect_button.set_label (string_compose (_("Connect to %1"), backend->name ())); connect_disconnect_button.show (); if (_have_control) { engine_status.set_markup (string_compose ("%1", _("Stopped"))); } else { engine_status.set_markup (X_("")); } update_sensitivity (); } void EngineControl::device_list_changed () { if (ignore_device_changes) { return; } PBD::Unwinder protect_ignore_changes (ignore_changes, ignore_changes + 1); // ?? if (!ARDOUR::AudioEngine::instance ()->running ()) { list_devices (); } midi_option_changed (); if (notebook.get_current_page () == midi_tab) { if (_midi_devices.empty ()) { notebook.set_current_page (0); } else { refresh_midi_display (); } } } void EngineControl::connect_disconnect_click () { if (ARDOUR::AudioEngine::instance ()->running ()) { stop_engine (); } else { if (!ARDOUR_UI::instance ()->the_session ()) { pop_splash (); hide (); ARDOUR::GUIIdle (); } start_engine (); if (!ARDOUR_UI::instance ()->the_session ()) { ArdourDialog::response (RESPONSE_OK); } } } void EngineControl::calibrate_audio_latency () { _measure_midi.reset (); have_lm_results = false; lm_use_button.set_sensitive (false); lm_results.set_markup (string_compose (results_markup, _("No measurement results yet"))); notebook.set_current_page (latency_tab); } void EngineControl::calibrate_midi_latency (MidiDeviceSettings s) { _measure_midi = s; have_lm_results = false; lm_use_button.set_sensitive (false); lm_results.set_markup (string_compose (results_markup, _("No measurement results yet"))); notebook.set_current_page (latency_tab); } void EngineControl::configure_midi_devices () { notebook.set_current_page (midi_tab); }