summaryrefslogtreecommitdiff
path: root/libs/surfaces/faderport8/fp8_button.h
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2017-04-05 11:04:16 +0200
committerRobin Gareus <robin@gareus.org>2017-04-13 21:21:59 +0200
commitd43a23fe28c6f54ed8ca6cde6497661c6cb73ce0 (patch)
treed89166f438d4a3d42034d6b106506cb892d2f068 /libs/surfaces/faderport8/fp8_button.h
parentd64ca9be08331756e936018ea4d06404faa2ca90 (diff)
Faderport8 control surface support
Diffstat (limited to 'libs/surfaces/faderport8/fp8_button.h')
-rw-r--r--libs/surfaces/faderport8/fp8_button.h490
1 files changed, 490 insertions, 0 deletions
diff --git a/libs/surfaces/faderport8/fp8_button.h b/libs/surfaces/faderport8/fp8_button.h
new file mode 100644
index 0000000000..a817dd8e79
--- /dev/null
+++ b/libs/surfaces/faderport8/fp8_button.h
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_fp8button_h_
+#define _ardour_surfaces_fp8button_h_
+
+#include <stdint.h>
+
+#include "pbd/base_ui.h"
+#include "pbd/signals.h"
+
+#include "fp8_base.h"
+
+namespace ArdourSurface {
+
+class FP8ButtonInterface
+{
+public:
+ FP8ButtonInterface () {}
+ virtual ~FP8ButtonInterface () {}
+
+ /* user API */
+ PBD::Signal0<void> pressed;
+ PBD::Signal0<void> released;
+
+ virtual bool is_pressed () const { return false; }
+ virtual bool is_active () const { return false; }
+
+ virtual void ignore_release () {}
+
+ /* internal API - called from midi thread,
+ * user pressed/released button the device
+ */
+ virtual bool midi_event (bool) = 0;
+
+ /* internal API - called from surface thread
+ * set Light on the button
+ */
+ virtual void set_active (bool a) = 0;
+ virtual void set_color (uint32_t rgba) {}
+ virtual void set_blinking (bool) {}
+
+ static bool force_change; // used during init
+};
+
+class FP8DummyButton : public FP8ButtonInterface
+{
+public:
+ virtual void set_active (bool a) {}
+ virtual bool midi_event (bool) { return false; }
+};
+
+
+class FP8ButtonBase : public FP8ButtonInterface
+{
+public:
+ FP8ButtonBase (FP8Base& b)
+ : _base (b)
+ , _pressed (false)
+ , _active (false)
+ , _ignore_release (false)
+ , _rgba (0)
+ , _blinking (false)
+ { }
+
+ bool is_pressed () const { return _pressed; }
+ bool is_active () const { return _active; }
+
+ virtual bool midi_event (bool a)
+ {
+ if (a == _pressed) {
+ return false;
+ }
+ _pressed = a;
+ if (a) {
+ pressed (); /* EMIT SIGNAL */
+ } else {
+ if (_ignore_release) {
+ _ignore_release = false;
+ } else {
+ released (); /* EMIT SIGNAL */
+ }
+ }
+ return true;
+ }
+
+ void ignore_release () {
+ if (_pressed) {
+ _ignore_release = true;
+ }
+ }
+
+ void set_blinking (bool yes) {
+ if (yes && !_blinking) {
+ _blinking = true;
+ _base.BlinkIt.connect_same_thread (_blink_connection, boost::bind (&FP8ButtonBase::blink, this, _1));
+ } else if (!yes && _blinking) {
+ _blink_connection.disconnect ();
+ blink (true);
+ _blinking = false;
+ }
+ }
+
+protected:
+ FP8Base& _base;
+ bool _pressed;
+ bool _active;
+ bool _ignore_release;
+ uint32_t _rgba;
+ virtual void blink (bool onoff) = 0;
+
+private:
+ PBD::ScopedConnection _blink_connection;
+ bool _blinking;
+};
+
+class FP8Button : public FP8ButtonBase
+{
+public:
+ FP8Button (FP8Base& b, uint8_t id, bool color = false)
+ : FP8ButtonBase (b)
+ , _midi_id (id)
+ , _has_color (color)
+ { }
+
+ virtual void set_active (bool a)
+ {
+ if (_active == a && !force_change) {
+ return;
+ }
+ _active = a;
+ _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
+ }
+
+ void set_color (uint32_t rgba)
+ {
+ if (!_has_color || _rgba == rgba) {
+ return;
+ }
+ _rgba = rgba;
+ _base.tx_midi3 (0x91, _midi_id, (_rgba >> 25) & 0x7f);
+ _base.tx_midi3 (0x92, _midi_id, (_rgba >> 17) & 0x7f);
+ _base.tx_midi3 (0x93, _midi_id, (_rgba >> 9) & 0x7f);
+ }
+
+protected:
+ void blink (bool onoff)
+ {
+ if (!_active) { return; }
+ _base.tx_midi3 (0x90, _midi_id, onoff ? 0x7f : 0x00);
+ }
+
+ uint8_t _midi_id; // MIDI-note
+ bool _has_color;
+};
+
+class FP8ReadOnlyButton : public FP8Button
+{
+public:
+ FP8ReadOnlyButton (FP8Base& b, uint8_t id, bool color = false)
+ : FP8Button (b, id, color)
+ {}
+
+ void set_active (bool) { }
+};
+
+/* virtual button. used for shift toggle. */
+class ShadowButton : public FP8ButtonBase
+{
+public:
+ ShadowButton (FP8Base& b)
+ : FP8ButtonBase (b)
+ {}
+
+ PBD::Signal1<void, bool> ActiveChanged;
+ PBD::Signal0<void> ColourChanged;
+
+ uint32_t color () const { return _rgba; }
+
+ bool midi_event (bool a)
+ {
+ assert (0);
+ return false;
+ }
+
+ bool set_pressed (bool a)
+ {
+ return FP8ButtonBase::midi_event (a);
+ }
+
+ void set_active (bool a)
+ {
+ if (_active == a && !force_change) {
+ return;
+ }
+ _active = a;
+ ActiveChanged (a); /* EMIT SIGNAL */
+ }
+
+ void set_color (uint32_t rgba)
+ {
+ if (_rgba == rgba) {
+ return;
+ }
+ _rgba = rgba;
+ ColourChanged ();
+ }
+
+protected:
+ void blink (bool onoff) {
+ if (!_active) { return; }
+ ActiveChanged (onoff);
+ }
+};
+
+/* Wraps 2 buttons with the same physical MIDI ID */
+class FP8DualButton : public FP8ButtonInterface
+{
+public:
+ FP8DualButton (FP8Base& b, uint8_t id, bool color = false)
+ : _base (b)
+ , _b0 (b)
+ , _b1 (b)
+ , _midi_id (id)
+ , _has_color (color)
+ , _rgba (0)
+ , _shift (false)
+ {
+ _b0.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, false, _1));
+ _b1.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, true, _1));
+ if (_has_color) {
+ _b0.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, false));
+ _b1.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, true));
+ }
+ }
+
+ bool midi_event (bool a) {
+ return (_shift ? _b1 : _b0).set_pressed (a);
+ }
+
+ void set_active (bool a) {
+ /* This button is never directly used
+ * by the libardour side API.
+ */
+ assert (0);
+ }
+
+ void active_changed (bool s, bool a) {
+ if (s != _shift) {
+ return;
+ }
+ _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
+ }
+
+ void colour_changed (bool s) {
+ if (s != _shift || !_has_color) {
+ return;
+ }
+ uint32_t rgba = (_shift ? _b1 : _b0).color ();
+ if (rgba == _rgba) {
+ return;
+ }
+ _rgba = rgba;
+ _base.tx_midi3 (0x91, _midi_id, (rgba >> 25) & 0x7f);
+ _base.tx_midi3 (0x92, _midi_id, (rgba >> 17) & 0x7f);
+ _base.tx_midi3 (0x93, _midi_id, (rgba >> 9) & 0x7f);
+ }
+
+ FP8ButtonInterface* button () { return &_b0; }
+ FP8ButtonInterface* button_shift () { return &_b1; }
+
+protected:
+ FP8Base& _base;
+
+ virtual void connect_toggle () = 0;
+
+ void shift_changed (bool shift) {
+ if (_shift == shift) {
+ return;
+ }
+ (_shift ? _b1 : _b0).set_pressed (false);
+ _shift = shift;
+ active_changed (_shift, (_shift ? _b1 : _b0).is_active());
+ colour_changed (_shift);
+ }
+
+private:
+ ShadowButton _b0;
+ ShadowButton _b1;
+ uint8_t _midi_id; // MIDI-note
+ bool _has_color;
+ uint32_t _rgba;
+ bool _shift;
+ PBD::ScopedConnectionList _button_connections;
+};
+
+class FP8ShiftSensitiveButton : public FP8DualButton
+{
+public:
+ FP8ShiftSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
+ :FP8DualButton (b, id, color)
+ {
+ connect_toggle ();
+ }
+
+protected:
+ void connect_toggle ()
+ {
+ _base.ShiftButtonChange.connect_same_thread (_shift_connection, boost::bind (&FP8ShiftSensitiveButton::shift_changed, this, _1));
+ }
+
+private:
+ PBD::ScopedConnection _shift_connection;
+};
+
+class FP8ARMSensitiveButton : public FP8DualButton
+{
+public:
+ FP8ARMSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
+ :FP8DualButton (b, id, color)
+ {
+ connect_toggle ();
+ }
+
+protected:
+ void connect_toggle ()
+ {
+ _base.ARMButtonChange.connect_same_thread (_arm_connection, boost::bind (&FP8ARMSensitiveButton::shift_changed, this, _1));
+ }
+
+private:
+ PBD::ScopedConnection _arm_connection;
+};
+
+
+// short press: activate in press, deactivate on release,
+// long press + hold, activate on press, de-activate directly on release
+// e.g. mute/solo press + hold => changed()
+class FP8MomentaryButton : public FP8ButtonInterface
+{
+public:
+ FP8MomentaryButton (FP8Base& b, uint8_t id)
+ : _base (b)
+ , _midi_id (id)
+ , _pressed (false)
+ , _active (false)
+ {}
+
+ ~FP8MomentaryButton () {
+ _hold_connection.disconnect ();
+ }
+
+ PBD::Signal1<void, bool> StateChange;
+
+ void set_active (bool a)
+ {
+ if (_active == a && !force_change) {
+ return;
+ }
+ _active = a;
+ _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
+ }
+
+ void reset ()
+ {
+ _was_active_on_press = false;
+ _hold_connection.disconnect ();
+ }
+
+ bool midi_event (bool a)
+ {
+ if (a == _pressed) {
+ return false;
+ }
+
+ _pressed = a;
+
+ if (a) {
+ _was_active_on_press = _active;
+ }
+
+ if (a && !_active) {
+ _momentaty = false;
+ StateChange (true); /* EMIT SIGNAL */
+ Glib::RefPtr<Glib::TimeoutSource> hold_timer =
+ Glib::TimeoutSource::create (500);
+ hold_timer->attach (fp8_loop()->get_context());
+ _hold_connection = hold_timer->connect (sigc::mem_fun (*this, &FP8MomentaryButton::hold_timeout));
+ } else if (!a && _was_active_on_press) {
+ _hold_connection.disconnect ();
+ _momentaty = false;
+ StateChange (false); /* EMIT SIGNAL */
+ } else if (!a && _momentaty) {
+ _hold_connection.disconnect ();
+ _momentaty = false;
+ StateChange (false); /* EMIT SIGNAL */
+ }
+ return true;
+ }
+
+protected:
+ FP8Base& _base;
+ uint8_t _midi_id; // MIDI-note
+ bool _pressed;
+ bool _momentaty;
+ bool _was_active_on_press;
+ bool _active;
+
+private:
+ bool hold_timeout ()
+ {
+ _momentaty = true;
+ return false;
+ }
+ sigc::connection _hold_connection;
+};
+
+class FP8RepeatButton : public FP8Button
+{
+public:
+ FP8RepeatButton (FP8Base& b, uint8_t id, bool color = false)
+ : FP8Button (b, id, color)
+ , _skip (0)
+ {}
+
+ ~FP8RepeatButton ()
+ {
+ stop_repeat ();
+ }
+
+ bool midi_event (bool a)
+ {
+ bool rv = FP8Button::midi_event (a);
+ if (rv && a) {
+ start_repeat ();
+ }
+ return rv;
+ }
+
+ void stop_repeat ()
+ {
+ _press_timeout_connection.disconnect ();
+ }
+
+private:
+ void start_repeat ()
+ {
+ stop_repeat ();
+ _skip = 5;
+ Glib::RefPtr<Glib::TimeoutSource> press_timer =
+ Glib::TimeoutSource::create (100);
+ press_timer->attach (fp8_loop()->get_context());
+ _press_timeout_connection = press_timer->connect (sigc::mem_fun (*this, &FP8RepeatButton::repeat_press));
+ }
+
+ bool repeat_press ()
+ {
+ if (!_pressed) {
+ return false;
+ }
+ if (_skip > 0) {
+ --_skip;
+ return true;
+ }
+ pressed ();
+ return true;
+ }
+
+ int _skip;
+ sigc::connection _press_timeout_connection;
+};
+
+
+} /* namespace */
+#endif /* _ardour_surfaces_fp8button_h_ */