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