FP8: unhardcode IDs and strip-count
[ardour.git] / libs / surfaces / faderport8 / fp8_strip.cc
1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (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
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #include "ardour/automation_control.h"
20 #include "ardour/gain_control.h"
21 #include "ardour/meter.h"
22 #include "ardour/plugin_insert.h"
23 #include "ardour/session.h"
24 #include "ardour/stripable.h"
25 #include "ardour/track.h"
26 #include "ardour/value_as_string.h"
27
28 #include "control_protocol/control_protocol.h"
29
30 #include "fp8_strip.h"
31
32 using namespace ARDOUR;
33 using namespace ArdourSurface;
34 using namespace ArdourSurface::FP8Types;
35
36 uint8_t /* static */
37 FP8Strip::midi_ctrl_id (CtrlElement type, uint8_t id)
38 {
39         assert (id < N_STRIPS);
40         switch (type) {
41                 case BtnSolo:
42                         return 0x08 + id;
43                 case BtnMute:
44                         return 0x10 + id;
45                 case BtnSelect:
46                         return 0x18 + id;
47                 case Fader:
48                         return 0xe0 + id;
49                 case Meter:
50                         return 0xd0 + id;
51                 case Redux:
52                         return 0xd8 + id;
53                 case BarVal:
54                         return 0x30 + id;
55                 case BarMode:
56                         return 0x38 + id;
57         }
58         assert (0);
59         return 0;
60 }
61
62 FP8Strip::FP8Strip (FP8Base& b, uint8_t id)
63         : _base (b)
64         , _id (id)
65         , _solo   (b, midi_ctrl_id (BtnSolo, id))
66         , _mute   (b, midi_ctrl_id (BtnMute, id))
67         , _selrec (b, midi_ctrl_id (BtnSelect, id), true)
68         , _touching (false)
69         , _strip_mode (0)
70         , _bar_mode (0)
71         , _displaymode (Stripables)
72 {
73         assert (id < N_STRIPS);
74
75         _last_fader = 65535;
76         _last_meter = _last_redux = _last_barpos = 0xff;
77
78         _mute.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_mute, this, _1));
79         _solo.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_solo, this, _1));
80         select_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_select, this));
81         recarm_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_recarm, this));
82         b.Periodic.connect_same_thread (_base_connection, boost::bind (&FP8Strip::periodic, this));
83 }
84
85 FP8Strip::~FP8Strip ()
86 {
87         drop_automation_controls ();
88         _base_connection.disconnect ();
89         _button_connections.drop_connections ();
90 }
91
92 void
93 FP8Strip::drop_automation_controls ()
94 {
95         _fader_connection.disconnect ();
96         _mute_connection.disconnect ();
97         _solo_connection.disconnect ();
98         _rec_connection.disconnect ();
99         _pan_connection.disconnect ();
100         _x_select_connection.disconnect ();
101
102         _fader_ctrl.reset ();
103         _mute_ctrl.reset ();
104         _solo_ctrl.reset ();
105         _rec_ctrl.reset ();
106         _pan_ctrl.reset ();
107         _x_select_ctrl.reset ();
108         _peak_meter.reset ();
109         _redux_ctrl.reset ();
110         _select_plugin_functor.clear ();
111 }
112
113 void
114 FP8Strip::initialize ()
115 {
116         /* this is called once midi transmission is possible,
117          * ie from FaderPort8::connected()
118          */
119         _solo.set_active (false);
120         _solo.set_blinking (false);
121         _mute.set_active (false);
122
123         /* reset momentary button state */
124         _mute.reset ();
125         _solo.reset ();
126
127         drop_automation_controls ();
128
129         select_button ().set_color (0xffffffff);
130         select_button ().set_active (false);
131         select_button ().set_blinking (false);
132
133         recarm_button ().set_active (false);
134         recarm_button ().set_color (0xffffffff);
135
136         set_strip_mode (0, true);
137
138         // force unset txt
139         _last_line[0].clear ();
140         _last_line[1].clear ();
141         _last_line[2].clear ();
142         _last_line[3].clear ();
143         _base.tx_sysex (4, 0x12, _id, 0x00, 0x00);
144         _base.tx_sysex (4, 0x12, _id, 0x01, 0x00);
145         _base.tx_sysex (4, 0x12, _id, 0x02, 0x00);
146         _base.tx_sysex (4, 0x12, _id, 0x03, 0x00);
147
148         set_bar_mode (4); // off
149
150         _base.tx_midi2 (midi_ctrl_id (Meter, _id), 0); // reset meter
151         _base.tx_midi2 (midi_ctrl_id (Redux, _id), 0); // reset redux
152
153         _base.tx_midi3 (midi_ctrl_id (Fader, _id), 0, 0); // fader
154
155         /* clear cached values */
156         _last_fader = 65535;
157         _last_meter = _last_redux = _last_barpos = 0xff;
158 }
159
160
161 #define GENERATE_SET_CTRL_FUNCTION(NAME)                                            \
162 void                                                                                \
163 FP8Strip::set_ ##NAME##_controllable (boost::shared_ptr<AutomationControl> ac)      \
164 {                                                                                   \
165   if (_##NAME##_ctrl == ac) {                                                       \
166     return;                                                                         \
167   }                                                                                 \
168   _##NAME##_connection.disconnect();                                                \
169   _##NAME##_ctrl = ac;                                                              \
170                                                                                     \
171   if (ac) {                                                                         \
172     ac->Changed.connect (_##NAME##_connection, MISSING_INVALIDATOR,                 \
173       boost::bind (&FP8Strip::notify_##NAME##_changed, this), fp8_context());       \
174   }                                                                                 \
175   notify_##NAME##_changed ();                                                       \
176 }
177
178
179 GENERATE_SET_CTRL_FUNCTION (fader)
180 GENERATE_SET_CTRL_FUNCTION (mute)
181 GENERATE_SET_CTRL_FUNCTION (solo)
182 GENERATE_SET_CTRL_FUNCTION (rec)
183 GENERATE_SET_CTRL_FUNCTION (pan)
184 GENERATE_SET_CTRL_FUNCTION (x_select)
185
186 #undef GENERATE_SET_CTRL_FUNCTION
187
188 // special case -- w/_select_plugin_functor
189 void
190 FP8Strip::set_select_controllable (boost::shared_ptr<AutomationControl> ac)
191 {
192         _select_plugin_functor.clear ();
193         set_x_select_controllable (ac);
194 }
195
196 void
197 FP8Strip::set_select_cb (boost::function<void ()>& functor)
198 {
199         set_select_controllable (boost::shared_ptr<AutomationControl>());
200         _select_plugin_functor = functor;
201 }
202
203 void
204 FP8Strip::unset_controllables (int which)
205 {
206         _peak_meter = boost::shared_ptr<ARDOUR::PeakMeter>();
207         _redux_ctrl = boost::shared_ptr<ARDOUR::ReadOnlyControl>();
208         _stripable_name.clear ();
209
210         if (which & CTRL_FADER) {
211                 set_fader_controllable (boost::shared_ptr<AutomationControl>());
212         }
213         if (which & CTRL_MUTE) {
214                 set_mute_controllable (boost::shared_ptr<AutomationControl>());
215         }
216         if (which & CTRL_SOLO) {
217                 set_solo_controllable (boost::shared_ptr<AutomationControl>());
218         }
219         if (which & CTRL_REC) {
220                 set_rec_controllable (boost::shared_ptr<AutomationControl>());
221         }
222         if (which & CTRL_PAN) {
223                 set_pan_controllable (boost::shared_ptr<AutomationControl>());
224         }
225         if (which & CTRL_SELECT) {
226                 set_select_controllable (boost::shared_ptr<AutomationControl>());
227                 select_button ().set_color (0xffffffff);
228                 select_button ().set_active (false);
229                 select_button ().set_blinking (false);
230         }
231         if (which & CTRL_TEXT0) {
232                 set_text_line (0, "");
233         }
234         if (which & CTRL_TEXT1) {
235                 set_text_line (1, "");
236         }
237         if (which & CTRL_TEXT2) {
238                 set_text_line (2, "");
239         }
240         if (which & CTRL_TEXT3) {
241                 set_text_line (3, "");
242         }
243         set_bar_mode (4); // Off
244 }
245
246 void
247 FP8Strip::set_strip_name ()
248 {
249         size_t lb = _base.show_meters () ? 6 : 9;
250         set_text_line (0, _stripable_name.substr (0, lb));
251         set_text_line (1, _stripable_name.length() > lb ? _stripable_name.substr (lb) : "");
252 }
253
254 void
255 FP8Strip::set_stripable (boost::shared_ptr<Stripable> s, bool panmode)
256 {
257         assert (s);
258
259         if (_base.show_meters () && _base.show_panner ()) {
260                 set_strip_mode (5, true);
261         } else if (_base.show_meters ()) {
262                 set_strip_mode (4, true);
263         } else {
264                 set_strip_mode (0, true);
265         }
266         if (!_base.show_panner ()) {
267                 set_bar_mode (4, true); // Off
268         }
269
270         if (panmode) {
271                 set_fader_controllable (s->pan_azimuth_control ());
272         } else {
273                 set_fader_controllable (s->gain_control ());
274         }
275         set_pan_controllable (s->pan_azimuth_control ());
276
277         if (s->is_monitor ()) {
278                 set_mute_controllable (boost::shared_ptr<AutomationControl>());
279         } else {
280                 set_mute_controllable (s->mute_control ());
281         }
282         set_solo_controllable (s->solo_control ());
283
284         if (boost::dynamic_pointer_cast<Track> (s)) {
285                 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(s);
286                 set_rec_controllable (t->rec_enable_control ());
287                 recarm_button ().set_color (0xff0000ff);
288         } else {
289                 set_rec_controllable (boost::shared_ptr<AutomationControl>());
290                 recarm_button ().set_color (0xffffffff);
291                 recarm_button ().set_active (false);
292         }
293         _peak_meter = s->peak_meter ();
294         _redux_ctrl = s->comp_redux_controllable ();
295
296         set_select_controllable (boost::shared_ptr<AutomationControl>());
297         select_button ().set_active (s->is_selected ());
298         select_button ().set_color (s->presentation_info ().color());
299         //select_button ().set_blinking (false);
300
301         _stripable_name = s->name ();
302
303         if (_base.twolinetext ()) {
304                 set_strip_name ();
305         } else {
306                 set_text_line (0, s->name ());
307                 set_text_line (1, _pan_ctrl ? _pan_ctrl->get_user_string () : "");
308         }
309         set_text_line (2, "");
310         set_text_line (3, "");
311 }
312
313 /* *****************************************************************************
314  * Parse Strip Specifig MIDI Events
315  */
316
317 bool
318 FP8Strip::midi_touch (bool t)
319 {
320         _touching = t;
321         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
322         if (!ac) {
323                 return false;
324         }
325         if (t) {
326                 ac->start_touch (ac->session().transport_sample());
327         } else {
328                 ac->stop_touch (ac->session().transport_sample());
329         }
330         return true;
331 }
332
333 bool
334 FP8Strip::midi_fader (float val)
335 {
336         assert (val >= 0.f && val <= 1.f);
337         if (!_touching) {
338                 return false;
339         }
340         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
341         if (!ac) {
342                 return false;
343         }
344         ac->start_touch (ac->session().transport_sample());
345         ac->set_value (ac->interface_to_internal (val), group_mode ());
346         return true;
347 }
348
349 /* *****************************************************************************
350  * Actions from Controller, Update Model
351  */
352
353 PBD::Controllable::GroupControlDisposition
354 FP8Strip::group_mode () const
355 {
356         if (_base.shift_mod ()) {
357                 return PBD::Controllable::InverseGroup;
358         } else {
359                 return PBD::Controllable::UseGroup;
360         }
361 }
362
363 void
364 FP8Strip::set_mute (bool on)
365 {
366         if (!_mute_ctrl) {
367                 return;
368         }
369         _mute_ctrl->start_touch (_mute_ctrl->session().transport_sample());
370         _mute_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
371 }
372
373 void
374 FP8Strip::set_solo (bool on)
375 {
376         if (!_solo_ctrl) {
377                 return;
378         }
379         _solo_ctrl->start_touch (_solo_ctrl->session().transport_sample());
380         _solo_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
381 }
382
383 void
384 FP8Strip::set_recarm ()
385 {
386         if (!_rec_ctrl) {
387                 return;
388         }
389         const bool on = !recarm_button ().is_active();
390         _rec_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
391 }
392
393 void
394 FP8Strip::set_select ()
395 {
396         if (!_select_plugin_functor.empty ()) {
397                 assert (!_x_select_ctrl);
398                 _select_plugin_functor ();
399         } else if (_x_select_ctrl) {
400                 _x_select_ctrl->start_touch (_x_select_ctrl->session().transport_sample());
401                 const bool on = !select_button ().is_active();
402                 _x_select_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
403         }
404 }
405
406 /* *****************************************************************************
407  * Callbacks from Stripable, Update View
408  */
409
410 void
411 FP8Strip::notify_fader_changed ()
412 {
413         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
414         if (_touching) {
415                 return;
416         }
417         float val = 0;
418         if (ac) {
419                 val = ac->internal_to_interface (ac->get_value());
420                 val = std::max (0.f, std::min (1.f, val)) * 16368.f; /* 16 * 1023 */
421         }
422         unsigned short mv = lrintf (val);
423         if (mv == _last_fader) {
424                 return;
425         }
426         _last_fader = mv;
427         _base.tx_midi3 (midi_ctrl_id (Fader, _id), (mv & 0x7f), (mv >> 7) & 0x7f);
428 }
429
430 void
431 FP8Strip::notify_solo_changed ()
432 {
433         if (_solo_ctrl) {
434                 boost::shared_ptr<SoloControl> sc = boost::dynamic_pointer_cast<SoloControl> (_solo_ctrl);
435                 if (sc) {
436                         _solo.set_blinking (sc->soloed_by_others () && !sc->self_soloed ());
437                         _solo.set_active (sc->self_soloed ());
438                 } else {
439                         _solo.set_blinking (false);
440                         _solo.set_active (_solo_ctrl->get_value () > 0);
441                 }
442         } else {
443                 _solo.set_blinking (false);
444                 _solo.set_active (false);
445         }
446 }
447
448 void
449 FP8Strip::notify_mute_changed ()
450 {
451         if (_mute_ctrl) {
452                 _mute.set_active (_mute_ctrl->get_value () > 0);
453         } else {
454                 _mute.set_active (false);
455         }
456 }
457
458 void
459 FP8Strip::notify_rec_changed ()
460 {
461         if (_rec_ctrl) {
462                 recarm_button ().set_active (_rec_ctrl->get_value() > 0.);
463         } else {
464                 recarm_button ().set_active (false);
465         }
466 }
467
468 void
469 FP8Strip::notify_pan_changed ()
470 {
471         // display only
472 }
473
474 void
475 FP8Strip::notify_x_select_changed ()
476 {
477         if (!_select_plugin_functor.empty ()) {
478                 assert (!_x_select_ctrl);
479                 return;
480         }
481
482         if (_x_select_ctrl) {
483                 assert (_select_plugin_functor.empty ());
484                 select_button ().set_active (_x_select_ctrl->get_value() > 0.);
485                 select_button ().set_color (0xffff00ff);
486                 select_button ().set_blinking (false);
487         }
488 }
489
490 /* *****************************************************************************
491  * Periodic View Updates 
492  */
493
494 void
495 FP8Strip::periodic_update_fader ()
496 {
497         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
498         if (!ac || _touching) {
499                 return;
500         }
501
502         if (!ac->automation_playback ()) {
503                 return;
504         }
505         notify_fader_changed ();
506 }
507
508 void
509 FP8Strip::set_periodic_display_mode (DisplayMode m) {
510         _displaymode = m;
511         if (_displaymode == SendDisplay || _displaymode == PluginParam) {
512                 // need to change to 4 lines before calling set_text()
513                 set_strip_mode (2); // 4 lines of small text
514         }
515 }
516
517 void
518 FP8Strip::periodic_update_meter ()
519 {
520         bool show_meters = _base.show_meters ();
521         bool have_meter = false;
522         bool have_panner = false;
523
524         if (_peak_meter && show_meters) {
525                 have_meter = true;
526                 float dB = _peak_meter->meter_level (0, MeterMCP);
527                 // TODO: deflect meter
528                 int val = std::min (127.f, std::max (0.f, 2.f * dB + 127.f));
529                 if (val != _last_meter || val > 0) {
530                         _base.tx_midi2 (midi_ctrl_id (Meter, _id), val & 0x7f); // falls off automatically
531                         _last_meter = val;
532                 }
533
534         } else if (show_meters) {
535                 if (0 != _last_meter) {
536                         _base.tx_midi2 (midi_ctrl_id (Meter, _id), 0);
537                         _last_meter = 0;
538                 }
539         }
540
541         // show redux only if there's a meter, too  (strip display mode 5)
542         if (_peak_meter && _redux_ctrl && show_meters) {
543                 float rx = (1.f - _redux_ctrl->get_parameter ()) * 127.f;
544                 // TODO: deflect redux
545                 int val = std::min (127.f, std::max (0.f, rx));
546                 if (val != _last_redux) {
547                         _base.tx_midi2 (midi_ctrl_id (Redux, _id), val & 0x7f);
548                         _last_redux = val;
549                 }
550         } else if (show_meters) {
551                 if (0 != _last_redux) {
552                         _base.tx_midi2 (midi_ctrl_id (Redux, _id), 0);
553                         _last_redux = 0;
554                 }
555         }
556
557         if (_displaymode == PluginParam) {
558                 if (_fader_ctrl) {
559                         set_bar_mode (2); // Fill
560                         set_text_line (2, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
561                         float barpos = _fader_ctrl->internal_to_interface (_fader_ctrl->get_value());
562                         int val = std::min (127.f, std::max (0.f, barpos * 128.f));
563                         if (val != _last_barpos) {
564                                 _base.tx_midi3 (0xb0, midi_ctrl_id (BarVal, _id), val & 0x7f);
565                                 _last_barpos = val;
566                         }
567                 } else {
568                         set_bar_mode (4); // Off
569                         set_text_line (2, "");
570                 }
571         }
572         else if (_displaymode == PluginSelect) {
573                 set_bar_mode (4); // Off
574         }
575         else if (_displaymode == SendDisplay) {
576                 set_bar_mode (4); // Off
577                 if (_fader_ctrl) {
578                         set_text_line (1, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
579                 } else {
580                         set_text_line (1, "");
581                 }
582         } else if (_pan_ctrl) {
583                 have_panner = _base.show_panner ();
584                 float panpos = _pan_ctrl->internal_to_interface (_pan_ctrl->get_value());
585                 int val = std::min (127.f, std::max (0.f, panpos * 128.f));
586                 set_bar_mode (have_panner ? 1 : 4); // Bipolar or Off
587                 if (val != _last_barpos && have_panner) {
588                         _base.tx_midi3 (0xb0, midi_ctrl_id (BarVal, _id), val & 0x7f);
589                         _last_barpos = val;
590                 }
591                 if (_base.twolinetext ()) {
592                         set_strip_name ();
593                 } else {
594                         set_text_line (1, _pan_ctrl->get_user_string ());
595                 }
596         } else {
597                 set_bar_mode (4); // Off
598                 if (_base.twolinetext ()) {
599                         set_strip_name ();
600                 } else {
601                         set_text_line (1, "");
602                 }
603         }
604
605         if (_displaymode == SendDisplay || _displaymode == PluginParam) {
606                 set_strip_mode (2); // 4 lines of small text + value-bar
607         }
608         else if (have_meter && have_panner) {
609                 set_strip_mode (5); // small meters + 3 lines of text (3rd is large)  + value-bar
610         }
611         else if (have_meter) {
612                 set_strip_mode (4); // big meters + 3 lines of text (3rd line is large)
613         }
614         else if (have_panner) {
615                 set_strip_mode (0); // 3 lines of text (3rd line is large + long) + value-bar
616         } else {
617                 set_strip_mode (0); // 3 lines of text (3rd line is large + long) + value-bar
618         }
619 }
620
621 void
622 FP8Strip::set_strip_mode (uint8_t strip_mode, bool clear)
623 {
624         if (strip_mode == _strip_mode && !clear) {
625                 return;
626         }
627
628         _strip_mode = strip_mode;
629         _base.tx_sysex (3, 0x13, _id, (_strip_mode & 0x07) | (clear ? 0x10 : 0));
630
631         if (clear) {
632                 /* work-around, when swiching modes, the FP8 may not
633                  * properly redraw long lines. Only update lines 0, 1
634                  * (line 2 is timecode, line 3 may be inverted)
635                  */
636                 _base.tx_text (_id, 0, 0x00, _last_line[0]);
637                 _base.tx_text (_id, 1, 0x00, _last_line[1]);
638         }
639 }
640
641 void
642 FP8Strip::set_bar_mode (uint8_t bar_mode, bool force)
643 {
644         if (bar_mode == _bar_mode && !force) {
645                 return;
646         }
647
648         if (bar_mode == 4) {
649                 _base.tx_midi3 (0xb0, midi_ctrl_id (BarVal, _id), 0);
650                 _last_barpos = 0xff;
651         }
652
653         _bar_mode = bar_mode;
654         _base.tx_midi3 (0xb0, midi_ctrl_id (BarMode, _id), bar_mode);
655 }
656
657 void
658 FP8Strip::set_text_line (uint8_t line, std::string const& txt, bool inv)
659 {
660         assert (line < 4);
661         if (_last_line[line] == txt) {
662                 return;
663         }
664         _base.tx_text (_id, line, inv ? 0x04 : 0x00, txt);
665         _last_line[line] = txt;
666 }
667
668 void
669 FP8Strip::periodic_update_timecode (uint32_t m)
670 {
671         if (m == 0) {
672                 return;
673         }
674         if (m == 3) {
675                 bool mc = _id >= 4;
676                 std::string const& tc = mc ? _base.musical_time () : _base.timecode();
677                 std::string t;
678                 if (tc.size () == 12) {
679                         t = tc.substr (1 + (_id - (mc ? 4 : 0)) * 3, 2);
680                 }
681                 set_text_line (2, t);
682         } else if (_id >= 2 && _id < 6) {
683                 std::string const& tc = (m == 2) ? _base.musical_time () : _base.timecode();
684                 //" HH:MM:SS:FF" or " BR|BT|TI|CK"
685                 std::string t;
686                 if (tc.size () == 12) {
687                         t = tc.substr (1 + (_id - 2) * 3, 2);
688                 }
689                 set_text_line (2, t);
690         } else {
691                 set_text_line (2, "");
692         }
693 }
694
695 void
696 FP8Strip::periodic ()
697 {
698         periodic_update_fader ();
699         periodic_update_meter ();
700         if (_displaymode != PluginSelect && _displaymode != PluginParam) {
701                 periodic_update_timecode (_base.clock_mode ());
702         }
703 }