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 { "Lock master speed", MIDIMappingProto::kToggleLockFieldNumber,
50 MIDIMappingProto::kToggleLockBankFieldNumber },
51 { "Cue in", MIDIMappingProto::kCueInFieldNumber,
52 MIDIMappingProto::kCueInBankFieldNumber },
53 { "Cue out", MIDIMappingProto::kCueOutFieldNumber,
54 MIDIMappingProto::kCueOutBankFieldNumber },
55 { "Previous bank", MIDIMappingProto::kPrevBankFieldNumber, 0 },
56 { "Next bank", MIDIMappingProto::kNextBankFieldNumber, 0 },
57 { "Select bank 1", MIDIMappingProto::kSelectBank1FieldNumber, 0 },
58 { "Select bank 2", MIDIMappingProto::kSelectBank2FieldNumber, 0 },
59 { "Select bank 3", MIDIMappingProto::kSelectBank3FieldNumber, 0 },
60 { "Select bank 4", MIDIMappingProto::kSelectBank4FieldNumber, 0 },
61 { "Select bank 5", MIDIMappingProto::kSelectBank5FieldNumber, 0 },
63 vector<MIDIMappingDialog::Control> button_lights = {
64 { "Preview playing", MIDIMappingProto::kPreviewPlayingFieldNumber, 0 },
65 { "Preview ready", MIDIMappingProto::kPreviewReadyFieldNumber, 0 },
66 { "Queue button enabled", MIDIMappingProto::kQueueEnabledFieldNumber, 0 },
67 { "Playing", MIDIMappingProto::kPlayingFieldNumber, 0 },
68 { "Play ready", MIDIMappingProto::kPlayReadyFieldNumber, 0 },
69 { "Master speed locked", MIDIMappingProto::kLockedFieldNumber, 0 },
70 { "Master speed locked (blinking)",
71 MIDIMappingProto::kLockedBlinkingFieldNumber, 0 },
72 { "Cue in enabled", MIDIMappingProto::kCueInEnabledFieldNumber, 0 },
73 { "Cue out enabled", MIDIMappingProto::kCueOutEnabledFieldNumber, 0 },
74 { "Bank 1 is selected", MIDIMappingProto::kBank1IsSelectedFieldNumber, 0 },
75 { "Bank 2 is selected", MIDIMappingProto::kBank2IsSelectedFieldNumber, 0 },
76 { "Bank 3 is selected", MIDIMappingProto::kBank3IsSelectedFieldNumber, 0 },
77 { "Bank 4 is selected", MIDIMappingProto::kBank4IsSelectedFieldNumber, 0 },
78 { "Bank 5 is selected", MIDIMappingProto::kBank5IsSelectedFieldNumber, 0 },
83 int get_bank(const MIDIMappingProto &mapping_proto, int bank_field_number, int default_value)
85 const FieldDescriptor *bank_descriptor = mapping_proto.GetDescriptor()->FindFieldByNumber(bank_field_number);
86 const Reflection *reflection = mapping_proto.GetReflection();
87 if (!reflection->HasField(mapping_proto, bank_descriptor)) {
90 return reflection->GetInt32(mapping_proto, bank_descriptor);
95 MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper)
96 : ui(new Ui::MIDIMappingDialog),
101 const MIDIMappingProto mapping_proto = mapper->get_current_mapping(); // Take a copy.
102 old_receiver = mapper->set_receiver(this);
106 labels << "Controller bank";
111 ui->treeWidget->setColumnCount(6);
112 ui->treeWidget->setHeaderLabels(labels);
114 vector<MIDIMappingDialog::Control> camera_select_buttons;
115 vector<MIDIMappingDialog::Control> camera_is_selected_lights;
116 for (size_t camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
118 snprintf(str, sizeof(str), "Switch to camera %zu", camera_idx + 1);
119 camera_select_buttons.emplace_back(Control{ str, CameraMIDIMappingProto::kButtonFieldNumber, 0 });
121 snprintf(str, sizeof(str), "Camera %zu is current", camera_idx + 1);
122 camera_is_selected_lights.emplace_back(Control{ str, CameraMIDIMappingProto::kIsCurrentFieldNumber, 0 });
125 add_controls("Controllers", ControlType::CONTROLLER, mapping_proto, controllers);
126 add_controls("Controller lights", ControlType::CONTROLLER_LIGHT, mapping_proto, controller_lights);
127 add_controls("Buttons", ControlType::BUTTON, mapping_proto, buttons);
128 add_controls("Button lights", ControlType::BUTTON_LIGHT, mapping_proto, button_lights);
129 add_controls("Camera select buttons", ControlType::CAMERA_BUTTON, mapping_proto, camera_select_buttons);
130 add_controls("Camera is selected lights", ControlType::CAMERA_BUTTON_LIGHT, mapping_proto, camera_is_selected_lights);
131 fill_controls_from_mapping(mapping_proto);
133 // Auto-resize every column but the last.
134 for (unsigned column_idx = 0; column_idx < 5; ++column_idx) {
135 ui->treeWidget->resizeColumnToContents(column_idx);
138 connect(ui->ok_cancel_buttons, &QDialogButtonBox::accepted, this, &MIDIMappingDialog::ok_clicked);
139 connect(ui->ok_cancel_buttons, &QDialogButtonBox::rejected, this, &MIDIMappingDialog::cancel_clicked);
140 connect(ui->save_button, &QPushButton::clicked, this, &MIDIMappingDialog::save_clicked);
141 connect(ui->load_button, &QPushButton::clicked, this, &MIDIMappingDialog::load_clicked);
144 MIDIMappingDialog::~MIDIMappingDialog()
146 mapper->set_receiver(old_receiver);
149 void MIDIMappingDialog::ok_clicked()
151 unique_ptr<MIDIMappingProto> new_mapping = construct_mapping_proto_from_ui();
152 mapper->set_midi_mapping(*new_mapping);
153 mapper->set_receiver(old_receiver);
157 void MIDIMappingDialog::cancel_clicked()
159 mapper->set_receiver(old_receiver);
163 void MIDIMappingDialog::save_clicked()
165 QFileDialog::Options options;
166 unique_ptr<MIDIMappingProto> new_mapping = construct_mapping_proto_from_ui();
167 QString filename = QFileDialog::getSaveFileName(this,
168 "Save MIDI mapping", QString(), tr("Mapping files (*.midimapping)"), /*selectedFilter=*/nullptr, options);
169 if (!filename.endsWith(".midimapping")) {
170 filename += ".midimapping";
172 if (!save_midi_mapping_to_file(*new_mapping, filename.toStdString())) {
174 box.setText("Could not save mapping to '" + filename + "'. Check that you have the right permissions and try again.");
179 void MIDIMappingDialog::load_clicked()
181 QFileDialog::Options options;
182 QString filename = QFileDialog::getOpenFileName(this,
183 "Load MIDI mapping", QString(), tr("Mapping files (*.midimapping)"), /*selectedFilter=*/nullptr, options);
184 MIDIMappingProto new_mapping;
185 if (!load_midi_mapping_from_file(filename.toStdString(), &new_mapping)) {
187 box.setText("Could not load mapping from '" + filename + "'. Check that the file exists, has the right permissions and is valid.");
192 fill_controls_from_mapping(new_mapping);
197 template<class T, class Proto>
198 T *get_mutable_message(Proto *proto, int field_number)
200 const FieldDescriptor *descriptor = proto->GetDescriptor()->FindFieldByNumber(field_number);
201 const Reflection *bus_reflection = proto->GetReflection();
202 return static_cast<T *>(bus_reflection->MutableMessage(proto, descriptor));
207 unique_ptr<MIDIMappingProto> MIDIMappingDialog::construct_mapping_proto_from_ui()
209 unique_ptr<MIDIMappingProto> mapping_proto(new MIDIMappingProto);
210 for (const InstantiatedSpinner &is : controller_spinners) {
211 const int val = is.spinner->value();
216 MIDIControllerProto *controller_proto =
217 get_mutable_message<MIDIControllerProto>(mapping_proto.get(), is.field_number);
218 controller_proto->set_controller_number(val);
220 for (const InstantiatedSpinner &is : controller_light_spinners) {
221 const int val = is.spinner->value();
226 MIDIControllerProto *controller_proto =
227 get_mutable_message<MIDIControllerProto>(mapping_proto.get(), is.field_number);
228 controller_proto->set_controller_number(val);
230 // HACK: We only have one of these right now, so min/max is a given;
231 // no need to store proto field numbers.
232 int val2 = is.spinner2->value();
234 mapping_proto->set_master_speed_light_min(val2);
236 int val3 = is.spinner3->value();
238 mapping_proto->set_master_speed_light_max(val3);
241 for (const InstantiatedSpinner &is : button_spinners) {
242 const int val = is.spinner->value();
247 MIDIButtonProto *button_proto =
248 get_mutable_message<MIDIButtonProto>(mapping_proto.get(), is.field_number);
249 button_proto->set_note_number(val);
251 for (const InstantiatedSpinner &is : button_light_spinners) {
252 const int val = is.spinner->value();
257 MIDILightProto *light_proto =
258 get_mutable_message<MIDILightProto>(mapping_proto.get(), is.field_number);
259 light_proto->set_note_number(val);
261 int val2 = is.spinner2->value();
263 light_proto->set_velocity(val2);
266 int highest_bank_used = 0; // 1-indexed.
267 for (const InstantiatedComboBox &ic : bank_combo_boxes) {
268 const int val = ic.combo_box->currentIndex();
269 highest_bank_used = std::max(highest_bank_used, val);
274 const FieldDescriptor *descriptor = mapping_proto->GetDescriptor()->FindFieldByNumber(ic.field_number);
275 const Reflection *bus_reflection = mapping_proto->GetReflection();
276 bus_reflection->SetInt32(mapping_proto.get(), descriptor, val - 1);
278 mapping_proto->set_num_controller_banks(highest_bank_used);
280 size_t num_cameras_used = 0;
281 for (size_t camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
282 if (camera_button_spinners[camera_idx].spinner->value() != -1) {
283 num_cameras_used = camera_idx + 1;
284 } else if (camera_button_light_spinners[camera_idx].spinner->value() != -1) {
285 num_cameras_used = camera_idx + 1;
288 for (size_t camera_idx = 0; camera_idx < num_cameras_used; ++camera_idx) {
289 CameraMIDIMappingProto *camera_proto = mapping_proto->add_camera();
292 const InstantiatedSpinner &is = camera_button_spinners[camera_idx];
293 MIDIButtonProto *button_proto =
294 get_mutable_message<MIDIButtonProto>(camera_proto, is.field_number);
295 int val = is.spinner->value();
297 button_proto->set_note_number(val);
301 const InstantiatedSpinner &is = camera_button_light_spinners[camera_idx];
302 MIDILightProto *light_proto =
303 get_mutable_message<MIDILightProto>(camera_proto, is.field_number);
304 int val = is.spinner->value();
306 light_proto->set_note_number(val);
309 int val2 = is.spinner2->value();
311 light_proto->set_velocity(val2);
316 return mapping_proto;
319 void MIDIMappingDialog::add_bank_selector(QTreeWidgetItem *item, const MIDIMappingProto &mapping_proto, int bank_field_number)
321 if (bank_field_number == 0) {
324 QComboBox *bank_selector = new QComboBox(this);
325 bank_selector->addItems(QStringList() << "" << "Bank 1" << "Bank 2" << "Bank 3" << "Bank 4" << "Bank 5");
326 bank_selector->setAutoFillBackground(true);
328 bank_combo_boxes.push_back(InstantiatedComboBox{ bank_selector, bank_field_number });
330 ui->treeWidget->setItemWidget(item, 1, bank_selector);
333 void MIDIMappingDialog::add_controls(const string &heading,
334 MIDIMappingDialog::ControlType control_type,
335 const MIDIMappingProto &mapping_proto,
336 const vector<MIDIMappingDialog::Control> &controls)
338 QTreeWidgetItem *heading_item = new QTreeWidgetItem(ui->treeWidget);
339 heading_item->setText(0, QString::fromStdString(heading));
340 if (control_type == ControlType::BUTTON_LIGHT) {
341 heading_item->setText(3, "Velocity");
342 } else if (control_type == ControlType::CONTROLLER_LIGHT) {
343 heading_item->setText(3, "Min");
344 heading_item->setText(4, "Max");
346 heading_item->setFirstColumnSpanned(true);
348 heading_item->setExpanded(true);
349 for (const Control &control : controls) {
350 QTreeWidgetItem *item = new QTreeWidgetItem(heading_item);
351 heading_item->addChild(item);
352 add_bank_selector(item, mapping_proto, control.bank_field_number);
353 item->setText(0, QString::fromStdString(control.label + " "));
356 if (control_type == ControlType::CONTROLLER) {
357 spinner = new ControllerSpinBox(this);
358 spinner->setRange(-1, 128); // 128 for pitch bend.
360 spinner = new QSpinBox(this);
361 spinner->setRange(-1, 127);
363 spinner->setAutoFillBackground(true);
364 spinner->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
365 ui->treeWidget->setItemWidget(item, 2, spinner);
367 if (control_type == ControlType::CONTROLLER) {
368 controller_spinners.push_back(InstantiatedSpinner{ spinner, nullptr, nullptr, control.field_number });
369 } else if (control_type == ControlType::CONTROLLER_LIGHT) {
370 QSpinBox *spinner2 = new QSpinBox(this);
371 spinner2->setRange(-1, 127);
372 spinner2->setAutoFillBackground(true);
373 spinner2->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
375 QSpinBox *spinner3 = new QSpinBox(this);
376 spinner3->setRange(-1, 127);
377 spinner3->setAutoFillBackground(true);
378 spinner3->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
380 ui->treeWidget->setItemWidget(item, 3, spinner2);
381 ui->treeWidget->setItemWidget(item, 4, spinner3);
383 controller_light_spinners.push_back(InstantiatedSpinner{ spinner, spinner2, spinner3, control.field_number });
384 } else if (control_type == ControlType::BUTTON) {
385 button_spinners.push_back(InstantiatedSpinner{ spinner, nullptr, nullptr, control.field_number });
386 } else if (control_type == ControlType::CAMERA_BUTTON) {
387 camera_button_spinners.push_back(InstantiatedSpinner{ spinner, nullptr, nullptr, control.field_number });
389 assert(control_type == ControlType::BUTTON_LIGHT || control_type == ControlType::CAMERA_BUTTON_LIGHT);
390 QSpinBox *spinner2 = new QSpinBox(this);
391 spinner2->setRange(-1, 127);
392 spinner2->setAutoFillBackground(true);
393 spinner2->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty).
394 ui->treeWidget->setItemWidget(item, 3, spinner2);
395 if (control_type == ControlType::BUTTON_LIGHT) {
396 button_light_spinners.push_back(InstantiatedSpinner{ spinner, spinner2, nullptr, control.field_number });
398 assert(control_type == ControlType::CAMERA_BUTTON_LIGHT);
399 camera_button_light_spinners.push_back(InstantiatedSpinner{ spinner, spinner2, nullptr, control.field_number });
402 spinners[control.field_number] = spinner;
404 ui->treeWidget->addTopLevelItem(heading_item);
407 void MIDIMappingDialog::fill_controls_from_mapping(const MIDIMappingProto &mapping_proto)
409 for (const InstantiatedSpinner &is : controller_spinners) {
410 is.spinner->setValue(get_controller_mapping_helper(mapping_proto, is.field_number, -1));
412 for (const InstantiatedSpinner &is : controller_light_spinners) {
413 is.spinner->setValue(get_controller_mapping_helper(mapping_proto, is.field_number, -1));
415 // HACK: We only have one of these right now, so min/max is a given;
416 // no need to store proto field numbers.
417 if (mapping_proto.has_master_speed_light_min()) {
418 is.spinner2->setValue(mapping_proto.master_speed_light_min());
420 if (mapping_proto.has_master_speed_light_max()) {
421 is.spinner3->setValue(mapping_proto.master_speed_light_max());
424 for (const InstantiatedSpinner &is : button_spinners) {
425 is.spinner->setValue(get_button_mapping_helper(mapping_proto, is.field_number, -1));
427 for (const InstantiatedSpinner &is : button_light_spinners) {
428 MIDILightProto light_proto = get_light_mapping_helper(mapping_proto, is.field_number);
429 if (light_proto.has_note_number()) {
430 is.spinner->setValue(light_proto.note_number());
432 is.spinner->setValue(-1);
434 if (light_proto.has_velocity()) {
435 is.spinner2->setValue(light_proto.velocity());
437 is.spinner2->setValue(-1);
440 for (size_t camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
441 CameraMIDIMappingProto camera_proto;
442 if (camera_idx < size_t(mapping_proto.camera_size())) {
443 camera_proto = mapping_proto.camera(camera_idx);
446 const InstantiatedSpinner &is = camera_button_spinners[camera_idx];
447 is.spinner->setValue(get_button_mapping_helper(camera_proto, is.field_number, -1));
450 const InstantiatedSpinner &is = camera_button_light_spinners[camera_idx];
451 const MIDILightProto &light_proto = get_light_mapping_helper(camera_proto, is.field_number);
452 if (light_proto.has_note_number()) {
453 is.spinner->setValue(light_proto.note_number());
455 is.spinner->setValue(-1);
457 if (light_proto.has_velocity()) {
458 is.spinner2->setValue(light_proto.velocity());
460 is.spinner2->setValue(-1);
464 for (const InstantiatedComboBox &ic : bank_combo_boxes) {
465 ic.combo_box->setCurrentIndex(get_bank(mapping_proto, ic.field_number, -1) + 1);
469 void MIDIMappingDialog::controller_changed(unsigned controller)
471 post_to_main_thread([=]{
472 for (const InstantiatedSpinner &is : controller_spinners) {
473 if (is.spinner->hasFocus()) {
474 is.spinner->setValue(controller);
475 is.spinner->selectAll();
478 for (const InstantiatedSpinner &is : controller_light_spinners) {
479 if (is.spinner->hasFocus()) {
480 is.spinner->setValue(controller);
481 is.spinner->selectAll();
487 void MIDIMappingDialog::note_on(unsigned note)
489 post_to_main_thread([=]{
490 for (const InstantiatedSpinner &is : button_spinners) {
491 if (is.spinner->hasFocus()) {
492 is.spinner->setValue(note);
493 is.spinner->selectAll();
496 for (const InstantiatedSpinner &is : button_light_spinners) {
497 if (is.spinner->hasFocus()) {
498 is.spinner->setValue(note);
499 is.spinner->selectAll();