From afe996bc7dfc8689ca356d00824fbfcd632f93a2 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 16 Oct 2016 00:45:29 +0200 Subject: [PATCH] Add an editor for the MIDI mappings. Some of the buttons (save, load, guess) are inert, and it doesn't listen to MIDI events yet. --- .gitignore | 1 + Makefile | 6 +- mainwindow.cpp | 8 ++ mainwindow.h | 1 + midi_mapper.cpp | 8 ++ midi_mapper.h | 10 +- midi_mapping_dialog.cpp | 256 ++++++++++++++++++++++++++++++++++++++++ midi_mapping_dialog.h | 72 +++++++++++ ui_mainwindow.ui | 6 + ui_midi_mapping.ui | 98 +++++++++++++++ 10 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 midi_mapping_dialog.cpp create mode 100644 midi_mapping_dialog.h create mode 100644 ui_midi_mapping.ui diff --git a/.gitignore b/.gitignore index 8724f1c..a64e22d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,6 @@ ui_audio_miniview.h ui_display.h ui_input_mapping.h ui_mainwindow.h +ui_midi_mapping.h nageru benchmark_audio_mixer diff --git a/Makefile b/Makefile index af09d61..0e317aa 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,8 @@ endif LDLIBS=$(shell pkg-config --libs $(PKG_MODULES)) -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lswscale -lavresample -lzita-resampler -lasound -ldl # Qt objects -OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o correlation_meter.o aboutdialog.o input_mapping_dialog.o nonlinear_fader.o -OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o correlation_meter.moc.o aboutdialog.moc.o ellipsis_label.moc.o input_mapping_dialog.moc.o nonlinear_fader.moc.o clickable_label.moc.o +OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o correlation_meter.o aboutdialog.o input_mapping_dialog.o midi_mapping_dialog.o nonlinear_fader.o +OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o correlation_meter.moc.o aboutdialog.moc.o ellipsis_label.moc.o input_mapping_dialog.moc.o midi_mapping_dialog.moc.o nonlinear_fader.moc.o clickable_label.moc.o OBJS += midi_mapper.o midi_mapping.pb.o # Mixer objects @@ -56,7 +56,7 @@ nageru: $(OBJS) benchmark_audio_mixer: $(BM_OBJS) $(CXX) -o $@ $^ $(LDFLAGS) $(LDLIBS) -mainwindow.o: mainwindow.cpp ui_mainwindow.h ui_display.h ui_audio_miniview.h ui_audio_expanded_view.h +mainwindow.o: mainwindow.cpp ui_mainwindow.h ui_display.h ui_audio_miniview.h ui_audio_expanded_view.h ui_midi_mapping.h aboutdialog.o: aboutdialog.cpp ui_aboutdialog.h diff --git a/mainwindow.cpp b/mainwindow.cpp index 1720940..f312705 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -26,6 +26,7 @@ #include "input_mapping_dialog.h" #include "lrameter.h" #include "midi_mapping.pb.h" +#include "midi_mapping_dialog.h" #include "mixer.h" #include "post_to_main_thread.h" #include "ui_audio_miniview.h" @@ -169,6 +170,7 @@ MainWindow::MainWindow() connect(ui->simple_audio_mode, &QAction::triggered, this, &MainWindow::simple_audio_mode_triggered); connect(ui->multichannel_audio_mode, &QAction::triggered, this, &MainWindow::multichannel_audio_mode_triggered); connect(ui->input_mapping_action, &QAction::triggered, this, &MainWindow::input_mapping_triggered); + connect(ui->midi_mapping_action, &QAction::triggered, this, &MainWindow::midi_mapping_triggered); if (global_flags.x264_video_to_http) { connect(ui->x264_bitrate_action, &QAction::triggered, this, &MainWindow::x264_bitrate_triggered); @@ -342,6 +344,7 @@ void MainWindow::reset_audio_mapping_ui() ui->simple_audio_mode->setChecked(simple); ui->multichannel_audio_mode->setChecked(!simple); ui->input_mapping_action->setEnabled(!simple); + ui->midi_mapping_action->setEnabled(!simple); ui->locut_enabled->setVisible(simple); ui->gainstaging_label->setVisible(simple); @@ -563,6 +566,11 @@ void MainWindow::input_mapping_triggered() } } +void MainWindow::midi_mapping_triggered() +{ + MIDIMappingDialog(&midi_mapper).exec(); +} + void MainWindow::gain_staging_knob_changed(unsigned bus_index, int value) { if (bus_index == 0) { diff --git a/mainwindow.h b/mainwindow.h index 307b77e..35e788e 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -46,6 +46,7 @@ public slots: void simple_audio_mode_triggered(); void multichannel_audio_mode_triggered(); void input_mapping_triggered(); + void midi_mapping_triggered(); void transition_clicked(int transition_number); void channel_clicked(int channel_number); void wb_button_clicked(int channel_number); diff --git a/midi_mapper.cpp b/midi_mapper.cpp index dc5eab0..27e843e 100644 --- a/midi_mapper.cpp +++ b/midi_mapper.cpp @@ -66,6 +66,7 @@ bool load_midi_mapping_from_file(const string &filename, MIDIMappingProto *new_m void MIDIMapper::set_midi_mapping(const MIDIMappingProto &new_mapping) { + lock_guard lock(mapping_mu); if (mapping_proto) { mapping_proto->CopyFrom(new_mapping); } else { @@ -81,6 +82,12 @@ void MIDIMapper::start_thread() midi_thread = thread(&MIDIMapper::thread_func, this); } +const MIDIMappingProto &MIDIMapper::get_current_mapping() const +{ + lock_guard lock(mapping_mu); + return *mapping_proto; +} + #define RETURN_ON_ERROR(msg, expr) do { \ int err = (expr); \ if (err < 0) { \ @@ -171,6 +178,7 @@ void MIDIMapper::thread_func() void MIDIMapper::handle_event(snd_seq_t *seq, snd_seq_event_t *event) { + lock_guard lock(mapping_mu); switch (event->type) { case SND_SEQ_EVENT_CONTROLLER: { printf("Controller %d changed to %d\n", event->data.control.param, event->data.control.value); diff --git a/midi_mapper.h b/midi_mapper.h index 2b59b9f..84baf1f 100644 --- a/midi_mapper.h +++ b/midi_mapper.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -46,7 +47,7 @@ public: virtual ~MIDIMapper(); void set_midi_mapping(const MIDIMappingProto &new_mapping); void start_thread(); - const MIDIMappingProto &get_current_mapping() const { return *mapping_proto; } + const MIDIMappingProto &get_current_mapping() const; private: void thread_func(); @@ -60,9 +61,10 @@ private: std::atomic should_quit{false}; int should_quit_fd; - std::unique_ptr mapping_proto; - int num_controller_banks; - int current_controller_bank = 0; + mutable std::mutex mapping_mu; + std::unique_ptr mapping_proto; // Under . + int num_controller_banks; // Under . + std::atomic current_controller_bank{0}; std::thread midi_thread; }; diff --git a/midi_mapping_dialog.cpp b/midi_mapping_dialog.cpp new file mode 100644 index 0000000..cd622d7 --- /dev/null +++ b/midi_mapping_dialog.cpp @@ -0,0 +1,256 @@ +#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 + +using namespace google::protobuf; +using namespace std; + +vector per_bus_controllers = { + { "Treble", MIDIMappingBusProto::kTrebleFieldNumber, MIDIMappingProto::kTrebleBankFieldNumber }, + { "Mid", MIDIMappingBusProto::kMidFieldNumber, MIDIMappingProto::kMidBankFieldNumber }, + { "Bass", MIDIMappingBusProto::kBassFieldNumber, MIDIMappingProto::kBassBankFieldNumber }, + { "Gain", MIDIMappingBusProto::kGainFieldNumber, MIDIMappingProto::kGainBankFieldNumber }, + { "Compressor threshold", MIDIMappingBusProto::kCompressorThresholdFieldNumber, + MIDIMappingProto::kCompressorThresholdBankFieldNumber}, + { "Fader", MIDIMappingBusProto::kFaderFieldNumber, MIDIMappingProto::kFaderBankFieldNumber } +}; +vector per_bus_buttons = { + { "Toggle locut", MIDIMappingBusProto::kToggleLocutFieldNumber, + MIDIMappingProto::kToggleLocutBankFieldNumber }, + { "Togle auto gain staging", MIDIMappingBusProto::kToggleAutoGainStagingFieldNumber, + MIDIMappingProto::kToggleAutoGainStagingBankFieldNumber }, + { "Togle compressor", MIDIMappingBusProto::kToggleCompressorFieldNumber, + MIDIMappingProto::kToggleCompressorBankFieldNumber }, + { "Clear peak", MIDIMappingBusProto::kClearPeakFieldNumber, + MIDIMappingProto::kClearPeakBankFieldNumber } +}; +vector global_controllers = { + { "Locut cutoff", MIDIMappingBusProto::kLocutFieldNumber, MIDIMappingProto::kLocutBankFieldNumber }, + { "Limiter threshold", MIDIMappingBusProto::kLimiterThresholdFieldNumber, + MIDIMappingProto::kLimiterThresholdBankFieldNumber }, + { "Makeup gain", MIDIMappingBusProto::kMakeupGainFieldNumber, + MIDIMappingProto::kMakeupGainBankFieldNumber } +}; +vector global_buttons = { + { "Previous bank", MIDIMappingBusProto::kPrevBankFieldNumber, 0 }, + { "Next bank", MIDIMappingBusProto::kNextBankFieldNumber, 0 }, + { "Select bank 1", MIDIMappingBusProto::kSelectBank1FieldNumber, 0 }, + { "Select bank 2", MIDIMappingBusProto::kSelectBank2FieldNumber, 0 }, + { "Select bank 3", MIDIMappingBusProto::kSelectBank3FieldNumber, 0 }, + { "Select bank 4", MIDIMappingBusProto::kSelectBank4FieldNumber, 0 }, + { "Select bank 5", MIDIMappingBusProto::kSelectBank5FieldNumber, 0 } +}; + +namespace { + +int get_bank(const MIDIMappingProto &mapping_proto, int bank_field_number, int default_value) +{ + const FieldDescriptor *bank_descriptor = mapping_proto.GetDescriptor()->FindFieldByNumber(bank_field_number); + const Reflection *reflection = mapping_proto.GetReflection(); + if (!reflection->HasField(mapping_proto, bank_descriptor)) { + return default_value; + } + return reflection->GetInt32(mapping_proto, bank_descriptor); +} + +int get_controller_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 MIDIControllerProto &controller_proto = + static_cast(bus_reflection->GetMessage(bus_mapping, descriptor)); + return controller_proto.controller_number(); +} + +int get_button_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 MIDIButtonProto &bus_proto = + static_cast(bus_reflection->GetMessage(bus_mapping, descriptor)); + return bus_proto.note_number(); +} + +} // namespace + +MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper) + : ui(new Ui::MIDIMappingDialog), + mapper(mapper) +{ + ui->setupUi(this); + + const MIDIMappingProto mapping_proto = mapper->get_current_mapping(); // Take a copy. + + QStringList labels; + labels << ""; + labels << "Controller bank"; + for (unsigned bus_idx = 0; bus_idx < num_buses; ++bus_idx) { + char buf[256]; + snprintf(buf, sizeof(buf), "Bus %d", bus_idx + 1); + labels << buf; + } + labels << ""; + 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); + + // Auto-resize every column but the last. + for (unsigned column_idx = 0; column_idx < num_buses + 3; ++column_idx) { + ui->treeWidget->resizeColumnToContents(column_idx); + } + + connect(ui->ok_cancel_buttons, &QDialogButtonBox::accepted, this, &MIDIMappingDialog::ok_clicked); + connect(ui->ok_cancel_buttons, &QDialogButtonBox::rejected, this, &MIDIMappingDialog::cancel_clicked); +} + +MIDIMappingDialog::~MIDIMappingDialog() +{ +} + +void MIDIMappingDialog::ok_clicked() +{ + unique_ptr new_mapping = construct_mapping_proto_from_ui(); + mapper->set_midi_mapping(*new_mapping); + accept(); +} + +void MIDIMappingDialog::cancel_clicked() +{ + reject(); +} + +namespace { + +template +T *get_mutable_bus_message(MIDIMappingProto *mapping_proto, unsigned bus_idx, int field_number) +{ + while (size_t(mapping_proto->bus_mapping_size()) <= bus_idx) { + mapping_proto->add_bus_mapping(); + } + + MIDIMappingBusProto *bus_mapping = mapping_proto->mutable_bus_mapping(bus_idx); + const FieldDescriptor *descriptor = bus_mapping->GetDescriptor()->FindFieldByNumber(field_number); + const Reflection *bus_reflection = bus_mapping->GetReflection(); + return static_cast(bus_reflection->MutableMessage(bus_mapping, descriptor)); +} + +} // namespace + +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) { + continue; + } + + MIDIControllerProto *controller_proto = + get_mutable_bus_message(mapping_proto.get(), is.bus_idx, is.field_number); + controller_proto->set_controller_number(val); + } + for (const InstantiatedSpinner &is : button_spinners) { + const int val = is.spinner->value(); + if (val == 0) { + continue; + } + + MIDIButtonProto *button_proto = + get_mutable_bus_message(mapping_proto.get(), is.bus_idx, is.field_number); + button_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(); + highest_bank_used = std::max(highest_bank_used, val); + if (val == 0) { + continue; + } + + const FieldDescriptor *descriptor = mapping_proto->GetDescriptor()->FindFieldByNumber(ic.field_number); + const Reflection *bus_reflection = mapping_proto->GetReflection(); + bus_reflection->SetInt32(mapping_proto.get(), descriptor, val - 1); + } + mapping_proto->set_num_controller_banks(highest_bank_used); + return mapping_proto; +} + +void MIDIMappingDialog::add_bank_selector(QTreeWidgetItem *item, const MIDIMappingProto &mapping_proto, int bank_field_number) +{ + if (bank_field_number == 0) { + return; + } + QComboBox *bank_selector = new QComboBox(this); + bank_selector->addItems(QStringList() << "" << "Bank 1" << "Bank 2" << "Bank 3" << "Bank 4" << "Bank 5"); + bank_selector->setAutoFillBackground(true); + bank_selector->setCurrentIndex(get_bank(mapping_proto, bank_field_number, -1) + 1); + + bank_combo_boxes.push_back(InstantiatedComboBox{ bank_selector, bank_field_number }); + + ui->treeWidget->setItemWidget(item, 1, bank_selector); +} + +void MIDIMappingDialog::add_controls(const string &heading, + MIDIMappingDialog::ControlType control_type, + const MIDIMappingProto &mapping_proto, + const vector &controls) +{ + QTreeWidgetItem *heading_item = new QTreeWidgetItem(ui->treeWidget); + heading_item->setText(0, QString::fromStdString(heading)); + heading_item->setFirstColumnSpanned(true); + heading_item->setExpanded(true); + for (const Control &control : controls) { + QTreeWidgetItem *item = new QTreeWidgetItem(heading_item); + heading_item->addChild(item); + add_bank_selector(item, mapping_proto, control.bank_field_number); + item->setText(0, QString::fromStdString(control.label + " ")); + + for (unsigned bus_idx = 0; bus_idx < num_buses; ++bus_idx) { + QSpinBox *spinner = new QSpinBox(this); + spinner->setRange(0, 127); + spinner->setAutoFillBackground(true); + spinner->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty). + ui->treeWidget->setItemWidget(item, bus_idx + 2, spinner); + + if (control_type == ControlType::CONTROLLER) { + spinner->setValue(get_controller_mapping(mapping_proto, bus_idx, control.field_number, 0)); + controller_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number }); + } else { + assert(control_type == ControlType::BUTTON); + spinner->setValue(get_button_mapping(mapping_proto, bus_idx, control.field_number, 0)); + button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number }); + } + } + } + ui->treeWidget->addTopLevelItem(heading_item); +} diff --git a/midi_mapping_dialog.h b/midi_mapping_dialog.h new file mode 100644 index 0000000..17cdc18 --- /dev/null +++ b/midi_mapping_dialog.h @@ -0,0 +1,72 @@ +#ifndef _MIDI_MAPPING_DIALOG_H +#define _MIDI_MAPPING_DIALOG_H + +#include +#include +#include +#include + +#include "audio_mixer.h" +#include "mixer.h" + +namespace Ui { +class MIDIMappingDialog; +} // namespace Ui + +class MIDIMapper; +class MIDIMappingProto; +class QComboBox; +class QSpinBox; +class QTreeWidgetItem; + +class MIDIMappingDialog : public QDialog +{ + Q_OBJECT + +public: + MIDIMappingDialog(MIDIMapper *mapper); + ~MIDIMappingDialog(); + + // For use in midi_mapping_dialog.cpp only. + struct Control { + std::string label; + int field_number; // In MIDIMappingBusProto. + int bank_field_number; // In MIDIMappingProto. + }; + +public slots: + void ok_clicked(); + void cancel_clicked(); + +private: + static constexpr unsigned num_buses = 8; + + void add_bank_selector(QTreeWidgetItem *item, const MIDIMappingProto &mapping_proto, int bank_field_number); + + enum class ControlType { CONTROLLER, BUTTON }; + void add_controls(const std::string &heading, ControlType control_type, + const MIDIMappingProto &mapping_proto, const std::vector &controls); + + std::unique_ptr construct_mapping_proto_from_ui(); + + + Ui::MIDIMappingDialog *ui; + MIDIMapper *mapper; + + // All controllers actually laid out on the grid (we need to store them + // so that we can read its values back into the new protobuf). + struct InstantiatedSpinner { + QSpinBox *spinner; + unsigned bus_idx; + int field_number; // In MIDIMappingBusProto. + }; + struct InstantiatedComboBox { + QComboBox *combo_box; + int field_number; // In MIDIMappingProto. + }; + std::vector controller_spinners; + std::vector button_spinners; + std::vector bank_combo_boxes; +}; + +#endif // !defined(_MIDI_MAPPING_DIALOG_H) diff --git a/ui_mainwindow.ui b/ui_mainwindow.ui index 67bf75c..8fb46ff 100644 --- a/ui_mainwindow.ui +++ b/ui_mainwindow.ui @@ -1335,6 +1335,7 @@ + @@ -1384,6 +1385,11 @@ Multichannel + + + Setup MIDI controller… + + diff --git a/ui_midi_mapping.ui b/ui_midi_mapping.ui new file mode 100644 index 0000000..9e4c6ec --- /dev/null +++ b/ui_midi_mapping.ui @@ -0,0 +1,98 @@ + + + MIDIMappingDialog + + + + 0 + 0 + 879 + 583 + + + + Input mapping + + + + + + + 1 + + + + + + + + Add or change a mapping by clicking in the cell, then moving the corresponding control on your MIDI device. + + + + + + + + + Guess &bus + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Save… + + + + + + + &Load… + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + -- 2.39.2