diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2018-09-18 18:51:59 -0400 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2018-09-18 19:06:04 -0400 |
commit | e6915e01de2e2167c3384c6c8f2408f763971616 (patch) | |
tree | c67200eda4cf4c595503a850fe6ae72d89032a6f /libs/ardour/ardour/transport_master.h | |
parent | 7390b88c2bb29b1b34624f441adec1e71c74bad8 (diff) |
new transport slave/master implementation, libs/ edition
Diffstat (limited to 'libs/ardour/ardour/transport_master.h')
-rw-r--r-- | libs/ardour/ardour/transport_master.h | 546 |
1 files changed, 546 insertions, 0 deletions
diff --git a/libs/ardour/ardour/transport_master.h b/libs/ardour/ardour/transport_master.h new file mode 100644 index 0000000000..9da0378fb1 --- /dev/null +++ b/libs/ardour/ardour/transport_master.h @@ -0,0 +1,546 @@ +/* + 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/atomic.hpp> +#include <boost/optional.hpp> + +#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 Location; +class MidiPort; +class AudioPort; +class Port; + + +/** + * @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: + + 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; + + 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; + std::string _name; + Session* _session; + bool _connected; + sampleoffset_t _current_delta; + bool _collect; + bool _pending_collect; + TransportRequestType _request_mask; /* lists transport requests still accepted when we're in control */ + bool _sclock_synced; + + /* 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; +}; + +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) : TransportMaster (type, name) {} + + 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); + + private: + 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__ */ |