]> git.sesse.net Git - nageru/commitdiff
Support pitch bend as a virtual MIDI controller.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Thu, 10 Jan 2019 18:28:46 +0000 (19:28 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Thu, 10 Jan 2019 18:28:50 +0000 (19:28 +0100)
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
nageru/controller_spin_box.h [new file with mode: 0644]
nageru/midi_mapper.cpp
nageru/midi_mapping_dialog.cpp
shared/midi_device.cpp
shared/midi_device.h

index db1422c90ce698e159302f3654151b8c1431010d..e3e194c3c081df1073f3fcbb2703e189f64e04ed 100644 (file)
@@ -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 (file)
index 0000000..8a98b47
--- /dev/null
@@ -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 <QSpinBox>
+#include <QString>
+
+#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)
index dac78f1773e8e3d1f0e60082f9888c51ff9e1a91..fe62546569c4defca910c6a06f917244bcaf7b37 100644 (file)
@@ -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);
 
index d099e688a8c266314c84342d557edb763926b6c6..7eec6d285d123c43f82b3233a471ee845f872590 100644 (file)
@@ -18,6 +18,7 @@
 #include <limits>
 #include <string>
 
+#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.
index 822beaeadbd4d95b3a03fe294c3da069603953e5..ac0fea271d12d8837d3f53a3fe67d3eed22819c2 100644 (file)
@@ -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<unsigned> &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) {
index 3c1e6b2f4e63bf1cd4390329c037630d4748dc9f..498443931ab368ba931d2ead88192a969a27b116 100644 (file)
@@ -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;