From c558d684af1bcbbca87207ede74071850c835b7c Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 11 Sep 2016 17:34:08 +0200 Subject: [PATCH] Update the UI with state changes and new ALSA cards as they come in. --- alsa_input.cpp | 28 +++++++++++++++-------- alsa_input.h | 3 --- audio_mixer.h | 19 ++++++++++++++++ input_mapping_dialog.cpp | 49 +++++++++++++++++++++++++++++++++++----- input_mapping_dialog.h | 6 ++++- mainwindow.cpp | 25 ++++++++++++++++++++ mainwindow.h | 2 ++ mixer.cpp | 1 + 8 files changed, 113 insertions(+), 20 deletions(-) diff --git a/alsa_input.cpp b/alsa_input.cpp index 0ba07b1..09cbbd2 100644 --- a/alsa_input.cpp +++ b/alsa_input.cpp @@ -467,6 +467,9 @@ ALSAPool::ProbeResult ALSAPool::probe_device_once(unsigned card_index, unsigned fprintf(stderr, "%s: Probed successfully.\n", address); reset_device(internal_dev_index); // Restarts it if it is held (ie., we just replaced a dead card). + if (global_audio_mixer) { + global_audio_mixer->trigger_state_changed_callback(); + } return ALSAPool::ProbeResult::SUCCESS; } @@ -589,6 +592,7 @@ void ALSAPool::set_card_state(unsigned index, ALSAPool::Device::State state) bool silence = (state != ALSAPool::Device::State::RUNNING); while (!global_audio_mixer->silence_card(spec, silence)) ; + global_audio_mixer->trigger_state_changed_callback(); } unsigned ALSAPool::find_free_device_index(const string &name, const string &info, unsigned num_channels, const string &address) @@ -643,15 +647,19 @@ void ALSAPool::free_card(unsigned index) while (!global_audio_mixer->silence_card(spec, true)) ; - lock_guard lock(mu); - if (devices[index].held) { - devices[index].state = Device::State::DEAD; - } else { - devices[index].state = Device::State::EMPTY; - inputs[index].reset(); - } - while (!devices.empty() && devices.back().state == Device::State::EMPTY) { - devices.pop_back(); - inputs.pop_back(); + { + lock_guard lock(mu); + if (devices[index].held) { + devices[index].state = Device::State::DEAD; + } else { + devices[index].state = Device::State::EMPTY; + inputs[index].reset(); + } + while (!devices.empty() && devices.back().state == Device::State::EMPTY) { + devices.pop_back(); + inputs.pop_back(); + } } + + global_audio_mixer->trigger_state_changed_callback(); } diff --git a/alsa_input.h b/alsa_input.h index c0dbafc..f1216de 100644 --- a/alsa_input.h +++ b/alsa_input.h @@ -150,9 +150,6 @@ public: // EMPTY or DEAD state. Only for ALSAInput and for internal use. void free_card(unsigned index); - // TODO: Add accessors and/or callbacks about changed state, so that - // the UI actually stands a chance in using that information. - private: mutable std::mutex mu; std::vector devices; // Under mu. diff --git a/audio_mixer.h b/audio_mixer.h index 855295d..2e73ebd 100644 --- a/audio_mixer.h +++ b/audio_mixer.h @@ -262,6 +262,24 @@ public: audio_level_callback = callback; } + typedef std::function state_changed_callback_t; + void set_state_changed_callback(state_changed_callback_t callback) + { + state_changed_callback = callback; + } + + state_changed_callback_t get_state_changed_callback() const + { + return state_changed_callback; + } + + void trigger_state_changed_callback() + { + if (state_changed_callback != nullptr) { + state_changed_callback(); + } + } + private: struct AudioDevice { std::unique_ptr resampling_queue; @@ -338,6 +356,7 @@ private: std::atomic eq_level_db[MAX_BUSES][NUM_EQ_BANDS] {{{ 0.0f }}}; audio_level_callback_t audio_level_callback = nullptr; + state_changed_callback_t state_changed_callback = nullptr; mutable std::mutex audio_measure_mutex; Ebu_r128_proc r128; // Under audio_measure_mutex. CorrelationMeasurer correlation; // Under audio_measure_mutex. diff --git a/input_mapping_dialog.cpp b/input_mapping_dialog.cpp index 08d68dc..0496d95 100644 --- a/input_mapping_dialog.cpp +++ b/input_mapping_dialog.cpp @@ -1,5 +1,6 @@ #include "input_mapping_dialog.h" +#include "post_to_main_thread.h" #include "ui_input_mapping.h" #include @@ -28,6 +29,21 @@ InputMappingDialog::InputMappingDialog() update_button_state(); connect(ui->table, &QTableWidget::itemSelectionChanged, this, &InputMappingDialog::update_button_state); + + saved_callback = global_audio_mixer->get_state_changed_callback(); + global_audio_mixer->set_state_changed_callback([this]{ + post_to_main_thread([this]{ + devices = global_audio_mixer->get_devices(); + for (unsigned row = 0; row < mapping.buses.size(); ++row) { + fill_row_from_bus(row, mapping.buses[row]); + } + }); + }); +} + +InputMappingDialog::~InputMappingDialog() +{ + global_audio_mixer->set_state_changed_callback(saved_callback); } void InputMappingDialog::fill_ui_from_mapping(const InputMapping &mapping) @@ -49,10 +65,17 @@ void InputMappingDialog::fill_row_from_bus(unsigned row, const InputMapping::Bus QString name(QString::fromStdString(bus.name)); ui->table->setItem(row, 0, new QTableWidgetItem(name)); - // Card choices. - QComboBox *card_combo = new QComboBox; + // Card choices. If there's already a combobox here, we try to modify + // the elements in-place, so that the UI doesn't go away under the user's feet + // if they are in the process of choosing an item. + QComboBox *card_combo = static_cast(ui->table->cellWidget(row, 1)); + if (card_combo == nullptr) { + card_combo = new QComboBox; + } unsigned current_index = 0; - card_combo->addItem(QString("(none) ")); + if (card_combo->count() == 0) { + card_combo->addItem(QString("(none) ")); + } for (const auto &spec_and_info : devices) { QString label(QString::fromStdString(spec_and_info.second.name)); if (spec_and_info.first.type == InputSourceType::ALSA_INPUT) { @@ -66,13 +89,23 @@ void InputMappingDialog::fill_row_from_bus(unsigned row, const InputMapping::Bus } } ++current_index; - card_combo->addItem( - label + " ", - qulonglong(DeviceSpec_to_key(spec_and_info.first))); + if (unsigned(card_combo->count()) > current_index) { + card_combo->setItemText(current_index, label + " "); + card_combo->setItemData(current_index, qulonglong(DeviceSpec_to_key(spec_and_info.first))); + } else { + card_combo->addItem( + label + " ", + qulonglong(DeviceSpec_to_key(spec_and_info.first))); + } if (bus.device == spec_and_info.first) { card_combo->setCurrentIndex(current_index); } } + // Remove any excess items from earlier. (This is only for paranoia; + // they should be held, so it shouldn't matter.) + while (unsigned(card_combo->count()) > current_index + 1) { + card_combo->removeItem(current_index + 1); + } connect(card_combo, static_cast(&QComboBox::currentIndexChanged), bind(&InputMappingDialog::card_selected, this, card_combo, row, _1)); ui->table->setCellWidget(row, 1, card_combo); @@ -83,6 +116,8 @@ void InputMappingDialog::fill_row_from_bus(unsigned row, const InputMapping::Bus void InputMappingDialog::setup_channel_choices_from_bus(unsigned row, const InputMapping::Bus &bus) { // Left and right channel. + // TODO: If there's already a widget here, modify it instead of creating a new one, + // as we do with card choices. for (unsigned channel = 0; channel < 2; ++channel) { QComboBox *channel_combo = new QComboBox; channel_combo->addItem(QString("(none)")); @@ -108,12 +143,14 @@ void InputMappingDialog::setup_channel_choices_from_bus(unsigned row, const Inpu void InputMappingDialog::ok_clicked() { + global_audio_mixer->set_state_changed_callback(saved_callback); global_audio_mixer->set_input_mapping(mapping); accept(); } void InputMappingDialog::cancel_clicked() { + global_audio_mixer->set_state_changed_callback(saved_callback); global_audio_mixer->set_input_mapping(old_mapping); reject(); } diff --git a/input_mapping_dialog.h b/input_mapping_dialog.h index d092a34..0ca81d9 100644 --- a/input_mapping_dialog.h +++ b/input_mapping_dialog.h @@ -6,6 +6,7 @@ #include #include +#include "audio_mixer.h" #include "mixer.h" namespace Ui { @@ -20,6 +21,7 @@ class InputMappingDialog : public QDialog public: InputMappingDialog(); + ~InputMappingDialog(); private: void fill_ui_from_mapping(const InputMapping &mapping); @@ -43,7 +45,9 @@ private: // held forever). InputMapping old_mapping; - const std::map devices; + std::map devices; + + AudioMixer::state_changed_callback_t saved_callback; }; #endif // !defined(_INPUT_MAPPING_DIALOG_H) diff --git a/mainwindow.cpp b/mainwindow.cpp index a16d5bd..0fad15a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -235,6 +235,7 @@ void MainWindow::mixer_created(Mixer *mixer) setup_audio_miniview(); setup_audio_expanded_view(); + global_audio_mixer->set_state_changed_callback(bind(&MainWindow::audio_state_changed, this)); slave_knob(ui->locut_cutoff_knob, ui->locut_cutoff_knob_2); slave_knob(ui->limiter_threshold_knob, ui->limiter_threshold_knob_2); @@ -841,3 +842,27 @@ void MainWindow::set_white_balance(int channel_number, int x, int y) global_mixer->set_wb(Mixer::OUTPUT_INPUT0 + channel_number, r, g, b); previews[channel_number]->display->updateGL(); } + +void MainWindow::audio_state_changed() +{ + post_to_main_thread([this]{ + InputMapping mapping = global_audio_mixer->get_input_mapping(); + for (unsigned bus_index = 0; bus_index < mapping.buses.size(); ++bus_index) { + const InputMapping::Bus &bus = mapping.buses[bus_index]; + string suffix; + if (bus.device.type == InputSourceType::ALSA_INPUT) { + ALSAPool::Device::State state = global_audio_mixer->get_alsa_card_state(bus.device.index); + if (state == ALSAPool::Device::State::STARTING) { + suffix = " (busy)"; + } else if (state == ALSAPool::Device::State::DEAD) { + suffix = " (dead)"; + } + } + + audio_miniviews[bus_index]->bus_desc_label->setFullText( + QString::fromStdString(bus.name + suffix)); + audio_expanded_views[bus_index]->bus_desc_label->setFullText( + QString::fromStdString(bus.name + suffix)); + } + }); +} diff --git a/mainwindow.h b/mainwindow.h index 09fc887..374bd49 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -72,6 +72,8 @@ private: void audio_level_callback(float level_lufs, float peak_db, std::vector bus_levels, float global_level_lufs, float range_low_lufs, float range_high_lufs, float final_makeup_gain_db, float correlation); std::chrono::steady_clock::time_point last_audio_level_callback; + void audio_state_changed(); + Ui::MainWindow *ui; QLabel *disk_free_label; QPushButton *transition_btn1, *transition_btn2, *transition_btn3; diff --git a/mixer.cpp b/mixer.cpp index 00f161a..d7f8655 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -281,6 +281,7 @@ void Mixer::configure_card(unsigned card_index, CaptureInterface *capture, bool DeviceSpec device{InputSourceType::CAPTURE_CARD, card_index}; audio_mixer.reset_resampler(device); audio_mixer.set_name(device, card->capture->get_description()); + audio_mixer.trigger_state_changed_callback(); } -- 2.39.2