1 #include "midi_mapping_dialog.h"
3 #include "midi_mapper.h"
4 #include "midi_mapping.pb.h"
5 #include "post_to_main_thread.h"
6 #include "ui_midi_mapping.h"
10 #include <QMessageBox>
15 using namespace google::protobuf;
18 vector<MIDIMappingDialog::Control> per_bus_controllers = {
19 { "Treble", MIDIMappingBusProto::kTrebleFieldNumber, MIDIMappingProto::kTrebleBankFieldNumber },
20 { "Mid", MIDIMappingBusProto::kMidFieldNumber, MIDIMappingProto::kMidBankFieldNumber },
21 { "Bass", MIDIMappingBusProto::kBassFieldNumber, MIDIMappingProto::kBassBankFieldNumber },
22 { "Gain", MIDIMappingBusProto::kGainFieldNumber, MIDIMappingProto::kGainBankFieldNumber },
23 { "Compressor threshold", MIDIMappingBusProto::kCompressorThresholdFieldNumber,
24 MIDIMappingProto::kCompressorThresholdBankFieldNumber},
25 { "Fader", MIDIMappingBusProto::kFaderFieldNumber, MIDIMappingProto::kFaderBankFieldNumber }
27 vector<MIDIMappingDialog::Control> per_bus_buttons = {
28 { "Toggle locut", MIDIMappingBusProto::kToggleLocutFieldNumber,
29 MIDIMappingProto::kToggleLocutBankFieldNumber },
30 { "Togle auto gain staging", MIDIMappingBusProto::kToggleAutoGainStagingFieldNumber,
31 MIDIMappingProto::kToggleAutoGainStagingBankFieldNumber },
32 { "Togle compressor", MIDIMappingBusProto::kToggleCompressorFieldNumber,
33 MIDIMappingProto::kToggleCompressorBankFieldNumber },
34 { "Clear peak", MIDIMappingBusProto::kClearPeakFieldNumber,
35 MIDIMappingProto::kClearPeakBankFieldNumber }
37 vector<MIDIMappingDialog::Control> global_controllers = {
38 { "Locut cutoff", MIDIMappingBusProto::kLocutFieldNumber, MIDIMappingProto::kLocutBankFieldNumber },
39 { "Limiter threshold", MIDIMappingBusProto::kLimiterThresholdFieldNumber,
40 MIDIMappingProto::kLimiterThresholdBankFieldNumber },
41 { "Makeup gain", MIDIMappingBusProto::kMakeupGainFieldNumber,
42 MIDIMappingProto::kMakeupGainBankFieldNumber }
44 vector<MIDIMappingDialog::Control> global_buttons = {
45 { "Previous bank", MIDIMappingBusProto::kPrevBankFieldNumber, 0 },
46 { "Next bank", MIDIMappingBusProto::kNextBankFieldNumber, 0 },
47 { "Select bank 1", MIDIMappingBusProto::kSelectBank1FieldNumber, 0 },
48 { "Select bank 2", MIDIMappingBusProto::kSelectBank2FieldNumber, 0 },
49 { "Select bank 3", MIDIMappingBusProto::kSelectBank3FieldNumber, 0 },
50 { "Select bank 4", MIDIMappingBusProto::kSelectBank4FieldNumber, 0 },
51 { "Select bank 5", MIDIMappingBusProto::kSelectBank5FieldNumber, 0 }
56 int get_bank(const MIDIMappingProto &mapping_proto, int bank_field_number, int default_value)
58 const FieldDescriptor *bank_descriptor = mapping_proto.GetDescriptor()->FindFieldByNumber(bank_field_number);
59 const Reflection *reflection = mapping_proto.GetReflection();
60 if (!reflection->HasField(mapping_proto, bank_descriptor)) {
63 return reflection->GetInt32(mapping_proto, bank_descriptor);
66 int get_controller_mapping(const MIDIMappingProto &mapping_proto, size_t bus_idx, int field_number, int default_value)
68 if (bus_idx >= size_t(mapping_proto.bus_mapping_size())) {
72 const MIDIMappingBusProto &bus_mapping = mapping_proto.bus_mapping(bus_idx);
73 const FieldDescriptor *descriptor = bus_mapping.GetDescriptor()->FindFieldByNumber(field_number);
74 const Reflection *bus_reflection = bus_mapping.GetReflection();
75 if (!bus_reflection->HasField(bus_mapping, descriptor)) {
78 const MIDIControllerProto &controller_proto =
79 static_cast<const MIDIControllerProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
80 return controller_proto.controller_number();
83 int get_button_mapping(const MIDIMappingProto &mapping_proto, size_t bus_idx, int field_number, int default_value)
85 if (bus_idx >= size_t(mapping_proto.bus_mapping_size())) {
89 const MIDIMappingBusProto &bus_mapping = mapping_proto.bus_mapping(bus_idx);
90 const FieldDescriptor *descriptor = bus_mapping.GetDescriptor()->FindFieldByNumber(field_number);
91 const Reflection *bus_reflection = bus_mapping.GetReflection();
92 if (!bus_reflection->HasField(bus_mapping, descriptor)) {
95 const MIDIButtonProto &bus_proto =
96 static_cast<const MIDIButtonProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
97 return bus_proto.note_number();
102 MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper)
103 : ui(new Ui::MIDIMappingDialog),
108 const MIDIMappingProto mapping_proto = mapper->get_current_mapping(); // Take a copy.
109 old_receiver = mapper->set_receiver(this);
113 labels << "Controller bank";
114 for (unsigned bus_idx = 0; bus_idx < num_buses; ++bus_idx) {
116 snprintf(buf, sizeof(buf), "Bus %d", bus_idx + 1);
120 ui->treeWidget->setColumnCount(num_buses + 3);
121 ui->treeWidget->setHeaderLabels(labels);
123 add_controls("Per-bus controllers", ControlType::CONTROLLER, mapping_proto, per_bus_controllers);
124 add_controls("Per-bus buttons", ControlType::BUTTON, mapping_proto, per_bus_buttons);
125 add_controls("Global controllers", ControlType::CONTROLLER, mapping_proto, global_controllers);
126 add_controls("Global buttons", ControlType::BUTTON, mapping_proto, global_buttons);
127 fill_controls_from_mapping(mapping_proto);
129 // Auto-resize every column but the last.
130 for (unsigned column_idx = 0; column_idx < num_buses + 3; ++column_idx) {
131 ui->treeWidget->resizeColumnToContents(column_idx);
134 connect(ui->ok_cancel_buttons, &QDialogButtonBox::accepted, this, &MIDIMappingDialog::ok_clicked);
135 connect(ui->ok_cancel_buttons, &QDialogButtonBox::rejected, this, &MIDIMappingDialog::cancel_clicked);
136 connect(ui->save_button, &QPushButton::clicked, this, &MIDIMappingDialog::save_clicked);
137 connect(ui->load_button, &QPushButton::clicked, this, &MIDIMappingDialog::load_clicked);
140 MIDIMappingDialog::~MIDIMappingDialog()
142 mapper->set_receiver(old_receiver);
145 void MIDIMappingDialog::ok_clicked()
147 unique_ptr<MIDIMappingProto> new_mapping = construct_mapping_proto_from_ui();
148 mapper->set_midi_mapping(*new_mapping);
149 mapper->set_receiver(old_receiver);
153 void MIDIMappingDialog::cancel_clicked()
155 mapper->set_receiver(old_receiver);
159 void MIDIMappingDialog::save_clicked()
161 unique_ptr<MIDIMappingProto> new_mapping = construct_mapping_proto_from_ui();
162 QString filename = QFileDialog::getSaveFileName(this,
163 "Save MIDI mapping", QString(), tr("Mapping files (*.midimapping)"));
164 if (!filename.endsWith(".midimapping")) {
165 filename += ".midimapping";
167 if (!save_midi_mapping_to_file(*new_mapping, filename.toStdString())) {
169 box.setText("Could not save mapping to '" + filename + "'. Check that you have the right permissions and try again.");
174 void MIDIMappingDialog::load_clicked()
176 QString filename = QFileDialog::getOpenFileName(this,
177 "Load MIDI mapping", QString(), tr("Mapping files (*.midimapping)"));
178 MIDIMappingProto new_mapping;
179 if (!load_midi_mapping_from_file(filename.toStdString(), &new_mapping)) {
181 box.setText("Could not load mapping from '" + filename + "'. Check that the file exists, has the right permissions and is valid.");
186 fill_controls_from_mapping(new_mapping);
192 T *get_mutable_bus_message(MIDIMappingProto *mapping_proto, unsigned bus_idx, int field_number)
194 while (size_t(mapping_proto->bus_mapping_size()) <= bus_idx) {
195 mapping_proto->add_bus_mapping();
198 MIDIMappingBusProto *bus_mapping = mapping_proto->mutable_bus_mapping(bus_idx);
199 const FieldDescriptor *descriptor = bus_mapping->GetDescriptor()->FindFieldByNumber(field_number);
200 const Reflection *bus_reflection = bus_mapping->GetReflection();
201 return static_cast<T *>(bus_reflection->MutableMessage(bus_mapping, descriptor));
206 unique_ptr<MIDIMappingProto> MIDIMappingDialog::construct_mapping_proto_from_ui()
208 unique_ptr<MIDIMappingProto> mapping_proto(new MIDIMappingProto);
209 for (const InstantiatedSpinner &is : controller_spinners) {
210 const int val = is.spinner->value();
215 MIDIControllerProto *controller_proto =
216 get_mutable_bus_message<MIDIControllerProto>(mapping_proto.get(), is.bus_idx, is.field_number);
217 controller_proto->set_controller_number(val);
219 for (const InstantiatedSpinner &is : button_spinners) {
220 const int val = is.spinner->value();
225 MIDIButtonProto *button_proto =
226 get_mutable_bus_message<MIDIButtonProto>(mapping_proto.get(), is.bus_idx, is.field_number);
227 button_proto->set_note_number(val);
229 int highest_bank_used = 0; // 1-indexed.
230 for (const InstantiatedComboBox &ic : bank_combo_boxes) {
231 const int val = ic.combo_box->currentIndex();
232 highest_bank_used = std::max(highest_bank_used, val);
237 const FieldDescriptor *descriptor = mapping_proto->GetDescriptor()->FindFieldByNumber(ic.field_number);
238 const Reflection *bus_reflection = mapping_proto->GetReflection();
239 bus_reflection->SetInt32(mapping_proto.get(), descriptor, val - 1);
241 mapping_proto->set_num_controller_banks(highest_bank_used);
242 return mapping_proto;
245 void MIDIMappingDialog::add_bank_selector(QTreeWidgetItem *item, const MIDIMappingProto &mapping_proto, int bank_field_number)
247 if (bank_field_number == 0) {
250 QComboBox *bank_selector = new QComboBox(this);
251 bank_selector->addItems(QStringList() << "" << "Bank 1" << "Bank 2" << "Bank 3" << "Bank 4" << "Bank 5");
252 bank_selector->setAutoFillBackground(true);
254 bank_combo_boxes.push_back(InstantiatedComboBox{ bank_selector, bank_field_number });
256 ui->treeWidget->setItemWidget(item, 1, bank_selector);
259 void MIDIMappingDialog::add_controls(const string &heading,
260 MIDIMappingDialog::ControlType control_type,
261 const MIDIMappingProto &mapping_proto,
262 const vector<MIDIMappingDialog::Control> &controls)
264 QTreeWidgetItem *heading_item = new QTreeWidgetItem(ui->treeWidget);
265 heading_item->setText(0, QString::fromStdString(heading));
266 heading_item->setFirstColumnSpanned(true);
267 heading_item->setExpanded(true);
268 for (const Control &control : controls) {
269 QTreeWidgetItem *item = new QTreeWidgetItem(heading_item);
270 heading_item->addChild(item);
271 add_bank_selector(item, mapping_proto, control.bank_field_number);
272 item->setText(0, QString::fromStdString(control.label + " "));
274 for (unsigned bus_idx = 0; bus_idx < num_buses; ++bus_idx) {
275 QSpinBox *spinner = new QSpinBox(this);
276 spinner->setRange(0, 127);
277 spinner->setAutoFillBackground(true);
278 spinner->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
279 ui->treeWidget->setItemWidget(item, bus_idx + 2, spinner);
281 if (control_type == ControlType::CONTROLLER) {
282 controller_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number });
284 assert(control_type == ControlType::BUTTON);
285 button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number });
289 ui->treeWidget->addTopLevelItem(heading_item);
292 void MIDIMappingDialog::fill_controls_from_mapping(const MIDIMappingProto &mapping_proto)
294 for (const InstantiatedSpinner &is : controller_spinners) {
295 is.spinner->setValue(get_controller_mapping(mapping_proto, is.bus_idx, is.field_number, 0));
297 for (const InstantiatedSpinner &is : button_spinners) {
298 is.spinner->setValue(get_button_mapping(mapping_proto, is.bus_idx, is.field_number, 0));
300 for (const InstantiatedComboBox &ic : bank_combo_boxes) {
301 ic.combo_box->setCurrentIndex(get_bank(mapping_proto, ic.field_number, -1) + 1);
305 void MIDIMappingDialog::controller_changed(unsigned controller)
307 for (const InstantiatedSpinner &is : controller_spinners) {
308 if (is.spinner->hasFocus()) {
309 is.spinner->setValue(controller);
310 is.spinner->selectAll();
315 void MIDIMappingDialog::note_on(unsigned note)
317 for (const InstantiatedSpinner &is : button_spinners) {
318 if (is.spinner->hasFocus()) {
319 is.spinner->setValue(note);
320 is.spinner->selectAll();