From 8725ed5bd1affa2e43023e9ab99e55a67866ba31 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 21 Jun 2018 22:28:11 -0400 Subject: [PATCH] add Ctl_Dial to get better behaviour when binding a MIDI controller KNOB/DIAL to a toggled controllable. No intent to change Ctl_Momentary or Ctl_Toggle behaviour, plus I tried to document what they are intended to support --- .../generic_midi_control_protocol.cc | 3 + .../surfaces/generic_midi/midicontrollable.cc | 63 +++++++++++-------- libs/surfaces/generic_midi/midicontrollable.h | 2 + 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc index 560bd43e57..a620998a1c 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc @@ -836,6 +836,9 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) } else if ((prop = node.property (X_("ctl-toggle"))) !=0) { ctltype = MIDIControllable::Ctl_Toggle; ev = MIDI::controller; + } else if ((prop = node.property (X_("ctl-dial"))) !=0) { + ctltype = MIDIControllable::Ctl_Dial; + ev = MIDI::controller; } else if ((prop = node.property (X_("note"))) != 0) { ev = MIDI::on; } else if ((prop = node.property (X_("pgm"))) != 0) { diff --git a/libs/surfaces/generic_midi/midicontrollable.cc b/libs/surfaces/generic_midi/midicontrollable.cc index 13b47a4d7c..cbf525bd1b 100644 --- a/libs/surfaces/generic_midi/midicontrollable.cc +++ b/libs/surfaces/generic_midi/midicontrollable.cc @@ -56,6 +56,7 @@ MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, MIDI::Parser& _encoder = No_enc; setting = false; last_value = 0; // got a better idea ? + last_incoming = 256; // any out of band value last_controllable_value = 0.0f; control_type = none; control_rpn = -1; @@ -135,6 +136,8 @@ MIDIControllable::set_controllable (Controllable* c) last_controllable_value = 0.0f; // is there a better value? } + last_incoming = 256; + if (controllable) { controllable->Destroyed.connect (controllable_death_connection, MISSING_INVALIDATOR, boost::bind (&MIDIControllable::drop_controllable, this, _1), @@ -372,32 +375,40 @@ MIDIControllable::midi_sense_controller (Parser &, EventTwoBytes *msg) } } else { - /* toggle control: make the toggle flip only if the - * incoming control value exceeds 0.5 (0x40), so that - * the typical button which sends "CC N=0x7f" on press - * and "CC N=0x0" on release can be used to drive - * toggles on press. - * - * No other arrangement really makes sense for a toggle - * controllable. Acting on the press+release makes the - * action momentary, which is almost never - * desirable. If the physical button only sends a - * message on press (or release), then it will be - * expected to send a controller value >= 0.5 - * (0x40). It is hard to imagine why anyone would make - * a MIDI controller button that sent 0x0 when pressed. - */ - if (msg->value >= 0x40) { - controllable->set_value (controllable->get_value() >= 0.5 ? 0.0 : 1.0, Controllable::UseGroup); - DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("Midi CC %1 value 1 %2\n", (int) msg->controller_number, current_uri())); - } else { - switch (get_ctltype()) { - case Ctl_Momentary: - break; - case Ctl_Toggle: - controllable->set_value (0.0, Controllable::NoGroup); - DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("Midi CC %1 value 0 %2\n", (int) msg->controller_number, current_uri())); - break; + switch (get_ctltype()) { + case Ctl_Dial: + /* toggle value whenever direction of knob motion changes */ + if (last_incoming > 127) { + /* relax ... first incoming message */ + } else { + if (msg->value > last_incoming) { + controllable->set_value (1.0, Controllable::UseGroup); + } else { + controllable->set_value (0.0, Controllable::UseGroup); + } + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("dial Midi CC %1 value 1 %2\n", (int) msg->controller_number, current_uri())); + } + last_incoming = msg->value; + break; + case Ctl_Momentary: + /* toggle it if over 64, otherwise leave it alone. This behaviour that works with buttons which send a value > 64 each + * time they are pressed. + */ + if (msg->value >= 0x40) { + controllable->set_value (controllable->get_value() >= 0.5 ? 0.0 : 1.0, Controllable::UseGroup); + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("toggle Midi CC %1 value 1 %2\n", (int) msg->controller_number, current_uri())); + } + break; + case Ctl_Toggle: + /* toggle if value is over 64, otherwise turn it off. This is behaviour designed for buttons which send a value > 64 when pressed, + maintain state (i.e. they know they were pressed) and then send zero the next time. + */ + if (msg->value >= 0x40) { + controllable->set_value (controllable->get_value() >= 0.5 ? 0.0 : 1.0, Controllable::UseGroup); + } else { + controllable->set_value (0.0, Controllable::NoGroup); + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("Midi CC %1 value 0 %2\n", (int) msg->controller_number, current_uri())); + break; } } } diff --git a/libs/surfaces/generic_midi/midicontrollable.h b/libs/surfaces/generic_midi/midicontrollable.h index b8c69e036e..b795067a61 100644 --- a/libs/surfaces/generic_midi/midicontrollable.h +++ b/libs/surfaces/generic_midi/midicontrollable.h @@ -58,6 +58,7 @@ class MIDIControllable : public PBD::Stateful enum CtlType { Ctl_Momentary, Ctl_Toggle, + Ctl_Dial, }; enum Encoder { @@ -119,6 +120,7 @@ class MIDIControllable : public PBD::Stateful MIDI::Parser& _parser; bool setting; int last_value; + int last_incoming; float last_controllable_value; bool _momentary; bool _is_gain_controller;