1 #include "midi_mapping_dialog.h"
4 #include <google/protobuf/descriptor.h>
5 #include <google/protobuf/message.h>
7 #include <QDialogButtonBox>
10 #include <QPushButton>
12 #include <QStringList>
13 #include <QTreeWidget>
21 #include "shared/controller_spin_box.h"
22 #include "midi_mapper.h"
23 #include "futatabi_midi_mapping.pb.h"
24 #include "shared/midi_mapper_util.h"
25 #include "shared/post_to_main_thread.h"
26 #include "ui_midi_mapping.h"
30 using namespace google::protobuf;
33 vector<MIDIMappingDialog::Control> controllers = {
34 { "Jog", MIDIMappingProto::kJogFieldNumber,
35 MIDIMappingProto::kJogBankFieldNumber },
36 { "Master speed", MIDIMappingProto::kMasterSpeedFieldNumber,
37 MIDIMappingProto::kMasterSpeedBankFieldNumber },
39 vector<MIDIMappingDialog::Control> controller_lights = {
40 { "Master speed light", MIDIMappingProto::kMasterSpeedLightFieldNumber, 0 },
42 vector<MIDIMappingDialog::Control> buttons = {
43 { "Preview", MIDIMappingProto::kPreviewFieldNumber,
44 MIDIMappingProto::kPreviewBankFieldNumber },
45 { "Queue", MIDIMappingProto::kQueueFieldNumber,
46 MIDIMappingProto::kQueueBankFieldNumber },
47 { "Play", MIDIMappingProto::kPlayFieldNumber,
48 MIDIMappingProto::kPlayBankFieldNumber },
49 { "Next", MIDIMappingProto::kNextFieldNumber,
50 MIDIMappingProto::kNextButtonBankFieldNumber },
51 { "Lock master speed", MIDIMappingProto::kToggleLockFieldNumber,
52 MIDIMappingProto::kToggleLockBankFieldNumber },
53 { "Cue in", MIDIMappingProto::kCueInFieldNumber,
54 MIDIMappingProto::kCueInBankFieldNumber },
55 { "Cue out", MIDIMappingProto::kCueOutFieldNumber,
56 MIDIMappingProto::kCueOutBankFieldNumber },
57 { "Previous bank", MIDIMappingProto::kPrevBankFieldNumber, 0 },
58 { "Next bank", MIDIMappingProto::kNextBankFieldNumber, 0 },
59 { "Select bank 1", MIDIMappingProto::kSelectBank1FieldNumber, 0 },
60 { "Select bank 2", MIDIMappingProto::kSelectBank2FieldNumber, 0 },
61 { "Select bank 3", MIDIMappingProto::kSelectBank3FieldNumber, 0 },
62 { "Select bank 4", MIDIMappingProto::kSelectBank4FieldNumber, 0 },
63 { "Select bank 5", MIDIMappingProto::kSelectBank5FieldNumber, 0 },
65 vector<MIDIMappingDialog::Control> button_lights = {
66 { "Preview playing", MIDIMappingProto::kPreviewPlayingFieldNumber, 0 },
67 { "Preview ready", MIDIMappingProto::kPreviewReadyFieldNumber, 0 },
68 { "Queue button enabled", MIDIMappingProto::kQueueEnabledFieldNumber, 0 },
69 { "Playing", MIDIMappingProto::kPlayingFieldNumber, 0 },
70 { "Play ready", MIDIMappingProto::kPlayReadyFieldNumber, 0 },
71 { "Next ready", MIDIMappingProto::kNextReadyFieldNumber, 0 },
72 { "Master speed locked", MIDIMappingProto::kLockedFieldNumber, 0 },
73 { "Master speed locked (blinking)",
74 MIDIMappingProto::kLockedBlinkingFieldNumber, 0 },
75 { "Cue in enabled", MIDIMappingProto::kCueInEnabledFieldNumber, 0 },
76 { "Cue out enabled", MIDIMappingProto::kCueOutEnabledFieldNumber, 0 },
77 { "Bank 1 is selected", MIDIMappingProto::kBank1IsSelectedFieldNumber, 0 },
78 { "Bank 2 is selected", MIDIMappingProto::kBank2IsSelectedFieldNumber, 0 },
79 { "Bank 3 is selected", MIDIMappingProto::kBank3IsSelectedFieldNumber, 0 },
80 { "Bank 4 is selected", MIDIMappingProto::kBank4IsSelectedFieldNumber, 0 },
81 { "Bank 5 is selected", MIDIMappingProto::kBank5IsSelectedFieldNumber, 0 },
86 int get_bank(const MIDIMappingProto &mapping_proto, int bank_field_number, int default_value)
88 const FieldDescriptor *bank_descriptor = mapping_proto.GetDescriptor()->FindFieldByNumber(bank_field_number);
89 const Reflection *reflection = mapping_proto.GetReflection();
90 if (!reflection->HasField(mapping_proto, bank_descriptor)) {
93 return reflection->GetInt32(mapping_proto, bank_descriptor);
98 MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper)
99 : ui(new Ui::MIDIMappingDialog),
104 const MIDIMappingProto mapping_proto = mapper->get_current_mapping(); // Take a copy.
105 old_receiver = mapper->set_receiver(this);
109 labels << "Controller bank";
114 ui->treeWidget->setColumnCount(6);
115 ui->treeWidget->setHeaderLabels(labels);
117 vector<MIDIMappingDialog::Control> camera_select_buttons;
118 vector<MIDIMappingDialog::Control> camera_is_selected_lights;
119 for (size_t camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
121 snprintf(str, sizeof(str), "Switch to camera %zu", camera_idx + 1);
122 camera_select_buttons.emplace_back(Control{ str, CameraMIDIMappingProto::kButtonFieldNumber, 0 });
124 snprintf(str, sizeof(str), "Camera %zu is current", camera_idx + 1);
125 camera_is_selected_lights.emplace_back(Control{ str, CameraMIDIMappingProto::kIsCurrentFieldNumber, 0 });
128 add_controls("Controllers", ControlType::CONTROLLER, mapping_proto, controllers);
129 add_controls("Controller lights", ControlType::CONTROLLER_LIGHT, mapping_proto, controller_lights);
130 add_controls("Buttons", ControlType::BUTTON, mapping_proto, buttons);
131 add_controls("Button lights", ControlType::BUTTON_LIGHT, mapping_proto, button_lights);
132 add_controls("Camera select buttons", ControlType::CAMERA_BUTTON, mapping_proto, camera_select_buttons);
133 add_controls("Camera is selected lights", ControlType::CAMERA_BUTTON_LIGHT, mapping_proto, camera_is_selected_lights);
134 fill_controls_from_mapping(mapping_proto);
136 // Auto-resize every column but the last.
137 for (unsigned column_idx = 0; column_idx < 5; ++column_idx) {
138 ui->treeWidget->resizeColumnToContents(column_idx);
141 connect(ui->ok_cancel_buttons, &QDialogButtonBox::accepted, this, &MIDIMappingDialog::ok_clicked);
142 connect(ui->ok_cancel_buttons, &QDialogButtonBox::rejected, this, &MIDIMappingDialog::cancel_clicked);
143 connect(ui->save_button, &QPushButton::clicked, this, &MIDIMappingDialog::save_clicked);
144 connect(ui->load_button, &QPushButton::clicked, this, &MIDIMappingDialog::load_clicked);
147 MIDIMappingDialog::~MIDIMappingDialog()
149 mapper->set_receiver(old_receiver);
152 void MIDIMappingDialog::ok_clicked()
154 unique_ptr<MIDIMappingProto> new_mapping = construct_mapping_proto_from_ui();
155 mapper->set_midi_mapping(*new_mapping);
156 mapper->set_receiver(old_receiver);
160 void MIDIMappingDialog::cancel_clicked()
162 mapper->set_receiver(old_receiver);
166 void MIDIMappingDialog::save_clicked()
168 QFileDialog::Options options;
169 unique_ptr<MIDIMappingProto> new_mapping = construct_mapping_proto_from_ui();
170 QString filename = QFileDialog::getSaveFileName(this,
171 "Save MIDI mapping", QString(), tr("Mapping files (*.midimapping)"), /*selectedFilter=*/nullptr, options);
172 if (!filename.endsWith(".midimapping")) {
173 filename += ".midimapping";
175 if (!save_midi_mapping_to_file(*new_mapping, filename.toStdString())) {
177 box.setText("Could not save mapping to '" + filename + "'. Check that you have the right permissions and try again.");
182 void MIDIMappingDialog::load_clicked()
184 QFileDialog::Options options;
185 QString filename = QFileDialog::getOpenFileName(this,
186 "Load MIDI mapping", QString(), tr("Mapping files (*.midimapping)"), /*selectedFilter=*/nullptr, options);
187 MIDIMappingProto new_mapping;
188 if (!load_midi_mapping_from_file(filename.toStdString(), &new_mapping)) {
190 box.setText("Could not load mapping from '" + filename + "'. Check that the file exists, has the right permissions and is valid.");
195 fill_controls_from_mapping(new_mapping);
200 template<class T, class Proto>
201 T *get_mutable_message(Proto *proto, int field_number)
203 const FieldDescriptor *descriptor = proto->GetDescriptor()->FindFieldByNumber(field_number);
204 const Reflection *bus_reflection = proto->GetReflection();
205 return static_cast<T *>(bus_reflection->MutableMessage(proto, descriptor));
210 unique_ptr<MIDIMappingProto> MIDIMappingDialog::construct_mapping_proto_from_ui()
212 unique_ptr<MIDIMappingProto> mapping_proto(new MIDIMappingProto);
213 for (const InstantiatedSpinner &is : controller_spinners) {
214 const int val = is.spinner->value();
219 MIDIControllerProto *controller_proto =
220 get_mutable_message<MIDIControllerProto>(mapping_proto.get(), is.field_number);
221 controller_proto->set_controller_number(val);
223 for (const InstantiatedSpinner &is : controller_light_spinners) {
224 const int val = is.spinner->value();
229 MIDIControllerProto *controller_proto =
230 get_mutable_message<MIDIControllerProto>(mapping_proto.get(), is.field_number);
231 controller_proto->set_controller_number(val);
233 // HACK: We only have one of these right now, so min/max is a given;
234 // no need to store proto field numbers.
235 int val2 = is.spinner2->value();
237 mapping_proto->set_master_speed_light_min(val2);
239 int val3 = is.spinner3->value();
241 mapping_proto->set_master_speed_light_max(val3);
244 for (const InstantiatedSpinner &is : button_spinners) {
245 const int val = is.spinner->value();
250 MIDIButtonProto *button_proto =
251 get_mutable_message<MIDIButtonProto>(mapping_proto.get(), is.field_number);
252 button_proto->set_note_number(val);
254 for (const InstantiatedSpinner &is : button_light_spinners) {
255 const int val = is.spinner->value();
260 MIDILightProto *light_proto =
261 get_mutable_message<MIDILightProto>(mapping_proto.get(), is.field_number);
262 light_proto->set_note_number(val);
264 int val2 = is.spinner2->value();
266 light_proto->set_velocity(val2);
269 int highest_bank_used = 0; // 1-indexed.
270 for (const InstantiatedComboBox &ic : bank_combo_boxes) {
271 const int val = ic.combo_box->currentIndex();
272 highest_bank_used = std::max(highest_bank_used, val);
277 const FieldDescriptor *descriptor = mapping_proto->GetDescriptor()->FindFieldByNumber(ic.field_number);
278 const Reflection *bus_reflection = mapping_proto->GetReflection();
279 bus_reflection->SetInt32(mapping_proto.get(), descriptor, val - 1);
281 mapping_proto->set_num_controller_banks(highest_bank_used);
283 size_t num_cameras_used = 0;
284 for (size_t camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
285 if (camera_button_spinners[camera_idx].spinner->value() != -1) {
286 num_cameras_used = camera_idx + 1;
287 } else if (camera_button_light_spinners[camera_idx].spinner->value() != -1) {
288 num_cameras_used = camera_idx + 1;
291 for (size_t camera_idx = 0; camera_idx < num_cameras_used; ++camera_idx) {
292 CameraMIDIMappingProto *camera_proto = mapping_proto->add_camera();
295 const InstantiatedSpinner &is = camera_button_spinners[camera_idx];
296 int val = is.spinner->value();
298 MIDIButtonProto *button_proto =
299 get_mutable_message<MIDIButtonProto>(camera_proto, is.field_number);
300 button_proto->set_note_number(val);
304 const InstantiatedSpinner &is = camera_button_light_spinners[camera_idx];
305 int val = is.spinner->value();
306 int val2 = is.spinner2->value();
308 if (val == -1 && val2 == -1) continue;
310 MIDILightProto *light_proto =
311 get_mutable_message<MIDILightProto>(camera_proto, is.field_number);
313 light_proto->set_note_number(val);
316 light_proto->set_velocity(val2);
321 return mapping_proto;
324 void MIDIMappingDialog::add_bank_selector(QTreeWidgetItem *item, const MIDIMappingProto &mapping_proto, int bank_field_number)
326 if (bank_field_number == 0) {
329 QComboBox *bank_selector = new QComboBox(this);
330 bank_selector->addItems(QStringList() << "" << "Bank 1" << "Bank 2" << "Bank 3" << "Bank 4" << "Bank 5");
331 bank_selector->setAutoFillBackground(true);
333 bank_combo_boxes.push_back(InstantiatedComboBox{ bank_selector, bank_field_number });
335 ui->treeWidget->setItemWidget(item, 1, bank_selector);
338 void MIDIMappingDialog::add_controls(const string &heading,
339 MIDIMappingDialog::ControlType control_type,
340 const MIDIMappingProto &mapping_proto,
341 const vector<MIDIMappingDialog::Control> &controls)
343 QTreeWidgetItem *heading_item = new QTreeWidgetItem(ui->treeWidget);
344 heading_item->setText(0, QString::fromStdString(heading));
345 if (control_type == ControlType::BUTTON_LIGHT) {
346 heading_item->setText(3, "Velocity");
347 } else if (control_type == ControlType::CONTROLLER_LIGHT) {
348 heading_item->setText(3, "Min");
349 heading_item->setText(4, "Max");
351 heading_item->setFirstColumnSpanned(true);
353 heading_item->setExpanded(true);
354 for (const Control &control : controls) {
355 QTreeWidgetItem *item = new QTreeWidgetItem(heading_item);
356 heading_item->addChild(item);
357 add_bank_selector(item, mapping_proto, control.bank_field_number);
358 item->setText(0, QString::fromStdString(control.label + " "));
361 if (control_type == ControlType::CONTROLLER) {
362 spinner = new ControllerSpinBox(this);
363 spinner->setRange(-1, 128); // 128 for pitch bend.
365 spinner = new QSpinBox(this);
366 spinner->setRange(-1, 127);
368 spinner->setAutoFillBackground(true);
369 spinner->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
370 ui->treeWidget->setItemWidget(item, 2, spinner);
372 if (control_type == ControlType::CONTROLLER) {
373 controller_spinners.push_back(InstantiatedSpinner{ spinner, nullptr, nullptr, control.field_number });
374 } else if (control_type == ControlType::CONTROLLER_LIGHT) {
375 QSpinBox *spinner2 = new QSpinBox(this);
376 spinner2->setRange(-1, 127);
377 spinner2->setAutoFillBackground(true);
378 spinner2->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
380 QSpinBox *spinner3 = new QSpinBox(this);
381 spinner3->setRange(-1, 127);
382 spinner3->setAutoFillBackground(true);
383 spinner3->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
385 ui->treeWidget->setItemWidget(item, 3, spinner2);
386 ui->treeWidget->setItemWidget(item, 4, spinner3);
388 controller_light_spinners.push_back(InstantiatedSpinner{ spinner, spinner2, spinner3, control.field_number });
389 } else if (control_type == ControlType::BUTTON) {
390 button_spinners.push_back(InstantiatedSpinner{ spinner, nullptr, nullptr, control.field_number });
391 } else if (control_type == ControlType::CAMERA_BUTTON) {
392 camera_button_spinners.push_back(InstantiatedSpinner{ spinner, nullptr, nullptr, control.field_number });
394 assert(control_type == ControlType::BUTTON_LIGHT || control_type == ControlType::CAMERA_BUTTON_LIGHT);
395 QSpinBox *spinner2 = new QSpinBox(this);
396 spinner2->setRange(-1, 127);
397 spinner2->setAutoFillBackground(true);
398 spinner2->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
399 ui->treeWidget->setItemWidget(item, 3, spinner2);
400 if (control_type == ControlType::BUTTON_LIGHT) {
401 button_light_spinners.push_back(InstantiatedSpinner{ spinner, spinner2, nullptr, control.field_number });
403 assert(control_type == ControlType::CAMERA_BUTTON_LIGHT);
404 camera_button_light_spinners.push_back(InstantiatedSpinner{ spinner, spinner2, nullptr, control.field_number });
407 spinners[control.field_number] = spinner;
409 ui->treeWidget->addTopLevelItem(heading_item);
412 void MIDIMappingDialog::fill_controls_from_mapping(const MIDIMappingProto &mapping_proto)
414 for (const InstantiatedSpinner &is : controller_spinners) {
415 is.spinner->setValue(get_controller_mapping_helper(mapping_proto, is.field_number, -1));
417 for (const InstantiatedSpinner &is : controller_light_spinners) {
418 is.spinner->setValue(get_controller_mapping_helper(mapping_proto, is.field_number, -1));
420 // HACK: We only have one of these right now, so min/max is a given;
421 // no need to store proto field numbers.
422 if (mapping_proto.has_master_speed_light_min()) {
423 is.spinner2->setValue(mapping_proto.master_speed_light_min());
425 if (mapping_proto.has_master_speed_light_max()) {
426 is.spinner3->setValue(mapping_proto.master_speed_light_max());
429 for (const InstantiatedSpinner &is : button_spinners) {
430 is.spinner->setValue(get_button_mapping_helper(mapping_proto, is.field_number, -1));
432 for (const InstantiatedSpinner &is : button_light_spinners) {
433 MIDILightProto light_proto = get_light_mapping_helper(mapping_proto, is.field_number);
434 if (light_proto.has_note_number()) {
435 is.spinner->setValue(light_proto.note_number());
437 is.spinner->setValue(-1);
439 if (light_proto.has_velocity()) {
440 is.spinner2->setValue(light_proto.velocity());
442 is.spinner2->setValue(-1);
445 for (size_t camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
446 CameraMIDIMappingProto camera_proto;
447 if (camera_idx < size_t(mapping_proto.camera_size())) {
448 camera_proto = mapping_proto.camera(camera_idx);
451 const InstantiatedSpinner &is = camera_button_spinners[camera_idx];
452 is.spinner->setValue(get_button_mapping_helper(camera_proto, is.field_number, -1));
455 const InstantiatedSpinner &is = camera_button_light_spinners[camera_idx];
456 const MIDILightProto &light_proto = get_light_mapping_helper(camera_proto, is.field_number);
457 if (light_proto.has_note_number()) {
458 is.spinner->setValue(light_proto.note_number());
460 is.spinner->setValue(-1);
462 if (light_proto.has_velocity()) {
463 is.spinner2->setValue(light_proto.velocity());
465 is.spinner2->setValue(-1);
469 for (const InstantiatedComboBox &ic : bank_combo_boxes) {
470 ic.combo_box->setCurrentIndex(get_bank(mapping_proto, ic.field_number, -1) + 1);
474 void MIDIMappingDialog::controller_changed(unsigned controller)
476 post_to_main_thread([=]{
477 for (const InstantiatedSpinner &is : controller_spinners) {
478 if (is.spinner->hasFocus()) {
479 is.spinner->setValue(controller);
480 is.spinner->selectAll();
483 for (const InstantiatedSpinner &is : controller_light_spinners) {
484 if (is.spinner->hasFocus()) {
485 is.spinner->setValue(controller);
486 is.spinner->selectAll();
492 void MIDIMappingDialog::note_on(unsigned note)
494 post_to_main_thread([=]{
495 for (const auto &spinners : { button_spinners, camera_button_spinners }) {
496 for (const InstantiatedSpinner &is : spinners) {
497 if (is.spinner->hasFocus()) {
498 is.spinner->setValue(note);
499 is.spinner->selectAll();
503 for (const auto &light_spinners : { button_light_spinners, camera_button_light_spinners }) {
504 for (const InstantiatedSpinner &is : light_spinners) {
505 if (is.spinner->hasFocus()) {
506 is.spinner->setValue(note);
507 is.spinner->selectAll();