X-Git-Url: https://git.sesse.net/?p=nageru;a=blobdiff_plain;f=midi_mapping_dialog.cpp;h=89656039698b3fe47d36a4e26f7a73767f876d09;hp=20c2dee46f0c0061b80f215dbd8b0b67178fade1;hb=703e00da89118df9be0354dda621bed023e6030e;hpb=a94d4fe6192a2f0111515b952dc53f5bda8324db diff --git a/midi_mapping_dialog.cpp b/midi_mapping_dialog.cpp index 20c2dee..8965603 100644 --- a/midi_mapping_dialog.cpp +++ b/midi_mapping_dialog.cpp @@ -1,18 +1,30 @@ #include "midi_mapping_dialog.h" -#include "midi_mapper.h" -#include "midi_mapping.pb.h" -#include "post_to_main_thread.h" -#include "ui_midi_mapping.h" - +#include +#include +#include #include +#include #include #include +#include #include - +#include +#include +#include #include +#include +#include +#include #include +#include "midi_mapper.h" +#include "midi_mapping.pb.h" +#include "post_to_main_thread.h" +#include "ui_midi_mapping.h" + +class QObject; + using namespace google::protobuf; using namespace std; @@ -26,6 +38,8 @@ vector per_bus_controllers = { { "Fader", MIDIMappingBusProto::kFaderFieldNumber, MIDIMappingProto::kFaderBankFieldNumber } }; vector per_bus_buttons = { + { "Toggle mute", MIDIMappingBusProto::kToggleMuteFieldNumber, + MIDIMappingProto::kToggleMuteBankFieldNumber }, { "Toggle locut", MIDIMappingBusProto::kToggleLocutFieldNumber, MIDIMappingProto::kToggleLocutBankFieldNumber }, { "Togle auto gain staging", MIDIMappingBusProto::kToggleAutoGainStagingFieldNumber, @@ -35,6 +49,13 @@ vector per_bus_buttons = { { "Clear peak", MIDIMappingBusProto::kClearPeakFieldNumber, MIDIMappingProto::kClearPeakBankFieldNumber } }; +vector per_bus_lights = { + { "Is muted", MIDIMappingBusProto::kIsMutedFieldNumber, 0 }, + { "Locut is on", MIDIMappingBusProto::kLocutIsOnFieldNumber, 0 }, + { "Auto gain staging is on", MIDIMappingBusProto::kAutoGainStagingIsOnFieldNumber, 0 }, + { "Compressor is on", MIDIMappingBusProto::kCompressorIsOnFieldNumber, 0 }, + { "Bus has peaked", MIDIMappingBusProto::kHasPeakedFieldNumber, 0 } +}; vector global_controllers = { { "Locut cutoff", MIDIMappingBusProto::kLocutFieldNumber, MIDIMappingProto::kLocutBankFieldNumber }, { "Limiter threshold", MIDIMappingBusProto::kLimiterThresholdFieldNumber, @@ -49,7 +70,18 @@ vector global_buttons = { { "Select bank 2", MIDIMappingBusProto::kSelectBank2FieldNumber, 0 }, { "Select bank 3", MIDIMappingBusProto::kSelectBank3FieldNumber, 0 }, { "Select bank 4", MIDIMappingBusProto::kSelectBank4FieldNumber, 0 }, - { "Select bank 5", MIDIMappingBusProto::kSelectBank5FieldNumber, 0 } + { "Select bank 5", MIDIMappingBusProto::kSelectBank5FieldNumber, 0 }, + { "Toggle limiter", MIDIMappingBusProto::kToggleLimiterFieldNumber, MIDIMappingProto::kToggleLimiterBankFieldNumber }, + { "Toggle auto makeup gain", MIDIMappingBusProto::kToggleAutoMakeupGainFieldNumber, MIDIMappingProto::kToggleAutoMakeupGainBankFieldNumber } +}; +vector global_lights = { + { "Bank 1 is selected", MIDIMappingBusProto::kBank1IsSelectedFieldNumber, 0 }, + { "Bank 2 is selected", MIDIMappingBusProto::kBank2IsSelectedFieldNumber, 0 }, + { "Bank 3 is selected", MIDIMappingBusProto::kBank3IsSelectedFieldNumber, 0 }, + { "Bank 4 is selected", MIDIMappingBusProto::kBank4IsSelectedFieldNumber, 0 }, + { "Bank 5 is selected", MIDIMappingBusProto::kBank5IsSelectedFieldNumber, 0 }, + { "Limiter is on", MIDIMappingBusProto::kLimiterIsOnFieldNumber, 0 }, + { "Auto makeup gain is on", MIDIMappingBusProto::kAutoMakeupGainIsOnFieldNumber, 0 }, }; namespace { @@ -76,7 +108,7 @@ int get_controller_mapping(const MIDIMappingProto &mapping_proto, size_t bus_idx if (!bus_reflection->HasField(bus_mapping, descriptor)) { return default_value; } - const MIDIControllerProto &controller_proto = + const MIDIControllerProto &controller_proto = static_cast(bus_reflection->GetMessage(bus_mapping, descriptor)); return controller_proto.controller_number(); } @@ -93,11 +125,28 @@ int get_button_mapping(const MIDIMappingProto &mapping_proto, size_t bus_idx, in if (!bus_reflection->HasField(bus_mapping, descriptor)) { return default_value; } - const MIDIButtonProto &bus_proto = + const MIDIButtonProto &bus_proto = static_cast(bus_reflection->GetMessage(bus_mapping, descriptor)); return bus_proto.note_number(); } +int get_light_mapping(const MIDIMappingProto &mapping_proto, size_t bus_idx, int field_number, int default_value) +{ + if (bus_idx >= size_t(mapping_proto.bus_mapping_size())) { + return default_value; + } + + const MIDIMappingBusProto &bus_mapping = mapping_proto.bus_mapping(bus_idx); + const FieldDescriptor *descriptor = bus_mapping.GetDescriptor()->FindFieldByNumber(field_number); + const Reflection *bus_reflection = bus_mapping.GetReflection(); + if (!bus_reflection->HasField(bus_mapping, descriptor)) { + return default_value; + } + const MIDILightProto &bus_proto = + static_cast(bus_reflection->GetMessage(bus_mapping, descriptor)); + return bus_proto.note_number(); +} + } // namespace MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper) @@ -121,10 +170,12 @@ MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper) ui->treeWidget->setColumnCount(num_buses + 3); ui->treeWidget->setHeaderLabels(labels); - add_controls("Per-bus controllers", ControlType::CONTROLLER, mapping_proto, per_bus_controllers); - add_controls("Per-bus buttons", ControlType::BUTTON, mapping_proto, per_bus_buttons); - add_controls("Global controllers", ControlType::CONTROLLER, mapping_proto, global_controllers); - add_controls("Global buttons", ControlType::BUTTON, mapping_proto, global_buttons); + add_controls("Per-bus controllers", ControlType::CONTROLLER, SpinnerGroup::PER_BUS_CONTROLLERS, mapping_proto, per_bus_controllers); + add_controls("Per-bus buttons", ControlType::BUTTON, SpinnerGroup::PER_BUS_BUTTONS, mapping_proto, per_bus_buttons); + add_controls("Per-bus lights", ControlType::LIGHT, SpinnerGroup::PER_BUS_LIGHTS, mapping_proto, per_bus_lights); + add_controls("Global controllers", ControlType::CONTROLLER, SpinnerGroup::GLOBAL_CONTROLLERS, mapping_proto, global_controllers); + add_controls("Global buttons", ControlType::BUTTON, SpinnerGroup::GLOBAL_BUTTONS, mapping_proto, global_buttons); + add_controls("Global lights", ControlType::LIGHT, SpinnerGroup::GLOBAL_LIGHTS, mapping_proto, global_lights); fill_controls_from_mapping(mapping_proto); // Auto-resize every column but the last. @@ -132,7 +183,10 @@ MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper) ui->treeWidget->resizeColumnToContents(column_idx); } - connect(ui->guess_button, &QPushButton::clicked, this, &MIDIMappingDialog::guess_clicked); + connect(ui->guess_bus_button, &QPushButton::clicked, + bind(&MIDIMappingDialog::guess_clicked, this, false)); + connect(ui->guess_group_button, &QPushButton::clicked, + bind(&MIDIMappingDialog::guess_clicked, this, true)); connect(ui->ok_cancel_buttons, &QDialogButtonBox::accepted, this, &MIDIMappingDialog::ok_clicked); connect(ui->ok_cancel_buttons, &QDialogButtonBox::rejected, this, &MIDIMappingDialog::cancel_clicked); connect(ui->save_button, &QPushButton::clicked, this, &MIDIMappingDialog::save_clicked); @@ -144,45 +198,70 @@ MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper) MIDIMappingDialog::~MIDIMappingDialog() { mapper->set_receiver(old_receiver); + mapper->refresh_highlights(); } bool MIDIMappingDialog::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) { - // We ignore the guess button itself; it should be allowed - // to navigate from a spinner to focus on the button (to click it). - if (obj != ui->guess_button) { + // We ignore the guess buttons themselves; it should be allowed + // to navigate from a spinner to focus on a button (to click it). + if (obj != ui->guess_bus_button && obj != ui->guess_group_button) { update_guess_button_state(); } } return false; } -void MIDIMappingDialog::guess_clicked() +void MIDIMappingDialog::guess_clicked(bool limit_to_group) { - int focus_bus_idx = find_focus_bus(); - if (focus_bus_idx == -1) { + FocusInfo focus = find_focus(); + if (focus.bus_idx == -1) { // The guess button probably took the focus away from us. - focus_bus_idx = last_focus_bus_idx; + focus = last_focus; } - assert(focus_bus_idx != -1); // The button should have been disabled. - pair bus_and_offset = guess_offset(focus_bus_idx); + assert(focus.bus_idx != -1); // The button should have been disabled. + pair bus_and_offset = guess_offset(focus.bus_idx, limit_to_group ? focus.spinner_group : SpinnerGroup::ALL_GROUPS); const int source_bus_idx = bus_and_offset.first; const int offset = bus_and_offset.second; assert(source_bus_idx != -1); // The button should have been disabled. - for (const auto &field_number_and_spinner : spinners[focus_bus_idx]) { + for (const auto &field_number_and_spinner : spinners[focus.bus_idx]) { int field_number = field_number_and_spinner.first; - QSpinBox *spinner = field_number_and_spinner.second; + QSpinBox *spinner = field_number_and_spinner.second.spinner; + SpinnerGroup this_spinner_group = field_number_and_spinner.second.group; + + if (limit_to_group && this_spinner_group != focus.spinner_group) { + continue; + } assert(spinners[source_bus_idx].count(field_number)); - QSpinBox *source_spinner = spinners[source_bus_idx][field_number]; + QSpinBox *source_spinner = spinners[source_bus_idx][field_number].spinner; + assert(spinners[source_bus_idx][field_number].group == this_spinner_group); - if (source_spinner->value() != 0) { + if (source_spinner->value() != -1) { spinner->setValue(source_spinner->value() + offset); } } + + // See if we can find a “next” bus to move the focus to. + const int next_bus_idx = focus.bus_idx + (focus.bus_idx - source_bus_idx); // Note: Could become e.g. -1. + for (const InstantiatedSpinner &is : controller_spinners) { + if (int(is.bus_idx) == next_bus_idx && is.field_number == focus.field_number) { + is.spinner->setFocus(); + } + } + for (const InstantiatedSpinner &is : button_spinners) { + if (int(is.bus_idx) == next_bus_idx && is.field_number == focus.field_number) { + is.spinner->setFocus(); + } + } + for (const InstantiatedSpinner &is : light_spinners) { + if (int(is.bus_idx) == next_bus_idx && is.field_number == focus.field_number) { + is.spinner->setFocus(); + } + } } void MIDIMappingDialog::ok_clicked() @@ -201,9 +280,15 @@ void MIDIMappingDialog::cancel_clicked() void MIDIMappingDialog::save_clicked() { +#if HAVE_CEF + // The native file dialog uses GTK+, which interferes with CEF's use of the GLib main loop. + QFileDialog::Option options(QFileDialog::DontUseNativeDialog); +#else + QFileDialog::Option options; +#endif unique_ptr new_mapping = construct_mapping_proto_from_ui(); QString filename = QFileDialog::getSaveFileName(this, - "Save MIDI mapping", QString(), tr("Mapping files (*.midimapping)")); + "Save MIDI mapping", QString(), tr("Mapping files (*.midimapping)"), /*selectedFilter=*/nullptr, options); if (!filename.endsWith(".midimapping")) { filename += ".midimapping"; } @@ -216,8 +301,14 @@ void MIDIMappingDialog::save_clicked() void MIDIMappingDialog::load_clicked() { +#if HAVE_CEF + // The native file dialog uses GTK+, which interferes with CEF's use of the GLib main loop. + QFileDialog::Option options(QFileDialog::DontUseNativeDialog); +#else + QFileDialog::Option options; +#endif QString filename = QFileDialog::getOpenFileName(this, - "Load MIDI mapping", QString(), tr("Mapping files (*.midimapping)")); + "Load MIDI mapping", QString(), tr("Mapping files (*.midimapping)"), /*selectedFilter=*/nullptr, options); MIDIMappingProto new_mapping; if (!load_midi_mapping_from_file(filename.toStdString(), &new_mapping)) { QMessageBox box; @@ -251,7 +342,7 @@ unique_ptr MIDIMappingDialog::construct_mapping_proto_from_ui( unique_ptr mapping_proto(new MIDIMappingProto); for (const InstantiatedSpinner &is : controller_spinners) { const int val = is.spinner->value(); - if (val == 0) { + if (val == -1) { continue; } @@ -261,7 +352,7 @@ unique_ptr MIDIMappingDialog::construct_mapping_proto_from_ui( } for (const InstantiatedSpinner &is : button_spinners) { const int val = is.spinner->value(); - if (val == 0) { + if (val == -1) { continue; } @@ -269,6 +360,16 @@ unique_ptr MIDIMappingDialog::construct_mapping_proto_from_ui( get_mutable_bus_message(mapping_proto.get(), is.bus_idx, is.field_number); button_proto->set_note_number(val); } + for (const InstantiatedSpinner &is : light_spinners) { + const int val = is.spinner->value(); + if (val == -1) { + continue; + } + + MIDILightProto *light_proto = + get_mutable_bus_message(mapping_proto.get(), is.bus_idx, is.field_number); + light_proto->set_note_number(val); + } int highest_bank_used = 0; // 1-indexed. for (const InstantiatedComboBox &ic : bank_combo_boxes) { const int val = ic.combo_box->currentIndex(); @@ -301,6 +402,7 @@ void MIDIMappingDialog::add_bank_selector(QTreeWidgetItem *item, const MIDIMappi void MIDIMappingDialog::add_controls(const string &heading, MIDIMappingDialog::ControlType control_type, + MIDIMappingDialog::SpinnerGroup spinner_group, const MIDIMappingProto &mapping_proto, const vector &controls) { @@ -316,19 +418,21 @@ void MIDIMappingDialog::add_controls(const string &heading, for (unsigned bus_idx = 0; bus_idx < num_buses; ++bus_idx) { QSpinBox *spinner = new QSpinBox(this); - spinner->setRange(0, 127); + spinner->setRange(-1, 127); spinner->setAutoFillBackground(true); spinner->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty). spinner->installEventFilter(this); // So we know when the focus changes. ui->treeWidget->setItemWidget(item, bus_idx + 2, spinner); if (control_type == ControlType::CONTROLLER) { - controller_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number }); + controller_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number }); + } else if (control_type == ControlType::BUTTON) { + button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number }); } else { - assert(control_type == ControlType::BUTTON); - button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number }); + assert(control_type == ControlType::LIGHT); + light_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number }); } - spinners[bus_idx][control.field_number] = spinner; + spinners[bus_idx][control.field_number] = SpinnerAndGroup{ spinner, spinner_group }; connect(spinner, static_cast(&QSpinBox::valueChanged), bind(&MIDIMappingDialog::update_guess_button_state, this)); } @@ -339,10 +443,13 @@ void MIDIMappingDialog::add_controls(const string &heading, void MIDIMappingDialog::fill_controls_from_mapping(const MIDIMappingProto &mapping_proto) { for (const InstantiatedSpinner &is : controller_spinners) { - is.spinner->setValue(get_controller_mapping(mapping_proto, is.bus_idx, is.field_number, 0)); + is.spinner->setValue(get_controller_mapping(mapping_proto, is.bus_idx, is.field_number, -1)); } for (const InstantiatedSpinner &is : button_spinners) { - is.spinner->setValue(get_button_mapping(mapping_proto, is.bus_idx, is.field_number, 0)); + is.spinner->setValue(get_button_mapping(mapping_proto, is.bus_idx, is.field_number, -1)); + } + for (const InstantiatedSpinner &is : light_spinners) { + is.spinner->setValue(get_light_mapping(mapping_proto, is.bus_idx, is.field_number, -1)); } for (const InstantiatedComboBox &ic : bank_combo_boxes) { ic.combo_box->setCurrentIndex(get_bank(mapping_proto, ic.field_number, -1) + 1); @@ -351,37 +458,47 @@ void MIDIMappingDialog::fill_controls_from_mapping(const MIDIMappingProto &mappi void MIDIMappingDialog::controller_changed(unsigned controller) { - for (const InstantiatedSpinner &is : controller_spinners) { - if (is.spinner->hasFocus()) { - is.spinner->setValue(controller); - is.spinner->selectAll(); + post_to_main_thread([=]{ + for (const InstantiatedSpinner &is : controller_spinners) { + if (is.spinner->hasFocus()) { + is.spinner->setValue(controller); + is.spinner->selectAll(); + } } - } + }); } void MIDIMappingDialog::note_on(unsigned note) { - for (const InstantiatedSpinner &is : button_spinners) { - if (is.spinner->hasFocus()) { - is.spinner->setValue(note); - is.spinner->selectAll(); + post_to_main_thread([=]{ + for (const InstantiatedSpinner &is : button_spinners) { + if (is.spinner->hasFocus()) { + is.spinner->setValue(note); + is.spinner->selectAll(); + } } - } + for (const InstantiatedSpinner &is : light_spinners) { + if (is.spinner->hasFocus()) { + is.spinner->setValue(note); + is.spinner->selectAll(); + } + } + }); } -pair MIDIMappingDialog::guess_offset(unsigned bus_idx) +pair MIDIMappingDialog::guess_offset(unsigned bus_idx, MIDIMappingDialog::SpinnerGroup spinner_group) { constexpr pair not_found(-1, 0); - if (bus_is_empty(bus_idx)) { + if (bus_is_empty(bus_idx, spinner_group)) { return not_found; } // See if we can find a non-empty bus to source from (prefer from the left). unsigned source_bus_idx; - if (bus_idx > 0 && !bus_is_empty(bus_idx - 1)) { + if (bus_idx > 0 && !bus_is_empty(bus_idx - 1, spinner_group)) { source_bus_idx = bus_idx - 1; - } else if (bus_idx < num_buses - 1 && !bus_is_empty(bus_idx + 1)) { + } else if (bus_idx < num_buses - 1 && !bus_is_empty(bus_idx + 1, spinner_group)) { source_bus_idx = bus_idx + 1; } else { return not_found; @@ -390,17 +507,30 @@ pair MIDIMappingDialog::guess_offset(unsigned bus_idx) // See if we can find a consistent offset. bool found_offset = false; int offset = 0; + int minimum_allowed_offset = numeric_limits::min(); + int maximum_allowed_offset = numeric_limits::max(); for (const auto &field_number_and_spinner : spinners[bus_idx]) { int field_number = field_number_and_spinner.first; - QSpinBox *spinner = field_number_and_spinner.second; + QSpinBox *spinner = field_number_and_spinner.second.spinner; + SpinnerGroup this_spinner_group = field_number_and_spinner.second.group; + assert(spinners[source_bus_idx].count(field_number)); + QSpinBox *source_spinner = spinners[source_bus_idx][field_number].spinner; + assert(spinners[source_bus_idx][field_number].group == this_spinner_group); - if (spinner->value() == 0) { + if (spinner_group != SpinnerGroup::ALL_GROUPS && + spinner_group != this_spinner_group) { continue; } - - assert(spinners[source_bus_idx].count(field_number)); - QSpinBox *source_spinner = spinners[source_bus_idx][field_number]; - if (source_spinner->value() == 0) { + if (spinner->value() == -1) { + if (source_spinner->value() != -1) { + // If the source value is e.g. 3, offset can't be less than -2 or larger than 124. + // Otherwise, we'd extrapolate values outside [1..127]. + minimum_allowed_offset = max(minimum_allowed_offset, 1 - source_spinner->value()); + maximum_allowed_offset = min(maximum_allowed_offset, 127 - source_spinner->value()); + } + continue; + } + if (source_spinner->value() == -1) { // The bus has a controller set that the source bus doesn't set. return not_found; } @@ -419,14 +549,23 @@ pair MIDIMappingDialog::guess_offset(unsigned bus_idx) assert(false); return not_found; } + + if (offset < minimum_allowed_offset || offset > maximum_allowed_offset) { + return not_found; + } return make_pair(source_bus_idx, offset); } -bool MIDIMappingDialog::bus_is_empty(unsigned bus_idx) +bool MIDIMappingDialog::bus_is_empty(unsigned bus_idx, SpinnerGroup spinner_group) { for (const auto &field_number_and_spinner : spinners[bus_idx]) { - QSpinBox *spinner = field_number_and_spinner.second; - if (spinner->value() != 0) { + QSpinBox *spinner = field_number_and_spinner.second.spinner; + SpinnerGroup this_spinner_group = field_number_and_spinner.second.group; + if (spinner_group != SpinnerGroup::ALL_GROUPS && + spinner_group != this_spinner_group) { + continue; + } + if (spinner->value() != -1) { return false; } } @@ -435,26 +574,37 @@ bool MIDIMappingDialog::bus_is_empty(unsigned bus_idx) void MIDIMappingDialog::update_guess_button_state() { - int focus_bus_idx = find_focus_bus(); - if (focus_bus_idx < 0) { + FocusInfo focus = find_focus(); + if (focus.bus_idx < 0) { return; } - pair bus_and_offset = guess_offset(focus_bus_idx); - ui->guess_button->setEnabled(bus_and_offset.first != -1); - last_focus_bus_idx = focus_bus_idx; + { + pair bus_and_offset = guess_offset(focus.bus_idx, SpinnerGroup::ALL_GROUPS); + ui->guess_bus_button->setEnabled(bus_and_offset.first != -1); + } + { + pair bus_and_offset = guess_offset(focus.bus_idx, focus.spinner_group); + ui->guess_group_button->setEnabled(bus_and_offset.first != -1); + } + last_focus = focus; } -int MIDIMappingDialog::find_focus_bus() +MIDIMappingDialog::FocusInfo MIDIMappingDialog::find_focus() const { for (const InstantiatedSpinner &is : controller_spinners) { if (is.spinner->hasFocus()) { - return is.bus_idx; + return FocusInfo{ int(is.bus_idx), is.spinner_group, is.field_number }; } } for (const InstantiatedSpinner &is : button_spinners) { if (is.spinner->hasFocus()) { - return is.bus_idx; + return FocusInfo{ int(is.bus_idx), is.spinner_group, is.field_number }; + } + } + for (const InstantiatedSpinner &is : light_spinners) { + if (is.spinner->hasFocus()) { + return FocusInfo{ int(is.bus_idx), is.spinner_group, is.field_number }; } } - return -1; + return FocusInfo{ -1, SpinnerGroup::ALL_GROUPS, -1 }; }