add a delta sign to the slave delta display.
[ardour.git] / libs / ardour / mtc_slave.cc
1 /*
2     Copyright (C) 2002-4 Paul Davis
3     Overhaul 2012 Robin Gareus <robin@gareus.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (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., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20 #include <iostream>
21 #include <errno.h>
22 #include <poll.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "pbd/error.h"
27
28 #include "midi++/port.h"
29 #include "ardour/debug.h"
30 #include "ardour/slave.h"
31 #include "ardour/session.h"
32 #include "ardour/audioengine.h"
33
34 #include "i18n.h"
35
36 using namespace std;
37 using namespace ARDOUR;
38 using namespace MIDI;
39 using namespace PBD;
40 using namespace Timecode;
41
42 /* length (in timecode frames) of the "window" that we consider legal given receipt of
43    a given timecode position. Ardour will try to chase within this window, and will
44    stop+locate+wait+chase if timecode arrives outside of it. The window extends entirely
45    in the current direction of motion, so if any timecode arrives that is before the most
46    recently received position (and without the direction of timecode reversing too), we
47    will stop+locate+wait+chase.
48 */
49 const int MTC_Slave::frame_tolerance = 2;
50
51 MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p)
52         : session (s)
53 {
54         can_notify_on_unknown_rate = true;
55         did_reset_tc_format = false;
56         reset_pending = 0;
57         reset_position = false;
58         mtc_frame = 0;
59         engine_dll_initstate = 0;
60         busy_guard1 = busy_guard2 = 0;
61
62         last_mtc_fps_byte = session.get_mtc_timecode_bits ();
63         quarter_frame_duration = (double(session.frames_per_timecode_frame()) / 4.0);
64
65         mtc_timecode = session.config.get_timecode_format();
66         a3e_timecode = session.config.get_timecode_format();
67         printed_timecode_warning = false;
68
69         reset (true);
70         rebind (p);
71 }
72
73 MTC_Slave::~MTC_Slave()
74 {
75         port_connections.drop_connections();
76
77         while (busy_guard1 != busy_guard2) {
78                 /* make sure MIDI parser is not currently calling any callbacks in here,
79                  * else there's a segfault ahead!
80                  *
81                  * XXX this is called from jack rt-context :(
82                  * TODO fix libs/ardour/session_transport.cc:1321 (delete _slave;)
83                  */
84                 sched_yield();
85         }
86
87         if (did_reset_tc_format) {
88                 session.config.set_timecode_format (saved_tc_format);
89         }
90 }
91
92 void
93 MTC_Slave::rebind (MIDI::Port& p)
94 {
95         port_connections.drop_connections ();
96
97         port = &p;
98
99         port->parser()->mtc_time.connect_same_thread (port_connections,  boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3));
100         port->parser()->mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3));
101         port->parser()->mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1));
102 }
103
104 bool
105 MTC_Slave::give_slave_full_control_over_transport_speed() const
106 {
107         return true; // DLL align to engine transport
108         // return false; // for Session-level computed varispeed
109 }
110
111 ARDOUR::framecnt_t
112 MTC_Slave::resolution () const
113 {
114         return (framecnt_t) quarter_frame_duration * 4.0;
115 }
116
117 ARDOUR::framecnt_t
118 MTC_Slave::seekahead_distance () const
119 {
120         return quarter_frame_duration * 8 * transport_direction;
121 }
122
123 bool
124 MTC_Slave::outside_window (framepos_t pos) const
125 {
126         return ((pos < window_begin) || (pos > window_end));
127 }
128
129
130 bool
131 MTC_Slave::locked () const
132 {
133         return port->parser()->mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0;
134 }
135
136 bool
137 MTC_Slave::ok() const
138 {
139         return true;
140 }
141
142 void
143 MTC_Slave::queue_reset (bool reset_pos)
144 {
145         Glib::Threads::Mutex::Lock lm (reset_lock);
146         reset_pending++;
147         if (reset_pos) {
148                 reset_position = true;
149         }
150 }
151
152 void
153 MTC_Slave::maybe_reset ()
154 {
155         Glib::Threads::Mutex::Lock lm (reset_lock);
156
157         if (reset_pending) {
158                 reset (reset_position);
159                 reset_pending = 0;
160                 reset_position = false;
161         }
162 }
163
164 void
165 MTC_Slave::reset (bool with_position)
166 {
167         DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC_Slave reset %1\n", with_position?"with position":"without position"));
168         if (with_position) {
169                 last_inbound_frame = 0;
170                 current.guard1++;
171                 current.position = 0;
172                 current.timestamp = 0;
173                 current.speed = 0;
174                 current.guard2++;
175         } else {
176                 last_inbound_frame = 0;
177                 current.guard1++;
178                 current.timestamp = 0;
179                 current.speed = 0;
180                 current.guard2++;
181         }
182         first_mtc_timestamp = 0;
183         window_begin = 0;
184         window_end = 0;
185         transport_direction = 1;
186         current_delta = 0;
187 }
188
189 void
190 MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
191 {
192         MIDI::byte mtc[5];
193         DEBUG_TRACE (DEBUG::MTC, "MTC_Slave::handle_locate\n");
194
195         mtc[4] = last_mtc_fps_byte;
196         mtc[3] = mmc_tc[0] & 0xf; /* hrs only */
197         mtc[2] = mmc_tc[1];
198         mtc[1] = mmc_tc[2];
199         mtc[0] = mmc_tc[3];
200
201         update_mtc_time (mtc, true, 0);
202 }
203
204 void
205 MTC_Slave::read_current (SafeTime *st) const
206 {
207         int tries = 0;
208
209         do {
210                 if (tries == 10) {
211                         error << _("MTC Slave: atomic read of current time failed, sleeping!") << endmsg;
212                         usleep (20);
213                         tries = 0;
214                 }
215                 *st = current;
216                 tries++;
217
218         } while (st->guard1 != st->guard2);
219 }
220
221 void
222 MTC_Slave::init_mtc_dll(framepos_t tme, double qtr)
223 {
224         omega = 2.0 * M_PI * qtr / 2.0 / double(session.frame_rate());
225         b = 1.4142135623730950488 * omega;
226         c = omega * omega;
227
228         e2 = qtr;
229         t0 = double(tme);
230         t1 = t0 + e2;
231         DEBUG_TRACE (DEBUG::MTC, string_compose ("[re-]init MTC DLL %1 %2 %3\n", t0, t1, e2));
232 }
233
234
235 /* called from MIDI parser */
236 void
237 MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, framepos_t now)
238 {
239         busy_guard1++;
240         const double qtr_d = quarter_frame_duration;
241         const framepos_t qtr = rint(qtr_d);
242
243         mtc_frame += qtr * transport_direction;
244
245         DEBUG_TRACE (DEBUG::MTC, string_compose ("qtr frame %1 at %2 -> mtc_frame: %3\n", which_qtr, now, mtc_frame));
246
247         double mtc_speed = 0;
248         if (first_mtc_timestamp != 0) {
249                 /* update MTC DLL and calculate speed */
250                 const double e = mtc_frame - (double(transport_direction) * (double(now) - double(current.timestamp) + t0));
251                 t0 = t1;
252                 t1 += b * e + e2;
253                 e2 += c * e;
254
255                 mtc_speed = (t1 - t0) / qtr_d;
256                 DEBUG_TRACE (DEBUG::MTC, string_compose ("qtr frame DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, mtc_speed, e2 - qtr_d));
257
258                 current.guard1++;
259                 current.position = mtc_frame;
260                 current.timestamp = now;
261                 current.speed = mtc_speed;
262                 current.guard2++;
263
264                 last_inbound_frame = now;
265         }
266
267         maybe_reset ();
268
269         busy_guard2++;
270 }
271
272 /* called from MIDI parser _after_ update_mtc_qtr()
273  * when a full TC has been received
274  * OR on locate */
275 void
276 MTC_Slave::update_mtc_time (const byte *msg, bool was_full, framepos_t now)
277 {
278         busy_guard1++;
279
280         /* "now" can be zero if this is called from a context where we do not have or do not want
281            to use a timestamp indicating when this MTC time was received. example: when we received
282            a locate command via MMC.
283         */
284
285         //DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::update_mtc_time - TID:%1\n", ::pthread_self()));
286         TimecodeFormat tc_format;
287         bool reset_tc = true;
288
289         timecode.hours = msg[3];
290         timecode.minutes = msg[2];
291         timecode.seconds = msg[1];
292         timecode.frames = msg[0];
293
294         last_mtc_fps_byte = msg[4];
295
296         DEBUG_TRACE (DEBUG::MTC, string_compose ("full mtc time known at %1, full ? %2\n", now, was_full));
297
298         if (now) {
299                 maybe_reset ();
300         }
301
302         switch (msg[4]) {
303         case MTC_24_FPS:
304                 timecode.rate = 24;
305                 timecode.drop = false;
306                 tc_format = timecode_24;
307                 can_notify_on_unknown_rate = true;
308                 break;
309         case MTC_25_FPS:
310                 timecode.rate = 25;
311                 timecode.drop = false;
312                 tc_format = timecode_25;
313                 can_notify_on_unknown_rate = true;
314                 break;
315         case MTC_30_FPS_DROP:
316                 if (Config->get_timecode_source_2997()) {
317                         tc_format = Timecode::timecode_2997000drop;
318                         timecode.rate = (29970.0/1000.0);
319                 } else {
320                         tc_format = timecode_2997drop;
321                         timecode.rate = (30000.0/1001.0);
322                 }
323                 timecode.drop = true;
324                 can_notify_on_unknown_rate = true;
325                 break;
326         case MTC_30_FPS:
327                 timecode.rate = 30;
328                 timecode.drop = false;
329                 can_notify_on_unknown_rate = true;
330                 tc_format = timecode_30;
331                 break;
332         default:
333                 /* throttle error messages about unknown MTC rates */
334                 if (can_notify_on_unknown_rate) {
335                         error << string_compose (_("Unknown rate/drop value %1 in incoming MTC stream, session values used instead"),
336                                                  (int) msg[4])
337                               << endmsg;
338                         can_notify_on_unknown_rate = false;
339                 }
340                 timecode.rate = session.timecode_frames_per_second();
341                 timecode.drop = session.timecode_drop_frames();
342                 reset_tc = false;
343         }
344
345         if (reset_tc) {
346                 TimecodeFormat cur_timecode = session.config.get_timecode_format();
347                 if (Config->get_timecode_sync_frame_rate()) {
348                         /* enforce time-code */
349                         if (!did_reset_tc_format) {
350                                 saved_tc_format = cur_timecode;
351                                 did_reset_tc_format = true;
352                         }
353                         if (cur_timecode != tc_format) {
354                                 if (ceil(Timecode::timecode_to_frames_per_second(cur_timecode)) != ceil(Timecode::timecode_to_frames_per_second(tc_format))) {
355                                         warning << string_compose(_("Session framerate adjusted from %1 TO: MTC's %2."),
356                                                         Timecode::timecode_format_name(cur_timecode),
357                                                         Timecode::timecode_format_name(tc_format))
358                                                 << endmsg;
359                                 }
360                         }
361                         session.config.set_timecode_format (tc_format);
362                 } else {
363                         /* only warn about TC mismatch */
364                         if (mtc_timecode != tc_format) printed_timecode_warning = false;
365                         if (a3e_timecode != cur_timecode) printed_timecode_warning = false;
366
367                         if (cur_timecode != tc_format && ! printed_timecode_warning) {
368                                 if (ceil(Timecode::timecode_to_frames_per_second(cur_timecode)) != ceil(Timecode::timecode_to_frames_per_second(tc_format))) {
369                                         warning << string_compose(_("Session and MTC framerate mismatch: MTC:%1 Ardour:%2."),
370                                                         Timecode::timecode_format_name(tc_format),
371                                                         Timecode::timecode_format_name(cur_timecode))
372                                                 << endmsg;
373                                 }
374                                 printed_timecode_warning = true;
375                         }
376                 }
377                 mtc_timecode = tc_format;
378                 a3e_timecode = cur_timecode;
379
380                 speedup_due_to_tc_mismatch = timecode.rate / Timecode::timecode_to_frames_per_second(a3e_timecode);
381         }
382
383         /* do a careful conversion of the timecode value to a position
384            so that we take drop/nondrop and all that nonsense into
385            consideration.
386         */
387
388         quarter_frame_duration = (double(session.frame_rate()) / (double) timecode.rate / 4.0);
389
390         Timecode::timecode_to_sample (timecode, mtc_frame, false, false,
391                 double(session.frame_rate()),
392                 session.config.get_subframes_per_frame(),
393                 session.config.get_timecode_offset_negative(), session.config.get_timecode_offset()
394                 );
395
396         DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC at %1 TC %2 = mtc_frame %3 (from full message ? %4) tc-ratio %5\n",
397                                                  now, timecode, mtc_frame, was_full, speedup_due_to_tc_mismatch));
398
399         if (was_full || outside_window (mtc_frame)) {
400                 DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC or outside window. - TID:%1\n", ::pthread_self()));
401                 session.request_locate (mtc_frame, false);
402                 session.request_transport_speed (0);
403                 update_mtc_status (MIDI::MTC_Stopped);
404                 reset (false);
405                 reset_window (mtc_frame);
406         } else {
407
408                 /* we've had the first set of 8 qtr frame messages, determine position
409                    and allow continuing qtr frame messages to provide position
410                    and speed information.
411                 */
412
413                 /* We received the last quarter frame 7 quarter frames (1.75 mtc
414                    frames) after the instance when the contents of the mtc quarter
415                    frames were decided. Add time to compensate for the elapsed 1.75
416                    frames.
417                 */
418                 double qtr = quarter_frame_duration;
419                 long int mtc_off = (long) rint(7.0 * qtr);
420
421                 DEBUG_TRACE (DEBUG::MTC, string_compose ("new mtc_frame: %1 | MTC-FpT: %2 A3-FpT:%3\n",
422                                                          mtc_frame, (4.0*qtr), session.frames_per_timecode_frame()));
423
424                 switch (port->parser()->mtc_running()) {
425                 case MTC_Backward:
426                         mtc_frame -= mtc_off;
427                         qtr *= -1.0;
428                         break;
429                 case MTC_Forward:
430                         mtc_frame += mtc_off;
431                         break;
432                 default:
433                         break;
434                 }
435
436                 DEBUG_TRACE (DEBUG::MTC, string_compose ("new mtc_frame (w/offset) = %1\n", mtc_frame));
437
438                 if (now) {
439                         if (first_mtc_timestamp == 0 || current.timestamp == 0) {
440                                 first_mtc_timestamp = now;
441                                 init_mtc_dll(mtc_frame, qtr);
442                         }
443                         current.guard1++;
444                         current.position = mtc_frame;
445                         current.timestamp = now;
446                         current.guard2++;
447                         reset_window (mtc_frame);
448                 }
449         }
450
451         if (now) {
452                 last_inbound_frame = now;
453         }
454         busy_guard2++;
455 }
456
457 void
458 MTC_Slave::update_mtc_status (MIDI::MTC_Status status)
459 {
460         /* XXX !!! thread safety ... called from MIDI I/O context
461          * on locate (via ::update_mtc_time())
462          */
463         DEBUG_TRACE (DEBUG::MTC, string_compose("MTC_Slave::update_mtc_status - TID:%1\n", ::pthread_self()));
464         return; // why was this fn needed anyway ? it just messes up things -> use reset.
465         busy_guard1++;
466
467         switch (status) {
468         case MTC_Stopped:
469                 current.guard1++;
470                 current.position = mtc_frame;
471                 current.timestamp = 0;
472                 current.speed = 0;
473                 current.guard2++;
474
475                 break;
476
477         case MTC_Forward:
478                 current.guard1++;
479                 current.position = mtc_frame;
480                 current.timestamp = 0;
481                 current.speed = 0;
482                 current.guard2++;
483                 break;
484
485         case MTC_Backward:
486                 current.guard1++;
487                 current.position = mtc_frame;
488                 current.timestamp = 0;
489                 current.speed = 0;
490                 current.guard2++;
491                 break;
492         }
493         busy_guard2++;
494 }
495
496 void
497 MTC_Slave::reset_window (framepos_t root)
498 {
499         /* if we're waiting for the master to catch us after seeking ahead, keep the window
500            of acceptable MTC frames wide open. otherwise, shrink it down to just 2 video frames
501            ahead of the window root (taking direction into account).
502         */
503         framecnt_t const d = (quarter_frame_duration * 4 * frame_tolerance);
504
505         switch (port->parser()->mtc_running()) {
506         case MTC_Forward:
507                 window_begin = root;
508                 transport_direction = 1;
509                 window_end = root + d;
510                 break;
511
512         case MTC_Backward:
513                 transport_direction = -1;
514                 if (root > d) {
515                         window_begin = root - d;
516                         window_end = root;
517                 } else {
518                         window_begin = 0;
519                 }
520                 window_end = root;
521                 break;
522
523         default:
524                 /* do nothing */
525                 break;
526         }
527
528         DEBUG_TRACE (DEBUG::MTC, string_compose ("legal MTC window now %1 .. %2\n", window_begin, window_end));
529 }
530
531 void
532 MTC_Slave::init_engine_dll (framepos_t pos, framepos_t inc)
533 {
534         /* the bandwidth of the DLL is a trade-off,
535          * because the max-speed of the transport in ardour is
536          * limited to +-8.0, a larger bandwidth would cause oscillations
537          *
538          * But this is only really a problem if the user performs manual
539          * seeks while transport is running and slaved to MTC.
540          */
541         oe = 2.0 * M_PI * double(inc) / 2.0 / double(session.frame_rate());
542         be = 1.4142135623730950488 * oe;
543         ce = oe * oe;
544
545         ee2 = double(transport_direction * inc);
546         te0 = double(pos);
547         te1 = te0 + ee2;
548         DEBUG_TRACE (DEBUG::MTC, string_compose ("[re-]init Engine DLL %1 %2 %3\n", te0, te1, ee2));
549 }
550
551 /* main entry point from session_process.cc
552  * in jack_process callback context */
553 bool
554 MTC_Slave::speed_and_position (double& speed, framepos_t& pos)
555 {
556         framepos_t now = session.engine().frame_time_at_cycle_start();
557         framepos_t sess_pos = session.transport_frame(); // corresponds to now
558         //sess_pos -= session.engine().frames_since_cycle_start();
559
560         SafeTime last;
561         frameoffset_t elapsed;
562         bool engine_dll_reinitialized = false;
563
564         read_current (&last);
565
566         /* re-init engine DLL here when state changed (direction, first_mtc_timestamp) */
567         if (last.timestamp == 0) { engine_dll_initstate = 0; }
568         else if (engine_dll_initstate != transport_direction && last.speed != 0) {
569                 engine_dll_initstate = transport_direction;
570                 init_engine_dll(last.position, session.engine().frames_per_cycle());
571                 engine_dll_reinitialized = true;
572         }
573
574         if (last.timestamp == 0) {
575                 speed = 0;
576                 pos = session.transport_frame() ; // last.position;
577                 DEBUG_TRACE (DEBUG::MTC, string_compose ("first call to MTC_Slave::speed_and_position, pos = %1\n", pos));
578                 return true;
579         }
580
581         /* no timecode for two frames - conclude that it's stopped */
582         if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > labs(seekahead_distance())) {
583                 speed = 0;
584                 pos = last.position;
585                 session.request_locate (pos, false);
586                 session.request_transport_speed (0);
587                 engine_dll_initstate = 0;
588                 queue_reset (false);
589                 DEBUG_TRACE (DEBUG::MTC, "MTC not seen for 2 frames - reset pending\n");
590                 return false;
591         }
592
593
594         DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position mtc-tme: %1 mtc-pos: %2 mtc-spd: %3\n", last.timestamp, last.position, last.speed));
595         DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position eng-tme: %1 eng-pos: %2\n", now, sess_pos));
596
597         double speed_flt = last.speed; ///< MTC speed from MTC-quarter-frame DLL
598
599         /* interpolate position according to speed and time since last quarter-frame*/
600         if (speed_flt == 0.0f) {
601                 elapsed = 0;
602         }
603         else
604         {
605                 /* scale elapsed time by the current MTC speed */
606                 elapsed = (framecnt_t) rint (speed_flt * (now - last.timestamp));
607                 if (give_slave_full_control_over_transport_speed() && !engine_dll_reinitialized) {
608                         /* there is an engine vs MTC position frame-delta.
609                          * This mostly due to quantization and rounding of (speed * nframes)
610                          * but can also due to the session-process not calling
611                          * speed_and_position() every cycle under some circumstances.
612                          * Thus we use an other DLL to align the engine and the MTC
613                          */
614
615                         /* update engine DLL and calculate speed */
616                         const double e = double (last.position + elapsed - sess_pos);
617                         te0 = te1;
618                         te1 += be * e + ee2;
619                         ee2 += ce * e;
620                         speed_flt = (te1 - te0) / double(session.engine().frames_per_cycle());
621                         DEBUG_TRACE (DEBUG::MTC, string_compose ("engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", te0, te1, e, speed_flt, ee2 - session.engine().frames_per_cycle() ));
622                 }
623         }
624
625         pos = last.position + elapsed;
626         speed = speed_flt;
627
628         /* may happen if the user performs a seek in the timeline while slaved to running MTC
629          * engine-DLL can oscillate back before 0.
630          * also see note in MTC_Slave::init_engine_dll
631          */
632         if (!session.actively_recording()
633             && speed != 0
634                         && ( (pos < 0) || (labs(pos - sess_pos) > 3 * session.frame_rate()) )
635             ) {
636                 engine_dll_initstate = 0;
637                 queue_reset (false);
638         }
639
640         /* provide a .1% deadzone to lock the speed */
641         if (fabs(speed - 1.0) <= 0.001)
642                 speed = 1.0;
643
644         DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
645                                                  speed, pos, last.position, elapsed,  pos - sess_pos));
646
647         current_delta = (pos - sess_pos);
648
649         return true;
650 }
651
652 Timecode::TimecodeFormat
653 MTC_Slave::apparent_timecode_format () const
654 {
655         return mtc_timecode;
656 }
657
658 std::string
659 MTC_Slave::approximate_current_position() const
660 {
661         SafeTime last;
662         read_current (&last);
663         if (last.timestamp == 0 || reset_pending) {
664                 return " \u2012\u2012:\u2012\u2012:\u2012\u2012:\u2012\u2012";
665         }
666         return Timecode::timecode_format_sampletime(
667                 last.position,
668                 double(session.frame_rate()),
669                 Timecode::timecode_to_frames_per_second(mtc_timecode),
670                 Timecode::timecode_has_drop_frames(mtc_timecode));
671 }
672
673 std::string
674 MTC_Slave::approximate_current_delta() const
675 {
676         char delta[24];
677         SafeTime last;
678         read_current (&last);
679         if (last.timestamp == 0 || reset_pending) {
680                 snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
681         } else {
682                 // TODO if current_delta > 1 frame -> display timecode.
683                 snprintf(delta, sizeof(delta), "\u0394 %s%4" PRIi64 " sm",
684                                 PLUSMINUS(-current_delta), abs(current_delta));
685         }
686         return std::string(delta);
687 }