summaryrefslogtreecommitdiff
path: root/libs/ardour/ardour/transport_master.h
blob: b41d1070150180e338ca2178a0097666a9d8d0ad (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
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
/*
    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_transport_master_h__
#define __ardour_transport_master_h__

#include <vector>

#include <boost/optional.hpp>

#include <glibmm/threads.h>

#include <ltc.h>

#include "pbd/properties.h"
#include "pbd/signals.h"
#include "pbd/stateful.h"

#include "temporal/time.h"

#include "ardour/libardour_visibility.h"
#include "ardour/region.h" /* for Properties::locked */
#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 Location;
class MidiPort;
class AudioPort;
class Port;

namespace Properties {
	LIBARDOUR_API extern PBD::PropertyDescriptor<bool> fr2997;
	LIBARDOUR_API extern PBD::PropertyDescriptor<bool> collect;
	LIBARDOUR_API extern PBD::PropertyDescriptor<bool> connected;
	LIBARDOUR_API extern PBD::PropertyDescriptor<bool> sclock_synced;
	LIBARDOUR_API extern PBD::PropertyDescriptor<ARDOUR::TransportRequestType> allowed_transport_requests;
};

/**
 * @class TransportMaster
 *
 * @brief The TransportMaster interface can be used to sync ARDOURs tempo to an external source
 * like MTC, MIDI Clock, etc. as well as a single internal pseudo master we
 * call "UI" because it is controlled from any of the user interfaces for
 * Ardour (GUI, control surfaces, OSC, etc.)
 *
 */
class LIBARDOUR_API TransportMaster : public PBD::Stateful {
  public:

	TransportMaster (SyncSource t, std::string const & name);
	virtual ~TransportMaster();

	static boost::shared_ptr<TransportMaster> factory (SyncSource, std::string const &);
	static boost::shared_ptr<TransportMaster> factory (XMLNode const &);

	virtual void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>) = 0;

	/**
	 * 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>
	 *       TransportMaster::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 TransportMaster
	 *     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 TransportMasters current requested transport speed
	 *      and transport position.
	 *    </li>
	 *   <li>
	 *     TransportMaster::resolution() should be greater than the maximum distance of
	 *     ARDOURs transport position to the slaves requested transport position.
	 *   </li>
	 *   <li>TransportMaster::locked() should return true, otherwise Session::no_roll will be called</li>
	 *   <li>TransportMaster::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, samplepos_t now) = 0;

	/**
	 * reports to ARDOUR whether the TransportMaster 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 TransportMaster - 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 sample_clock_synced() const { return _sclock_synced; }
	virtual void set_sample_clock_synced (bool);

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

	sampleoffset_t current_delta() const { return _current_delta; }

	/* 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;

	virtual bool can_loop() const { return false; }

	virtual Location* loop_location() const { return 0; }
	bool has_loop() const { return loop_location() != 0; }

	SyncSource type() const { return _type; }
	TransportRequestSource request_type() const {
		switch (_type) {
		case Engine: /* also JACK */
			return TRS_Engine;
		case MTC:
			return TRS_MTC;
		case LTC:
			return TRS_LTC;
		case MIDIClock:
			break;
		}
		return TRS_MIDIClock;
	}

	std::string name() const { return _name; }
	void set_name (std::string const &);

	int set_state (XMLNode const &, int);
	XMLNode& get_state();

	static const std::string state_node_name;
	static void make_property_quarks ();

	virtual void set_session (Session*);

	boost::shared_ptr<Port> port() const { return _port; }

	bool check_collect();
	virtual void set_collect (bool);
	bool collect() const { return _collect; }

	/* called whenever the manager starts collecting (processing) this
	   transport master. Typically will re-initialize any state used to
	   deal with incoming data.
	*/
	virtual void init() = 0;

	virtual void check_backend() {}
	virtual bool allow_request (TransportRequestSource, TransportRequestType) const;

	TransportRequestType request_mask() const { return _request_mask; }
	void set_request_mask (TransportRequestType);
  protected:
	SyncSource      _type;
	PBD::Property<std::string>   _name;
	Session*        _session;
	sampleoffset_t  _current_delta;
	bool            _pending_collect;
	PBD::Property<TransportRequestType> _request_mask; /* lists transport requests still accepted when we're in control */
	PBD::Property<bool> _locked;
	PBD::Property<bool> _sclock_synced;
	PBD::Property<bool> _collect;
	PBD::Property<bool> _connected;

	/* DLL - chase incoming data */

	int    transport_direction;
	int    dll_initstate;

	double t0;
	double t1;
	double e2;
	double b, c;

	boost::shared_ptr<Port>  _port;

	PBD::ScopedConnection port_connection;
	bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);

	PBD::ScopedConnection backend_connection;

	virtual void register_properties ();
};

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;
	}
};

/** a helper class for any TransportMaster that receives its input via a MIDI
 * port
 */
class LIBARDOUR_API TransportMasterViaMIDI {
  public:
	boost::shared_ptr<MidiPort> midi_port() const { return _midi_port; }
	boost::shared_ptr<Port> create_midi_port (std::string const & port_name);

  protected:
	TransportMasterViaMIDI () {};

	MIDI::Parser                 parser;
	boost::shared_ptr<MidiPort> _midi_port;
};

class LIBARDOUR_API TimecodeTransportMaster : public TransportMaster {
  public:
	TimecodeTransportMaster (std::string const & name, SyncSource type);

	virtual Timecode::TimecodeFormat apparent_timecode_format() const = 0;
	samplepos_t        timecode_offset;
	bool              timecode_negative_offset;

	bool fr2997() const { return _fr2997; }
	void set_fr2997 (bool);

  protected:
	void register_properties ();

  private:
	PBD::Property<bool> _fr2997;
};

class LIBARDOUR_API MTC_TransportMaster : public TimecodeTransportMaster, public TransportMasterViaMIDI {
  public:
	MTC_TransportMaster (std::string const &);
	~MTC_TransportMaster ();

	void set_session (Session*);

	void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);

	bool speed_and_position (double&, samplepos_t&, 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;
	void init ();

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

  private:
	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;

	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 parse_timecode_offset();
	void parameter_changed(std::string const & p);
};

class LIBARDOUR_API LTC_TransportMaster : public TimecodeTransportMaster {
public:
	LTC_TransportMaster (std::string const &);
	~LTC_TransportMaster ();

	void set_session (Session*);

	void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
	bool speed_and_position (double&, samplepos_t&, 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; }
	void init ();

	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_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);

	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;
	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;
	double         samples_per_timecode_frame;

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

class LIBARDOUR_API MIDIClock_TransportMaster : public TransportMaster, public TransportMasterViaMIDI {
  public:
	MIDIClock_TransportMaster (std::string const & name, int ppqn = 24);

	/// Constructor for unit tests
	~MIDIClock_TransportMaster ();

	void set_session (Session*);

	void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);

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

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

	samplecnt_t resolution () const;
	bool requires_seekahead () const { return false; }
	void init ();

	std::string position_string() const;
	std::string delta_string() const;

	float bpm() const { return _bpm; }

  protected:
	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;

	/// a DLL to track MIDI clock

	double _speed;
	bool _running;
	double _bpm;

	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 (double qpm);
	void update_midi_clock (MIDI::Parser& parser, samplepos_t timestamp);
	void read_current (SafeTime *) const;
};

class LIBARDOUR_API Engine_TransportMaster : public TransportMaster
{
  public:
	Engine_TransportMaster (AudioEngine&);
	~Engine_TransportMaster  ();

	void pre_process (pframes_t nframes, samplepos_t now,  boost::optional<samplepos_t>);
	bool speed_and_position (double& speed, samplepos_t& pos, samplepos_t);

	bool starting() const { return _starting; }
	bool locked() const;
	bool ok() const;
	samplecnt_t resolution () const { return 1; }
	bool requires_seekahead () const { return false; }
	bool sample_clock_synced() const { return true; }
	void init ();
	void check_backend();
	bool allow_request (TransportRequestSource, TransportRequestType) const;

	std::string position_string() const;
	std::string delta_string() const;

  private:
        AudioEngine& engine;
        bool _starting;
};

} /* namespace */

#endif /* __ardour_transport_master_h__ */