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