OSC make strippable null on drop references
[ardour.git] / libs / surfaces / faderport8 / fp8_button.h
1 /* FaderPort8 Button Interface
2  *
3  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19
20 #ifndef _ardour_surfaces_fp8button_h_
21 #define _ardour_surfaces_fp8button_h_
22
23 #include <stdint.h>
24
25 #include "pbd/base_ui.h"
26 #include "pbd/signals.h"
27
28 #include "fp8_base.h"
29
30 namespace ArdourSurface { namespace FP_NAMESPACE {
31
32 /* virtual base-class and interface */
33 class FP8ButtonInterface
34 {
35 public:
36         FP8ButtonInterface () {}
37         virtual ~FP8ButtonInterface () {}
38
39         /* user API */
40         PBD::Signal0<void> pressed;
41         PBD::Signal0<void> released;
42
43         virtual bool is_pressed () const { return false; }
44         virtual bool is_active () const { return false; }
45
46         virtual void ignore_release () {}
47
48         /* internal API - called from midi thread,
49          * user pressed/released button the device
50          */
51         virtual bool midi_event (bool) = 0;
52
53         /* internal API - called from surface thread
54          * set Light on the button
55          */
56         virtual void set_active (bool a) = 0;
57         virtual void set_color (uint32_t rgba) {}
58         virtual void set_blinking (bool) {}
59
60         static bool force_change; // used during init
61 };
62
63 /* ****************************************************************************
64  * Implementations
65  */
66
67 class FP8DummyButton : public FP8ButtonInterface
68 {
69 public:
70         virtual void set_active (bool a) {}
71         virtual bool midi_event (bool) { return false; }
72 };
73
74
75 /* common implementation */
76 class FP8ButtonBase : public FP8ButtonInterface
77 {
78 public:
79         FP8ButtonBase (FP8Base& b)
80                 : _base (b)
81                 , _pressed (false)
82                 , _active (false)
83                 , _ignore_release (false)
84                 , _rgba (0)
85                 , _blinking (false)
86         { }
87
88         bool is_pressed () const { return _pressed; }
89         bool is_active () const { return _active; }
90
91         virtual bool midi_event (bool a)
92         {
93                 if (a == _pressed) {
94                         return false;
95                 }
96                 _pressed = a;
97                 if (a) {
98                         pressed (); /* EMIT SIGNAL */
99                 } else {
100                         if (_ignore_release) {
101                                 _ignore_release = false;
102                         } else {
103                                 released (); /* EMIT SIGNAL */
104                         }
105                 }
106                 return true;
107         }
108
109         virtual void ignore_release () {
110                 if (_pressed) {
111                         _ignore_release = true;
112                 }
113         }
114
115         bool blinking () const { return _blinking; }
116
117         void set_blinking (bool yes) {
118                 if (yes && !_blinking) {
119                         _blinking = true;
120                         _base.BlinkIt.connect_same_thread (_blink_connection, boost::bind (&FP8ButtonBase::blink, this, _1));
121                 } else if (!yes && _blinking) {
122                         _blink_connection.disconnect ();
123                         _blinking = false;
124                         blink (true);
125                 }
126         }
127
128 protected:
129         FP8Base&              _base;
130         bool                  _pressed;
131         bool                  _active;
132         bool                  _ignore_release;
133         uint32_t              _rgba;
134         virtual void blink (bool onoff) = 0;
135
136 private:
137         PBD::ScopedConnection _blink_connection;
138         bool _blinking;
139 };
140
141 /* A basic LED or RGB button, not shift sensitive */
142 class FP8Button : public FP8ButtonBase
143 {
144 public:
145         FP8Button (FP8Base& b, uint8_t id, bool color = false)
146                 : FP8ButtonBase (b)
147                 , _midi_id (id)
148                 , _has_color (color)
149         { }
150
151         virtual void set_active (bool a)
152         {
153                 if (_active == a && !force_change) {
154                         return;
155                 }
156                 _active = a;
157                 _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
158         }
159
160         void set_color (uint32_t rgba)
161         {
162                 if (!_has_color || _rgba == rgba) {
163                         return;
164                 }
165                 _rgba = rgba;
166                 _base.tx_midi3 (0x91, _midi_id, (_rgba >> 25) & 0x7f);
167                 _base.tx_midi3 (0x92, _midi_id, (_rgba >> 17) & 0x7f);
168                 _base.tx_midi3 (0x93, _midi_id, (_rgba >>  9) & 0x7f);
169         }
170
171 protected:
172         void blink (bool onoff)
173         {
174                 if (!_active) { return; }
175                 _base.tx_midi3 (0x90, _midi_id, onoff ? 0x7f : 0x00);
176         }
177
178         uint8_t  _midi_id; // MIDI-note
179         bool     _has_color;
180 };
181
182 /* footswitch and encoder-press buttons */
183 class FP8ReadOnlyButton : public FP8Button
184 {
185 public:
186         FP8ReadOnlyButton (FP8Base& b, uint8_t id, bool color = false)
187                 : FP8Button (b, id, color)
188         {}
189
190         void set_active (bool) { }
191 };
192
193 /* virtual button. used for shift toggle. */
194 class ShadowButton : public FP8ButtonBase
195 {
196 public:
197         ShadowButton (FP8Base& b)
198                 : FP8ButtonBase (b)
199         {}
200
201         PBD::Signal1<void, bool> ActiveChanged;
202         PBD::Signal0<void> ColourChanged;
203
204         uint32_t color () const { return _rgba; }
205
206         bool midi_event (bool a)
207         {
208                 assert (0);
209                 return false;
210         }
211
212         bool set_pressed (bool a)
213         {
214                 return FP8ButtonBase::midi_event (a);
215         }
216
217         void set_active (bool a)
218         {
219                 if (_active == a && !force_change) {
220                         return;
221                 }
222                 _active = a;
223                 ActiveChanged (a); /* EMIT SIGNAL */
224         }
225
226         void set_color (uint32_t rgba)
227         {
228                 if (_rgba == rgba) {
229                         return;
230                 }
231                 _rgba = rgba;
232                 ColourChanged ();
233         }
234
235 protected:
236         void blink (bool onoff) {
237                 if (!_active) { return; }
238                 ActiveChanged (onoff);
239         }
240 };
241
242 /* Wraps 2 buttons with the same physical MIDI ID */
243 class FP8DualButton : public FP8ButtonInterface
244 {
245 public:
246         FP8DualButton (FP8Base& b, uint8_t id, bool color = false)
247                 : _base (b)
248                 , _b0 (b)
249                 , _b1 (b)
250                 , _midi_id (id)
251                 , _has_color (color)
252                 , _rgba (0)
253                 , _shift (false)
254         {
255                 _b0.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, false, _1));
256                 _b1.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, true, _1));
257                 if (_has_color) {
258                         _b0.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, false));
259                         _b1.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, true));
260                 }
261         }
262
263         bool midi_event (bool a) {
264                 return (_shift ? _b1 : _b0).set_pressed (a);
265         }
266
267         void set_active (bool a) {
268                 /* This button is never directly used
269                  * by the libardour side API.
270                  */
271                 assert (0);
272         }
273
274         void active_changed (bool s, bool a) {
275                 if (s != _shift) {
276                         return;
277                 }
278                 _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
279         }
280
281         void colour_changed (bool s) {
282                 if (s != _shift || !_has_color) {
283                         return;
284                 }
285                 uint32_t rgba = (_shift ? _b1 : _b0).color ();
286                 if (rgba == _rgba) {
287                         return;
288                 }
289                 _rgba = rgba;
290                 _base.tx_midi3 (0x91, _midi_id, (rgba >> 25) & 0x7f);
291                 _base.tx_midi3 (0x92, _midi_id, (rgba >> 17) & 0x7f);
292                 _base.tx_midi3 (0x93, _midi_id, (rgba >>  9) & 0x7f);
293         }
294
295         FP8ButtonInterface* button () { return &_b0; }
296         FP8ButtonInterface* button_shift () { return &_b1; }
297
298 protected:
299         FP8Base&     _base;
300
301         virtual void connect_toggle () = 0;
302
303         void shift_changed (bool shift) {
304                 if (_shift == shift) {
305                         return;
306                 }
307                 (_shift ? _b1 : _b0).set_pressed (false);
308                 _shift = shift;
309                 active_changed (_shift, (_shift ? _b1 : _b0).is_active());
310                 colour_changed (_shift);
311         }
312
313 private:
314         ShadowButton _b0;
315         ShadowButton _b1;
316         uint8_t      _midi_id; // MIDI-note
317         bool         _has_color;
318         uint32_t     _rgba;
319         bool         _shift;
320         PBD::ScopedConnectionList _button_connections;
321 };
322
323 class FP8ShiftSensitiveButton : public FP8DualButton
324 {
325 public:
326         FP8ShiftSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
327                 :FP8DualButton (b, id, color)
328         {
329                 connect_toggle ();
330         }
331
332 protected:
333         void connect_toggle ()
334         {
335                 _base.ShiftButtonChange.connect_same_thread (_shift_connection, boost::bind (&FP8ShiftSensitiveButton::shift_changed, this, _1));
336         }
337
338 private:
339         PBD::ScopedConnection _shift_connection;
340 };
341
342 class FP8ARMSensitiveButton : public FP8DualButton
343 {
344 public:
345         FP8ARMSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
346                 :FP8DualButton (b, id, color)
347         {
348                 connect_toggle ();
349         }
350
351 protected:
352         void connect_toggle ()
353         {
354                 _base.ARMButtonChange.connect_same_thread (_arm_connection, boost::bind (&FP8ARMSensitiveButton::shift_changed, this, _1));
355         }
356
357 private:
358         PBD::ScopedConnection _arm_connection;
359 };
360
361
362 // short press: activate in press, deactivate on release,
363 // long press + hold, activate on press, de-activate directly on release
364 // e.g. mute/solo  press + hold => changed()
365 class FP8MomentaryButton : public FP8ButtonBase
366 {
367 public:
368         FP8MomentaryButton (FP8Base& b, uint8_t id)
369                 : FP8ButtonBase (b)
370                 , _midi_id (id)
371         {}
372
373         ~FP8MomentaryButton () {
374                 _hold_connection.disconnect ();
375         }
376
377         PBD::Signal1<void, bool> StateChange;
378
379         void set_active (bool a)
380         {
381                 if (_active == a && !force_change) {
382                         return;
383                 }
384                 _active = a;
385                 _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
386         }
387
388         void reset ()
389         {
390                 _was_active_on_press = false;
391                 _hold_connection.disconnect ();
392         }
393
394         void ignore_release () { }
395
396         bool midi_event (bool a)
397         {
398                 if (a == _pressed) {
399                         return false;
400                 }
401
402                 _pressed = a;
403
404                 if (a) {
405                         _was_active_on_press = _active;
406                 }
407
408                 if (a && !_active) {
409                         _momentaty = false;
410                         StateChange (true); /* EMIT SIGNAL */
411                         Glib::RefPtr<Glib::TimeoutSource> hold_timer =
412                                 Glib::TimeoutSource::create (500);
413                         hold_timer->attach (fp8_loop()->get_context());
414                         _hold_connection = hold_timer->connect (sigc::mem_fun (*this, &FP8MomentaryButton::hold_timeout));
415                 } else if (!a && _was_active_on_press) {
416                         _hold_connection.disconnect ();
417                         _momentaty = false;
418                         StateChange (false); /* EMIT SIGNAL */
419                 } else if (!a && _momentaty) {
420                         _hold_connection.disconnect ();
421                         _momentaty = false;
422                         StateChange (false); /* EMIT SIGNAL */
423                 }
424                 return true;
425         }
426
427 protected:
428         void blink (bool onoff)
429         {
430                 if (!blinking ()) {
431                         _base.tx_midi3 (0x90, _midi_id, _active ? 0x7f : 0x00);
432                         return;
433                 }
434                 _base.tx_midi3 (0x90, _midi_id, onoff ? 0x7f : 0x00);
435         }
436
437         uint8_t  _midi_id; // MIDI-note
438         bool     _momentaty;
439         bool     _was_active_on_press;
440
441 private:
442         bool hold_timeout ()
443         {
444                 _momentaty = true;
445                 return false;
446         }
447         sigc::connection _hold_connection;
448 };
449
450 /* an auto-repeat button.
451  * press + hold emits continuous "press" events.
452  */
453 class FP8RepeatButton : public FP8Button
454 {
455 public:
456         FP8RepeatButton (FP8Base& b, uint8_t id, bool color = false)
457                 : FP8Button (b, id, color)
458                 , _skip (0)
459         {}
460
461         ~FP8RepeatButton ()
462         {
463                 stop_repeat ();
464         }
465
466         bool midi_event (bool a)
467         {
468                 bool rv = FP8Button::midi_event (a);
469                 if (rv && a) {
470                         start_repeat ();
471                 }
472                 return rv;
473         }
474
475         void stop_repeat ()
476         {
477                 _press_timeout_connection.disconnect ();
478         }
479
480 private:
481         void start_repeat ()
482         {
483                 stop_repeat ();
484                 _skip = 5;
485                 Glib::RefPtr<Glib::TimeoutSource> press_timer =
486                         Glib::TimeoutSource::create (100);
487                 press_timer->attach (fp8_loop()->get_context());
488                 _press_timeout_connection = press_timer->connect (sigc::mem_fun (*this, &FP8RepeatButton::repeat_press));
489         }
490
491         bool repeat_press ()
492         {
493                 if (!_pressed) {
494                         return false;
495                 }
496                 if (_skip > 0) {
497                         --_skip;
498                         return true;
499                 }
500                 pressed ();
501                 return true;
502         }
503
504         int _skip;
505         sigc::connection _press_timeout_connection;
506 };
507
508 } } /* namespace */
509 #endif /* _ardour_surfaces_fp8button_h_ */