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