add locale-guard when saving engine states, also #6418
[ardour.git] / gtk2_ardour / engine_dialog.cc
index f8675a5ad4f13585c7bd7407e21e8b45d1297565..3e9b12348502b510c0fbec87ec69b1392dafcdb4 100644 (file)
 #include "ardour/mididm.h"
 #include "ardour/rc_configuration.h"
 #include "ardour/types.h"
+#include "ardour/profile.h"
 
 #include "pbd/convert.h"
 #include "pbd/error.h"
 
+#include "opts.h"
 #include "ardour_ui.h"
 #include "engine_dialog.h"
 #include "gui_thread.h"
@@ -58,6 +60,7 @@ using namespace Gtk;
 using namespace Gtkmm2ext;
 using namespace PBD;
 using namespace Glib;
+using namespace ARDOUR_UI_UTILS;
 
 static const unsigned int midi_tab = 2;
 static const unsigned int latency_tab = 1; /* zero-based, page zero is the main setup page */
@@ -66,6 +69,7 @@ static const char* results_markup = X_("<span weight=\"bold\" size=\"larger\">%1
 
 EngineControl::EngineControl ()
        : ArdourDialog (_("Audio/MIDI Setup"))
+       , engine_status ("")
        , basic_packer (9, 4)
        , input_latency_adjustment (0, 0, 99999, 1)
        , input_latency (input_latency_adjustment)
@@ -90,9 +94,10 @@ EngineControl::EngineControl ()
        , ignore_changes (0)
        , _desired_sample_rate (0)
        , started_at_least_once (false)
+       , queue_device_changed (false)
 {
        using namespace Notebook_Helpers;
-       vector<string> strings;
+       vector<string> backend_names;
        Label* label;
        AttachOptions xopt = AttachOptions (FILL|EXPAND);
        int row;
@@ -110,11 +115,10 @@ EngineControl::EngineControl ()
        }
 
        for (vector<const ARDOUR::AudioBackendInfo*>::const_iterator b = backends.begin(); b != backends.end(); ++b) {
-               strings.push_back ((*b)->name);
+               backend_names.push_back ((*b)->name);
        }
 
-       set_popdown_strings (backend_combo, strings);
-       backend_combo.set_active_text (strings.front());
+       set_popdown_strings (backend_combo, backend_names);
        backend_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::backend_changed));
 
        /* setup basic packing characteristics for the table used on the main
@@ -141,16 +145,14 @@ EngineControl::EngineControl ()
        lm_table.attach (lm_title, 0, 3, row, row+1, xopt, (AttachOptions) 0);
        row++;
 
-       Gtk::Label* preamble;
+       lm_preamble.set_width_chars (60);
+       lm_preamble.set_line_wrap (true);
+       lm_preamble.set_markup (_("<span weight=\"bold\">Turn down the volume on your audio equipment to a very low level.</span>"));
 
-       preamble = manage (new Label);
-       preamble->set_width_chars (60);
-       preamble->set_line_wrap (true);
-       preamble->set_markup (_("<span weight=\"bold\">Turn down the volume on your audio equipment to a very low level.</span>"));
-
-       lm_table.attach (*preamble, 0, 3, row, row+1, AttachOptions(FILL|EXPAND), (AttachOptions) 0);
+       lm_table.attach (lm_preamble, 0, 3, row, row+1, AttachOptions(FILL|EXPAND), (AttachOptions) 0);
        row++;
 
+       Gtk::Label* preamble;
        preamble = manage (new Label);
        preamble->set_width_chars (60);
        preamble->set_line_wrap (true);
@@ -248,6 +250,9 @@ EngineControl::EngineControl ()
        get_vbox()->set_border_width (12);
        get_vbox()->pack_start (notebook);
 
+       get_action_area()->pack_start (engine_status);
+       engine_status.show();
+
        /* need a special function to print "all available channels" when the
         * channel counts hit zero.
         */
@@ -263,9 +268,9 @@ EngineControl::EngineControl ()
        control_app_button.signal_clicked().connect (mem_fun (*this, &EngineControl::control_app_button_clicked));
        manage_control_app_sensitivity ();
 
-       cancel_button = add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
-       ok_button = add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
+       cancel_button = add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_CANCEL);
        apply_button = add_button (Gtk::Stock::APPLY, Gtk::RESPONSE_APPLY);
+       ok_button = add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
 
        /* Pick up any existing audio setup configuration, if appropriate */
 
@@ -274,12 +279,27 @@ EngineControl::EngineControl ()
        ARDOUR::AudioEngine::instance()->Running.connect (running_connection, MISSING_INVALIDATOR, boost::bind (&EngineControl::engine_running, this), gui_context());
        ARDOUR::AudioEngine::instance()->Stopped.connect (stopped_connection, MISSING_INVALIDATOR, boost::bind (&EngineControl::engine_stopped, this), gui_context());
        ARDOUR::AudioEngine::instance()->Halted.connect (stopped_connection, MISSING_INVALIDATOR, boost::bind (&EngineControl::engine_stopped, this), gui_context());
+       ARDOUR::AudioEngine::instance()->DeviceListChanged.connect (devicelist_connection, MISSING_INVALIDATOR, boost::bind (&EngineControl::device_list_changed, this), gui_context());
 
-       {
+       if (audio_setup) {
+               set_state (*audio_setup);
+       }
+
+       if (backend_combo.get_active_text().empty()) {
+               PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
+               backend_combo.set_active_text (backend_names.front());
+       }
+
+       backend_changed ();
+
+       /* in case the setting the backend failed, e.g. stale config, from set_state(), try again */
+       if (0 == ARDOUR::AudioEngine::instance()->current_backend()) {
+               backend_combo.set_active_text (backend_names.back());
+               /* ignore: don't save state */
                PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
                backend_changed ();
        }
-       maybe_display_saved_state();
+
 
        /* Connect to signals */
 
@@ -289,16 +309,33 @@ EngineControl::EngineControl ()
        device_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::device_changed));
        midi_option_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::midi_option_changed));
 
+       input_device_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::input_device_changed));
+       output_device_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::output_device_changed));
+
        input_latency.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::parameter_changed));
        output_latency.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::parameter_changed));
        input_channels.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::parameter_changed));
        output_channels.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::parameter_changed));
 
-       if (audio_setup) {
-               set_state (*audio_setup);
-       }
-
        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();
+
+}
+
+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 ();
+       input_device_changed ();
+       output_device_changed ();
+       ok_button->grab_focus();
 }
 
 void
@@ -311,9 +348,28 @@ EngineControl::on_response (int response_id)
                        push_state_to_backend (true);
                        break;
                case RESPONSE_OK:
+#ifdef PLATFORM_WINDOWS
+                       // For some reason we don't understand, 'hide()'
+                       // needs to get called first in Windows
+                       hide ();
+
+                       // But if there's no session open, this can produce
+                       // a long gap when nothing appears to be happening.
+                       // Let's show the splash image while we're waiting.
+                       if ( !ARDOUR_COMMAND_LINE::no_splash ) {
+                               if ( ARDOUR_UI::instance() ) {
+                                       if ( !ARDOUR_UI::instance()->session_loaded ) {
+                                               ARDOUR_UI::instance()->show_splash();
+                                       }
+                               }
+                       }
+                       push_state_to_backend (true);
+                       break;
+#else
                        push_state_to_backend (true);
                        hide ();
                        break;
+#endif
                case RESPONSE_DELETE_EVENT:
                        {
                                GdkEventButton ev;
@@ -358,16 +414,10 @@ EngineControl::build_notebook ()
 
        basic_vbox.pack_start (basic_hbox, false, false);
 
-       if (_have_control) {
-               Gtk::HBox* hpacker = manage (new HBox);
-               hpacker->set_border_width (12);
-               hpacker->pack_start (control_app_button, false, false);
-               hpacker->show ();
-               control_app_button.show();
-               basic_vbox.pack_start (*hpacker);
+       {
+               PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
+               basic_vbox.show_all ();
        }
-
-       basic_vbox.show_all ();
 }
 
 void
@@ -391,10 +441,26 @@ EngineControl::build_full_control_notebook ()
                row++;
        }
 
-       label = manage (left_aligned_label (_("Device:")));
-       basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0);
-       basic_packer.attach (device_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0);
-       row++;
+       if (backend->use_separate_input_and_output_devices()) {
+               label = manage (left_aligned_label (_("Input Device:")));
+               basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0);
+               basic_packer.attach (input_device_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0);
+               row++;
+               label = manage (left_aligned_label (_("Output Device:")));
+               basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0);
+               basic_packer.attach (output_device_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0);
+               row++;
+               // reset so it isn't used in state comparisons
+               device_combo.set_active_text ("");
+       } else {
+               label = manage (left_aligned_label (_("Device:")));
+               basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0);
+               basic_packer.attach (device_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0);
+               row++;
+               // reset these so they don't get used in state comparisons
+               input_device_combo.set_active_text ("");
+               output_device_combo.set_active_text ("");
+       }
 
        label = manage (left_aligned_label (_("Sample rate:")));
        basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0);
@@ -407,6 +473,10 @@ EngineControl::build_full_control_notebook ()
        basic_packer.attach (buffer_size_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0);
        buffer_size_duration_label.set_alignment (0.0); /* left-align */
        basic_packer.attach (buffer_size_duration_label, 2, 3, row, row+1, SHRINK, (AttachOptions) 0);
+
+       /* button spans 2 rows */
+
+       basic_packer.attach (control_app_button, 3, 4, row-1, row+1, xopt, xopt);
        row++;
 
        input_channels.set_name ("InputChannels");
@@ -415,22 +485,26 @@ EngineControl::build_full_control_notebook ()
        input_channels.set_wrap (false);
        output_channels.set_editable (true);
 
-       label = manage (left_aligned_label (_("Input Channels:")));
-       basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0);
-       basic_packer.attach (input_channels, 1, 2, row, row+1, xopt, (AttachOptions) 0);
-       ++row;
-
+       if (!ARDOUR::Profile->get_mixbus()) {
+               label = manage (left_aligned_label (_("Input Channels:")));
+               basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0);
+               basic_packer.attach (input_channels, 1, 2, row, row+1, xopt, (AttachOptions) 0);
+               ++row;
+       }
+       
        output_channels.set_name ("OutputChannels");
        output_channels.set_flags (Gtk::CAN_FOCUS);
        output_channels.set_digits (0);
        output_channels.set_wrap (false);
        output_channels.set_editable (true);
 
-       label = manage (left_aligned_label (_("Output Channels:")));
-       basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0);
-       basic_packer.attach (output_channels, 1, 2, row, row+1, xopt, (AttachOptions) 0);
-       ++row;
-
+       if (!ARDOUR::Profile->get_mixbus()) {
+               label = manage (left_aligned_label (_("Output Channels:")));
+               basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0);
+               basic_packer.attach (output_channels, 1, 2, row, row+1, xopt, (AttachOptions) 0);
+               ++row;
+       }
+       
        input_latency.set_name ("InputLatency");
        input_latency.set_flags (Gtk::CAN_FOCUS);
        input_latency.set_digits (0);
@@ -461,7 +535,7 @@ EngineControl::build_full_control_notebook ()
        basic_packer.attach (lm_button_audio, 3, 4, row-1, row+1, xopt, xopt);
        ++row;
 
-       label = manage (left_aligned_label (_("MIDI System")));
+       label = manage (left_aligned_label (_("MIDI System:")));
        basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0);
        basic_packer.attach (midi_option_combo, 1, 2, row, row + 1, SHRINK, (AttachOptions) 0);
        basic_packer.attach (midi_devices_button, 3, 4, row, row+1, xopt, xopt);
@@ -479,7 +553,7 @@ EngineControl::build_no_control_notebook ()
        vector<string> strings;
        AttachOptions xopt = AttachOptions (FILL|EXPAND);
        int row = 1; // row zero == backend combo
-       const string msg = string_compose (_("The %1 audio backend was configured and started externally.\nThis limits your control over it."), backend->name());
+       const string msg = string_compose (_("%1 is already running. %2 will connect to it and use the existing settings."), backend->name(), PROGRAM_NAME);
 
        label = manage (new Label);
        label->set_markup (string_compose ("<span weight=\"bold\" foreground=\"red\">%1</span>", msg));
@@ -502,8 +576,6 @@ EngineControl::build_no_control_notebook ()
                row++;
        }
 
-       connect_disconnect_button.signal_clicked().connect (sigc::mem_fun (*this, &EngineControl::connect_disconnect_click));
-
        basic_packer.attach (connect_disconnect_button, 0, 2, row, row+1, FILL, AttachOptions (0));
        row++;
 }
@@ -533,29 +605,35 @@ EngineControl::enable_latency_tab ()
        ARDOUR::AudioEngine::instance()->get_physical_outputs (type, outputs);
        ARDOUR::AudioEngine::instance()->get_physical_inputs (type, inputs);
 
-       if (inputs.empty() || outputs.empty()) {
+       if (!ARDOUR::AudioEngine::instance()->running()) {
+               MessageDialog 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()) {
                MessageDialog msg (_("Your selected audio configuration is playback- or capture-only.\n\nLatency calibration requires playback and capture"));
-               lm_measure_button.set_sensitive (false);
                notebook.set_current_page (0);
                msg.run ();
                return;
        }
 
-       if (!outputs.empty()) {
-               set_popdown_strings (lm_output_channel_combo, outputs);
-               lm_output_channel_combo.set_active_text (outputs.front());
-               lm_output_channel_combo.set_sensitive (true);
+       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_output_channel_combo.set_sensitive (false);
+               lm_back_button_signal = lm_back_button.signal_clicked().connect (sigc::bind (sigc::mem_fun (notebook, &Gtk::Notebook::set_current_page), 0));
+               lm_preamble.show ();
        }
 
-       if (!inputs.empty()) {
-               set_popdown_strings (lm_input_channel_combo, inputs);
-               lm_input_channel_combo.set_active_text (inputs.front());
-               lm_input_channel_combo.set_sensitive (true);
-       } else {
-               lm_input_channel_combo.set_sensitive (false);
-       }
+       set_popdown_strings (lm_output_channel_combo, outputs);
+       lm_output_channel_combo.set_active_text (outputs.front());
+       lm_output_channel_combo.set_sensitive (true);
+
+       set_popdown_strings (lm_input_channel_combo, inputs);
+       lm_input_channel_combo.set_active_text (inputs.front());
+       lm_input_channel_combo.set_sensitive (true);
 
        lm_measure_button.set_sensitive (true);
 }
@@ -686,6 +764,7 @@ EngineControl::backend_changed ()
 
        if (!(backend = ARDOUR::AudioEngine::instance()->set_backend (backend_name, "ardour", ""))) {
                /* eh? setting the backend failed... how ? */
+               /* A: stale config contains a backend that does not exist in current build */
                return;
        }
 
@@ -701,9 +780,20 @@ EngineControl::backend_changed ()
 
                if (!drivers.empty()) {
                        {
+                               string current_driver;
+                               current_driver = backend->driver_name ();
+
+                               // driver might not have been set yet
+                               if (current_driver == "") {
+                                       current_driver = driver_combo.get_active_text ();
+                                       if (current_driver == "")
+                                               // driver has never been set, make sure it's not blank
+                                               current_driver = drivers.front ();
+                               }
+
                                PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
                                set_popdown_strings (driver_combo, drivers);
-                               driver_combo.set_active_text (drivers.front());
+                               driver_combo.set_active_text (current_driver);
                        }
 
                        driver_changed ();
@@ -732,6 +822,8 @@ EngineControl::backend_changed ()
                }
        }
 
+       connect_disconnect_button.hide();
+
        midi_option_changed();
 
        started_at_least_once = false;
@@ -744,6 +836,10 @@ EngineControl::backend_changed ()
 bool
 EngineControl::print_channel_count (Gtk::SpinButton* sb)
 {
+       if (ARDOUR::Profile->get_mixbus()) {
+               return true;
+       }
+       
        uint32_t cnt = (uint32_t) sb->get_value();
        if (cnt == 0) {
                sb->set_text (_("all available channels"));
@@ -755,14 +851,11 @@ EngineControl::print_channel_count (Gtk::SpinButton* sb)
        return true;
 }
 
-void
-EngineControl::list_devices ()
+// @return true if there are devices available
+bool
+EngineControl::set_device_popdown_strings ()
 {
        boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
-       assert (backend);
-
-       /* now fill out devices, mark sample rates, buffer sizes insensitive */
-
        vector<ARDOUR::AudioBackend::DeviceStatus> all_devices = backend->enumerate_devices ();
 
        /* NOTE: Ardour currently does not display the "available" field of the
@@ -780,23 +873,165 @@ EngineControl::list_devices ()
                available_devices.push_back (i->name);
        }
 
-       if (!available_devices.empty()) {
 
+       if (!available_devices.empty()) {
                update_sensitivity ();
 
                {
+                       string current_device, found_device;
+                       current_device = device_combo.get_active_text ();
+                       if (current_device == "") {
+                               current_device = backend->device_name ();
+                       }
+
+                       // Make sure that the active text is still relevant for this
+                       // device (it might only be relevant to the previous device!!)
+                       for (vector<string>::const_iterator i = available_devices.begin(); i != available_devices.end(); ++i) {
+                               if (*i == current_device)
+                                       found_device = current_device;
+                       }
+                       if (found_device == "")
+                               // device has never been set (or was not relevant
+                               // for this backend) Let's make sure it's not blank
+                               current_device = available_devices.front ();
+
                        PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
                        set_popdown_strings (device_combo, available_devices);
-                       device_combo.set_active_text (available_devices.front());
+
+                       device_combo.set_active_text (current_device);
                }
 
                device_changed ();
+               return true;
+       }
+       return false;
+}
+
+// @return true if there are input devices available
+bool
+EngineControl::set_input_device_popdown_strings ()
+{
+       boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+       vector<ARDOUR::AudioBackend::DeviceStatus> all_devices = backend->enumerate_input_devices ();
+
+       vector<string> available_devices;
+
+       for (vector<ARDOUR::AudioBackend::DeviceStatus>::const_iterator i = all_devices.begin(); i != all_devices.end(); ++i) {
+               available_devices.push_back (i->name);
+       }
+
+       if (!available_devices.empty()) {
+               update_sensitivity ();
+
+               {
+                       string current_device, found_device;
+                       current_device = input_device_combo.get_active_text ();
+                       if (current_device == "") {
+                               current_device = backend->input_device_name ();
+                       }
+
+                       // Make sure that the active text is still relevant for this
+                       // device (it might only be relevant to the previous device!!)
+                       for (vector<string>::const_iterator i = available_devices.begin(); i != available_devices.end(); ++i) {
+                               if (*i == current_device)
+                                       found_device = current_device;
+                       }
+                       if (found_device == "")
+                               // device has never been set (or was not relevant
+                               // for this backend) Let's make sure it's not blank
+                               current_device = available_devices.front ();
+
+                       PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
+                       set_popdown_strings (input_device_combo, available_devices);
+
+                       input_device_combo.set_active_text (current_device);
+               }
+
+               input_device_changed ();
+               return true;
+       }
+
+       return false;
+}
+
+// @return true if there are output devices available
+bool
+EngineControl::set_output_device_popdown_strings ()
+{
+       boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+       vector<ARDOUR::AudioBackend::DeviceStatus> all_devices = backend->enumerate_output_devices ();
+
+       vector<string> available_devices;
+
+       for (vector<ARDOUR::AudioBackend::DeviceStatus>::const_iterator i = all_devices.begin(); i != all_devices.end(); ++i) {
+               available_devices.push_back (i->name);
+       }
+
+       if (!available_devices.empty()) {
+               update_sensitivity ();
+
+               {
+                       string current_device, found_device;
+                       current_device = output_device_combo.get_active_text ();
+                       if (current_device == "") {
+                               current_device = backend->output_device_name ();
+                       }
+
+                       // Make sure that the active text is still relevant for this
+                       // device (it might only be relevant to the previous device!!)
+                       for (vector<string>::const_iterator i = available_devices.begin(); i != available_devices.end(); ++i) {
+                               if (*i == current_device)
+                                       found_device = current_device;
+                       }
+                       if (found_device == "")
+                               // device has never been set (or was not relevant
+                               // for this backend) Let's make sure it's not blank
+                               current_device = available_devices.front ();
+
+                       PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
+                       set_popdown_strings (output_device_combo, available_devices);
+
+                       output_device_combo.set_active_text (current_device);
+               }
+
+               output_device_changed ();
+               return true;
+       }
+
+       return false;
+}
+
+void
+EngineControl::list_devices ()
+{
+       boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+       assert (backend);
+
+       /* now fill out devices, mark sample rates, buffer sizes insensitive */
+
+       bool devices_available = false;
+
+       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 ();
+       }
+
+       if (devices_available) {
+               input_latency.set_sensitive (true);
+               output_latency.set_sensitive (true);
+               input_channels.set_sensitive (true);
+               output_channels.set_sensitive (true);
 
                ok_button->set_sensitive (true);
                apply_button->set_sensitive (true);
 
        } else {
                device_combo.clear();
+               input_device_combo.clear();
+               output_device_combo.clear();
                sample_rate_combo.set_sensitive (false);
                buffer_size_combo.set_sensitive (false);
                input_latency.set_sensitive (false);
@@ -809,6 +1044,13 @@ EngineControl::list_devices ()
                } else {
                        ok_button->set_sensitive (true);
                        apply_button->set_sensitive (true);
+                       if (backend->can_change_sample_rate_when_running() && sample_rate_combo.get_children().size() > 0) {
+                               sample_rate_combo.set_sensitive (true);
+                       }
+                       if (backend->can_change_buffer_size_when_running() && buffer_size_combo.get_children().size() > 0) {
+                               buffer_size_combo.set_sensitive (true);
+                       }
+
                }
        }
 }
@@ -828,97 +1070,204 @@ EngineControl::driver_changed ()
 }
 
 void
-EngineControl::device_changed ()
+EngineControl::set_samplerate_popdown_strings (const std::string& device_name)
 {
+       boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+       string desired;
+       vector<float> sr;
+       vector<string> s;
+
+       if (_have_control) {
+               sr = backend->available_sample_rates (device_name);
+       } else {
+
+               sr.push_back (8000.0f);
+               sr.push_back (16000.0f);
+               sr.push_back (32000.0f);
+               sr.push_back (44100.0f);
+               sr.push_back (48000.0f);
+               sr.push_back (88200.0f);
+               sr.push_back (96000.0f);
+               sr.push_back (192000.0f);
+               sr.push_back (384000.0f);
+       }
+
+       for (vector<float>::const_iterator x = sr.begin(); x != sr.end(); ++x) {
+               s.push_back (rate_as_string (*x));
+               if (*x == _desired_sample_rate) {
+                       desired = s.back();
+               }
+       }
 
+       if (!s.empty()) {
+               sample_rate_combo.set_sensitive (true);
+               set_popdown_strings (sample_rate_combo, s);
+
+               if (desired.empty()) {
+                       sample_rate_combo.set_active_text (rate_as_string (backend->default_sample_rate()));
+               } else {
+                       sample_rate_combo.set_active_text (desired);
+               }
+
+       } else {
+               sample_rate_combo.set_sensitive (false);
+       }
+}
+
+void
+EngineControl::set_buffersize_popdown_strings (const std::string& device_name)
+{
        boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
-       assert (backend);
-       string device_name = device_combo.get_active_text ();
+       vector<uint32_t> bs;
        vector<string> s;
 
-       {
-               PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
+       if (_have_control) {
+               bs = backend->available_buffer_sizes (device_name);
+       } else if (backend->can_change_buffer_size_when_running()) {
+               bs.push_back (8);
+               bs.push_back (16);
+               bs.push_back (32);
+               bs.push_back (64);
+               bs.push_back (128);
+               bs.push_back (256);
+               bs.push_back (512);
+               bs.push_back (1024);
+               bs.push_back (2048);
+               bs.push_back (4096);
+               bs.push_back (8192);
+       }
+       s.clear ();
+       for (vector<uint32_t>::const_iterator x = bs.begin(); x != bs.end(); ++x) {
+               s.push_back (bufsize_as_string (*x));
+       }
+
+       if (!s.empty()) {
+               buffer_size_combo.set_sensitive (true);
+               set_popdown_strings (buffer_size_combo, s);
+
+               uint32_t period = backend->buffer_size();
+               if (0 == period) {
+                       period = backend->default_buffer_size(device_name);
+               }
+               set_active_text_if_present (buffer_size_combo, bufsize_as_string (period));
+               show_buffer_duration ();
+       } else {
+               buffer_size_combo.set_sensitive (false);
+       }
+}
+
+void
+EngineControl::device_changed ()
+{
+       boost::shared_ptr<ARDOUR::AudioBackend> 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 (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 (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<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
 
-               /* sample rates */
+               /* backends that support separate devices, need to ignore
+                * the device-name - and use the devies set above
+                */
+               set_samplerate_popdown_strings (device_name_in);
+               set_buffersize_popdown_strings (device_name_in);
+               /* XXX theoretically need to set min + max channel counts here
+               */
 
-               string desired;
+               manage_control_app_sensitivity ();
+       }
 
-               vector<float> sr;
+       /* pick up any saved state for this device */
 
-               if (_have_control) {
-                       sr = backend->available_sample_rates (device_name);
-               } else {
+       if (!ignore_changes) {
+               maybe_display_saved_state ();
+       }
+}
 
-                       sr.push_back (8000.0f);
-                       sr.push_back (16000.0f);
-                       sr.push_back (32000.0f);
-                       sr.push_back (44100.0f);
-                       sr.push_back (48000.0f);
-                       sr.push_back (88200.0f);
-                       sr.push_back (96000.0f);
-                       sr.push_back (192000.0f);
-                       sr.push_back (384000.0f);
-               }
+void
+EngineControl::input_device_changed ()
+{
+       boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+       assert (backend);
+       string input_device_name = input_device_combo.get_active_text ();
 
-               for (vector<float>::const_iterator x = sr.begin(); x != sr.end(); ++x) {
-                       s.push_back (rate_as_string (*x));
-                       if (*x == _desired_sample_rate) {
-                               desired = s.back();
-                       }
-               }
+       if (!ignore_changes && input_device_name != backend->input_device_name()) {
+               queue_device_changed = true;
+       }
 
-               if (!s.empty()) {
-                       sample_rate_combo.set_sensitive (true);
-                       set_popdown_strings (sample_rate_combo, s);
+       backend->set_input_device_name(input_device_name);
 
-                       if (desired.empty()) {
-                               sample_rate_combo.set_active_text (rate_as_string (backend->default_sample_rate()));
-                       } else {
-                               sample_rate_combo.set_active_text (desired);
-                       }
+       {
+               PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
 
-               } else {
-                       sample_rate_combo.set_sensitive (false);
-               }
+               set_samplerate_popdown_strings (input_device_name);
+               set_buffersize_popdown_strings (input_device_name);
+               /* XXX theoretically need to set min + max channel counts here
+               */
 
-               /* buffer sizes */
+               manage_control_app_sensitivity ();
+       }
 
-               vector<uint32_t> bs;
+       /* pick up any saved state for this device */
 
-               if (_have_control) {
-                       bs = backend->available_buffer_sizes (device_name);
-               } else if (backend->can_change_buffer_size_when_running()) {
-                       bs.push_back (8);
-                       bs.push_back (16);
-                       bs.push_back (32);
-                       bs.push_back (64);
-                       bs.push_back (128);
-                       bs.push_back (256);
-                       bs.push_back (512);
-                       bs.push_back (1024);
-                       bs.push_back (2048);
-                       bs.push_back (4096);
-                       bs.push_back (8192);
-               }
-               s.clear ();
-               for (vector<uint32_t>::const_iterator x = bs.begin(); x != bs.end(); ++x) {
-                       s.push_back (bufsize_as_string (*x));
-               }
+       if (!ignore_changes) {
+               maybe_display_saved_state ();
+       }
+}
+
+void
+EngineControl::output_device_changed ()
+{
+       boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+       assert (backend);
+       string output_device_name = output_device_combo.get_active_text ();
 
-               if (!s.empty()) {
-                       buffer_size_combo.set_sensitive (true);
-                       set_popdown_strings (buffer_size_combo, s);
+       if (!ignore_changes && output_device_name != backend->output_device_name()) {
+               queue_device_changed = true;
+       }
 
-                       buffer_size_combo.set_active_text (bufsize_as_string (backend->default_buffer_size()));
-                       show_buffer_duration ();
-               } else {
-                       buffer_size_combo.set_sensitive (false);
-               }
+       backend->set_output_device_name(output_device_name);
 
+       {
+               PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
+
+               set_samplerate_popdown_strings (output_device_name);
+               set_buffersize_popdown_strings (output_device_name);
                /* XXX theoretically need to set min + max channel counts here
                */
 
@@ -938,8 +1287,8 @@ EngineControl::bufsize_as_string (uint32_t sz)
        /* Translators: "samples" is always plural here, so no
           need for plural+singular forms.
         */
-       char buf[32];
-       snprintf (buf, sizeof (buf), _("%u samples"), sz);
+       char buf[64];
+       snprintf (buf, sizeof (buf), "%u %s", sz, P_("sample", "samples", sz));
        return buf;
 }
 
@@ -951,9 +1300,6 @@ EngineControl::sample_rate_changed ()
         */
 
        show_buffer_duration ();
-       if (!ignore_changes) {
-               save_state ();
-       }
 
 }
 
@@ -961,9 +1307,6 @@ void
 EngineControl::buffer_size_changed ()
 {
        show_buffer_duration ();
-       if (!ignore_changes) {
-               save_state ();
-       }
 }
 
 void
@@ -978,15 +1321,25 @@ EngineControl::show_buffer_duration ()
        uint32_t samples = atoi (bs_text); /* will ignore trailing text */
        uint32_t rate = get_rate();
 
-       /* Translators: "msecs" is ALWAYS plural here, so we do not
-          need singular form as well.
-        */
        /* Developers: note the hard-coding of a double buffered model
           in the (2 * samples) computation of latency. we always start
           the audiobackend in this configuration.
         */
+       /* note to jack1 developers: ardour also always starts the engine
+        * in async mode (no jack2 --sync option) which adds an extra cycle
+        * of latency with jack2 (and *3 would be correct)
+        * The value can also be wrong if jackd is started externally..
+        *
+        * At the time of writing the ALSA backend always uses double-buffering *2,
+        * The Dummy backend *1, and who knows what ASIO really does :)
+        *
+        * 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 msecs)"), (2 * samples) / (rate/1000.0));
+       snprintf (buf, sizeof (buf), _("(%.1f ms)"), (samples / (rate/1000.0f)));
        buffer_size_duration_label.set_text (buf);
 }
 
@@ -1027,18 +1380,11 @@ EngineControl::midi_option_changed ()
        } else {
                midi_devices_button.set_sensitive (true);
        }
-
-       if (!ignore_changes) {
-               save_state ();
-       }
 }
 
 void
 EngineControl::parameter_changed ()
 {
-       if (!ignore_changes) {
-               save_state ();
-       }
 }
 
 EngineControl::State
@@ -1049,8 +1395,25 @@ EngineControl::get_matching_state (
 {
        for (StateList::iterator i = states.begin(); i != states.end(); ++i) {
                if ((*i)->backend == backend &&
-                               (*i)->driver == driver &&
-                               (*i)->device == device) {
+                               (!_have_control || ((*i)->driver == driver && (*i)->device == device)))
+               {
+                       return (*i);
+               }
+       }
+       return State();
+}
+
+EngineControl::State
+EngineControl::get_matching_state (
+               const string& backend,
+               const string& driver,
+               const string& input_device,
+               const string& output_device)
+{
+       for (StateList::iterator i = states.begin(); i != states.end(); ++i) {
+               if ((*i)->backend == backend &&
+                               (!_have_control || ((*i)->driver == driver && ((*i)->input_device == input_device) && (*i)->output_device == output_device)))
+               {
                        return (*i);
                }
        }
@@ -1063,30 +1426,55 @@ EngineControl::get_saved_state_for_currently_displayed_backend_and_device ()
        boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
 
        if (backend) {
-               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());
+               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());
+               } 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());
+               }
        }
 
-
        return get_matching_state (backend_combo.get_active_text(),
                        string(),
                        device_combo.get_active_text());
 }
 
+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) {
+               return true;
+       }
+       return false;
+}
+
 EngineControl::State
 EngineControl::save_state ()
 {
+       State state;
+
        if (!_have_control) {
-               return State();
+               state = get_matching_state (backend_combo.get_active_text(), string(), string());
+               if (state) {
+                       return state;
+               }
+               state.reset(new StateStruct);
+               state->backend = get_backend ();
+       } else {
+               state.reset(new StateStruct);
+               store_state (state);
        }
-       State state (new StateStruct);
-       store_state (state);
 
        for (StateList::iterator i = states.begin(); i != states.end();) {
-               if ((*i)->backend == state->backend &&
-                               (*i)->driver == state->driver &&
-                               (*i)->device == state->device) {
+               if (equivalent_states (*i, state)) {
                        i =  states.erase(i);
                } else {
                        ++i;
@@ -1104,6 +1492,8 @@ 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->input_latency = get_input_latency ();
@@ -1129,7 +1519,7 @@ EngineControl::maybe_display_saved_state ()
                if (!_desired_sample_rate) {
                        sample_rate_combo.set_active_text (rate_as_string (state->sample_rate));
                }
-               buffer_size_combo.set_active_text (bufsize_as_string (state->buffer_size));
+               set_active_text_if_present (buffer_size_combo, bufsize_as_string (state->buffer_size));
                /* call this explicitly because we're ignoring changes to
                   the controls at this point.
                 */
@@ -1147,6 +1537,8 @@ EngineControl::maybe_display_saved_state ()
 XMLNode&
 EngineControl::get_state ()
 {
+       LocaleGuard lg (X_("C"));
+
        XMLNode* root = new XMLNode ("AudioMIDISetup");
        std::string path;
 
@@ -1160,6 +1552,8 @@ EngineControl::get_state ()
                        node->add_property ("backend", (*i)->backend);
                        node->add_property ("driver", (*i)->driver);
                        node->add_property ("device", (*i)->device);
+                       node->add_property ("input-device", (*i)->input_device);
+                       node->add_property ("output-device", (*i)->output_device);
                        node->add_property ("sample-rate", (*i)->sample_rate);
                        node->add_property ("buffer-size", (*i)->buffer_size);
                        node->add_property ("input-latency", (*i)->input_latency);
@@ -1198,6 +1592,8 @@ EngineControl::set_state (const XMLNode& root)
        XMLNode* grandchild;
        XMLProperty* prop = NULL;
 
+       fprintf (stderr, "EngineControl::set_state\n");
+
        if (root.name() != "AudioMIDISetup") {
                return;
        }
@@ -1240,6 +1636,16 @@ EngineControl::set_state (const XMLNode& root)
                        }
                        state->device = prop->value ();
 
+                       if ((prop = grandchild->property ("input-device")) == 0) {
+                               continue;
+                       }
+                       state->input_device = prop->value ();
+
+                       if ((prop = grandchild->property ("output-device")) == 0) {
+                               continue;
+                       }
+                       state->output_device = prop->value ();
+
                        if ((prop = grandchild->property ("sample-rate")) == 0) {
                                continue;
                        }
@@ -1282,7 +1688,7 @@ EngineControl::set_state (const XMLNode& root)
 
                        state->midi_devices.clear();
                        XMLNode* midinode;
-                       if ((midinode = find_named_node (*grandchild, "MIDIDevices")) != 0) {
+                       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) {
                                        if ((*n)->property (X_("name")) == 0
@@ -1324,19 +1730,37 @@ EngineControl::set_state (const XMLNode& root)
 
        /* 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<const ARDOUR::AudioBackendInfo*> backends = ARDOUR::AudioEngine::instance()->available_backends();
+       vector<std::string> backend_names;
+
+       for (vector<const ARDOUR::AudioBackendInfo*>::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;
+               }
+       }
+
        for (StateList::const_iterator i = states.begin(); i != states.end(); ++i) {
 
                if ((*i)->active) {
-                       ignore_changes++;
+                       PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1);
                        backend_combo.set_active_text ((*i)->backend);
                        driver_combo.set_active_text ((*i)->driver);
                        device_combo.set_active_text ((*i)->device);
+                       fprintf (stderr, "setting input device to: %s ", (*i)->input_device.c_str());
+                       input_device_combo.set_active_text ((*i)->input_device);
+                       fprintf (stderr, "setting output device to: %s ", (*i)->output_device.c_str());
+                       output_device_combo.set_active_text ((*i)->output_device);
                        sample_rate_combo.set_active_text (rate_as_string ((*i)->sample_rate));
-                       buffer_size_combo.set_active_text (bufsize_as_string ((*i)->buffer_size));
+                       set_active_text_if_present (buffer_size_combo, bufsize_as_string ((*i)->buffer_size));
                        input_latency.set_value ((*i)->input_latency);
                        output_latency.set_value ((*i)->output_latency);
                        midi_option_combo.set_active_text ((*i)->midi_option);
-                       ignore_changes--;
                        break;
                }
        }
@@ -1378,7 +1802,20 @@ EngineControl::push_state_to_backend (bool start)
                                }
                        }
 
-                       if (get_device_name() != backend->device_name()) {
+                       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;
                        }
 
@@ -1447,6 +1884,8 @@ EngineControl::push_state_to_backend (bool start)
                }
        }
 
+       queue_device_changed = false;
+
        if (!_have_control) {
 
                /* We do not have control over the backend, so the best we can
@@ -1526,9 +1965,20 @@ EngineControl::push_state_to_backend (bool start)
                error << string_compose (_("Cannot set driver to %1"), get_driver()) << endmsg;
                return -1;
        }
-       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 (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;
@@ -1600,26 +2050,28 @@ EngineControl::post_push ()
         * necessary
         */
 
-       if (_have_control) {
-               State state = get_saved_state_for_currently_displayed_backend_and_device ();
+       State state = get_saved_state_for_currently_displayed_backend_and_device ();
 
-               if (!state) {
-                       state = save_state ();
-                       assert (state);
-               }
+       if (!state) {
+               state = save_state ();
+               assert (state);
+       } else {
+               store_state(state);
+       }
 
-               /* all off */
+       /* all off */
 
-               for (StateList::iterator i = states.begin(); i != states.end(); ++i) {
-                       (*i)->active = false;
-               }
+       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)
-                */
+       /* mark this one active (to be used next time the dialog is
+        * shown)
+        */
 
-               state->active = true;
+       state->active = true;
 
+       if (_have_control) { // XXX
                manage_control_app_sensitivity ();
        }
 
@@ -1649,6 +2101,8 @@ EngineControl::get_buffer_size () const
        uint32_t samples;
 
        if (sscanf (txt.c_str(), "%d", &samples) != 1) {
+               fprintf(stderr, "Find a trout and repeatedly slap the nearest C++ who throws exceptions without catching them.\n");
+               fprintf(stderr, "Ardour will likely crash now, giving you time to get the trout.\n");
                throw exception ();
        }
 
@@ -1664,12 +2118,22 @@ EngineControl::get_midi_option () const
 uint32_t
 EngineControl::get_input_channels() const
 {
+       if (ARDOUR::Profile->get_mixbus()) {
+               boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+               if (!backend) return 0;
+               return backend->input_channels();
+       }
        return (uint32_t) input_channels_adjustment.get_value();
 }
 
 uint32_t
 EngineControl::get_output_channels() const
 {
+       if (ARDOUR::Profile->get_mixbus()) {
+               boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
+               if (!backend) return 0;
+               return backend->input_channels();
+       }
        return (uint32_t) output_channels_adjustment.get_value();
 }
 
@@ -1707,6 +2171,18 @@ 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 ()
 {
@@ -1742,6 +2218,8 @@ EngineControl::set_desired_sample_rate (uint32_t sr)
 {
        _desired_sample_rate = sr;
        device_changed ();
+       input_device_changed ();
+       output_device_changed ();
 }
 
 void
@@ -1861,14 +2339,15 @@ EngineControl::check_audio_latency_measurement ()
                solid = false;
        }
 
+       lm_results.set_markup (string_compose (results_markup, buf));
+
        if (solid) {
+               have_lm_results = true;
                end_latency_detection ();
                lm_use_button.set_sensitive (true);
-               have_lm_results = true;
+               return false;
        }
 
-       lm_results.set_markup (string_compose (results_markup, buf));
-
        return true;
 }
 
@@ -1917,9 +2396,16 @@ EngineControl::check_midi_latency_measurement ()
        }
 
        if (solid) {
+               have_lm_results = true;
                end_latency_detection ();
                lm_use_button.set_sensitive (true);
-               have_lm_results = 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));
@@ -1947,13 +2433,6 @@ EngineControl::start_latency_detection ()
                lm_output_channel_combo.set_sensitive (false);
                lm_running = true;
        }
-
-       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));
-       } else {
-               lm_back_button_signal = lm_back_button.signal_clicked().connect (sigc::bind (sigc::mem_fun (notebook, &Gtk::Notebook::set_current_page), 0));
-       }
 }
 
 void
@@ -1963,8 +2442,6 @@ EngineControl::end_latency_detection ()
        ARDOUR::AudioEngine::instance()->stop_latency_detection ();
        lm_measure_label.set_text (_("Measure"));
        if (!have_lm_results) {
-               lm_results.set_markup (string_compose (results_markup, _("No measurement results yet")));
-       } else {
                lm_use_button.set_sensitive (false);
        }
        lm_input_channel_combo.set_sensitive (true);
@@ -2031,15 +2508,17 @@ EngineControl::engine_running ()
        boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
        assert (backend);
 
-       buffer_size_combo.set_active_text (bufsize_as_string (backend->buffer_size()));
+       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()));
 
        buffer_size_combo.set_sensitive (true);
        sample_rate_combo.set_sensitive (true);
 
        connect_disconnect_button.set_label (string_compose (_("Disconnect from %1"), backend->name()));
+       connect_disconnect_button.show();
 
        started_at_least_once = true;
+       engine_status.set_markup(string_compose ("<span foreground=\"green\">%1</span>", _("Active")));
 }
 
 void
@@ -2050,9 +2529,19 @@ EngineControl::engine_stopped ()
 
        buffer_size_combo.set_sensitive (false);
        connect_disconnect_button.set_label (string_compose (_("Connect to %1"), backend->name()));
+       connect_disconnect_button.show();
 
        sample_rate_combo.set_sensitive (true);
        buffer_size_combo.set_sensitive (true);
+       engine_status.set_markup(string_compose ("<span foreground=\"red\">%1</span>", _("Inactive")));
+}
+
+void
+EngineControl::device_list_changed ()
+{
+       PBD::Unwinder<uint32_t> protect_ignore_changes (ignore_changes, ignore_changes + 1); // ??
+       list_devices ();
+       midi_option_changed();
 }
 
 void