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