#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 <assert.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/message.h>
#include <QComboBox>
+#include <QDialogButtonBox>
#include <QFileDialog>
#include <QMessageBox>
+#include <QPushButton>
#include <QSpinBox>
-
+#include <QStringList>
+#include <QTreeWidget>
+#include <stdio.h>
#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <limits>
#include <string>
+#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;
{ "Fader", MIDIMappingBusProto::kFaderFieldNumber, MIDIMappingProto::kFaderBankFieldNumber }
};
vector<MIDIMappingDialog::Control> per_bus_buttons = {
+ { "Toggle mute", MIDIMappingBusProto::kToggleMuteFieldNumber,
+ MIDIMappingProto::kToggleMuteBankFieldNumber },
{ "Toggle locut", MIDIMappingBusProto::kToggleLocutFieldNumber,
MIDIMappingProto::kToggleLocutBankFieldNumber },
{ "Togle auto gain staging", MIDIMappingBusProto::kToggleAutoGainStagingFieldNumber,
{ "Clear peak", MIDIMappingBusProto::kClearPeakFieldNumber,
MIDIMappingProto::kClearPeakBankFieldNumber }
};
+vector<MIDIMappingDialog::Control> 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<MIDIMappingDialog::Control> global_controllers = {
{ "Locut cutoff", MIDIMappingBusProto::kLocutFieldNumber, MIDIMappingProto::kLocutBankFieldNumber },
{ "Limiter threshold", MIDIMappingBusProto::kLimiterThresholdFieldNumber,
{ "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<MIDIMappingDialog::Control> 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 {
if (!bus_reflection->HasField(bus_mapping, descriptor)) {
return default_value;
}
- const MIDIControllerProto &controller_proto =
+ const MIDIControllerProto &controller_proto =
static_cast<const MIDIControllerProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
return controller_proto.controller_number();
}
if (!bus_reflection->HasField(bus_mapping, descriptor)) {
return default_value;
}
- const MIDIButtonProto &bus_proto =
+ const MIDIButtonProto &bus_proto =
static_cast<const MIDIButtonProto &>(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<const MIDILightProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
+ return bus_proto.note_number();
+}
+
} // namespace
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.
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);
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<int, int> bus_and_offset = guess_offset(focus_bus_idx);
+ assert(focus.bus_idx != -1); // The button should have been disabled.
+ pair<int, int> 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()
unique_ptr<MIDIMappingProto> mapping_proto(new MIDIMappingProto);
for (const InstantiatedSpinner &is : controller_spinners) {
const int val = is.spinner->value();
- if (val == 0) {
+ if (val == -1) {
continue;
}
}
for (const InstantiatedSpinner &is : button_spinners) {
const int val = is.spinner->value();
- if (val == 0) {
+ if (val == -1) {
continue;
}
get_mutable_bus_message<MIDIButtonProto>(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<MIDILightProto>(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();
void MIDIMappingDialog::add_controls(const string &heading,
MIDIMappingDialog::ControlType control_type,
+ MIDIMappingDialog::SpinnerGroup spinner_group,
const MIDIMappingProto &mapping_proto,
const vector<MIDIMappingDialog::Control> &controls)
{
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<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
bind(&MIDIMappingDialog::update_guess_button_state, this));
}
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);
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<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx)
+pair<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx, MIDIMappingDialog::SpinnerGroup spinner_group)
{
constexpr pair<int, int> 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;
// See if we can find a consistent offset.
bool found_offset = false;
int offset = 0;
+ int minimum_allowed_offset = numeric_limits<int>::min();
+ int maximum_allowed_offset = numeric_limits<int>::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;
}
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;
}
}
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<int, int> 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<int, int> bus_and_offset = guess_offset(focus.bus_idx, SpinnerGroup::ALL_GROUPS);
+ ui->guess_bus_button->setEnabled(bus_and_offset.first != -1);
+ }
+ {
+ pair<int, int> 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 };
}