From beead04c23b42c815f448cd0126d87f9627bbfde Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Thu, 10 Jan 2019 19:28:46 +0100 Subject: [PATCH] Support pitch bend as a virtual MIDI controller. The primary reason this is important is that it's got higher range (up to 14-bit). Also, DJ controllers with a slider typically send pitch bend values, so it's useful just to get to use them. --- meson.build | 2 +- nageru/controller_spin_box.h | 47 ++++++++++++++++++++++++++++++++++ nageru/midi_mapper.cpp | 20 +++++++++++++-- nageru/midi_mapping_dialog.cpp | 11 ++++++-- shared/midi_device.cpp | 7 ++++- shared/midi_device.h | 5 ++++ 6 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 nageru/controller_spin_box.h diff --git a/meson.build b/meson.build index db1422c..e3e194c 100644 --- a/meson.build +++ b/meson.build @@ -169,7 +169,7 @@ nageru_link_with += protobuf_lib qt_files = qt5.preprocess( moc_headers: ['nageru/analyzer.h', 'nageru/clickable_label.h', 'nageru/compression_reduction_meter.h', 'nageru/correlation_meter.h', 'nageru/ellipsis_label.h', 'nageru/glwidget.h', 'nageru/input_mapping_dialog.h', 'nageru/lrameter.h', 'nageru/mainwindow.h', 'nageru/midi_mapping_dialog.h', - 'nageru/nonlinear_fader.h', 'nageru/vumeter.h'], + 'nageru/nonlinear_fader.h', 'nageru/vumeter.h', 'nageru/controller_spin_box.h'], ui_files: ['nageru/analyzer.ui', 'nageru/audio_expanded_view.ui', 'nageru/audio_miniview.ui', 'nageru/display.ui', 'nageru/input_mapping.ui', 'nageru/mainwindow.ui', 'nageru/midi_mapping.ui'], dependencies: qt5deps) diff --git a/nageru/controller_spin_box.h b/nageru/controller_spin_box.h new file mode 100644 index 0000000..8a98b47 --- /dev/null +++ b/nageru/controller_spin_box.h @@ -0,0 +1,47 @@ +#ifndef _CONTROLLER_SPIN_BOX_H +#define _CONTROLLER_SPIN_BOX_H 1 + +// ControllerSpinBox is like QSpinBox, except it has a second special value +// "PB" (in addition to the standard minimum value of -1, representing blank), +// representing the virtual pitch bend controller. + +#include +#include + +#include "shared/midi_device.h" + +class ControllerSpinBox : public QSpinBox { + Q_OBJECT + +public: + ControllerSpinBox(QWidget *parent) : QSpinBox(parent) {} + + int valueFromText(const QString &text) const override + { + if (text == "PB") { + return MIDIReceiver::PITCH_BEND_CONTROLLER; + } else { + return QSpinBox::valueFromText(text); + } + } + + QString textFromValue(int value) const override + { + if (value == MIDIReceiver::PITCH_BEND_CONTROLLER) { + return "PB"; + } else { + return QSpinBox::textFromValue(value); + } + } + + QValidator::State validate(QString &input, int &pos) const override + { + if (input == "PB") { + return QValidator::Acceptable; + } else { + return QSpinBox::validate(input, pos); + } + } +}; + +#endif // !defined(_CONTROLLER_SPIN_BOX_H) diff --git a/nageru/midi_mapper.cpp b/nageru/midi_mapper.cpp index dac78f1..fe62546 100644 --- a/nageru/midi_mapper.cpp +++ b/nageru/midi_mapper.cpp @@ -20,6 +20,7 @@ #include "audio_mixer.h" #include "nageru_midi_mapping.pb.h" +#include "shared/midi_device.h" #include "shared/midi_mapper_util.h" #include "shared/text_proto.h" @@ -29,8 +30,23 @@ using namespace std::placeholders; namespace { -double map_controller_to_float(int val) +double map_controller_to_float(int controller, int val) { + if (controller == MIDIReceiver::PITCH_BEND_CONTROLLER) { + // We supposedly go from -8192 to 8191 (inclusive), but there are + // controllers that only have 10-bit precision and do the upconversion + // to 14-bit wrong (just padding with zeros), making 8176 the highest + // attainable value. We solve this by making the effective range + // -8176..8176 (inclusive). + if (val <= -8176) { + return 0.0; + } else if (val >= 8176) { + return 1.0; + } else { + return 0.5 * (double(val) / 8176.0) + 0.5; + } + } + // Slightly hackish mapping so that we can represent exactly 0.0, 0.5 and 1.0. if (val <= 0) { return 0.0; @@ -96,7 +112,7 @@ ControllerReceiver *MIDIMapper::set_receiver(ControllerReceiver *new_receiver) void MIDIMapper::controller_received(int controller, int value_int) { - const float value = map_controller_to_float(value_int); + const float value = map_controller_to_float(controller, value_int); receiver->controller_changed(controller); diff --git a/nageru/midi_mapping_dialog.cpp b/nageru/midi_mapping_dialog.cpp index d099e68..7eec6d2 100644 --- a/nageru/midi_mapping_dialog.cpp +++ b/nageru/midi_mapping_dialog.cpp @@ -18,6 +18,7 @@ #include #include +#include "controller_spin_box.h" #include "midi_mapper.h" #include "nageru_midi_mapping.pb.h" #include "shared/post_to_main_thread.h" @@ -419,8 +420,14 @@ void MIDIMappingDialog::add_controls(const string &heading, 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(-1, 127); + QSpinBox *spinner; + if (control_type == ControlType::CONTROLLER) { + spinner = new ControllerSpinBox(this); + spinner->setRange(-1, 128); // 128 for pitch bend. + } else { + spinner = new QSpinBox(this); + 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. diff --git a/shared/midi_device.cpp b/shared/midi_device.cpp index 822beae..ac0fea2 100644 --- a/shared/midi_device.cpp +++ b/shared/midi_device.cpp @@ -160,6 +160,11 @@ void MIDIDevice::handle_event(snd_seq_t *seq, snd_seq_event_t *event) receiver->controller_received(event->data.control.param, event->data.control.value); break; } + case SND_SEQ_EVENT_PITCHBEND: { + // Note, -8192 to 8191 instead of 0 to 127. + receiver->controller_received(MIDIReceiver::PITCH_BEND_CONTROLLER, event->data.control.value); + break; + } case SND_SEQ_EVENT_NOTEON: { receiver->note_on_received(event->data.note.note); break; @@ -245,7 +250,7 @@ void MIDIDevice::update_lights_lock_held(const set &active_lights) } unsigned num_events = 0; - for (unsigned note_num = 1; note_num <= 127; ++note_num) { + for (unsigned note_num = 1; note_num <= 127; ++note_num) { // Note: Pitch bend is ignored. bool active = active_lights.count(note_num); if (current_light_status.count(note_num) && current_light_status[note_num] == active) { diff --git a/shared/midi_device.h b/shared/midi_device.h index 3c1e6b2..4984439 100644 --- a/shared/midi_device.h +++ b/shared/midi_device.h @@ -16,6 +16,11 @@ typedef struct _snd_seq snd_seq_t; class MIDIReceiver { public: + // Pitch bend events are received as a virtual controller with + // range -8192..8191 instead of 0..127 (but see the comment + // in map_controller_to_float() in midi_mapper.cpp). + static constexpr int PITCH_BEND_CONTROLLER = 128; + virtual ~MIDIReceiver() {} virtual void controller_received(int controller, int value) = 0; virtual void note_on_received(int note) = 0; -- 2.39.2