summaryrefslogtreecommitdiff
path: root/libs/ardour/ardour/slave.h
blob: 2a05268c776d09c9c11b376dcb41493235628990 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
/*
    Copyright (C) 2002 Paul Davis

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#ifndef __ardour_slave_h__
#define __ardour_slave_h__

#include <vector>

#include <glibmm/threads.h>

#include <ltc.h>

#include "pbd/signals.h"

#include "temporal/time.h"

#include "ardour/libardour_visibility.h"
#include "ardour/types.h"
#include "midi++/parser.h"
#include "midi++/types.h"


/* used for delta_string(): */
#define PLUSMINUS(A) ( ((A)<0) ? "-" : (((A)>0) ? "+" : "\u00B1") )
#define LEADINGZERO(A) ( (A)<10 ? "   " : (A)<100 ? "  " : (A)<1000 ? " " : "" )

namespace ARDOUR {

class TempoMap;
class Session;
class AudioEngine;
class MidiPort;

/**
 * @class Slave
 *
 * @brief The Slave interface can be used to sync ARDOURs tempo to an external source
 * like MTC, MIDI Clock, etc.
 *
 * The name of the interface may be a bit misleading: A subclass of Slave actually
 * acts as a time master for ARDOUR, that means ARDOUR will try to follow the
 * speed and transport position of the implementation of Slave.
 * Therefore it is rather that class, that makes ARDOUR a slave by connecting it
 * to its external time master.
 */
class LIBARDOUR_API Slave {
  public:
	Slave() { }
	virtual ~Slave() {}

	/**
	 * This is the most important function to implement:
	 * Each process cycle, Session::follow_slave will call this method.
	 *  and after the method call they should
	 *
	 * Session::follow_slave will then try to follow the given
	 * <em>position</em> using a delay locked loop (DLL),
	 * starting with the first given transport speed.
	 * If the values of speed and position contradict each other,
	 * ARDOUR will always follow the position and disregard the speed.
	 * Although, a correct speed is important so that ARDOUR
	 * can sync to the master time source quickly.
	 *
	 * For background information on delay locked loops,
	 * see http://www.kokkinizita.net/papers/usingdll.pdf
	 *
	 * The method has the following precondition:
	 * <ul>
	 *   <li>
	 *       Slave::ok() should return true, otherwise playback will stop
	 *       immediately and the method will not be called
	 *   </li>
	 *   <li>
	 *     when the references speed and position are passed into the Slave
	 *     they are uninitialized
	 *   </li>
	 * </ul>
	 *
	 * After the method call the following postconditions should be met:
	 * <ul>
	 *    <li>
	 *       The first position value on transport start should be 0,
	 *       otherwise ARDOUR will try to locate to the new position
	 *       rather than move to it
	 *    </li>
	 *    <li>
	 *      the references speed and position should be assigned
	 *      to the Slaves current requested transport speed
	 *      and transport position.
	 *    </li>
	 *   <li>
	 *     Slave::resolution() should be greater than the maximum distance of
	 *     ARDOURs transport position to the slaves requested transport position.
	 *   </li>
	 *   <li>Slave::locked() should return true, otherwise Session::no_roll will be called</li>
	 *   <li>Slave::starting() should be false, otherwise the transport will not move until it becomes true</li>	 *
	 * </ul>
	 *
	 * @param speed - The transport speed requested
	 * @param position - The transport position requested
	 * @return - The return value is currently ignored (see Session::follow_slave)
	 */
	virtual bool speed_and_position (double& speed, samplepos_t& position) = 0;

	/**
	 * reports to ARDOUR whether the Slave is currently synced to its external
	 * time source.
	 *
	 * @return - when returning false, the transport will stop rolling
	 */
	virtual bool locked() const = 0;

	/**
	 * reports to ARDOUR whether the slave is in a sane state
	 *
	 * @return - when returning false, the transport will be stopped and the slave
	 * disconnected from ARDOUR.
	 */
	virtual bool ok() const = 0;

	/**
	 * reports to ARDOUR whether the slave is in the process of starting
	 * to roll
	 *
	 * @return - when returning false, transport will not move until this method returns true
	 */
	virtual bool starting() const { return false; }

	/**
	 * @return - the timing resolution of the Slave - If the distance of ARDOURs transport
	 * to the slave becomes greater than the resolution, sound will stop
	 */
	virtual samplecnt_t resolution() const = 0;

	/**
	 * @return - when returning true, ARDOUR will wait for seekahead_distance() before transport
	 * starts rolling
	 */
	virtual bool requires_seekahead () const = 0;

	/**
	 * @return the number of samples that this slave wants to seek ahead. Relevant
	 * only if requires_seekahead() returns true.
	 */

	virtual samplecnt_t seekahead_distance() const { return 0; }

	/**
	 * @return - when returning true, ARDOUR will use transport speed 1.0 no matter what
	 *           the slave returns
	 */
	virtual bool is_always_synced() const { return false; }

	/**
	 * @return - whether ARDOUR should use the slave speed without any adjustments
	 */
	virtual bool give_slave_full_control_over_transport_speed() const { return false; }

	/**
	 * @return - current time-delta between engine and sync-source
	 */
	virtual std::string delta_string () const { return ""; }

};

/// We need this wrapper for testability, it's just too hard to mock up a session class
class LIBARDOUR_API ISlaveSessionProxy {
  public:
	virtual ~ISlaveSessionProxy() {}
	virtual TempoMap&  tempo_map()                  const   { return *((TempoMap *) 0); }
	virtual samplecnt_t sample_rate()                 const   { return 0; }
	virtual pframes_t  samples_per_cycle()           const   { return 0; }
	virtual samplepos_t audible_sample ()             const   { return 0; }
	virtual samplepos_t transport_sample ()           const   { return 0; }
	virtual pframes_t  samples_since_cycle_start ()  const   { return 0; }
	virtual samplepos_t sample_time_at_cycle_start() const   { return 0; }
	virtual samplepos_t sample_time ()                const   { return 0; }
};


/// The Session Proxy for use in real Ardour
class LIBARDOUR_API SlaveSessionProxy : public ISlaveSessionProxy {
	Session&    session;

  public:
	SlaveSessionProxy(Session &s) : session(s) {}

	TempoMap&  tempo_map()                   const;
	samplecnt_t sample_rate()                  const;
	pframes_t  samples_per_cycle()            const;
	samplepos_t audible_sample ()              const;
	samplepos_t transport_sample ()            const;
	pframes_t  samples_since_cycle_start ()   const;
	samplepos_t sample_time_at_cycle_start()  const;
	samplepos_t sample_time ()                 const;
};

struct LIBARDOUR_API SafeTime {
	volatile int guard1;
	samplepos_t   position;
	samplepos_t   timestamp;
	double       speed;
	volatile int guard2;

	SafeTime() {
		guard1 = 0;
		position = 0;
		timestamp = 0;
		speed = 0;
		guard2 = 0;
	}
};

class LIBARDOUR_API TimecodeSlave : public Slave {
  public:
	TimecodeSlave () {}

	virtual Timecode::TimecodeFormat apparent_timecode_format() const = 0;

	/* this is intended to be used by a UI and polled from a timeout. it should
	   return a string describing the current position of the TC source. it
	   should NOT do any computation, but should use a cached value
	   of the TC source position.
	*/
	virtual std::string position_string () const = 0;

	samplepos_t        timecode_offset;
	bool              timecode_negative_offset;

	PBD::Signal1<void, bool> ActiveChanged;
};

class LIBARDOUR_API MTC_Slave : public TimecodeSlave {
  public:
	MTC_Slave (Session&, MidiPort&);
	~MTC_Slave ();

	void rebind (MidiPort&);
	bool speed_and_position (double&, samplepos_t&);

	bool locked() const;
	bool ok() const;
	void handle_locate (const MIDI::byte*);

	samplecnt_t resolution () const;
	bool requires_seekahead () const { return false; }
	samplecnt_t seekahead_distance() const;
	bool give_slave_full_control_over_transport_speed() const;

        Timecode::TimecodeFormat apparent_timecode_format() const;
        std::string position_string () const;
	std::string delta_string () const;

  private:
	Session&    session;
	MidiPort*   port;
	PBD::ScopedConnectionList port_connections;
	PBD::ScopedConnection     config_connection;
	bool        can_notify_on_unknown_rate;

	static const int sample_tolerance;

	SafeTime       current;
	samplepos_t     mtc_frame;               /* current time */
	double         mtc_frame_dll;
	samplepos_t     last_inbound_frame;      /* when we got it; audio clocked */
	MIDI::byte     last_mtc_fps_byte;
	samplepos_t     window_begin;
	samplepos_t     window_end;
	samplepos_t     first_mtc_timestamp;
	bool           did_reset_tc_format;
	Timecode::TimecodeFormat saved_tc_format;
	Glib::Threads::Mutex    reset_lock;
	uint32_t       reset_pending;
	bool           reset_position;
	int            transport_direction;
	int            busy_guard1;
	int            busy_guard2;

	double         speedup_due_to_tc_mismatch;
	double         quarter_frame_duration;
	Timecode::TimecodeFormat mtc_timecode;
	Timecode::TimecodeFormat a3e_timecode;
	Timecode::Time timecode;
	bool           printed_timecode_warning;
	sampleoffset_t  current_delta;

	/* DLL - chase MTC */
	double t0; ///< time at the beginning of the MTC quater sample
	double t1; ///< calculated end of the MTC quater sample
	double e2; ///< second order loop error
	double b, c, omega; ///< DLL filter coefficients

	/* DLL - sync engine */
	int    engine_dll_initstate;
	double te0; ///< time at the beginning of the engine process
	double te1; ///< calculated sync time
	double ee2; ///< second order loop error
	double be, ce, oe; ///< DLL filter coefficients

	void reset (bool with_pos);
	void queue_reset (bool with_pos);
	void maybe_reset ();

	void update_mtc_qtr (MIDI::Parser&, int, samplepos_t);
	void update_mtc_time (const MIDI::byte *, bool, samplepos_t);
	void update_mtc_status (MIDI::MTC_Status);
	void read_current (SafeTime *) const;
	void reset_window (samplepos_t);
	bool outside_window (samplepos_t) const;
	void init_mtc_dll(samplepos_t, double);
	void init_engine_dll (samplepos_t, samplepos_t);
	void parse_timecode_offset();
	void parameter_changed(std::string const & p);
};

class LIBARDOUR_API LTC_Slave : public TimecodeSlave {
public:
	LTC_Slave (Session&);
	~LTC_Slave ();

	bool speed_and_position (double&, samplepos_t&);

	bool locked() const;
	bool ok() const;

	samplecnt_t resolution () const;
	bool requires_seekahead () const { return false; }
	samplecnt_t seekahead_distance () const { return 0; }
	bool give_slave_full_control_over_transport_speed() const { return true; }

	Timecode::TimecodeFormat apparent_timecode_format() const;
	std::string position_string() const;
	std::string delta_string() const;

  private:
	void parse_ltc(const pframes_t, const Sample* const, const samplecnt_t);
	void process_ltc(samplepos_t const);
	void init_engine_dll (samplepos_t, int32_t);
	bool detect_discontinuity(LTCFrameExt *, int, bool);
	bool detect_ltc_fps(int, bool);
	bool equal_ltc_sample_time(LTCFrame *a, LTCFrame *b);
	void reset (bool with_ts = true);
	void resync_xrun();
	void resync_latency();
	void parse_timecode_offset();
	void parameter_changed(std::string const & p);

	Session&       session;
	bool           did_reset_tc_format;
	Timecode::TimecodeFormat saved_tc_format;

	LTCDecoder *   decoder;
	double         samples_per_ltc_frame;
	Timecode::Time timecode;
	LTCFrameExt    prev_sample;
	bool           fps_detected;

	samplecnt_t     monotonic_cnt;
	samplecnt_t     last_timestamp;
	samplecnt_t     last_ltc_sample;
	double         ltc_speed;
	sampleoffset_t  current_delta;
	int            delayedlocked;

	int            ltc_detect_fps_cnt;
	int            ltc_detect_fps_max;
	bool           printed_timecode_warning;
	bool           sync_lock_broken;
	Timecode::TimecodeFormat ltc_timecode;
	Timecode::TimecodeFormat a3e_timecode;

	PBD::ScopedConnectionList port_connections;
	PBD::ScopedConnection     config_connection;
        LatencyRange  ltc_slave_latency;

	/* DLL - chase LTC */
	int    transport_direction;
	int    engine_dll_initstate;
	double t0; ///< time at the beginning of the MTC quater sample
	double t1; ///< calculated end of the MTC quater sample
	double e2; ///< second order loop error
	double b, c; ///< DLL filter coefficients
};

class LIBARDOUR_API MIDIClock_Slave : public Slave {
  public:
	MIDIClock_Slave (Session&, MidiPort&, int ppqn = 24);

	/// Constructor for unit tests
	MIDIClock_Slave (ISlaveSessionProxy* session_proxy = 0, int ppqn = 24);
	~MIDIClock_Slave ();

	void rebind (MidiPort&);
	bool speed_and_position (double&, samplepos_t&);

	bool locked() const;
	bool ok() const;
	bool starting() const;

	samplecnt_t resolution () const;
	bool requires_seekahead () const { return false; }
	bool give_slave_full_control_over_transport_speed() const { return true; }

	void set_bandwidth (double a_bandwith) { bandwidth = a_bandwith; }
	std::string delta_string () const;

  protected:
	ISlaveSessionProxy* session;
	PBD::ScopedConnectionList port_connections;

	/// pulses per quarter note for one MIDI clock sample (default 24)
	int         ppqn;

	/// the duration of one ppqn in sample time
	double      one_ppqn_in_samples;

	/// the timestamp of the first MIDI clock message
	samplepos_t  first_timestamp;

	/// the time stamp and should-be transport position of the last inbound MIDI clock message
	samplepos_t  last_timestamp;
	double      should_be_position;

	/// the number of midi clock messages received (zero-based)
	/// since start
	long midi_clock_count;

	//the delay locked loop (DLL), see www.kokkinizita.net/papers/usingdll.pdf

	/// time at the beginning of the MIDI clock sample
	double t0;

	/// calculated end of the MIDI clock sample
	double t1;

	/// loop error = real value - expected value
	double e;

	/// second order loop error
	double e2;

	/// DLL filter bandwidth
	double bandwidth;

	/// DLL filter coefficients
	double b, c, omega;

	sampleoffset_t  current_delta;

	void reset ();
	void start (MIDI::Parser& parser, samplepos_t timestamp);
	void contineu (MIDI::Parser& parser, samplepos_t timestamp);
	void stop (MIDI::Parser& parser, samplepos_t timestamp);
	void position (MIDI::Parser& parser, MIDI::byte* message, size_t size);
	// we can't use continue because it is a C++ keyword
	void calculate_one_ppqn_in_samples_at(samplepos_t time);
	samplepos_t calculate_song_position(uint16_t song_position_in_sixteenth_notes);
	void calculate_filter_coefficients();
	void update_midi_clock (MIDI::Parser& parser, samplepos_t timestamp);
	void read_current (SafeTime *) const;
	bool stop_if_no_more_clock_events(samplepos_t& pos, samplepos_t now);

	/// whether transport should be rolling
	bool _started;

	/// is true if the MIDI Start message has just been received until
	/// the first MIDI Clock Event
	bool _starting;
};

class LIBARDOUR_API Engine_Slave : public Slave
{
  public:
	Engine_Slave (AudioEngine&);
	~Engine_Slave ();

	bool speed_and_position (double& speed, samplepos_t& pos);

	bool starting() const { return _starting; }
	bool locked() const;
	bool ok() const;
	samplecnt_t resolution () const { return 1; }
	bool requires_seekahead () const { return false; }
	bool is_always_synced() const { return true; }

  private:
        AudioEngine& engine;
	bool _starting;
};

} /* namespace */

#endif /* __ardour_slave_h__ */