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