new transport slave/master implementation, libs/ edition
authorPaul Davis <paul@linuxaudiosystems.com>
Tue, 18 Sep 2018 22:51:59 +0000 (18:51 -0400)
committerPaul Davis <paul@linuxaudiosystems.com>
Tue, 18 Sep 2018 23:06:04 +0000 (19:06 -0400)
42 files changed:
libs/ardour/ardour/debug.h
libs/ardour/ardour/disk_io.h
libs/ardour/ardour/midi_buffer.h
libs/ardour/ardour/midi_port.h
libs/ardour/ardour/midiport_manager.h
libs/ardour/ardour/port.h
libs/ardour/ardour/rc_configuration.h
libs/ardour/ardour/rc_configuration_vars.h
libs/ardour/ardour/session.h
libs/ardour/ardour/session_event.h
libs/ardour/ardour/slave.h
libs/ardour/ardour/track.h
libs/ardour/ardour/transport_master.h [new file with mode: 0644]
libs/ardour/ardour/transport_master_manager.h [new file with mode: 0644]
libs/ardour/ardour/types.h
libs/ardour/audio_port.cc
libs/ardour/audioengine.cc
libs/ardour/debug.cc
libs/ardour/disk_io.cc
libs/ardour/engine_slave.cc
libs/ardour/enums.cc
libs/ardour/globals.cc
libs/ardour/ltc_slave.cc
libs/ardour/midi_clock_slave.cc
libs/ardour/midi_port.cc
libs/ardour/midiport_manager.cc
libs/ardour/mtc_slave.cc
libs/ardour/port.cc
libs/ardour/port_manager.cc
libs/ardour/rc_configuration.cc
libs/ardour/session.cc
libs/ardour/session_ltc.cc
libs/ardour/session_midi.cc
libs/ardour/session_process.cc
libs/ardour/session_state.cc
libs/ardour/session_transport.cc
libs/ardour/track.cc
libs/ardour/transport_master.cc [new file with mode: 0644]
libs/ardour/transport_master_manager.cc [new file with mode: 0644]
libs/ardour/wscript
libs/midi++2/midi++/parser.h
libs/midi++2/parser.cc

index e91e0edf51b2395fb807a6f869b9be6ab628746d..069840a14948077fa71b72d30e429478520f110c 100644 (file)
@@ -44,6 +44,7 @@ namespace PBD {
                LIBARDOUR_API extern DebugBits Destruction;
                LIBARDOUR_API extern DebugBits MTC;
                LIBARDOUR_API extern DebugBits LTC;
+               LIBARDOUR_API extern DebugBits TXLTC;
                LIBARDOUR_API extern DebugBits Transport;
                LIBARDOUR_API extern DebugBits Slave;
                LIBARDOUR_API extern DebugBits SessionEvents;
index 4f9eb2257ea86e50337f56940caa7e1c4223d48a..0f79930654d25ac7c552b10a65d5fa16b1e58235 100644 (file)
@@ -81,9 +81,6 @@ public:
 
        virtual void non_realtime_locate (samplepos_t);
 
-       void non_realtime_speed_change ();
-       bool realtime_speed_change ();
-
        virtual void punch_in()  {}
        virtual void punch_out() {}
 
@@ -118,7 +115,6 @@ protected:
        uint32_t      i_am_the_modifier;
        double       _actual_speed;
        double       _target_speed;
-       bool         _seek_required;
        bool         _slaved;
        bool          in_set_state;
        samplepos_t   playback_sample;
index 509b60f12d354efccf1f5d7dd9ad106a7ae8fb97..7793f7e0bd83946564f3e1925793398b1414faaa 100644 (file)
@@ -48,6 +48,8 @@ public:
        void copy(const MidiBuffer& copy);
        void copy(MidiBuffer const * const);
 
+       void skip_to (TimeType when);
+
        bool     push_back(const Evoral::Event<TimeType>& event);
        bool     push_back(TimeType time, size_t size, const uint8_t* data);
 
index e23914c4ce5f1433924ea0ddd782d8c8d57382cd..4b453b80bc0bbc0629e5ed0ace53abc91a97a046 100644 (file)
@@ -24,6 +24,7 @@
 #include "midi++/parser.h"
 
 #include "ardour/port.h"
+#include "ardour/midi_buffer.h"
 
 namespace ARDOUR {
 
@@ -51,19 +52,20 @@ class LIBARDOUR_API MidiPort : public Port {
        bool input_active() const { return _input_active; }
        void set_input_active (bool yn);
 
-       Buffer& get_buffer (pframes_t nframes);
+       Buffer& get_buffer (pframes_t nframes) {
+               return get_midi_buffer (nframes);
+       }
 
        MidiBuffer& get_midi_buffer (pframes_t nframes);
 
-       void set_always_parse (bool yn);
-       void set_trace_on (bool yn);
+       void set_trace (MIDI::Parser* trace_parser);
 
        typedef boost::function<bool(MidiBuffer&,MidiBuffer&)> MidiFilter;
        void set_inbound_filter (MidiFilter);
        int add_shadow_port (std::string const &, MidiFilter);
        boost::shared_ptr<MidiPort> shadow_port() const { return _shadow_port; }
 
-       MIDI::Parser& self_parser() { return _self_parser; }
+       void read_and_parse_entire_midi_buffer_with_no_speed_adjustment (pframes_t nframes, MIDI::Parser& parser, samplepos_t now);
 
   protected:
        friend class PortManager;
@@ -72,29 +74,17 @@ class LIBARDOUR_API MidiPort : public Port {
 
   private:
        MidiBuffer* _buffer;
-       bool        _has_been_mixed_down;
        bool        _resolve_required;
        bool        _input_active;
-       bool        _always_parse;
-       bool        _trace_on;
        MidiFilter   inbound_midi_filter;
        boost::shared_ptr<MidiPort> _shadow_port;
        MidiFilter   shadow_midi_filter;
-
-       /* Naming this is tricky. AsyncMIDIPort inherits (for now, aug 2013) from
-        * both MIDI::Port, which has _parser, and this (ARDOUR::MidiPort). We
-        * need parsing support in this object, independently of what the
-        * MIDI::Port/AsyncMIDIPort stuff does. Rather than risk errors coming
-        * from not explicitly naming which _parser we want, we will call this
-        * _self_parser for now.
-        *
-        * Ultimately, MIDI::Port should probably go away or be fully integrated
-        * into this object, somehow.
-        */
-
-       MIDI::Parser _self_parser;
-
+       MIDI::Parser* _trace_parser;
+       bool  _data_fetched_for_cycle;
+       
        void resolve_notes (void* buffer, samplepos_t when);
+       void pull_input (pframes_t nframes, bool adjust_speed);
+       void parse_input (pframes_t nframes, MIDI::Parser& parser);
 };
 
 } // namespace ARDOUR
index 2fb5d5a57a9f788c410ddd773c5a3fd4638fbbb1..69b603723fee798f0393e63908dcbc51c58f3957 100644 (file)
@@ -62,13 +62,11 @@ class LIBARDOUR_API MidiPortManager {
     boost::shared_ptr<ARDOUR::Port> scene_input_port() const { return boost::dynamic_pointer_cast<MidiPort>(_scene_in); }
     boost::shared_ptr<ARDOUR::Port> scene_output_port() const { return boost::dynamic_pointer_cast<MidiPort>(_scene_out); }
 
-    /* Ports used for synchronization. These have their I/O handled inside the
+    /* Ports used to send synchronization. These have their output handled inside the
      * process callback.
      */
 
-    boost::shared_ptr<MidiPort> mtc_input_port() const { return _mtc_input_port; }
     boost::shared_ptr<MidiPort> mtc_output_port() const { return _mtc_output_port; }
-    boost::shared_ptr<MidiPort> midi_clock_input_port() const { return _midi_clock_input_port; }
     boost::shared_ptr<MidiPort> midi_clock_output_port() const { return _midi_clock_output_port; }
 
     void set_midi_port_states (const XMLNodeList&);
@@ -86,9 +84,7 @@ class LIBARDOUR_API MidiPortManager {
     boost::shared_ptr<Port> _scene_out;
 
     /* synchronously handled ports: ARDOUR::MidiPort */
-    boost::shared_ptr<MidiPort> _mtc_input_port;
     boost::shared_ptr<MidiPort> _mtc_output_port;
-    boost::shared_ptr<MidiPort> _midi_clock_input_port;
     boost::shared_ptr<MidiPort> _midi_clock_output_port;
 
     void create_ports ();
index 99a2b60cc3a6b287b3f5429db3b64e9b1f033c98..9ab850c86baf7c0520cbbefba7517998b66cca1c 100644 (file)
@@ -123,7 +123,10 @@ public:
        virtual void realtime_locate () {}
 
        bool physically_connected () const;
-       bool externally_connected () const;
+       uint32_t externally_connected () const { return _externally_connected; }
+
+       void increment_external_connections() { _externally_connected++; }
+       void decrement_external_connections() { if (_externally_connected) _externally_connected--; }
 
        PBD::Signal1<void,bool> MonitorInputChanged;
        static PBD::Signal2<void,boost::shared_ptr<Port>,boost::shared_ptr<Port> > PostDisconnect;
@@ -170,6 +173,7 @@ private:
        std::string _name;  ///< port short name
        PortFlags   _flags; ///< flags
        bool        _last_monitor;
+       uint32_t    _externally_connected;
 
        /** ports that we are connected to, kept so that we can
            reconnect to the backend when required
index a644d76250002cf8897f866c59d9e3afa1b7b403..f3b98c777433681031fc33387d4a6daa1ec6871d 100644 (file)
@@ -55,6 +55,7 @@ class LIBARDOUR_API RCConfiguration : public PBD::Configuration
        XMLNode * instant_xml (const std::string& str);
 
        XMLNode* control_protocol_state () { return _control_protocol_state; }
+       XMLNode* transport_master_state () { return _transport_master_state; }
 
        /* define accessor methods */
 
@@ -83,6 +84,7 @@ class LIBARDOUR_API RCConfiguration : public PBD::Configuration
 #undef  CONFIG_VARIABLE_SPECIAL
 
        XMLNode* _control_protocol_state;
+       XMLNode* _transport_master_state;
 };
 
 /* XXX: rename this */
index b7ee95e9e5a145175bca20b4bc051de760cfd6b2..7ffc8879e2466003dd8349253797a17389685c2c 100644 (file)
@@ -45,6 +45,11 @@ CONFIG_VARIABLE (bool, strict_io, "strict-io", true)
 /* Naming */
 CONFIG_VARIABLE (TracksAutoNamingRule, tracks_auto_naming, "tracks-auto-naming", UseDefaultNames)
 
+/* Transport Masters (all) */
+
+CONFIG_VARIABLE (bool, transport_masters_just_roll_when_sync_lost, "transport-masters-just-roll-when-sync-lost", false)
+CONFIG_VARIABLE (bool, midi_clock_sets_tempo, "midi-clock-sets-tempo", true)
+
 /* MIDI and MIDI related */
 
 CONFIG_VARIABLE (bool, trace_midi_input, "trace-midi-input", false)
@@ -63,15 +68,11 @@ CONFIG_VARIABLE (bool, midi_input_follows_selection, "midi-input-follows-selecti
 
 /* Timecode and related */
 
+CONFIG_VARIABLE (bool, run_all_transport_masters_always, "run-all-transport-masters-always", true)
+CONFIG_VARIABLE (bool, use_session_timecode_format, "use-session-timecode-format", true)
 CONFIG_VARIABLE (int, mtc_qf_speed_tolerance, "mtc-qf-speed-tolerance", 5)
 CONFIG_VARIABLE (bool, timecode_sync_frame_rate, "timecode-sync-frame-rate", true)
 #ifdef USE_TRACKS_CODE_FEATURES
-CONFIG_VARIABLE (bool, timecode_source_is_synced, "timecode-source-is-synced", true)
-#else
-CONFIG_VARIABLE (bool, timecode_source_is_synced, "timecode-source-is-synced", false)
-#endif
-CONFIG_VARIABLE (bool, timecode_source_2997, "timecode-source-2997", false)
-#ifdef USE_TRACKS_CODE_FEATURES
 CONFIG_VARIABLE (SyncSource, sync_source, "sync-source", MTC)
 #else
 CONFIG_VARIABLE (SyncSource, sync_source, "sync-source", Engine)
index c4c745867006f8a1f30eef33ac93eeb2727ca863..6e7ec93dec3846585652ef8fc39e285c82734258 100644 (file)
@@ -149,11 +149,12 @@ class SceneChanger;
 class SessionDirectory;
 class SessionMetadata;
 class SessionPlaylists;
-class Slave;
 class Source;
 class Speakers;
 class TempoMap;
+class TransportMaster;
 class Track;
+class UI_TransportMaster;
 class VCAManager;
 class WindowsVSTPlugin;
 
@@ -358,10 +359,6 @@ public:
 
        PBD::Signal0<void> IOConnectionsComplete;
 
-       /* Timecode status signals */
-       PBD::Signal1<void, bool> MTCSyncStateChanged;
-       PBD::Signal1<void, bool> LTCSyncStateChanged;
-
        /* Record status signals */
 
        PBD::Signal0<void> RecordStateChanged; /* signals changes in recording state (i.e. are we recording) */
@@ -415,8 +412,8 @@ public:
 
        void request_roll_at_and_return (samplepos_t start, samplepos_t return_to);
        void request_bounded_roll (samplepos_t start, samplepos_t end);
-       void request_stop (bool abort = false, bool clear_state = false);
-       void request_locate (samplepos_t sample, bool with_roll = false);
+       void request_stop (bool abort = false, bool clear_state = false, TransportRequestSource origin = TRS_UI);
+       void request_locate (samplepos_t sample, bool with_roll = false, TransportRequestSource origin = TRS_UI);
 
        void request_play_loop (bool yn, bool leave_rolling = false);
        bool get_play_loop () const { return play_loop; }
@@ -426,8 +423,8 @@ public:
        void goto_start (bool and_roll = false);
        void use_rf_shuttle_speed ();
        void allow_auto_play (bool yn);
-       void request_transport_speed (double speed, bool as_default = true);
-       void request_transport_speed_nonzero (double, bool as_default = true);
+       void request_transport_speed (double speed, bool as_default = true, TransportRequestSource origin = TRS_UI);
+       void request_transport_speed_nonzero (double, bool as_default = true, TransportRequestSource origin = TRS_UI);
        void request_overwrite_buffer (boost::shared_ptr<Route>);
        void adjust_playback_buffering();
        void adjust_capture_buffering();
@@ -687,6 +684,7 @@ public:
        samplepos_t requested_return_sample() const { return _requested_return_sample; }
        void set_requested_return_sample(samplepos_t return_to);
 
+       bool compute_audible_delta (samplepos_t& pos_and_delta) const;
        samplecnt_t remaining_latency_preroll () const { return _remaining_latency_preroll; }
 
        enum PullupFormat {
@@ -719,10 +717,8 @@ public:
        static PBD::Signal1<void, samplepos_t> StartTimeChanged;
        static PBD::Signal1<void, samplepos_t> EndTimeChanged;
 
-       void   request_sync_source (Slave*);
-       bool   synced_to_engine() const { return _slave && config.get_external_sync() && Config->get_sync_source() == Engine; }
-       bool   synced_to_mtc () const { return config.get_external_sync() && Config->get_sync_source() == MTC && g_atomic_int_get (const_cast<gint*>(&_mtc_active)); }
-       bool   synced_to_ltc () const { return config.get_external_sync() && Config->get_sync_source() == LTC && g_atomic_int_get (const_cast<gint*>(&_ltc_active)); }
+       void   request_sync_source (boost::shared_ptr<TransportMaster>);
+       bool   synced_to_engine() const { return config.get_external_sync() && Config->get_sync_source() == Engine; }
 
        double engine_speed() const { return _engine_speed; }
        double actual_speed() const {
@@ -1104,7 +1100,7 @@ public:
                PostTransportRoll               = 0x8,
                PostTransportAbort              = 0x10,
                PostTransportOverWrite          = 0x20,
-               PostTransportSpeed              = 0x40,
+               /* was ... PostTransportSpeed              = 0x40, */
                PostTransportAudition           = 0x80,
                PostTransportReverse            = 0x100,
                PostTransportInputChange        = 0x200,
@@ -1114,15 +1110,6 @@ public:
                PostTransportAdjustCaptureBuffering   = 0x2000
        };
 
-       enum SlaveState {
-               Stopped,
-               Waiting,
-               Running
-       };
-
-       SlaveState slave_state() const { return _slave_state; }
-       Slave* slave() const { return _slave; }
-
        boost::shared_ptr<SessionPlaylists> playlists;
 
        void send_mmc_locate (samplepos_t);
@@ -1189,22 +1176,16 @@ public:
        /* synchronous MIDI ports used for synchronization */
 
        boost::shared_ptr<MidiPort> midi_clock_output_port () const;
-       boost::shared_ptr<MidiPort> midi_clock_input_port () const;
        boost::shared_ptr<MidiPort> mtc_output_port () const;
-       boost::shared_ptr<MidiPort> mtc_input_port () const;
-       boost::shared_ptr<Port> ltc_input_port() const;
        boost::shared_ptr<Port> ltc_output_port() const;
 
-       boost::shared_ptr<IO> ltc_input_io() { return _ltc_input; }
        boost::shared_ptr<IO> ltc_output_io() { return _ltc_output; }
 
        MIDI::MachineControl& mmc() { return *_mmc; }
 
        void reconnect_midi_scene_ports (bool);
-       void reconnect_mtc_ports ();
        void reconnect_mmc_ports (bool);
 
-       void reconnect_ltc_input ();
        void reconnect_ltc_output ();
 
        VCAManager& vca_manager() { return *_vca_manager; }
@@ -1212,6 +1193,9 @@ public:
 
        void auto_connect_thread_wakeup ();
 
+       double compute_speed_from_master (pframes_t nframes);
+       bool   transport_master_is_external() const;
+       boost::shared_ptr<TransportMaster> transport_master() const;
 
 protected:
        friend class AudioEngine;
@@ -1254,7 +1238,6 @@ private:
        gint                    _seek_counter;
        Location*               _session_range_location; ///< session range, or 0 if there is nothing in the session yet
        bool                    _session_range_end_is_free;
-       Slave*                  _slave;
        bool                    _silent;
        samplecnt_t             _remaining_latency_preroll;
 
@@ -1267,7 +1250,6 @@ private:
        double                  _target_transport_speed;
 
        bool                     auto_play_legal;
-       samplepos_t             _last_slave_transport_sample;
        samplepos_t             _requested_return_sample;
        pframes_t                current_block_size;
        samplecnt_t             _worst_output_latency;
@@ -1286,11 +1268,6 @@ private:
 
        std::string             _missing_file_replacement;
 
-       void mtc_status_changed (bool);
-       PBD::ScopedConnection mtc_status_connection;
-       void ltc_status_changed (bool);
-       PBD::ScopedConnection ltc_status_connection;
-
        void initialize_latencies ();
        void update_latency (bool playback);
        bool update_route_latency (bool reverse, bool apply_to_delayline);
@@ -1317,28 +1294,21 @@ private:
 
        static const samplecnt_t bounce_chunk_size;
 
-       /* slave tracking */
+       /* Transport master DLL */
 
-       static const int delta_accumulator_size = 25;
-       int delta_accumulator_cnt;
-       int32_t delta_accumulator[delta_accumulator_size];
-       int32_t average_slave_delta;
-       int  average_dir;
-       bool have_first_delta_accumulator;
+       enum TransportMasterState {
+               Stopped, /* no incoming or invalid signal/data for master to run with */
+               Waiting, /* waiting to get full lock on incoming signal/data */
+               Running  /* lock achieved, master is generating meaningful speed & position */
+       };
 
-       SlaveState _slave_state;
-       gint _mtc_active;
-       gint _ltc_active;
-       samplepos_t slave_wait_end;
+       TransportMasterState transport_master_tracking_state;
+       samplepos_t master_wait_end;
+       void track_transport_master (float slave_speed, samplepos_t slave_transport_sample);
+       bool follow_transport_master (pframes_t nframes);
 
+       void sync_source_changed (SyncSource, samplepos_t pos, pframes_t cycle_nframes);
        void reset_slave_state ();
-       bool follow_slave (pframes_t);
-       void calculate_moving_average_of_slave_delta (int dir, samplecnt_t this_delta);
-       void track_slave_state (float slave_speed, samplepos_t slave_transport_sample, samplecnt_t this_delta);
-
-       void switch_to_sync_source (SyncSource); /* !RT context */
-       void drop_sync_source ();  /* !RT context */
-       void use_sync_source (Slave*); /* RT context */
 
        bool post_export_sync;
        samplepos_t post_export_position;
@@ -1673,6 +1643,8 @@ private:
 
        int           start_midi_thread ();
 
+       bool should_ignore_transport_request (TransportRequestSource, TransportRequestType) const;
+
        void set_play_loop (bool yn, double speed);
        void unset_play_loop ();
        void overwrite_some_buffers (Track *);
@@ -2048,7 +2020,6 @@ private:
 
        MidiClockTicker* midi_clock;
 
-       boost::shared_ptr<IO>   _ltc_input;
        boost::shared_ptr<IO>   _ltc_output;
 
        boost::shared_ptr<RTTaskList> _rt_tasklist;
index aaa254e0033f8a52b8954d88d8bf1a7a8ed4fcaa..37e229acfb35a815fe75cdf347be386d248fd7f9 100644 (file)
@@ -33,7 +33,7 @@
 
 namespace ARDOUR {
 
-class Slave;
+class TransportMaster;
 class Region;
 
 class LIBARDOUR_API SessionEvent {
@@ -49,7 +49,6 @@ public:
                RangeStop,
                RangeLocate,
                Overwrite,
-               SetSyncSource,
                Audition,
                SetPlayAudioRange,
                CancelPlayAudioRange,
@@ -58,6 +57,7 @@ public:
                AdjustCaptureBuffering,
                SetTimecodeTransmission,
                Skip,
+               SetTransportMaster,
 
                /* only one of each of these events can be queued at any one time */
 
@@ -79,11 +79,10 @@ public:
        double     speed;
 
        union {
-               void*        ptr;
-               bool         yes_or_no;
-               samplepos_t   target2_sample;
-               Slave*       slave;
-               Route*       route;
+               void*            ptr;
+               bool             yes_or_no;
+               samplepos_t      target2_sample;
+               Route*           route;
        };
 
        union {
@@ -109,6 +108,7 @@ public:
        std::list<MusicRange> music_range;
 
        boost::shared_ptr<Region> region;
+       boost::shared_ptr<TransportMaster> transport_master;
 
        SessionEvent (Type t, Action a, samplepos_t when, samplepos_t where, double spd, bool yn = false, bool yn2 = false, bool yn3 = false);
 
index 155e295cbfda99f86955acb8ca4514b35f0e3e90..2a05268c776d09c9c11b376dcb41493235628990 100644 (file)
@@ -36,7 +36,7 @@
 #include "midi++/types.h"
 
 
-/* used for approximate_current_delta(): */
+/* used for delta_string(): */
 #define PLUSMINUS(A) ( ((A)<0) ? "-" : (((A)>0) ? "+" : "\u00B1") )
 #define LEADINGZERO(A) ( (A)<10 ? "   " : (A)<100 ? "  " : (A)<1000 ? " " : "" )
 
@@ -175,7 +175,7 @@ class LIBARDOUR_API Slave {
        /**
         * @return - current time-delta between engine and sync-source
         */
-       virtual std::string approximate_current_delta() const { return ""; }
+       virtual std::string delta_string () const { return ""; }
 
 };
 
@@ -191,11 +191,6 @@ class LIBARDOUR_API ISlaveSessionProxy {
        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; }
-
-       virtual void request_locate (samplepos_t /*sample*/, bool with_roll = false) {
-               (void) with_roll;
-       }
-       virtual void request_transport_speed (double /*speed*/)                   {}
 };
 
 
@@ -214,9 +209,6 @@ class LIBARDOUR_API SlaveSessionProxy : public ISlaveSessionProxy {
        pframes_t  samples_since_cycle_start ()   const;
        samplepos_t sample_time_at_cycle_start()  const;
        samplepos_t sample_time ()                 const;
-
-       void request_locate (samplepos_t sample, bool with_roll = false);
-       void request_transport_speed (double speed);
 };
 
 struct LIBARDOUR_API SafeTime {
@@ -246,7 +238,7 @@ class LIBARDOUR_API TimecodeSlave : public Slave {
           should NOT do any computation, but should use a cached value
           of the TC source position.
        */
-       virtual std::string approximate_current_position() const = 0;
+       virtual std::string position_string () const = 0;
 
        samplepos_t        timecode_offset;
        bool              timecode_negative_offset;
@@ -272,8 +264,8 @@ class LIBARDOUR_API MTC_Slave : public TimecodeSlave {
        bool give_slave_full_control_over_transport_speed() const;
 
         Timecode::TimecodeFormat apparent_timecode_format() const;
-        std::string approximate_current_position() const;
-       std::string approximate_current_delta() const;
+        std::string position_string () const;
+       std::string delta_string () const;
 
   private:
        Session&    session;
@@ -354,8 +346,8 @@ public:
        bool give_slave_full_control_over_transport_speed() const { return true; }
 
        Timecode::TimecodeFormat apparent_timecode_format() const;
-       std::string approximate_current_position() const;
-       std::string approximate_current_delta() const;
+       std::string position_string() const;
+       std::string delta_string() const;
 
   private:
        void parse_ltc(const pframes_t, const Sample* const, const samplecnt_t);
@@ -427,7 +419,7 @@ class LIBARDOUR_API MIDIClock_Slave : public Slave {
        bool give_slave_full_control_over_transport_speed() const { return true; }
 
        void set_bandwidth (double a_bandwith) { bandwidth = a_bandwith; }
-       std::string approximate_current_delta() const;
+       std::string delta_string () const;
 
   protected:
        ISlaveSessionProxy* session;
index 2ec5a0f4aa740041cf0d0762b35b3b7de2756279..b6abece1f0ab84830565da836d27bb6c093c8d0e 100644 (file)
@@ -140,7 +140,7 @@ public:
        int can_internal_playback_seek (samplecnt_t);
        int internal_playback_seek (samplecnt_t);
        void non_realtime_locate (samplepos_t);
-       void non_realtime_speed_change ();
+       void realtime_handle_transport_stopped ();
        int overwrite_existing_buffers ();
        samplecnt_t get_captured_samples (uint32_t n = 0) const;
        void transport_looped (samplepos_t);
diff --git a/libs/ardour/ardour/transport_master.h b/libs/ardour/ardour/transport_master.h
new file mode 100644 (file)
index 0000000..9da0378
--- /dev/null
@@ -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__ */
diff --git a/libs/ardour/ardour/transport_master_manager.h b/libs/ardour/ardour/transport_master_manager.h
new file mode 100644 (file)
index 0000000..4c4159c
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 Paul Davis (paul@linuxaudiosystems.com)
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __ardour_transport_master_manager_h__
+#define __ardour_transport_master_manager_h__
+
+#include <string>
+
+#include <boost/noncopyable.hpp>
+
+#include "ardour/transport_master.h"
+#include "ardour/types.h"
+
+namespace ARDOUR {
+
+class UI_TransportMaster;
+
+class LIBARDOUR_API TransportMasterManager : public boost::noncopyable
+{
+  public:
+       ~TransportMasterManager ();
+
+       int init ();
+
+       static TransportMasterManager& instance();
+
+       typedef std::list<boost::shared_ptr<TransportMaster> > TransportMasters;
+
+       int add (SyncSource type, std::string const & name);
+       int remove (std::string const & name);
+       void clear ();
+
+       PBD::Signal1<void,boost::shared_ptr<TransportMaster> > Added;
+       PBD::Signal1<void,boost::shared_ptr<TransportMaster> > Removed; // null argument means "clear"
+
+       double pre_process_transport_masters (pframes_t, samplepos_t session_transport_position);
+
+       double get_current_speed_in_process_context() const { return _master_speed; }
+       samplepos_t get_current_position_in_process_context() const { return _master_position; }
+
+       boost::shared_ptr<TransportMaster> current() const { return _current_master; }
+       int set_current (boost::shared_ptr<TransportMaster>);
+       int set_current (SyncSource);
+       int set_current (std::string const &);
+
+       PBD::Signal2<void,boost::shared_ptr<TransportMaster>, boost::shared_ptr<TransportMaster> > CurrentChanged;
+
+       int set_state (XMLNode const &, int);
+       XMLNode& get_state();
+
+       void set_session (Session*);
+       Session* session() const { return _session; }
+
+       bool master_invalid_this_cycle() const { return _master_invalid_this_cycle; }
+
+       boost::shared_ptr<TransportMaster> master_by_type (SyncSource src) const;
+       boost::shared_ptr<TransportMaster> master_by_name (std::string const &) const;
+
+       TransportMasters const & transport_masters() const { return _transport_masters; }
+
+       static const std::string state_node_name;
+
+  private:
+       TransportMasterManager();
+
+       TransportMasters      _transport_masters;
+       mutable Glib::Threads::RWLock  lock;
+       double                _master_speed;
+       samplepos_t           _master_position;
+       boost::shared_ptr<TransportMaster>    _current_master;
+       Session* _session;
+
+       bool _master_invalid_this_cycle;
+
+       // a DLL to chase the transport master
+
+       int    transport_dll_initstate;
+       double t0; /// time at the beginning of ???
+       double t1; /// calculated end of the ???
+       double e2; /// second order loop error
+       double bandwidth; /// DLL filter bandwidth
+       double b, c, omega; /// DLL filter coefficients
+
+       void init_transport_master_dll (double speed, samplepos_t pos);
+       int master_dll_initstate;
+
+       static TransportMasterManager* _instance;
+
+       int add_locked (boost::shared_ptr<TransportMaster>);
+       double compute_matching_master_speed (pframes_t nframes, samplepos_t, bool& locate_required);
+       int set_current_locked (boost::shared_ptr<TransportMaster>);
+
+       PBD::ScopedConnection config_connection;
+       void parameter_changed (std::string const & what);
+};
+
+} // namespace ARDOUR
+
+#endif /* __ardour_transport_master_manager_h__ */
index 366e3031862dcb4efa5c50dc6f7516adcc79fcd6..635b78334b4c58a64361f8c90f96a2185f4ed6d7 100644 (file)
@@ -53,696 +53,712 @@ typedef int intptr_t;
 
 namespace ARDOUR {
 
-       class Source;
-       class AudioSource;
-       class Route;
-       class Region;
-       class Stripable;
-       class VCA;
-       class AutomationControl;
-       class SlavableAutomationControl;
-
-       typedef float    Sample;
-       typedef float    pan_t;
-       typedef float    gain_t;
-       typedef uint32_t layer_t;
-       typedef uint64_t microseconds_t;
-       typedef uint32_t pframes_t;
-
-       /* rebind Temporal position types into ARDOUR namespace */
-       typedef Temporal::samplecnt_t samplecnt_t;
-       typedef Temporal::samplepos_t samplepos_t;
-       typedef Temporal::sampleoffset_t sampleoffset_t;
-
-       static const layer_t    max_layer    = UINT32_MAX;
-
-       // a set of (time) intervals: first of pair is the offset of the start within the region, second is the offset of the end
-       typedef std::list<std::pair<sampleoffset_t, sampleoffset_t> > AudioIntervalResult;
-       // associate a set of intervals with regions (e.g. for silence detection)
-       typedef std::map<boost::shared_ptr<ARDOUR::Region>,AudioIntervalResult> AudioIntervalMap;
-
-       typedef std::list<boost::shared_ptr<Region> > RegionList;
-
-       struct IOChange {
-
-               enum Type {
-                       NoChange = 0,
-                       ConfigurationChanged = 0x1,
-                       ConnectionsChanged = 0x2
-               } type;
-
-               IOChange () : type (NoChange) {}
-               IOChange (Type t) : type (t) {}
-
-               /** channel count of IO before a ConfigurationChanged, if appropriate */
-               ARDOUR::ChanCount before;
-               /** channel count of IO after a ConfigurationChanged, if appropriate */
-               ARDOUR::ChanCount after;
-       };
-
-       /* policies for inserting/pasting material where overlaps
-          might be an issue.
-       */
-
-       enum InsertMergePolicy {
-               InsertMergeReject,  ///< no overlaps allowed
-               InsertMergeRelax,   ///< we just don't care about overlaps
-               InsertMergeReplace, ///< replace old with new
-               InsertMergeTruncateExisting, ///< shorten existing to avoid overlap
-               InsertMergeTruncateAddition, ///< shorten new to avoid overlap
-               InsertMergeExtend   ///< extend new (or old) to the range of old+new
-       };
-
-       /** See evoral/Parameter.hpp
-        *
-        * When you add things here, you REALLY SHOULD add a case clause to
-        * the constructor of ParameterDescriptor, unless the Controllables
-        * that the enum refers to are completely standard (0-1.0 range, 0.0 as
-        * normal, non-toggled, non-enumerated). Anything else needs to be
-        * added there so that things that try to represent them can do so
-        * with as much information as possible.
-        */
-       enum AutomationType {
-               NullAutomation,
-               GainAutomation,
-               PanAzimuthAutomation,
-               PanElevationAutomation,
-               PanWidthAutomation,
-               PanFrontBackAutomation,
-               PanLFEAutomation,
-               PluginAutomation,
-               PluginPropertyAutomation,
-               SoloAutomation,
-               SoloIsolateAutomation,
-               SoloSafeAutomation,
-               MuteAutomation,
-               MidiCCAutomation,
-               MidiPgmChangeAutomation,
-               MidiPitchBenderAutomation,
-               MidiChannelPressureAutomation,
-               MidiNotePressureAutomation,
-               MidiSystemExclusiveAutomation,
-               FadeInAutomation,
-               FadeOutAutomation,
-               EnvelopeAutomation,
-               RecEnableAutomation,
-               RecSafeAutomation,
-               TrimAutomation,
-               PhaseAutomation,
-               MonitoringAutomation,
-               BusSendLevel,
-               BusSendEnable,
-
-               /* used only by Controllable Descriptor to access send parameters */
-
-               SendLevelAutomation,
-               SendEnableAutomation,
-               SendAzimuthAutomation,
-       };
-
-       enum AutoState {
-               Off   = 0x00,
-               Write = 0x01,
-               Touch = 0x02,
-               Play  = 0x04,
-               Latch = 0x08
-       };
-
-       std::string auto_state_to_string (AutoState);
-       AutoState string_to_auto_state (std::string);
-
-       enum AlignStyle {
-               CaptureTime,
-               ExistingMaterial
-       };
-
-       enum AlignChoice {
-               UseCaptureTime,
-               UseExistingMaterial,
-               Automatic
-       };
-
-       enum MeterPoint {
-               MeterInput,
-               MeterPreFader,
-               MeterPostFader,
-               MeterOutput,
-               MeterCustom
-       };
-
-       enum DiskIOPoint {
-               DiskIOPreFader,  /* after the trim control, but before other processors */
-               DiskIOPostFader, /* before the main outs, after other processors */
-               DiskIOCustom,   /* up to the user. Caveat Emptor! */
-       };
-
-       enum MeterType {
-               MeterMaxSignal = 0x0001,
-               MeterMaxPeak   = 0x0002,
-               MeterPeak      = 0x0004,
-               MeterKrms      = 0x0008,
-               MeterK20       = 0x0010,
-               MeterK14       = 0x0020,
-               MeterIEC1DIN   = 0x0040,
-               MeterIEC1NOR   = 0x0080,
-               MeterIEC2BBC   = 0x0100,
-               MeterIEC2EBU   = 0x0200,
-               MeterVU        = 0x0400,
-               MeterK12       = 0x0800,
-               MeterPeak0dB   = 0x1000,
-               MeterMCP       = 0x2000
-       };
-
-       enum TrackMode {
-               Normal,
-               NonLayered,
-               Destructive
-       };
-
-       enum NoteMode {
-               Sustained,
-               Percussive
-       };
-
-       enum ChannelMode {
-               AllChannels = 0, ///< Pass through all channel information unmodified
-               FilterChannels,  ///< Ignore events on certain channels
-               ForceChannel     ///< Force all events to a certain channel
-       };
-
-       enum ColorMode {
-               MeterColors = 0,
-               ChannelColors,
-               TrackColor
-       };
-
-       enum RoundMode {
-               RoundDownMaybe  = -2,  ///< Round down only if necessary
-               RoundDownAlways = -1,  ///< Always round down, even if on a division
-               RoundNearest    = 0,   ///< Round to nearest
-               RoundUpAlways   = 1,   ///< Always round up, even if on a division
-               RoundUpMaybe    = 2    ///< Round up only if necessary
-       };
-
-       enum SnapPref {
-               SnapToAny_Visual    = 0, /**< Snap to the editor's visual snap
-                                         * (incoprorating snap prefs and the current zoom scaling)
-                                         * this defines the behavior for visual mouse drags, for example */
-
-               SnapToGrid_Scaled   = 1, /**< Snap to the selected grid quantization with visual scaling.
-                                         * Ignores other snap preferences (markers, regions, etc)
-                                         * this defines the behavior for nudging the playhead to next/prev grid, for example */
-
-               SnapToGrid_Unscaled = 2, /**< Snap to the selected grid quantization.
-                                         * If one is selected, and ignore any visual scaling
-                                         * this is the behavior for automated processes like "snap regions to grid"
-                                         * but note that midi quantization uses its own mechanism, not the grid */
-       };
-
-       class AnyTime {
-       public:
-               enum Type {
-                       Timecode,
-                       BBT,
-                       Samples,
-                       Seconds
-               };
-
-               Type type;
-
-               Timecode::Time     timecode;
-               Timecode::BBT_Time bbt;
-
-               union {
-                       samplecnt_t     samples;
-                       double         seconds;
-               };
-
-               AnyTime() { type = Samples; samples = 0; }
-
-               bool operator== (AnyTime const & other) const {
-                       if (type != other.type) { return false; }
-
-                       switch (type) {
-                         case Timecode:
-                               return timecode == other.timecode;
-                         case BBT:
-                               return bbt == other.bbt;
-                         case Samples:
-                               return samples == other.samples;
-                         case Seconds:
-                               return seconds == other.seconds;
-                       }
-                       return false; // get rid of warning
-               }
-
-               bool not_zero() const
-               {
-                       switch (type) {
-                         case Timecode:
-                               return timecode.hours != 0 || timecode.minutes != 0 ||
-                                      timecode.seconds != 0 || timecode.frames != 0;
-                         case BBT:
-                               return bbt.bars != 0 || bbt.beats != 0 || bbt.ticks != 0;
-                         case Samples:
-                               return samples != 0;
-                         case Seconds:
-                               return seconds != 0;
-                       }
-
-                       abort(); /* NOTREACHED */
-                       return false;
-               }
-       };
-
-       /* used for translating audio samples to an exact musical position using a note divisor.
-          an exact musical position almost never falls exactly on an audio sample, but for sub-sample
-          musical accuracy we need to derive exact musical locations from a sample position
-          the division follows TempoMap::exact_beat_at_sample().
-          division
-          -1       musical location is the bar closest to sample
-           0       musical location is the musical position of the sample
-           1       musical location is the BBT beat closest to sample
-           n       musical location is the quarter-note division n closest to sample
-       */
-       struct MusicSample {
-               samplepos_t sample;
-               int32_t    division;
-
-               MusicSample (samplepos_t f, int32_t d) : sample (f), division (d) {}
-
-               void set (samplepos_t f, int32_t d) {sample = f; division = d; }
-
-               MusicSample operator- (MusicSample other) { return MusicSample (sample - other.sample, 0); }
-       };
-
-       /* XXX: slightly unfortunate that there is this and Evoral::Range<>,
-          but this has a uint32_t id which Evoral::Range<> does not.
-       */
-       struct AudioRange {
-               samplepos_t start;
-               samplepos_t end;
-               uint32_t id;
-
-               AudioRange (samplepos_t s, samplepos_t e, uint32_t i) : start (s), end (e) , id (i) {}
-
-               samplecnt_t length() const { return end - start + 1; }
-
-               bool operator== (const AudioRange& other) const {
-                       return start == other.start && end == other.end && id == other.id;
-               }
-
-               bool equal (const AudioRange& other) const {
-                       return start == other.start && end == other.end;
-               }
+class Source;
+class AudioSource;
+class Route;
+class Region;
+class Stripable;
+class VCA;
+class AutomationControl;
+class SlavableAutomationControl;
+
+typedef float    Sample;
+typedef float    pan_t;
+typedef float    gain_t;
+typedef uint32_t layer_t;
+typedef uint64_t microseconds_t;
+typedef uint32_t pframes_t;
+
+/* rebind Temporal position types into ARDOUR namespace */
+typedef Temporal::samplecnt_t samplecnt_t;
+typedef Temporal::samplepos_t samplepos_t;
+typedef Temporal::sampleoffset_t sampleoffset_t;
+
+static const layer_t    max_layer    = UINT32_MAX;
+
+// a set of (time) intervals: first of pair is the offset of the start within the region, second is the offset of the end
+typedef std::list<std::pair<sampleoffset_t, sampleoffset_t> > AudioIntervalResult;
+// associate a set of intervals with regions (e.g. for silence detection)
+typedef std::map<boost::shared_ptr<ARDOUR::Region>,AudioIntervalResult> AudioIntervalMap;
+
+typedef std::list<boost::shared_ptr<Region> > RegionList;
+
+struct IOChange {
+
+       enum Type {
+               NoChange = 0,
+               ConfigurationChanged = 0x1,
+               ConnectionsChanged = 0x2
+       } type;
+
+       IOChange () : type (NoChange) {}
+       IOChange (Type t) : type (t) {}
+
+       /** channel count of IO before a ConfigurationChanged, if appropriate */
+       ARDOUR::ChanCount before;
+       /** channel count of IO after a ConfigurationChanged, if appropriate */
+       ARDOUR::ChanCount after;
+};
+
+/* policies for inserting/pasting material where overlaps
+   might be an issue.
+*/
 
-               Evoral::OverlapType coverage (samplepos_t s, samplepos_t e) const {
-                       return Evoral::coverage (start, end, s, e);
+enum InsertMergePolicy {
+       InsertMergeReject,  ///< no overlaps allowed
+       InsertMergeRelax,   ///< we just don't care about overlaps
+       InsertMergeReplace, ///< replace old with new
+       InsertMergeTruncateExisting, ///< shorten existing to avoid overlap
+       InsertMergeTruncateAddition, ///< shorten new to avoid overlap
+       InsertMergeExtend   ///< extend new (or old) to the range of old+new
+};
+
+/** See evoral/Parameter.hpp
+ *
+ * When you add things here, you REALLY SHOULD add a case clause to
+ * the constructor of ParameterDescriptor, unless the Controllables
+ * that the enum refers to are completely standard (0-1.0 range, 0.0 as
+ * normal, non-toggled, non-enumerated). Anything else needs to be
+ * added there so that things that try to represent them can do so
+ * with as much information as possible.
+ */
+enum AutomationType {
+       NullAutomation,
+       GainAutomation,
+       PanAzimuthAutomation,
+       PanElevationAutomation,
+       PanWidthAutomation,
+       PanFrontBackAutomation,
+       PanLFEAutomation,
+       PluginAutomation,
+       PluginPropertyAutomation,
+       SoloAutomation,
+       SoloIsolateAutomation,
+       SoloSafeAutomation,
+       MuteAutomation,
+       MidiCCAutomation,
+       MidiPgmChangeAutomation,
+       MidiPitchBenderAutomation,
+       MidiChannelPressureAutomation,
+       MidiNotePressureAutomation,
+       MidiSystemExclusiveAutomation,
+       FadeInAutomation,
+       FadeOutAutomation,
+       EnvelopeAutomation,
+       RecEnableAutomation,
+       RecSafeAutomation,
+       TrimAutomation,
+       PhaseAutomation,
+       MonitoringAutomation,
+       BusSendLevel,
+       BusSendEnable,
+
+       /* used only by Controllable Descriptor to access send parameters */
+
+       SendLevelAutomation,
+       SendEnableAutomation,
+       SendAzimuthAutomation,
+};
+
+enum AutoState {
+       Off   = 0x00,
+       Write = 0x01,
+       Touch = 0x02,
+       Play  = 0x04,
+       Latch = 0x08
+};
+
+std::string auto_state_to_string (AutoState);
+AutoState string_to_auto_state (std::string);
+
+enum AlignStyle {
+       CaptureTime,
+       ExistingMaterial
+};
+
+enum AlignChoice {
+       UseCaptureTime,
+       UseExistingMaterial,
+       Automatic
+};
+
+enum MeterPoint {
+       MeterInput,
+       MeterPreFader,
+       MeterPostFader,
+       MeterOutput,
+       MeterCustom
+};
+
+enum DiskIOPoint {
+       DiskIOPreFader,  /* after the trim control, but before other processors */
+       DiskIOPostFader, /* before the main outs, after other processors */
+       DiskIOCustom,   /* up to the user. Caveat Emptor! */
+};
+
+enum MeterType {
+       MeterMaxSignal = 0x0001,
+       MeterMaxPeak   = 0x0002,
+       MeterPeak      = 0x0004,
+       MeterKrms      = 0x0008,
+       MeterK20       = 0x0010,
+       MeterK14       = 0x0020,
+       MeterIEC1DIN   = 0x0040,
+       MeterIEC1NOR   = 0x0080,
+       MeterIEC2BBC   = 0x0100,
+       MeterIEC2EBU   = 0x0200,
+       MeterVU        = 0x0400,
+       MeterK12       = 0x0800,
+       MeterPeak0dB   = 0x1000,
+       MeterMCP       = 0x2000
+};
+
+enum TrackMode {
+       Normal,
+       NonLayered,
+       Destructive
+};
+
+enum NoteMode {
+       Sustained,
+       Percussive
+};
+
+enum ChannelMode {
+       AllChannels = 0, ///< Pass through all channel information unmodified
+       FilterChannels,  ///< Ignore events on certain channels
+       ForceChannel     ///< Force all events to a certain channel
+};
+
+enum ColorMode {
+       MeterColors = 0,
+       ChannelColors,
+       TrackColor
+};
+
+enum RoundMode {
+       RoundDownMaybe  = -2,  ///< Round down only if necessary
+       RoundDownAlways = -1,  ///< Always round down, even if on a division
+       RoundNearest    = 0,   ///< Round to nearest
+       RoundUpAlways   = 1,   ///< Always round up, even if on a division
+       RoundUpMaybe    = 2    ///< Round up only if necessary
+};
+
+enum SnapPref {
+       SnapToAny_Visual    = 0, /**< Snap to the editor's visual snap
+                                 * (incoprorating snap prefs and the current zoom scaling)
+                                 * this defines the behavior for visual mouse drags, for example */
+
+       SnapToGrid_Scaled   = 1, /**< Snap to the selected grid quantization with visual scaling.
+                                 * Ignores other snap preferences (markers, regions, etc)
+                                 * this defines the behavior for nudging the playhead to next/prev grid, for example */
+
+       SnapToGrid_Unscaled = 2, /**< Snap to the selected grid quantization.
+                                 * If one is selected, and ignore any visual scaling
+                                 * this is the behavior for automated processes like "snap regions to grid"
+                                 * but note that midi quantization uses its own mechanism, not the grid */
+};
+
+class AnyTime {
+                                        public:
+       enum Type {
+               Timecode,
+               BBT,
+               Samples,
+               Seconds
+       };
+
+       Type type;
+
+       Timecode::Time     timecode;
+       Timecode::BBT_Time bbt;
+
+       union {
+               samplecnt_t     samples;
+               double         seconds;
+       };
+
+       AnyTime() { type = Samples; samples = 0; }
+
+       bool operator== (AnyTime const & other) const {
+               if (type != other.type) { return false; }
+
+               switch (type) {
+               case Timecode:
+                       return timecode == other.timecode;
+               case BBT:
+                       return bbt == other.bbt;
+               case Samples:
+                       return samples == other.samples;
+               case Seconds:
+                       return seconds == other.seconds;
                }
-       };
-
-       struct MusicRange {
-               Timecode::BBT_Time start;
-               Timecode::BBT_Time end;
-               uint32_t id;
-
-               MusicRange (Timecode::BBT_Time& s, Timecode::BBT_Time& e, uint32_t i)
-                       : start (s), end (e), id (i) {}
-
-               bool operator== (const MusicRange& other) const {
-                       return start == other.start && end == other.end && id == other.id;
+               return false; // get rid of warning
+       }
+
+       bool not_zero() const
+       {
+               switch (type) {
+               case Timecode:
+                       return timecode.hours != 0 || timecode.minutes != 0 ||
+                               timecode.seconds != 0 || timecode.frames != 0;
+               case BBT:
+                       return bbt.bars != 0 || bbt.beats != 0 || bbt.ticks != 0;
+               case Samples:
+                       return samples != 0;
+               case Seconds:
+                       return seconds != 0;
                }
 
-               bool equal (const MusicRange& other) const {
-                       return start == other.start && end == other.end;
-               }
-       };
-
-       /*
-           Slowest = 6.6dB/sec falloff at update rate of 40ms
-           Slow    = 6.8dB/sec falloff at update rate of 40ms
-       */
-
-       enum MeterFalloff {
-               MeterFalloffOff = 0,
-               MeterFalloffSlowest = 1,
-               MeterFalloffSlow = 2,
-               MeterFalloffSlowish = 3,
-               MeterFalloffModerate = 4,
-               MeterFalloffMedium = 5,
-               MeterFalloffFast = 6,
-               MeterFalloffFaster = 7,
-               MeterFalloffFastest = 8,
-       };
-
-       enum MeterHold {
-               MeterHoldOff = 0,
-               MeterHoldShort = 40,
-               MeterHoldMedium = 100,
-               MeterHoldLong = 200
-       };
-
-       enum EditMode {
-               Slide,
-               Splice,
-               Ripple,
-               Lock
-       };
-
-       enum RegionSelectionAfterSplit {
-               None = 0,
-               NewlyCreatedLeft = 1,  // bit 0
-               NewlyCreatedRight = 2, // bit 1
-               NewlyCreatedBoth = 3,
-               Existing = 4,          // bit 2
-               ExistingNewlyCreatedLeft = 5,
-               ExistingNewlyCreatedRight = 6,
-               ExistingNewlyCreatedBoth = 7
-       };
-
-       enum RegionPoint {
-               Start,
-               End,
-               SyncPoint
-       };
-
-       enum Placement {
-               PreFader,
-               PostFader
-       };
-
-       enum MonitorModel {
-               HardwareMonitoring, ///< JACK does monitoring
-               SoftwareMonitoring, ///< Ardour does monitoring
-               ExternalMonitoring  ///< we leave monitoring to the audio hardware
-       };
-
-       enum MonitorChoice {
-               MonitorAuto = 0,
-               MonitorInput = 0x1,
-               MonitorDisk = 0x2,
-               MonitorCue = 0x3,
-       };
-
-       enum MonitorState {
-               MonitoringSilence = 0x1,
-               MonitoringInput = 0x2,
-               MonitoringDisk = 0x4,
-               MonitoringCue = 0x6,
-       };
-
-       enum MeterState {
-               MeteringInput, ///< meter the input IO, regardless of what is going through the route
-               MeteringRoute  ///< meter what is going through the route
-       };
-
-       enum VUMeterStandard {
-               MeteringVUfrench,   // 0VU = -2dBu
-               MeteringVUamerican, // 0VU =  0dBu
-               MeteringVUstandard, // 0VU = +4dBu
-               MeteringVUeight     // 0VU = +8dBu
-       };
-
-       enum MeterLineUp {
-               MeteringLineUp24,
-               MeteringLineUp20,
-               MeteringLineUp18,
-               MeteringLineUp15
-       };
-
-       enum PFLPosition {
-               /** PFL signals come from before pre-fader processors */
-               PFLFromBeforeProcessors,
-               /** PFL signals come pre-fader but after pre-fader processors */
-               PFLFromAfterProcessors
-       };
-
-       enum AFLPosition {
-               /** AFL signals come post-fader and before post-fader processors */
-               AFLFromBeforeProcessors,
-               /** AFL signals come post-fader but after post-fader processors */
-               AFLFromAfterProcessors
-       };
-
-       enum ClockDeltaMode {
-               NoDelta,
-               DeltaEditPoint,
-               DeltaOriginMarker
-       };
-
-       enum DenormalModel {
-               DenormalNone,
-               DenormalFTZ,
-               DenormalDAZ,
-               DenormalFTZDAZ
-       };
-
-       enum LayerModel {
-               LaterHigher,
-               Manual
-       };
-
-       enum ListenPosition {
-               AfterFaderListen,
-               PreFaderListen
-       };
-
-       enum AutoConnectOption {
-               ManualConnect = 0x0,
-               AutoConnectPhysical = 0x1,
-               AutoConnectMaster = 0x2
-       };
-
-       enum TracksAutoNamingRule {
-               UseDefaultNames = 0x1,
-               NameAfterDriver = 0x2
-       };
-
-       enum SampleFormat {
-               FormatFloat = 0,
-               FormatInt24,
-               FormatInt16
-       };
-
-       int format_data_width (ARDOUR::SampleFormat);
-
-       enum CDMarkerFormat {
-               CDMarkerNone,
-               CDMarkerCUE,
-               CDMarkerTOC,
-               MP4Chaps
-       };
-
-       enum HeaderFormat {
-               BWF,
-               WAVE,
-               WAVE64,
-               CAF,
-               AIFF,
-               iXML,
-               RF64,
-               RF64_WAV,
-               MBWF,
-       };
+               abort(); /* NOTREACHED */
+               return false;
+       }
+};
+
+/* used for translating audio samples to an exact musical position using a note divisor.
+   an exact musical position almost never falls exactly on an audio sample, but for sub-sample
+   musical accuracy we need to derive exact musical locations from a sample position
+   the division follows TempoMap::exact_beat_at_sample().
+   division
+   -1       musical location is the bar closest to sample
+   0       musical location is the musical position of the sample
+   1       musical location is the BBT beat closest to sample
+   n       musical location is the quarter-note division n closest to sample
+*/
+struct MusicSample {
+       samplepos_t sample;
+       int32_t    division;
 
-       struct PeakData {
-               typedef Sample PeakDatum;
+       MusicSample (samplepos_t f, int32_t d) : sample (f), division (d) {}
 
-               PeakDatum min;
-               PeakDatum max;
-       };
+       void set (samplepos_t f, int32_t d) {sample = f; division = d; }
 
-       enum RunContext {
-               ButlerContext = 0,
-               TransportContext,
-               ExportContext
-       };
+       MusicSample operator- (MusicSample other) { return MusicSample (sample - other.sample, 0); }
+};
 
-       enum SyncSource {
-               /* These are "synonyms". It is important for JACK to be first
-                  both here and in enums.cc, so that the string "JACK" is
-                  correctly recognized in older session and preference files.
-               */
-               JACK = 0,
-               Engine = 0,
-               MTC,
-               MIDIClock,
-               LTC
-       };
-
-       enum ShuttleBehaviour {
-               Sprung,
-               Wheel
-       };
-
-       enum ShuttleUnits {
-               Percentage,
-               Semitones
-       };
+/* XXX: slightly unfortunate that there is this and Evoral::Range<>,
+   but this has a uint32_t id which Evoral::Range<> does not.
+*/
+struct AudioRange {
+       samplepos_t start;
+       samplepos_t end;
+       uint32_t id;
 
-       typedef std::vector<boost::shared_ptr<Source> > SourceList;
+       AudioRange (samplepos_t s, samplepos_t e, uint32_t i) : start (s), end (e) , id (i) {}
 
-       enum SrcQuality {
-               SrcBest,
-               SrcGood,
-               SrcQuick,
-               SrcFast,
-               SrcFastest
-       };
+       samplecnt_t length() const { return end - start + 1; }
 
-       typedef std::list<samplepos_t> AnalysisFeatureList;
+       bool operator== (const AudioRange& other) const {
+               return start == other.start && end == other.end && id == other.id;
+       }
 
-       typedef std::list<boost::shared_ptr<Route> > RouteList;
-       typedef std::list<boost::shared_ptr<Stripable> > StripableList;
-       typedef std::list<boost::weak_ptr  <Route> > WeakRouteList;
-       typedef std::list<boost::weak_ptr  <Stripable> > WeakStripableList;
-       typedef std::list<boost::shared_ptr<AutomationControl> > ControlList;
-       typedef std::list<boost::shared_ptr<SlavableAutomationControl> > SlavableControlList;
-       typedef std::set <boost::shared_ptr<AutomationControl> > AutomationControlSet;
+       bool equal (const AudioRange& other) const {
+               return start == other.start && end == other.end;
+       }
 
-       typedef std::list<boost::shared_ptr<VCA> > VCAList;
+       Evoral::OverlapType coverage (samplepos_t s, samplepos_t e) const {
+               return Evoral::coverage (start, end, s, e);
+       }
+};
 
-       class Bundle;
-       typedef std::vector<boost::shared_ptr<Bundle> > BundleList;
+struct MusicRange {
+       Timecode::BBT_Time start;
+       Timecode::BBT_Time end;
+       uint32_t id;
 
-       enum RegionEquivalence {
-               Exact,
-               Enclosed,
-               Overlap
-       };
-
-       enum WaveformScale {
-               Linear,
-               Logarithmic
-       };
+       MusicRange (Timecode::BBT_Time& s, Timecode::BBT_Time& e, uint32_t i)
+               : start (s), end (e), id (i) {}
 
-       enum WaveformShape {
-               Traditional,
-               Rectified
-       };
+       bool operator== (const MusicRange& other) const {
+               return start == other.start && end == other.end && id == other.id;
+       }
 
-       struct CleanupReport {
-               std::vector<std::string> paths;
-               size_t                   space;
-       };
+       bool equal (const MusicRange& other) const {
+               return start == other.start && end == other.end;
+       }
+};
 
-       enum PositionLockStyle {
-               AudioTime,
-               MusicTime
-       };
+/*
+  Slowest = 6.6dB/sec falloff at update rate of 40ms
+  Slow    = 6.8dB/sec falloff at update rate of 40ms
+*/
 
-       /** A struct used to describe changes to processors in a route.
-        *  This is useful because objects that respond to a change in processors
-        *  can optimise what work they do based on details of what has changed.
+enum MeterFalloff {
+       MeterFalloffOff = 0,
+       MeterFalloffSlowest = 1,
+       MeterFalloffSlow = 2,
+       MeterFalloffSlowish = 3,
+       MeterFalloffModerate = 4,
+       MeterFalloffMedium = 5,
+       MeterFalloffFast = 6,
+       MeterFalloffFaster = 7,
+       MeterFalloffFastest = 8,
+};
+
+enum MeterHold {
+       MeterHoldOff = 0,
+       MeterHoldShort = 40,
+       MeterHoldMedium = 100,
+       MeterHoldLong = 200
+};
+
+enum EditMode {
+       Slide,
+       Splice,
+       Ripple,
+       Lock
+};
+
+enum RegionSelectionAfterSplit {
+       None = 0,
+       NewlyCreatedLeft = 1,  // bit 0
+       NewlyCreatedRight = 2, // bit 1
+       NewlyCreatedBoth = 3,
+       Existing = 4,          // bit 2
+       ExistingNewlyCreatedLeft = 5,
+       ExistingNewlyCreatedRight = 6,
+       ExistingNewlyCreatedBoth = 7
+};
+
+enum RegionPoint {
+       Start,
+       End,
+       SyncPoint
+};
+
+enum Placement {
+       PreFader,
+       PostFader
+};
+
+enum MonitorModel {
+       HardwareMonitoring, ///< JACK does monitoring
+       SoftwareMonitoring, ///< Ardour does monitoring
+       ExternalMonitoring  ///< we leave monitoring to the audio hardware
+};
+
+enum MonitorChoice {
+       MonitorAuto = 0,
+       MonitorInput = 0x1,
+       MonitorDisk = 0x2,
+       MonitorCue = 0x3,
+};
+
+enum MonitorState {
+       MonitoringSilence = 0x1,
+       MonitoringInput = 0x2,
+       MonitoringDisk = 0x4,
+       MonitoringCue = 0x6,
+};
+
+enum MeterState {
+       MeteringInput, ///< meter the input IO, regardless of what is going through the route
+       MeteringRoute  ///< meter what is going through the route
+};
+
+enum VUMeterStandard {
+       MeteringVUfrench,   // 0VU = -2dBu
+       MeteringVUamerican, // 0VU =  0dBu
+       MeteringVUstandard, // 0VU = +4dBu
+       MeteringVUeight     // 0VU = +8dBu
+};
+
+enum MeterLineUp {
+       MeteringLineUp24,
+       MeteringLineUp20,
+       MeteringLineUp18,
+       MeteringLineUp15
+};
+
+enum PFLPosition {
+       /** PFL signals come from before pre-fader processors */
+       PFLFromBeforeProcessors,
+       /** PFL signals come pre-fader but after pre-fader processors */
+       PFLFromAfterProcessors
+};
+
+enum AFLPosition {
+       /** AFL signals come post-fader and before post-fader processors */
+       AFLFromBeforeProcessors,
+       /** AFL signals come post-fader but after post-fader processors */
+       AFLFromAfterProcessors
+};
+
+enum ClockDeltaMode {
+       NoDelta,
+       DeltaEditPoint,
+       DeltaOriginMarker
+};
+
+enum DenormalModel {
+       DenormalNone,
+       DenormalFTZ,
+       DenormalDAZ,
+       DenormalFTZDAZ
+};
+
+enum LayerModel {
+       LaterHigher,
+       Manual
+};
+
+enum ListenPosition {
+       AfterFaderListen,
+       PreFaderListen
+};
+
+enum AutoConnectOption {
+       ManualConnect = 0x0,
+       AutoConnectPhysical = 0x1,
+       AutoConnectMaster = 0x2
+};
+
+enum TracksAutoNamingRule {
+       UseDefaultNames = 0x1,
+       NameAfterDriver = 0x2
+};
+
+enum SampleFormat {
+       FormatFloat = 0,
+       FormatInt24,
+       FormatInt16
+};
+
+int format_data_width (ARDOUR::SampleFormat);
+
+enum CDMarkerFormat {
+       CDMarkerNone,
+       CDMarkerCUE,
+       CDMarkerTOC,
+       MP4Chaps
+};
+
+enum HeaderFormat {
+       BWF,
+       WAVE,
+       WAVE64,
+       CAF,
+       AIFF,
+       iXML,
+       RF64,
+       RF64_WAV,
+       MBWF,
+};
+
+struct PeakData {
+       typedef Sample PeakDatum;
+
+       PeakDatum min;
+       PeakDatum max;
+};
+
+enum RunContext {
+       ButlerContext = 0,
+       TransportContext,
+       ExportContext
+};
+
+enum SyncSource {
+       /* The first two are "synonyms". It is important for JACK to be first
+          both here and in enums.cc, so that the string "JACK" is
+          correctly recognized in older session and preference files.
        */
-       struct RouteProcessorChange {
-               enum Type {
-                       GeneralChange = 0x0,
-                       MeterPointChange = 0x1,
-                       RealTimeChange = 0x2
-               };
-
-               RouteProcessorChange () : type (GeneralChange), meter_visibly_changed (true)
-               {}
-
-               RouteProcessorChange (Type t) : type (t), meter_visibly_changed (true)
-               {}
-
-               RouteProcessorChange (Type t, bool m) : type (t), meter_visibly_changed (m)
-               {}
-
-               /** type of change; "GeneralChange" means anything could have changed */
-               Type type;
-               /** true if, when a MeterPointChange has occurred, the change is visible to the user */
-               bool meter_visibly_changed;
-       };
-
-       struct BusProfile {
-               uint32_t master_out_channels; /* how many channels for the master bus, 0: no master bus */
-       };
-
-       enum FadeShape {
-               FadeLinear,
-               FadeFast,
-               FadeSlow,
-               FadeConstantPower,
-               FadeSymmetric,
-       };
-
-       enum TransportState {
-               /* these values happen to match the constants used by JACK but
-                  this equality cannot be assumed.
-               */
-               TransportStopped = 0,
-               TransportRolling = 1,
-               TransportLooping = 2,
-               TransportStarting = 3,
-       };
-
-       enum PortFlags {
-               /* these values happen to match the constants used by JACK but
-                  this equality cannot be assumed.
-               */
-               IsInput = 0x1,
-               IsOutput = 0x2,
-               IsPhysical = 0x4,
-               CanMonitor = 0x8,
-               IsTerminal = 0x10,
-
-               /* non-JACK related flags */
-               Hidden = 0x20,
-               Shadow = 0x40
-       };
-
-       enum MidiPortFlags {
-               MidiPortMusic = 0x1,
-               MidiPortControl = 0x2,
-               MidiPortSelection = 0x4,
-               MidiPortVirtual = 0x8
-       };
-
-       struct LatencyRange {
-           uint32_t min; //< samples
-           uint32_t max; //< samples
-       };
-
-       enum BufferingPreset {
-               Small,
-               Medium,
-               Large,
-               Custom,
-       };
-
-       enum AutoReturnTarget {
-               LastLocate = 0x1,
-               RangeSelectionStart = 0x2,
-               Loop = 0x4,
-               RegionSelectionStart = 0x8,
-       };
-
-       enum PlaylistDisposition {
-               CopyPlaylist,
-               NewPlaylist,
-               SharePlaylist
-       };
-
-       enum MidiTrackNameSource {
-               SMFTrackNumber,
-               SMFTrackName,
-               SMFInstrumentName
-       };
-
-       enum MidiTempoMapDisposition {
-               SMFTempoIgnore,
-               SMFTempoUse,
-       };
-
-       struct CaptureInfo {
-               samplepos_t start;
-               samplecnt_t samples;
-       };
-
-       typedef std::vector<CaptureInfo*> CaptureInfos;
+       JACK = 0,
+       Engine = 0,
+       MTC,
+       MIDIClock,
+       LTC,
+};
+
+enum TransportRequestSource {
+       TRS_Engine,
+       TRS_MTC,
+       TRS_MIDIClock,
+       TRS_LTC,
+       TRS_MMC,
+       TRS_UI,
+};
+
+enum TransportRequestType {
+       TR_Stop   = 0x1,
+       TR_Start  = 0x2,
+       TR_Speed  = 0x4,
+       TR_Locate = 0x8
+};
+
+enum ShuttleBehaviour {
+       Sprung,
+       Wheel
+};
+
+enum ShuttleUnits {
+       Percentage,
+       Semitones
+};
+
+typedef std::vector<boost::shared_ptr<Source> > SourceList;
+
+enum SrcQuality {
+       SrcBest,
+       SrcGood,
+       SrcQuick,
+       SrcFast,
+       SrcFastest
+};
+
+typedef std::list<samplepos_t> AnalysisFeatureList;
+
+typedef std::list<boost::shared_ptr<Route> > RouteList;
+typedef std::list<boost::shared_ptr<Stripable> > StripableList;
+typedef std::list<boost::weak_ptr  <Route> > WeakRouteList;
+typedef std::list<boost::weak_ptr  <Stripable> > WeakStripableList;
+typedef std::list<boost::shared_ptr<AutomationControl> > ControlList;
+typedef std::list<boost::shared_ptr<SlavableAutomationControl> > SlavableControlList;
+typedef std::set <boost::shared_ptr<AutomationControl> > AutomationControlSet;
+
+typedef std::list<boost::shared_ptr<VCA> > VCAList;
+
+class Bundle;
+typedef std::vector<boost::shared_ptr<Bundle> > BundleList;
+
+enum RegionEquivalence {
+       Exact,
+       Enclosed,
+       Overlap
+};
+
+enum WaveformScale {
+       Linear,
+       Logarithmic
+};
+
+enum WaveformShape {
+       Traditional,
+       Rectified
+};
+
+struct CleanupReport {
+       std::vector<std::string> paths;
+       size_t                   space;
+};
+
+enum PositionLockStyle {
+       AudioTime,
+       MusicTime
+};
+
+/** A struct used to describe changes to processors in a route.
+ *  This is useful because objects that respond to a change in processors
+ *  can optimise what work they do based on details of what has changed.
+ */
+struct RouteProcessorChange {
+       enum Type {
+               GeneralChange = 0x0,
+               MeterPointChange = 0x1,
+               RealTimeChange = 0x2
+       };
+
+       RouteProcessorChange () : type (GeneralChange), meter_visibly_changed (true)
+       {}
+
+       RouteProcessorChange (Type t) : type (t), meter_visibly_changed (true)
+       {}
+
+       RouteProcessorChange (Type t, bool m) : type (t), meter_visibly_changed (m)
+       {}
+
+       /** type of change; "GeneralChange" means anything could have changed */
+       Type type;
+       /** true if, when a MeterPointChange has occurred, the change is visible to the user */
+       bool meter_visibly_changed;
+};
+
+struct BusProfile {
+       uint32_t master_out_channels; /* how many channels for the master bus, 0: no master bus */
+};
+
+enum FadeShape {
+       FadeLinear,
+       FadeFast,
+       FadeSlow,
+       FadeConstantPower,
+       FadeSymmetric,
+};
+
+enum TransportState {
+       /* these values happen to match the constants used by JACK but
+          this equality cannot be assumed.
+       */
+       TransportStopped = 0,
+       TransportRolling = 1,
+       TransportLooping = 2,
+       TransportStarting = 3,
+};
+
+enum PortFlags {
+       /* these values happen to match the constants used by JACK but
+          this equality cannot be assumed.
+       */
+       IsInput = 0x1,
+       IsOutput = 0x2,
+       IsPhysical = 0x4,
+       CanMonitor = 0x8,
+       IsTerminal = 0x10,
+
+       /* non-JACK related flags */
+       Hidden = 0x20,
+       Shadow = 0x40
+};
+
+enum MidiPortFlags {
+       MidiPortMusic = 0x1,
+       MidiPortControl = 0x2,
+       MidiPortSelection = 0x4,
+       MidiPortVirtual = 0x8
+};
+
+struct LatencyRange {
+       uint32_t min; //< samples
+       uint32_t max; //< samples
+};
+
+enum BufferingPreset {
+       Small,
+       Medium,
+       Large,
+       Custom,
+};
+
+enum AutoReturnTarget {
+       LastLocate = 0x1,
+       RangeSelectionStart = 0x2,
+       Loop = 0x4,
+       RegionSelectionStart = 0x8,
+};
+
+enum PlaylistDisposition {
+       CopyPlaylist,
+       NewPlaylist,
+       SharePlaylist
+};
+
+enum MidiTrackNameSource {
+       SMFTrackNumber,
+       SMFTrackName,
+       SMFInstrumentName
+};
+
+enum MidiTempoMapDisposition {
+       SMFTempoIgnore,
+       SMFTempoUse,
+};
+
+struct CaptureInfo {
+       samplepos_t start;
+       samplecnt_t samples;
+};
+
+typedef std::vector<CaptureInfo*> CaptureInfos;
 
 } // namespace ARDOUR
 
index 44e0298f1d7325a59eabd05b91bc23d825a0a879..1a6c30dcf25b5e6b07c66a27257603d59ad1d76c 100644 (file)
@@ -30,6 +30,7 @@
 using namespace ARDOUR;
 using namespace std;
 
+#define ENGINE AudioEngine::instance()
 #define port_engine AudioEngine::instance()->port_engine()
 
 AudioPort::AudioPort (const std::string& name, PortFlags flags)
@@ -121,12 +122,18 @@ AudioPort::get_audio_buffer (pframes_t nframes)
 {
        /* caller must hold process lock */
        assert (_port_handle);
+
+       Sample* addr;
+
        if (!externally_connected ()) {
-               _buffer->set_data ((Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes) +
-                               _global_port_buffer_offset, nframes);
+               addr = (Sample *) port_engine.get_buffer (_port_handle, nframes);
        } else {
-               _buffer->set_data (&_data[_global_port_buffer_offset], nframes);
+               /* _data was read and resampled as necessary in ::cycle_start */
+               addr = &_data[_global_port_buffer_offset];
        }
+
+       _buffer->set_data (addr, nframes);
+
        return *_buffer;
 }
 
@@ -135,5 +142,5 @@ AudioPort::engine_get_whole_audio_buffer ()
 {
        /* caller must hold process lock */
        assert (_port_handle);
-       return (Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes);
+       return (Sample *) port_engine.get_buffer (_port_handle, ENGINE->samples_per_cycle());
 }
index 83919bbe6a17b7d56acd78db318251b9cbc15047..91f58d44adfd0ee299b49df7de5338456d9821d2 100644 (file)
@@ -55,6 +55,7 @@
 #include "ardour/process_thread.h"
 #include "ardour/rc_configuration.h"
 #include "ardour/session.h"
+#include "ardour/transport_master_manager.h"
 
 #include "pbd/i18n.h"
 
@@ -77,7 +78,7 @@ AudioEngine::AudioEngine ()
        , _freewheeling (false)
        , monitor_check_interval (INT32_MAX)
        , last_monitor_check (0)
-       , _processed_samples (0)
+       , _processed_samples (-1)
        , m_meter_thread (0)
        , _main_thread (0)
        , _mtdm (0)
@@ -198,6 +199,11 @@ AudioEngine::process_callback (pframes_t nframes)
        /// The number of samples that will have been processed when we've finished
        pframes_t next_processed_samples;
 
+       if (_processed_samples < 0) {
+               _processed_samples = sample_time();
+               cerr << "IIIIINIT PS to " << _processed_samples << endl;
+       }
+
        /* handle wrap around of total samples counter */
 
        if (max_samplepos - _processed_samples < nframes) {
@@ -346,6 +352,14 @@ AudioEngine::process_callback (pframes_t nframes)
                return 0;
        }
 
+       TransportMasterManager& tmm (TransportMasterManager::instance());
+
+       /* make sure the TMM is up to date about the current session */
+
+       if (_session != tmm.session()) {
+               tmm.set_session (_session);
+       }
+
        if (_session == 0) {
 
                if (!_freewheeling) {
@@ -358,16 +372,9 @@ AudioEngine::process_callback (pframes_t nframes)
        }
 
        if (!_freewheeling || Freewheel.empty()) {
-               // TODO: Run a list of slaves here
-               // - multiple TC slaves (how_many_dsp_threads() in parallel)
-               //   (note this can be multiple slaves of each type. e.g.
-               //    3 LTC slaves on different ports, 2 MTC..)
-               // - GUI can display all slaves, user picks one.
-               // - active "slave" is a session property.
-               // - here we ask the session about the active slave
-               //   and get playback speed (for this cycle) here.
-               // - Internal Transport is-a Slave too (!)
-               Port::set_speed_ratio (_session->engine_speed ()); // HACK
+               const double engine_speed = tmm.pre_process_transport_masters (nframes, sample_time_at_cycle_start());
+               Port::set_speed_ratio (engine_speed);
+               DEBUG_TRACE (DEBUG::Slave, string_compose ("transport master (current=%1) gives speed %2 (ports using %3)\n", tmm.current() ? tmm.current()->name() : string("[]"), engine_speed, Port::speed_ratio()));
        }
 
        /* tell all relevant objects that we're starting a new cycle */
index a578bd40ff3997b0bfd87752bf355fdb8b31905b..3eced3063b70d5624152e2e6a0156ae3b33bbae8 100644 (file)
@@ -40,6 +40,7 @@ PBD::DebugBits PBD::DEBUG::Graph = PBD::new_debug_bit ("graph");
 PBD::DebugBits PBD::DEBUG::Destruction = PBD::new_debug_bit ("destruction");
 PBD::DebugBits PBD::DEBUG::MTC = PBD::new_debug_bit ("mtc");
 PBD::DebugBits PBD::DEBUG::LTC = PBD::new_debug_bit ("ltc");
+PBD::DebugBits PBD::DEBUG::TXLTC = PBD::new_debug_bit ("tx-ltc");
 PBD::DebugBits PBD::DEBUG::Transport = PBD::new_debug_bit ("transport");
 PBD::DebugBits PBD::DEBUG::Slave = PBD::new_debug_bit ("slave");
 PBD::DebugBits PBD::DEBUG::SessionEvents = PBD::new_debug_bit ("sessionevents");
index decacc850fbaba73121dae7ac48c236169467153..427f3644041db6ceec5e5858835c89a2beb74b9c 100644 (file)
@@ -50,7 +50,6 @@ DiskIOProcessor::DiskIOProcessor (Session& s, string const & str, Flag f)
        : Processor (s, str)
        , _flags (f)
        , i_am_the_modifier (false)
-       , _seek_required (false)
        , _slaved (false)
        , in_set_state (false)
        , playback_sample (0)
@@ -206,21 +205,6 @@ DiskIOProcessor::non_realtime_locate (samplepos_t location)
        seek (location, true);
 }
 
-void
-DiskIOProcessor::non_realtime_speed_change ()
-{
-       if (_seek_required) {
-               seek (_session.transport_sample(), true);
-               _seek_required = false;
-       }
-}
-
-bool
-DiskIOProcessor::realtime_speed_change ()
-{
-       return true;
-}
-
 int
 DiskIOProcessor::set_state (const XMLNode& node, int version)
 {
index 7ac767c3e84565054c99b0527353c9791b31207e..10313705578f5078bd92d7465c88540a10eddf72 100644 (file)
 #include <iostream>
 #include <cerrno>
 
+#include "pbd/i18n.h"
+
 #include "ardour/audioengine.h"
 #include "ardour/audio_backend.h"
-#include "ardour/slave.h"
+#include "ardour/session.h"
+#include "ardour/transport_master.h"
 
 using namespace std;
 using namespace ARDOUR;
 
-Engine_Slave::Engine_Slave (AudioEngine& e)
-       : engine (e)
+Engine_TransportMaster::Engine_TransportMaster (AudioEngine& e)
+       : TransportMaster (Engine, X_("JACK"))
+       , engine (e)
+       , _starting (false)
+{
+       check_backend ();
+}
+
+Engine_TransportMaster::~Engine_TransportMaster ()
 {
-       double x;
-       samplepos_t p;
-       /* call this to initialize things */
-       speed_and_position (x, p);
 }
 
-Engine_Slave::~Engine_Slave ()
+void
+Engine_TransportMaster::init ()
 {
 }
 
+void
+Engine_TransportMaster::check_backend()
+{
+       if (AudioEngine::instance()->current_backend_name() == X_("JACK")) {
+               _connected = true;
+       } else {
+               _connected = false;
+       }
+}
+
 bool
-Engine_Slave::locked() const
+Engine_TransportMaster::locked() const
 {
        return true;
 }
 
 bool
-Engine_Slave::ok() const
+Engine_TransportMaster::ok() const
 {
        return true;
 }
 
+void
+Engine_TransportMaster::pre_process (pframes_t, samplepos_t, boost::optional<samplepos_t>)
+{
+       /* nothing to do */
+}
+
 bool
-Engine_Slave::speed_and_position (double& sp, samplepos_t& position)
+Engine_TransportMaster::speed_and_position (double& sp, samplepos_t& position, samplepos_t /* now */)
 {
        boost::shared_ptr<AudioBackend> backend = engine.current_backend();
 
-       if (backend) {
-               _starting = backend->speed_and_position (sp, position);
-       } else {
-               _starting = false;
+       /* 3rd argument (now) doesn't matter here because we're always being
+        * called synchronously with the engine.
+        */
+
+       if (backend && backend->speed_and_position (sp, position)) {
+               return true;
+       }
+
+       _current_delta = 0;
+
+       return false;
+}
+
+std::string
+Engine_TransportMaster::position_string () const
+{
+       if (_session) {
+               return to_string (_session->audible_sample());
+       }
+
+       return std::string();
+}
+
+std::string
+Engine_TransportMaster::delta_string () const
+{
+       return string ("0");
+}
+
+bool
+Engine_TransportMaster::allow_request (TransportRequestSource src, TransportRequestType type) const
+{
+       if (_session) {
+               if (_session->config.get_jack_time_master()) {
+                       return true;
+               } else {
+                       return false;
+               }
        }
 
        return true;
index 1173a99b076cebdd3861eb6b8a7ead013630b9e8..e90f07fe1251b74c5cafce2e7d32a8b784d634b1 100644 (file)
@@ -38,6 +38,7 @@
 #include "ardour/source.h"
 #include "ardour/tempo.h"
 #include "ardour/track.h"
+#include "ardour/transport_master.h"
 #include "ardour/types.h"
 
 using namespace std;
@@ -131,7 +132,6 @@ setup_enum_writer ()
        WaveformScale _WaveformScale;
        WaveformShape _WaveformShape;
        Session::PostTransportWork _Session_PostTransportWork;
-       Session::SlaveState _Session_SlaveState;
        MTC_Status _MIDI_MTC_Status;
        Evoral::OverlapType _OverlapType;
        BufferingPreset _BufferingPreset;
@@ -139,7 +139,7 @@ setup_enum_writer ()
        PresentationInfo::Flag _PresentationInfo_Flag;
        MusicalMode::Type mode;
        MidiPortFlags _MidiPortFlags;
-       
+
 #define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
 #define REGISTER_BITS(e) enum_writer.register_bits (typeid(e).name(), i, s); i.clear(); s.clear()
 #define REGISTER_ENUM(e) i.push_back (e); s.push_back (#e)
@@ -420,7 +420,6 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (SessionEvent, RangeStop);
        REGISTER_CLASS_ENUM (SessionEvent, RangeLocate);
        REGISTER_CLASS_ENUM (SessionEvent, Overwrite);
-       REGISTER_CLASS_ENUM (SessionEvent, SetSyncSource);
        REGISTER_CLASS_ENUM (SessionEvent, Audition);
        REGISTER_CLASS_ENUM (SessionEvent, SetPlayAudioRange);
        REGISTER_CLASS_ENUM (SessionEvent, CancelPlayAudioRange);
@@ -429,6 +428,7 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (SessionEvent, AdjustCaptureBuffering);
        REGISTER_CLASS_ENUM (SessionEvent, SetTimecodeTransmission);
        REGISTER_CLASS_ENUM (SessionEvent, Skip);
+       REGISTER_CLASS_ENUM (SessionEvent, SetTransportMaster);
        REGISTER_CLASS_ENUM (SessionEvent, StopOnce);
        REGISTER_CLASS_ENUM (SessionEvent, AutoLoop);
        REGISTER (_SessionEvent_Type);
@@ -439,11 +439,6 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (SessionEvent, Clear);
        REGISTER (_SessionEvent_Action);
 
-       REGISTER_CLASS_ENUM (Session, Stopped);
-       REGISTER_CLASS_ENUM (Session, Waiting);
-       REGISTER_CLASS_ENUM (Session, Running);
-       REGISTER (_Session_SlaveState);
-
        REGISTER_ENUM (MTC_Stopped);
        REGISTER_ENUM (MTC_Forward);
        REGISTER_ENUM (MTC_Backward);
@@ -455,7 +450,6 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (Session, PostTransportRoll);
        REGISTER_CLASS_ENUM (Session, PostTransportAbort);
        REGISTER_CLASS_ENUM (Session, PostTransportOverWrite);
-       REGISTER_CLASS_ENUM (Session, PostTransportSpeed);
        REGISTER_CLASS_ENUM (Session, PostTransportAudition);
        REGISTER_CLASS_ENUM (Session, PostTransportReverse);
        REGISTER_CLASS_ENUM (Session, PostTransportInputChange);
index fb5be2d4ac984e1b07acfdf50d93db7374b11457..a806b693b4a072c153a80ba55cc80b866d4dd314 100644 (file)
 #include "ardour/runtime_functions.h"
 #include "ardour/session_event.h"
 #include "ardour/source_factory.h"
+#include "ardour/transport_master_manager.h"
 #ifdef LV2_SUPPORT
 #include "ardour/uri_map.h"
 #endif
@@ -595,17 +596,30 @@ void
 ARDOUR::init_post_engine ()
 {
        XMLNode* node;
+
        if ((node = Config->control_protocol_state()) != 0) {
                ControlProtocolManager::instance().set_state (*node, Stateful::loading_state_version);
        }
 
+       if ((node = Config->transport_master_state()) != 0) {
+               if (TransportMasterManager::instance().set_state (*node, Stateful::loading_state_version)) {
+                       error << _("Cannot restore transport master manager") << endmsg;
+                       /* XXX now what? */
+               }
+       } else {
+               if (TransportMasterManager::instance().init ()) {
+                       error << _("Cannot initialize transport master manager") << endmsg;
+                       /* XXX now what? */
+               }
+       }
+
        /* find plugins */
 
        ARDOUR::PluginManager::instance().refresh (!Config->get_discover_vst_on_start());
 }
 
 void
-ARDOUR::cleanup ()
+       ARDOUR::cleanup ()
 {
        if (!libardour_initialized) {
                return;
index 9baa3276ef016b39d79ca27aab9e5df690cf9aef..b1b4f7173484ba2b235187332ad275f96eb59296 100644 (file)
 #include <unistd.h>
 
 #include "pbd/error.h"
+#include "pbd/failed_constructor.h"
 #include "pbd/pthread_utils.h"
 
 #include "ardour/debug.h"
 #include "ardour/profile.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
 #include "ardour/session.h"
 #include "ardour/audioengine.h"
 #include "ardour/audio_port.h"
@@ -40,61 +41,101 @@ using namespace MIDI;
 using namespace PBD;
 using namespace Timecode;
 
-#define FLYWHEEL_TIMEOUT ( 1 * session.sample_rate() )
-
-LTC_Slave::LTC_Slave (Session& s)
-       : session (s)
+#define ENGINE AudioEngine::instance()
+#define FLYWHEEL_TIMEOUT ( 1 * ENGINE->sample_rate() )
+
+/* XXX USE Config->get_ltc_input */
+
+LTC_TransportMaster::LTC_TransportMaster (std::string const & name)
+       : TimecodeTransportMaster (name, LTC)
+       , did_reset_tc_format (false)
+       , decoder (0)
+       , samples_per_ltc_frame (0)
+       , fps_detected (false)
+       , monotonic_cnt (0)
+       , last_timestamp (0)
+       , last_ltc_sample (0)
+       , delayedlocked (10)
+       , ltc_detect_fps_cnt (0)
+       , ltc_detect_fps_max (0)
+       , sync_lock_broken (false)
 {
-       samples_per_ltc_frame = session.samples_per_timecode_frame();
-       timecode.rate = session.timecode_frames_per_second();
-       timecode.drop  = session.timecode_drop_frames();
+       if ((_port = AudioEngine::instance()->register_input_port (DataType::AUDIO, string_compose ("%1 in", _name))) == 0) {
+               throw failed_constructor();
+       }
 
-       did_reset_tc_format = false;
-       delayedlocked = 10;
-       monotonic_cnt = 0;
-       fps_detected=false;
-       sync_lock_broken = false;
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("LTC registered %1\n", _port->name()));
 
-       ltc_timecode = session.config.get_timecode_format();
-       a3e_timecode = session.config.get_timecode_format();
-       printed_timecode_warning = false;
-       ltc_detect_fps_cnt = ltc_detect_fps_max = 0;
        memset(&prev_sample, 0, sizeof(LTCFrameExt));
 
-       decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);
-
-       session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&LTC_Slave::parameter_changed, this, _1));
-       parse_timecode_offset();
-       reset();
        resync_latency();
-       session.Xrun.connect_same_thread (port_connections, boost::bind (&LTC_Slave::resync_xrun, this));
-       session.engine().GraphReordered.connect_same_thread (port_connections, boost::bind (&LTC_Slave::resync_latency, this));
+
+       AudioEngine::instance()->Xrun.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_xrun, this));
+       AudioEngine::instance()->GraphReordered.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_latency, this));
+}
+
+void
+LTC_TransportMaster::init ()
+{
+       reset (true);
+}
+
+void
+LTC_TransportMaster::set_session (Session *s)
+{
+       config_connection.disconnect ();
+       _session = s;
+
+       if (_session) {
+
+               samples_per_ltc_frame = _session->samples_per_timecode_frame();
+               timecode.rate = _session->timecode_frames_per_second();
+               timecode.drop  = _session->timecode_drop_frames();
+               printed_timecode_warning = false;
+               ltc_timecode = _session->config.get_timecode_format();
+               a3e_timecode = _session->config.get_timecode_format();
+
+               if (Config->get_use_session_timecode_format() && _session) {
+                       samples_per_timecode_frame = _session->samples_per_timecode_frame();
+               }
+
+               if (decoder) {
+                       ltc_decoder_free (decoder);
+               }
+
+               decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);
+
+               parse_timecode_offset();
+               reset();
+
+               _session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&LTC_TransportMaster::parameter_changed, this, _1));
+       }
 }
 
-LTC_Slave::~LTC_Slave()
+LTC_TransportMaster::~LTC_TransportMaster()
 {
        port_connections.drop_connections();
        config_connection.disconnect();
 
        if (did_reset_tc_format) {
-               session.config.set_timecode_format (saved_tc_format);
+               _session->config.set_timecode_format (saved_tc_format);
        }
 
        ltc_decoder_free(decoder);
 }
 
 void
-LTC_Slave::parse_timecode_offset() {
+LTC_TransportMaster::parse_timecode_offset() {
        Timecode::Time offset_tc;
-       Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc);
-       offset_tc.rate = session.timecode_frames_per_second();
-       offset_tc.drop = session.timecode_drop_frames();
-       session.timecode_to_sample(offset_tc, timecode_offset, false, false);
+       Timecode::parse_timecode_format(_session->config.get_slave_timecode_offset(), offset_tc);
+       offset_tc.rate = _session->timecode_frames_per_second();
+       offset_tc.drop = _session->timecode_drop_frames();
+       _session->timecode_to_sample(offset_tc, timecode_offset, false, false);
        timecode_negative_offset = offset_tc.negative;
 }
 
 void
-LTC_Slave::parameter_changed (std::string const & p)
+LTC_TransportMaster::parameter_changed (std::string const & p)
 {
        if (p == "slave-timecode-offset"
                        || p == "timecode-format"
@@ -104,65 +145,61 @@ LTC_Slave::parameter_changed (std::string const & p)
 }
 
 ARDOUR::samplecnt_t
-LTC_Slave::resolution () const
+LTC_TransportMaster::resolution () const
 {
-       return (samplecnt_t) (session.sample_rate() / 1000);
+       return (samplecnt_t) (ENGINE->sample_rate() / 1000);
 }
 
 bool
-LTC_Slave::locked () const
+LTC_TransportMaster::locked () const
 {
        return (delayedlocked < 5);
 }
 
 bool
-LTC_Slave::ok() const
+LTC_TransportMaster::ok() const
 {
        return true;
 }
 
 void
-LTC_Slave::resync_xrun()
+LTC_TransportMaster::resync_xrun()
 {
        DEBUG_TRACE (DEBUG::LTC, "LTC resync_xrun()\n");
-       engine_dll_initstate = 0;
        sync_lock_broken = false;
 }
 
 void
-LTC_Slave::resync_latency()
+LTC_TransportMaster::resync_latency()
 {
        DEBUG_TRACE (DEBUG::LTC, "LTC resync_latency()\n");
-       engine_dll_initstate = 0;
        sync_lock_broken = false;
 
-       if (!session.deletion_in_progress() && session.ltc_output_io()) { /* check if Port exits */
-               boost::shared_ptr<Port> ltcport = session.ltc_input_port();
-               ltcport->get_connected_latency_range (ltc_slave_latency, false);
+       if (!_port) {
+               _port->get_connected_latency_range (ltc_slave_latency, false);
        }
 }
 
 void
-LTC_Slave::reset (bool with_ts)
+LTC_TransportMaster::reset (bool with_ts)
 {
        DEBUG_TRACE (DEBUG::LTC, "LTC reset()\n");
        if (with_ts) {
                last_timestamp = 0;
-               current_delta = 0;
+               _current_delta = 0;
        }
        transport_direction = 0;
        ltc_speed = 0;
-       engine_dll_initstate = 0;
        sync_lock_broken = false;
-
-       ActiveChanged (false); /* EMIT SIGNAL */
+       monotonic_cnt = 0;
 }
 
 void
-LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
+LTC_TransportMaster::parse_ltc (const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
 {
        pframes_t i;
        unsigned char sound[8192];
+
        if (nframes > 8192) {
                /* TODO warn once or wrap, loop conversion below
                 * does jack/A3 support > 8192 spp anyway?
@@ -171,32 +208,33 @@ LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, co
        }
 
        for (i = 0; i < nframes; i++) {
-               const int snd=(int)rint((127.0*in[i])+128.0);
+               const int snd=(int) rint ((127.0*in[i])+128.0);
                sound[i] = (unsigned char) (snd&0xff);
        }
-       ltc_decoder_write(decoder, sound, nframes, posinfo);
+
+       ltc_decoder_write (decoder, sound, nframes, posinfo);
+
        return;
 }
 
 bool
-LTC_Slave::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
-       if (       a->frame_units != b->frame_units
-               || a->frame_tens  != b->frame_tens
-               || a->dfbit       != b->dfbit
-               || a->secs_units  != b->secs_units
-               || a->secs_tens   != b->secs_tens
-               || a->mins_units  != b->mins_units
-               || a->mins_tens   != b->mins_tens
-               || a->hours_units != b->hours_units
-               || a->hours_tens  != b->hours_tens
-            ) {
+LTC_TransportMaster::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
+       if (a->frame_units != b->frame_units ||
+           a->frame_tens  != b->frame_tens ||
+           a->dfbit       != b->dfbit ||
+           a->secs_units  != b->secs_units ||
+           a->secs_tens   != b->secs_tens ||
+           a->mins_units  != b->mins_units ||
+           a->mins_tens   != b->mins_tens ||
+           a->hours_units != b->hours_units ||
+           a->hours_tens  != b->hours_tens) {
                return false;
        }
        return true;
 }
 
 bool
-LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
+LTC_TransportMaster::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
        bool discontinuity_detected = false;
 
        if (fuzzy && (
@@ -221,7 +259,7 @@ LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
 }
 
 bool
-LTC_Slave::detect_ltc_fps(int frameno, bool df)
+LTC_TransportMaster::detect_ltc_fps(int frameno, bool df)
 {
        bool fps_changed = false;
        double detected_fps = 0;
@@ -236,7 +274,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
                        detected_fps = ltc_detect_fps_max + 1;
                        if (df) {
                                /* LTC df -> indicates fractional framerate */
-                               if (Config->get_timecode_source_2997()) {
+                               if (fr2997()) {
                                        detected_fps = detected_fps * 999.0 / 1000.0;
                                } else {
                                        detected_fps = detected_fps * 1000.0 / 1001.0;
@@ -256,7 +294,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
        if (detected_fps != 0 && (detected_fps != timecode.rate || df != timecode.drop)) {
                timecode.rate = detected_fps;
                timecode.drop = df;
-               samples_per_ltc_frame = double(session.sample_rate()) / timecode.rate;
+               samples_per_ltc_frame = double(_session->sample_rate()) / timecode.rate;
                DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC reset to FPS: %1%2 ; audio-samples per LTC: %3\n",
                                detected_fps, df?"df":"ndf", samples_per_ltc_frame));
                fps_changed=true;
@@ -264,7 +302,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
 
        /* poll and check session TC */
        TimecodeFormat tc_format = apparent_timecode_format();
-       TimecodeFormat cur_timecode = session.config.get_timecode_format();
+       TimecodeFormat cur_timecode = _session->config.get_timecode_format();
 
        if (Config->get_timecode_sync_frame_rate()) {
                /* enforce time-code */
@@ -279,7 +317,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
                                                Timecode::timecode_format_name(tc_format))
                                        << endmsg;
                        }
-                       session.config.set_timecode_format (tc_format);
+                       _session->config.set_timecode_format (tc_format);
                }
        } else {
                /* only warn about TC mismatch */
@@ -299,45 +337,63 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
        ltc_timecode = tc_format;
        a3e_timecode = cur_timecode;
 
+       if (Config->get_use_session_timecode_format() && _session) {
+               samples_per_timecode_frame = _session->samples_per_timecode_frame();
+       } else {
+               samples_per_timecode_frame = ENGINE->sample_rate() / Timecode::timecode_to_frames_per_second (ltc_timecode);
+       }
+
        return fps_changed;
 }
 
 void
-LTC_Slave::process_ltc(samplepos_t const /*now*/)
+LTC_TransportMaster::process_ltc(samplepos_t const now)
 {
        LTCFrameExt sample;
-       enum LTC_TV_STANDARD tv_standard = LTC_TV_625_50;
-       while (ltc_decoder_read(decoder, &sample)) {
+       LTC_TV_STANDARD tv_standard = LTC_TV_625_50;
+
+       while (ltc_decoder_read (decoder, &sample)) {
+
                SMPTETimecode stime;
 
-               ltc_frame_to_time(&stime, &sample.ltc, 0);
+               ltc_frame_to_time (&stime, &sample.ltc, 0);
                timecode.negative  = false;
                timecode.subframes  = 0;
 
                /* set timecode.rate and timecode.drop: */
-               bool ltc_is_static = equal_ltc_sample_time(&prev_sample.ltc, &sample.ltc);
 
-               if (detect_discontinuity(&sample, ceil(timecode.rate), !fps_detected)) {
-                       if (fps_detected) { ltc_detect_fps_cnt = ltc_detect_fps_max = 0; }
-                       fps_detected=false;
+               const bool ltc_is_stationary = equal_ltc_sample_time (&prev_sample.ltc, &sample.ltc);
+
+               if (detect_discontinuity (&sample, ceil(timecode.rate), !fps_detected)) {
+
+                       if (fps_detected) {
+                               ltc_detect_fps_cnt = ltc_detect_fps_max = 0;
+                       }
+
+                       fps_detected = false;
                }
 
-               if (!ltc_is_static && detect_ltc_fps(stime.frame, (sample.ltc.dfbit)? true : false)) {
+               if (!ltc_is_stationary && detect_ltc_fps (stime.frame, (sample.ltc.dfbit)? true : false)) {
                        reset();
                        fps_detected=true;
                }
 
-#if 0 // Devel/Debug
-               fprintf(stdout, "LTC %02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
-                       stime.hours,
-                       stime.mins,
-                       stime.secs,
-                       (sample.ltc.dfbit) ? '.' : ':',
-                       stime.frame,
-                       sample.off_start,
-                       sample.off_end,
-                       sample.reverse ? " R" : "  "
-                       );
+#ifndef NDEBUG
+               if (DEBUG_ENABLED (DEBUG::LTC)) {
+                       /* use fprintf for simpler correct formatting of times
+                        */
+                       fprintf (stderr, "LTC@%ld %02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
+                                now,
+                                stime.hours,
+                                stime.mins,
+                                stime.secs,
+                                (sample.ltc.dfbit) ? '.' : ':',
+                                stime.frame,
+                                sample.off_start,
+                                sample.off_end,
+                                sample.reverse ? " R" : "  "
+                               );
+               }
 #endif
 
                /* when a full LTC sample is decoded, the timecode the LTC sample
@@ -367,13 +423,13 @@ LTC_Slave::process_ltc(samplepos_t const /*now*/)
                        ltc_frame_increment(&sample.ltc, fps_i, tv_standard, 0);
                        ltc_frame_to_time(&stime, &sample.ltc, 0);
                        transport_direction = 1;
-                       sample.off_start -= ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
-                       sample.off_end -= ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
+                       sample.off_start -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
+                       sample.off_end -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
                } else {
                        ltc_frame_decrement(&sample.ltc, fps_i, tv_standard, 0);
                        int off = sample.off_end - sample.off_start;
-                       sample.off_start += off - ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
-                       sample.off_end += off - ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
+                       sample.off_start += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
+                       sample.off_end += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
                        transport_direction = -1;
                }
 
@@ -382,236 +438,194 @@ LTC_Slave::process_ltc(samplepos_t const /*now*/)
                timecode.seconds = stime.secs;
                timecode.frames  = stime.frame;
 
-               /* map LTC timecode to session TC setting */
-               samplepos_t ltc_frame; ///< audio-sample corresponding to LTC sample
-               Timecode::timecode_to_sample (timecode, ltc_frame, true, false,
-                       double(session.sample_rate()),
-                       session.config.get_subframes_per_frame(),
-                       timecode_negative_offset, timecode_offset
-                       );
+               samplepos_t ltc_sample; // audio-sample corresponding to position of LTC frame
+
+               if (_session && Config->get_use_session_timecode_format()) {
+                       Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), _session->config.get_subframes_per_frame(), timecode_negative_offset, timecode_offset);
+               } else {
+                       Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), 100, timecode_negative_offset, timecode_offset);
+               }
 
-               ltc_frame += ltc_slave_latency.max;
+               ltc_sample += ltc_slave_latency.max;
+
+               /* This LTC frame spans sample time between sample.off_start  .. sample.off_end
+                *
+                * NOTE: these sample times are NOT the ones that LTC is representing. They are
+                * derived our own audioengine's monotonic audio clock.
+                *
+                * So we expect the next frame to span sample.off_end+1 and ... <don't care for now>.
+                * That isn't the time we will necessarily receive the LTC frame, but the decoder
+                * should tell us that its span begins there.
+                *
+                */
 
                samplepos_t cur_timestamp = sample.off_end + 1;
-               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC F: %1 LF: %2  N: %3 L: %4\n", ltc_frame, last_ltc_sample, cur_timestamp, last_timestamp));
-               if (sample.off_end + 1 <= last_timestamp || last_timestamp == 0) {
+
+               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC S: %1 LS: %2  N: %3 L: %4\n", ltc_sample, last_ltc_sample, cur_timestamp, last_timestamp));
+
+               if (cur_timestamp <= last_timestamp || last_timestamp == 0) {
                        DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: UNCHANGED: %1\n", ltc_speed));
                } else {
-                       ltc_speed = double(ltc_frame - last_ltc_sample) / double(cur_timestamp - last_timestamp);
+                       ltc_speed = double (ltc_sample - last_ltc_sample) / double (cur_timestamp - last_timestamp);
                        DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: %1\n", ltc_speed));
                }
 
-               if (fabs(ltc_speed) > 10.0) {
+               if (fabs (ltc_speed) > 10.0) {
                        ltc_speed = 0;
                }
 
-               last_timestamp = sample.off_end + 1;
-               last_ltc_sample = ltc_frame;
-       } /* end foreach decoded LTC sample */
-}
+               last_timestamp = cur_timestamp;
+               last_ltc_sample = ltc_sample;
 
-void
-LTC_Slave::init_engine_dll (samplepos_t pos, int32_t inc)
-{
-       double omega = 2.0 * M_PI * double(inc) / double(session.sample_rate());
-       b = 1.4142135623730950488 * omega;
-       c = omega * omega;
-
-       e2 = double(ltc_speed * inc);
-       t0 = double(pos);
-       t1 = t0 + e2;
-       DEBUG_TRACE (DEBUG::LTC, string_compose ("[re-]init Engine DLL %1 %2 %3\n", t0, t1, e2));
+       } /* end foreach decoded LTC sample */
 }
 
-/* main entry point from session_process.cc
- * called from process callback context
- * so it is OK to use get_buffer()
- */
 bool
-LTC_Slave::speed_and_position (double& speed, samplepos_t& pos)
+LTC_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
 {
-       bool engine_init_called = false;
-       samplepos_t now = session.engine().sample_time_at_cycle_start();
-       samplepos_t sess_pos = session.transport_sample(); // corresponds to now
-       samplecnt_t nframes = session.engine().samples_per_cycle();
+       if (!_collect || last_timestamp == 0) {
+               return false;
+       }
 
-       Sample* in;
+       /* XXX these are not atomics and maybe modified in a thread other other than the one
+          that is executing this.
+       */
 
-       boost::shared_ptr<Port> ltcport = session.ltc_input_port();
+       speed = ltc_speed;
 
-       in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (ltcport->port_handle(), nframes);
+       /* provide a .1% deadzone to lock the speed */
+       if (fabs (speed - 1.0) <= 0.001) {
+               speed = 1.0;
+       }
 
+       if (speed != 0 && delayedlocked == 0 && fabs(speed) != 1.0) {
+               sync_lock_broken = true;
+               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 based on %2\n", speed, ltc_speed));
+       }
+
+       pos =  last_ltc_sample;
+       pos += (now - last_timestamp) * speed;
+
+       return true;
+}
+
+void
+LTC_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
+{
+       Sample* in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (_port->port_handle(), nframes);
        sampleoffset_t skip = now - (monotonic_cnt + nframes);
        monotonic_cnt = now;
-       DEBUG_TRACE (DEBUG::LTC, string_compose ("speed_and_position - TID:%1 | latency: %2 | skip %3\n", pthread_name(), ltc_slave_latency.max, skip));
+
+       DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process - TID:%1 | latency: %2 | skip %3 | session ? %4| last %5 | dir %6 | sp %7\n",
+                                                pthread_name(), ltc_slave_latency.max, skip, (_session ? 'y' : 'n'), last_timestamp, transport_direction, ltc_speed));
 
        if (last_timestamp == 0) {
-               engine_dll_initstate = 0;
-               if (delayedlocked < 10) ++delayedlocked;
-       } else if (engine_dll_initstate != transport_direction && ltc_speed != 0) {
+               if (delayedlocked < 10) {
+                       ++delayedlocked;
+               }
 
-               ActiveChanged (true); /* EMIT SIGNAL */
+       } else if (ltc_speed != 0) {
 
-               engine_dll_initstate = transport_direction;
-               init_engine_dll(last_ltc_sample + rint(ltc_speed * double(2 * nframes + now - last_timestamp)),
-                               session.engine().samples_per_cycle());
-               engine_init_called = true;
        }
 
-       if (in) {
-               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC Process eng-tme: %1 eng-pos: %2\n", now, sess_pos));
-               /* when the jack-graph changes and if ardour performs
-                * locates, the audioengine is stopped (skipping samples) while
-                * jack [time] moves along.
-                */
-               if (skip > 0) {
-                       DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples. Feeding silence to LTC parser.\n", skip));
-                       if (skip >= 8192) skip = 8192;
-                       unsigned char sound[8192];
-                       memset(sound, 0x80, sizeof(char) * skip);
-                       ltc_decoder_write(decoder, sound, nframes, now);
-               } else if (skip != 0) {
-                       /* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
-                       DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
-                       reset();
-               }
-
-               parse_ltc(nframes, in, now);
-               process_ltc(now);
+       DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process with audio clock time: %1\n", now));
+
+       /* if the audioengine failed to take the process lock, it won't
+          call this method, and time will appear to skip. Reset the
+          LTC decoder's state by giving it some silence.
+       */
+
+       if (skip > 0) {
+               DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples. Feeding silence to LTC parser.\n", skip));
+               if (skip >= 8192) skip = 8192;
+               unsigned char sound[8192];
+               memset (sound, 0x80, sizeof(char) * skip);
+               ltc_decoder_write (decoder, sound, nframes, now);
+       } else if (skip != 0) {
+               /* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
+               DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
+               reset();
        }
 
+       /* Now feed the incoming LTC signal into the decoder */
+
+       parse_ltc (nframes, in, now);
+
+       /* and pull out actual LTC frame data */
+
+       process_ltc (now);
+
        if (last_timestamp == 0) {
                DEBUG_TRACE (DEBUG::LTC, "last timestamp == 0\n");
-               speed = 0;
-               pos = session.transport_sample();
-               return true;
+               return;
        } else if (ltc_speed != 0) {
-               if (delayedlocked > 1) delayedlocked--;
-               else if (current_delta == 0) delayedlocked = 0;
+               DEBUG_TRACE (DEBUG::LTC, string_compose ("speed non-zero (%1)\n", ltc_speed));
+               if (delayedlocked > 1) {
+                       delayedlocked--;
+               } else if (_current_delta == 0) {
+                       delayedlocked = 0;
+               }
        }
 
-       if (abs(now - last_timestamp) > FLYWHEEL_TIMEOUT) {
+       if (abs (now - last_timestamp) > FLYWHEEL_TIMEOUT) {
                DEBUG_TRACE (DEBUG::LTC, "flywheel timeout\n");
                reset();
-               speed = 0;
-               pos = session.transport_sample();
-               ActiveChanged (false); /* EMIT SIGNAL */
-               return true;
-       }
+               /* don't change position from last known */
 
-       /* it take 2 cycles from naught to rolling.
-        * during these to initial cycles the speed == 0
-        *
-        * the first cycle:
-        * DEBUG::Slave: slave stopped, move to NNN
-        * DEBUG::Transport: Request forced locate to NNN
-        * DEBUG::Slave: slave state 0 @ NNN speed 0 cur delta VERY-LARGE-DELTA avg delta 1800
-        * DEBUG::Slave: silent motion
-        * DEBUG::Transport: realtime stop @ NNN
-        * DEBUG::Transport: Butler transport work, todo = PostTransportStop,PostTransportLocate,PostTransportClearSubstate
-        *
-        * [engine skips samples to locate, jack time keeps rolling on]
-        *
-        * the second cycle:
-        *
-        * DEBUG::LTC: [re-]init Engine DLL
-        * DEBUG::Slave: slave stopped, move to NNN+
-        * ...
-        *
-        * we need to seek two cycles ahead: 2 * nframes
-        */
-       if (engine_dll_initstate == 0) {
-               DEBUG_TRACE (DEBUG::LTC, "engine DLL not initialized. ltc_speed\n");
-               speed = 0;
-               pos = last_ltc_sample + rint(ltc_speed * double(2 * nframes + now - last_timestamp));
-               return true;
+               return;
        }
 
-       /* interpolate position according to speed and time since last LTC-sample*/
-       double speed_flt = ltc_speed;
-       double elapsed = (now - last_timestamp) * speed_flt;
-
-       if (!engine_init_called) {
-               const double e = elapsed + double (last_ltc_sample - sess_pos);
-               t0 = t1;
-               t1 += b * e + e2;
-               e2 += c * e;
-               speed_flt = (t1 - t0) / double(session.engine().samples_per_cycle());
-               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, speed_flt, e2 - session.engine().samples_per_cycle() ));
+       if (session_pos) {
+               const samplepos_t current_pos = last_ltc_sample + ((now - last_timestamp) * ltc_speed);
+               _current_delta = current_pos - *session_pos;
        } else {
-               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC adjusting elapsed (no DLL) from %1 by %2\n", elapsed, (2 * nframes * ltc_speed)));
-               speed_flt = 0;
-               elapsed += 2.0 * nframes * ltc_speed; /* see note above */
-       }
-
-       pos = last_ltc_sample + rint(elapsed);
-       speed = speed_flt;
-       current_delta = (pos - sess_pos);
-
-       if (((pos < 0) || (labs(current_delta) > 2 * session.sample_rate()))) {
-               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC large drift: %1\n", current_delta));
-               reset();
-               speed = 0;
-               return true;
-       }
-
-       DEBUG_TRACE (DEBUG::LTC, string_compose ("LTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
-                                                speed, pos, last_ltc_sample, elapsed, current_delta));
-
-       /* provide a .1% deadzone to lock the speed */
-       if (fabs(speed - 1.0) <= 0.001) {
-               speed = 1.0;
-       }
-
-       if (speed != 0 && delayedlocked == 0 && fabs(speed) != 1.0) {
-               sync_lock_broken = true;
-               DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 %2\n", speed, ltc_speed));
+               _current_delta = 0;
        }
-
-       return true;
 }
 
 Timecode::TimecodeFormat
-LTC_Slave::apparent_timecode_format () const
+LTC_TransportMaster::apparent_timecode_format () const
 {
        if      (timecode.rate == 24 && !timecode.drop)
                return timecode_24;
        else if (timecode.rate == 25 && !timecode.drop)
                return timecode_25;
        else if (rint(timecode.rate * 100) == 2997 && !timecode.drop)
-               return (Config->get_timecode_source_2997() ? timecode_2997000 : timecode_2997);
+               return (fr2997() ? timecode_2997000 : timecode_2997);
        else if (rint(timecode.rate * 100) == 2997 &&  timecode.drop)
-               return (Config->get_timecode_source_2997() ? timecode_2997000drop : timecode_2997drop);
+               return (fr2997() ? timecode_2997000drop : timecode_2997drop);
        else if (timecode.rate == 30 &&  timecode.drop)
                return timecode_2997drop; // timecode_30drop; // LTC counting to 30 samples w/DF *means* 29.97 df
        else if (timecode.rate == 30 && !timecode.drop)
                return timecode_30;
 
        /* XXX - unknown timecode format */
-       return session.config.get_timecode_format();
+       return _session->config.get_timecode_format();
 }
 
 std::string
-LTC_Slave::approximate_current_position() const
+LTC_TransportMaster::position_string() const
 {
-       if (last_timestamp == 0) {
+       if (!_collect || last_timestamp == 0) {
                return " --:--:--:--";
        }
        return Timecode::timecode_format_time(timecode);
 }
 
 std::string
-LTC_Slave::approximate_current_delta() const
+LTC_TransportMaster::delta_string() const
 {
        char delta[80];
-       if (last_timestamp == 0 || engine_dll_initstate == 0) {
-               snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
+
+       if (!_collect || last_timestamp == 0) {
+               snprintf (delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
        } else if ((monotonic_cnt - last_timestamp) > 2 * samples_per_ltc_frame) {
-               snprintf(delta, sizeof(delta), "%s", _("flywheel"));
+               snprintf (delta, sizeof(delta), "%s", _("flywheel"));
        } else {
-               snprintf(delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
+               snprintf (delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
                                sync_lock_broken ? "red" : "green",
-                               LEADINGZERO(::llabs(current_delta)), PLUSMINUS(-current_delta), ::llabs(current_delta));
+                               LEADINGZERO(::llabs(_current_delta)), PLUSMINUS(-_current_delta), ::llabs(_current_delta));
        }
-       return std::string(delta);
+
+       return delta;
 }
index 273c397733a72b211ac92bfb977d1f8c161a8631..93ca9059ef8dbcd08e41f34dc1f1b9cc5f2ed9e5 100644 (file)
 
 #include "midi++/port.h"
 
+#include "ardour/audioengine.h"
 #include "ardour/debug.h"
 #include "ardour/midi_buffer.h"
 #include "ardour/midi_port.h"
-#include "ardour/slave.h"
+#include "ardour/session.h"
 #include "ardour/tempo.h"
+#include "ardour/transport_master.h"
 
 #include "pbd/i18n.h"
 
@@ -42,54 +44,124 @@ using namespace ARDOUR;
 using namespace MIDI;
 using namespace PBD;
 
-MIDIClock_Slave::MIDIClock_Slave (Session& s, MidiPort& p, int ppqn)
-       : ppqn (ppqn)
-       , bandwidth (2.0 / 60.0) // 1 BpM = 1 / 60 Hz
+#define ENGINE AudioEngine::instance()
+
+MIDIClock_TransportMaster::MIDIClock_TransportMaster (std::string const & name, int ppqn)
+       : TransportMaster (MIDIClock, name)
+       , ppqn (ppqn)
+       , last_timestamp (0)
+       , should_be_position (0)
+       , midi_clock_count (0)
+       , _speed (0)
+       , _running (false)
+       , _bpm (0)
 {
-       session = (ISlaveSessionProxy *) new SlaveSessionProxy(s);
-       rebind (p);
-       reset ();
+       if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
+               throw failed_constructor();
+       }
 }
 
-MIDIClock_Slave::MIDIClock_Slave (ISlaveSessionProxy* session_proxy, int ppqn)
-       : session(session_proxy)
-       , ppqn (ppqn)
-       , bandwidth (2.0 / 60.0) // 1 BpM = 1 / 60 Hz
+MIDIClock_TransportMaster::~MIDIClock_TransportMaster()
 {
-       reset ();
+       port_connections.drop_connections ();
 }
 
-MIDIClock_Slave::~MIDIClock_Slave()
+void
+MIDIClock_TransportMaster::init ()
 {
-       delete session;
+       midi_clock_count = 0;
+       last_timestamp = 0;
 }
 
 void
-MIDIClock_Slave::rebind (MidiPort& port)
+MIDIClock_TransportMaster::set_session (Session *session)
 {
-       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port.name()));
+       port_connections.drop_connections();
+       _session = session;
 
-       port_connections.drop_connections ();
+       /* only connect to signals if we have a proxy, because otherwise we
+        * cannot interpet incoming data (no tempo map etc.)
+        */
 
-       port.self_parser().timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::update_midi_clock, this, _1, _2));
-       port.self_parser().start.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::start, this, _1, _2));
-       port.self_parser().contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::contineu, this, _1, _2));
-       port.self_parser().stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::stop, this, _1, _2));
-       port.self_parser().position.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::position, this, _1, _2, 3));
+       if (_session) {
+               parser.timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::update_midi_clock, this, _1, _2));
+               parser.start.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::start, this, _1, _2));
+               parser.contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::contineu, this, _1, _2));
+               parser.stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::stop, this, _1, _2));
+               parser.position.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::position, this, _1, _2, 3));
 
+               reset ();
+       }
+}
+
+bool
+MIDIClock_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
+{
+       if (!_running || !_collect) {
+               return false;
+       }
+
+       if (fabs (_speed - 1.0) < 0.001) {
+               speed = 1.0;
+       } else {
+               speed = _speed;
+       }
+
+       pos = should_be_position;
+       pos += (now - last_timestamp) * _speed;
+
+       return true;
 }
 
 void
-MIDIClock_Slave::calculate_one_ppqn_in_samples_at(samplepos_t time)
+MIDIClock_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
 {
-       const double samples_per_quarter_note = session->tempo_map().samples_per_quarter_note_at (time, session->sample_rate());
+       /* Read and parse incoming MIDI */
+
+       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("preprocess with lt = %1 @ %2, running ? %3\n", last_timestamp, now, _running));
+
+       _midi_port->read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, parser, now);
+
+       /* no clock messages ever, or no clock messages for 1/4 second ? conclude that its stopped */
+
+       if (!last_timestamp || (now > last_timestamp && ((now - last_timestamp) > (ENGINE->sample_rate() / 4)))) {
+               _speed = 0.0;
+               _bpm = 0.0;
+               last_timestamp = 0;
+               _running = false;
+               _current_delta = 0;
+               midi_clock_count = 0;
+
+               DEBUG_TRACE (DEBUG::MidiClock, "No MIDI Clock messages received for some time, stopping!\n");
+               return;
+       }
+
+       if (!_running && midi_clock_count == 0 && session_pos) {
+               should_be_position = *session_pos;
+               DEBUG_TRACE (DEBUG::MidiClock, string_compose ("set sbp to %1\n", should_be_position));
+       }
+
+       if (session_pos) {
+               const samplepos_t current_pos = should_be_position + ((now - last_timestamp) * _speed);
+               _current_delta = current_pos - *session_pos;
+       } else {
+               _current_delta = 0;
+       }
+
+       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("speed_and_position: speed %1 should-be %2 transport %3 \n", _speed, should_be_position, _session->transport_sample()));
+}
+
+void
+MIDIClock_TransportMaster::calculate_one_ppqn_in_samples_at(samplepos_t time)
+{
+       const double samples_per_quarter_note = _session->tempo_map().samples_per_quarter_note_at (time, ENGINE->sample_rate());
 
        one_ppqn_in_samples = samples_per_quarter_note / double (ppqn);
        // DEBUG_TRACE (DEBUG::MidiClock, string_compose ("at %1, one ppqn = %2\n", time, one_ppqn_in_samples));
 }
 
 ARDOUR::samplepos_t
-MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
+MIDIClock_TransportMaster::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
 {
        samplepos_t song_position_samples = 0;
        for (uint16_t i = 1; i <= song_position_in_sixteenth_notes; ++i) {
@@ -102,81 +174,129 @@ MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_not
 }
 
 void
-MIDIClock_Slave::calculate_filter_coefficients()
+MIDIClock_TransportMaster::calculate_filter_coefficients (double qpm)
 {
-       // omega = 2 * PI * Bandwidth / MIDI clock sample frequency in Hz
-       omega = 2.0 * M_PI * bandwidth * one_ppqn_in_samples / session->sample_rate();
-       b = 1.4142135623730950488 * omega;
+       /* Paul says: I don't understand this computation of bandwidth
+       */
+
+       const double bandwidth = 2.0 / qpm;
+
+       /* Frequency of the clock messages is ENGINE->sample_rate() / * one_ppqn_in_samples, per second or in Hz */
+       const double freq = (double) ENGINE->sample_rate() / one_ppqn_in_samples;
+
+       const double omega = 2.0 * M_PI * bandwidth / freq;
+       b = 1.4142135623730950488 * omega; // sqrt (2.0) * omega
        c = omega * omega;
+
+       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("DLL coefficients: bw:%1 omega:%2 b:%3 c:%4\n", bandwidth, omega, b, c));
 }
 
 void
-MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
+MIDIClock_TransportMaster::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
 {
-       // some pieces of hardware send MIDI Clock all the time
-       if ( (!_starting) && (!_started) ) {
-               return;
-       }
-
-       pframes_t cycle_offset = timestamp - session->sample_time_at_cycle_start();
+       samplepos_t elapsed_since_start = timestamp - first_timestamp;
+       double e = 0;
 
-       calculate_one_ppqn_in_samples_at(should_be_position);
+       calculate_one_ppqn_in_samples_at (should_be_position);
 
-       samplepos_t elapsed_since_start = timestamp - first_timestamp;
-       double error = 0;
+       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("clock count %1, sbp %2\n", midi_clock_count, should_be_position));
 
-       if (_starting || last_timestamp == 0) {
-               midi_clock_count = 0;
+       if (midi_clock_count == 0) {
+               /* second 0xf8 message after start/reset has arrived */
 
                first_timestamp = timestamp;
-               elapsed_since_start = should_be_position;
+               last_timestamp = timestamp;
 
                DEBUG_TRACE (DEBUG::MidiClock, string_compose ("first clock message after start received @ %1\n", timestamp));
 
-               // calculate filter coefficients
-               calculate_filter_coefficients();
+               midi_clock_count++;
 
-               // initialize DLL
-               e2 = double(one_ppqn_in_samples) / double(session->sample_rate());
-               t0 = double(elapsed_since_start) / double(session->sample_rate());
-               t1 = t0 + e2;
+               should_be_position += one_ppqn_in_samples;
+
+       } else if (midi_clock_count == 1) {
+
+               /* second 0xf8 message has arrived. we can now estimate QPM
+                * (quarters per minute, and fully initialize the DLL
+                */
+
+               e  = timestamp - last_timestamp;
+
+               const samplecnt_t samples_per_quarter = e * 24;
+               _bpm = (ENGINE->sample_rate() * 60.0) / samples_per_quarter;
+
+               calculate_filter_coefficients (_bpm);
+
+               /* finish DLL initialization */
+
+               t0 = timestamp;
+               e2 = e;
+               t1 = t0 + e2; /* timestamp we predict for the next 0xf8 clock message */
 
-               // let ardour go after first MIDI Clock Event
-               _starting = false;
-       } else {
                midi_clock_count++;
-               should_be_position  += one_ppqn_in_samples;
-               calculate_filter_coefficients();
-
-               // calculate loop error
-               // we use session->transport_sample() instead of t1 here
-               // because t1 is used to calculate the transport speed,
-               // so the loop will compensate for accumulating rounding errors
-               error = (double(should_be_position) - (double(session->transport_sample()) + double(cycle_offset)));
-               e = error / double(session->sample_rate());
-               current_delta = error;
-
-               // update DLL
+               should_be_position += one_ppqn_in_samples;
+
+       } else {
+
+               /* 3rd or later MIDI clock message. We can now compute actual
+                * speed (and tempo) with the DLL
+                */
+
+               e = timestamp - t1; // error between actual time of arrival of clock message and our predicted time
                t0 = t1;
                t1 += b * e + e2;
                e2 += c * e;
+
+               const double samples_per_quarter = (timestamp - last_timestamp) * 24.0;
+               const double instantaneous_bpm = (ENGINE->sample_rate() * 60.0) / samples_per_quarter;
+               const double lpf_coeff = 0.05;
+
+               const double predicted_clock_interval_in_samples = (t1 - t0);
+
+               /* _speed is relative to session tempo map */
+
+               _speed = predicted_clock_interval_in_samples / one_ppqn_in_samples;
+
+               /* _bpm (really, _qpm) is absolute */
+
+               /* detect substantial changes in apparent tempo (defined as a
+                * change of more than 20% of the current tempo.
+                */
+
+               if (fabs (instantaneous_bpm - _bpm) > (0.20 * _bpm)) {
+                       _bpm = instantaneous_bpm;
+               } else {
+                       _bpm += lpf_coeff * (instantaneous_bpm - _bpm);
+               }
+
+               calculate_filter_coefficients (_bpm);
+
+               // need at least two clock events to compute speed
+
+               if (!_running) {
+                       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("start mclock running with speed = %1\n", (t1 - t0) / one_ppqn_in_samples));
+                       _running = true;
+               }
+
+               midi_clock_count++;
+               should_be_position += one_ppqn_in_samples;
        }
 
        DEBUG_TRACE (DEBUG::MidiClock, string_compose ("clock #%1 @ %2 should-be %3 transport %4 error %5 appspeed %6 "
-                                                      "read-delta %7 should-be delta %8 t1-t0 %9 t0 %10 t1 %11 framerate %12 engine %13\n",
+                                                      "read-delta %7 should-be delta %8 t1-t0 %9 t0 %10 t1 %11 framerate %12 engine %13 running %14\n",
                                                       midi_clock_count,                                          // #
                                                       elapsed_since_start,                                       // @
                                                       should_be_position,                                        // should-be
-                                                      session->transport_sample(),                                // transport
-                                                      error,                                                     // error
-                                                      ((t1 - t0) * session->sample_rate()) / one_ppqn_in_samples, // appspeed
+                                                      _session->transport_sample(),                                // transport
+                                                      e,                                                     // error
+                                                      (t1 - t0) / one_ppqn_in_samples, // appspeed
                                                       timestamp - last_timestamp,                                // read delta
                                                       one_ppqn_in_samples,                                        // should-be delta
-                                                      (t1 - t0) * session->sample_rate(),                         // t1-t0
-                                                      t0 * session->sample_rate(),                                // t0
-                                                      t1 * session->sample_rate(),                                // t1
-                                                      session->sample_rate(),                                      // framerate
-                                                      session->sample_time()
+                                                      (t1 - t0),                         // t1-t0
+                                                      t0,                                // t0
+                                                      t1,                                // t1
+                                                      ENGINE->sample_rate(),                                      // framerate
+                                                      ENGINE->sample_time(),
+                                                      _running
 
        ));
 
@@ -184,57 +304,47 @@ MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
 }
 
 void
-MIDIClock_Slave::start (Parser& /*parser*/, samplepos_t timestamp)
+MIDIClock_TransportMaster::start (Parser& /*parser*/, samplepos_t timestamp)
 {
-       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave got start message at time %1 engine time %2 transport_sample %3\n", timestamp, session->sample_time(), session->transport_sample()));
+       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_TransportMaster got start message at time %1 engine time %2 transport_sample %3\n", timestamp, ENGINE->sample_time(), _session->transport_sample()));
 
-       if (!_started) {
+       if (!_running) {
                reset();
-
-               _started = true;
-               _starting = true;
-
-               should_be_position = session->transport_sample();
+               _running = true;
+               should_be_position = _session->transport_sample();
        }
 }
 
 void
-MIDIClock_Slave::reset ()
+MIDIClock_TransportMaster::reset ()
 {
-       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MidiClock_Slave reset(): calculated filter bandwidth is %1 for period size %2\n", bandwidth, session->samples_per_cycle()));
+       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MidiClock Master reset(): calculated filter for period size %2\n", ENGINE->samples_per_cycle()));
 
-       should_be_position = session->transport_sample();
+       should_be_position = _session->transport_sample();
+       _speed = 0;
        last_timestamp = 0;
 
-       _starting = true;
-       _started  = true;
-
-       // session->request_locate(0, false);
-       current_delta = 0;
+       _running = false;
+       _current_delta = 0;
 }
 
 void
-MIDIClock_Slave::contineu (Parser& /*parser*/, samplepos_t /*timestamp*/)
+MIDIClock_TransportMaster::contineu (Parser& /*parser*/, samplepos_t /*timestamp*/)
 {
-       DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_Slave got continue message\n");
+       DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_TransportMaster got continue message\n");
 
-       if (!_started) {
-               _starting = true;
-               _started  = true;
-       }
+       _running = true;
 }
 
-
 void
-MIDIClock_Slave::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
+MIDIClock_TransportMaster::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
 {
-       DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_Slave got stop message\n");
+       DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_TransportMaster got stop message\n");
 
-       if (_started || _starting) {
-               _starting = false;
-               _started  = false;
-               // locate to last MIDI clock position
-               session->request_transport_speed(0.0);
+       if (_running) {
+               _running = false;
+               _speed = 0;
+               last_timestamp = 0;
 
                // we need to go back to the last MIDI beat (6 ppqn)
                // and lets hope the tempo didnt change in the meantime :)
@@ -243,24 +353,19 @@ MIDIClock_Slave::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
                // that is the position of the last MIDI Clock
                // message and that is probably what the master
                // expects where we are right now
-               samplepos_t stop_position = should_be_position;
-
+               //
                // find out the last MIDI beat: go back #midi_clocks mod 6
                // and lets hope the tempo didnt change in those last 6 beats :)
-               stop_position -= (midi_clock_count % 6) * one_ppqn_in_samples;
-
-               session->request_locate(stop_position, false);
-               should_be_position = stop_position;
-               last_timestamp = 0;
+               should_be_position -= (midi_clock_count % 6) * one_ppqn_in_samples;
        }
 }
 
 void
-MIDIClock_Slave::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
+MIDIClock_TransportMaster::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
 {
-       // we are note supposed to get position messages while we are running
+       // we are not supposed to get position messages while we are running
        // so lets be robust and ignore those
-       if (_started || _starting) {
+       if (_running) {
                return;
        }
 
@@ -274,102 +379,51 @@ MIDIClock_Slave::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
 
        DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Song Position: %1 samples: %2\n", position_in_sixteenth_notes, position_in_samples));
 
-       session->request_locate(position_in_samples, false);
-       should_be_position  = position_in_samples;
+       should_be_position = position_in_samples;
        last_timestamp = 0;
 
 }
 
 bool
-MIDIClock_Slave::locked () const
+MIDIClock_TransportMaster::locked () const
 {
        return true;
 }
 
 bool
-MIDIClock_Slave::ok() const
+MIDIClock_TransportMaster::ok() const
 {
        return true;
 }
 
 bool
-MIDIClock_Slave::starting() const
+MIDIClock_TransportMaster::starting() const
 {
        return false;
 }
 
-bool
-MIDIClock_Slave::stop_if_no_more_clock_events(samplepos_t& pos, samplepos_t now)
-{
-       /* no timecode for 1/4 second ? conclude that its stopped */
-       if (last_timestamp &&
-           now > last_timestamp &&
-           now - last_timestamp > session->sample_rate() / 4) {
-               DEBUG_TRACE (DEBUG::MidiClock, "No MIDI Clock samples received for some time, stopping!\n");
-               pos = should_be_position;
-               session->request_transport_speed (0);
-               session->request_locate (should_be_position, false);
-               return true;
-       } else {
-               return false;
-       }
-}
-
-bool
-MIDIClock_Slave::speed_and_position (double& speed, samplepos_t& pos)
-{
-       if (!_started || _starting) {
-               speed = 0.0;
-               pos   = should_be_position;
-               return true;
-       }
-
-       samplepos_t engine_now = session->sample_time();
-
-       if (stop_if_no_more_clock_events(pos, engine_now)) {
-               return false;
-       }
-
-       // calculate speed
-       speed = ((t1 - t0) * session->sample_rate()) / one_ppqn_in_samples;
-
-       // provide a 0.1% deadzone to lock the speed
-       if (fabs(speed - 1.0) <= 0.001)
-               speed = 1.0;
-
-       // calculate position
-       if (engine_now > last_timestamp) {
-               // we are in between MIDI clock messages
-               // so we interpolate position according to speed
-               samplecnt_t elapsed = engine_now - last_timestamp;
-               pos = (samplepos_t) (should_be_position + double(elapsed) * speed);
-       } else {
-               // A new MIDI clock message has arrived this cycle
-               pos = should_be_position;
-       }
-
-       DEBUG_TRACE (DEBUG::MidiClock, string_compose ("speed_and_position: speed %1 should-be %2 transport %3 \n", speed, pos, session->transport_sample()));
-
-       return true;
-}
-
 ARDOUR::samplecnt_t
-MIDIClock_Slave::resolution() const
+MIDIClock_TransportMaster::resolution() const
 {
        // one beat
        return (samplecnt_t) one_ppqn_in_samples * ppqn;
 }
 
 std::string
-MIDIClock_Slave::approximate_current_delta() const
+MIDIClock_TransportMaster::position_string () const
+{
+       return std::string();
+}
+
+std::string
+MIDIClock_TransportMaster::delta_string() const
 {
        char delta[80];
-       if (last_timestamp == 0 || _starting) {
+       if (last_timestamp == 0 || starting()) {
                snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
        } else {
                snprintf(delta, sizeof(delta), "\u0394<span foreground=\"green\" face=\"monospace\" >%s%s%" PRIi64 "</span>sm",
-                               LEADINGZERO(abs(current_delta)), PLUSMINUS(-current_delta), abs(current_delta));
+                               LEADINGZERO(abs(_current_delta)), PLUSMINUS(-_current_delta), abs(_current_delta));
        }
        return std::string(delta);
 }
-
index dfc1c37e8720d3f827d885aa13dffe41332797ab..f8259c891700bf384a660e066098e18414804abd 100644 (file)
@@ -37,11 +37,10 @@ using namespace PBD;
 
 MidiPort::MidiPort (const std::string& name, PortFlags flags)
        : Port (name, DataType::MIDI, flags)
-       , _has_been_mixed_down (false)
        , _resolve_required (false)
        , _input_active (true)
-       , _always_parse (false)
-       , _trace_on (false)
+       , _trace_parser (0)
+       , _data_fetched_for_cycle (false)
 {
        _buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI));
 }
@@ -57,10 +56,13 @@ MidiPort::~MidiPort()
 }
 
 void
-MidiPort::cycle_start (pframes_t nframes)
+MidiPort::parse_input (pframes_t nframes, MIDI::Parser& parser)
 {
-       samplepos_t now = AudioEngine::instance()->sample_time_at_cycle_start();
+}
 
+void
+MidiPort::cycle_start (pframes_t nframes)
+{
        Port::cycle_start (nframes);
 
        _buffer->clear ();
@@ -69,22 +71,8 @@ MidiPort::cycle_start (pframes_t nframes)
                port_engine.midi_clear (port_engine.get_buffer (_port_handle, nframes));
        }
 
-       if (_always_parse || (receives_input() && _trace_on)) {
-               MidiBuffer& mb (get_midi_buffer (nframes));
-
-               /* dump incoming MIDI to parser */
-
-               for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) {
-                       uint8_t* buf = (*b).buffer();
-
-                       _self_parser.set_timestamp (now + (*b).time());
-
-                       uint32_t limit = (*b).size();
-
-                       for (size_t n = 0; n < limit; ++n) {
-                               _self_parser.scanner (buf[n]);
-                       }
-               }
+       if (receives_input() && _trace_parser) {
+               read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, *_trace_parser, AudioEngine::instance()->sample_time_at_cycle_start());
        }
 
        if (inbound_midi_filter) {
@@ -101,80 +89,64 @@ MidiPort::cycle_start (pframes_t nframes)
 
 }
 
-Buffer&
-MidiPort::get_buffer (pframes_t nframes)
-{
-       return get_midi_buffer (nframes);
-}
-
 MidiBuffer &
 MidiPort::get_midi_buffer (pframes_t nframes)
 {
-       if (_has_been_mixed_down) {
+       if (_data_fetched_for_cycle) {
                return *_buffer;
        }
 
-       if (receives_input ()) {
-
-               if (_input_active) {
+       if (receives_input () && _input_active) {
 
-                       void* buffer = port_engine.get_buffer (_port_handle, nframes);
-                       const pframes_t event_count = port_engine.get_midi_event_count (buffer);
+               void* buffer = port_engine.get_buffer (_port_handle, nframes);
+               const pframes_t event_count = port_engine.get_midi_event_count (buffer);
 
-                       /* suck all relevant MIDI events from the MIDI port buffer
-                          into our MidiBuffer
-                       */
+               /* suck all MIDI events for this cycle of nframes from
+                  the MIDI port buffer into our MidiBuffer.
+               */
 
-                       for (pframes_t i = 0; i < event_count; ++i) {
+               for (pframes_t i = 0; i < event_count; ++i) {
 
-                               pframes_t timestamp;
-                               size_t size;
-                               uint8_t const* buf;
+                       pframes_t timestamp;
+                       size_t size;
+                       uint8_t const* buf;
 
-                               port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
+                       port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
 
-                               if (buf[0] == 0xfe) {
-                                       /* throw away active sensing */
-                                       continue;
-                               }
+                       if (buf[0] == 0xfe) {
+                               /* throw away active sensing */
+                               continue;
+                       }
 
-                               timestamp = floor (timestamp * _speed_ratio);
+                       timestamp = floor (timestamp * _speed_ratio);
 
-                               /* check that the event is in the acceptable time range */
-                               if ((timestamp <  (_global_port_buffer_offset)) ||
-                                   (timestamp >= (_global_port_buffer_offset + nframes))) {
-                                       // XXX this is normal after a split cycles:
-                                       // The engine buffer contains the data for the complete cycle, but
-                                       // only the part after _global_port_buffer_offset is needed.
+                       /* check that the event is in the acceptable time range */
+                       if ((timestamp <  (_global_port_buffer_offset)) ||
+                           (timestamp >= (_global_port_buffer_offset + nframes))) {
+                               // XXX this is normal after a split cycles:
+                               // The engine buffer contains the data for the complete cycle, but
+                               // only the part after _global_port_buffer_offset is needed.
 #ifndef NDEBUG
-                                       cerr << "Dropping incoming MIDI at time " << timestamp << "; offset="
-                                               << _global_port_buffer_offset << " limit="
-                                               << (_global_port_buffer_offset + nframes)
-                                               << " = (" << _global_port_buffer_offset
-                                               << " + " << nframes
-                                               << ")\n";
+                               cerr << "Dropping incoming MIDI at time " << timestamp << "; offset="
+                                    << _global_port_buffer_offset << " limit="
+                                    << (_global_port_buffer_offset + nframes)
+                                    << " = (" << _global_port_buffer_offset
+                                    << " + " << nframes
+                                    << ")\n";
 #endif
-                                       continue;
-                               }
-
-                               /* adjust timestamp to match current cycle */
-                               timestamp -= _global_port_buffer_offset;
-                               assert (timestamp < nframes);
-
-                               if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
-                                       /* normalize note on with velocity 0 to proper note off */
-                                       uint8_t ev[3];
-                                       ev[0] = 0x80 | (buf[0] & 0x0F);  /* note off */
-                                       ev[1] = buf[1];
-                                       ev[2] = 0x40;  /* default velocity */
-                                       _buffer->push_back (timestamp, size, ev);
-                               } else {
-                                       _buffer->push_back (timestamp, size, buf);
-                               }
+                               continue;
                        }
 
-               } else {
-                       _buffer->silence (nframes);
+                       if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
+                               /* normalize note on with velocity 0 to proper note off */
+                               uint8_t ev[3];
+                               ev[0] = 0x80 | (buf[0] & 0x0F);  /* note off */
+                               ev[1] = buf[1];
+                               ev[2] = 0x40;  /* default velocity */
+                               _buffer->push_back (timestamp, size, ev);
+                       } else {
+                               _buffer->push_back (timestamp, size, buf);
+                       }
                }
 
        } else {
@@ -182,22 +154,63 @@ MidiPort::get_midi_buffer (pframes_t nframes)
        }
 
        if (nframes) {
-               _has_been_mixed_down = true;
+               _data_fetched_for_cycle = true;
        }
 
        return *_buffer;
 }
 
+void
+MidiPort::read_and_parse_entire_midi_buffer_with_no_speed_adjustment (pframes_t nframes, MIDI::Parser& parser, samplepos_t now)
+{
+       void* buffer = port_engine.get_buffer (_port_handle, nframes);
+       const pframes_t event_count = port_engine.get_midi_event_count (buffer);
+
+       for (pframes_t i = 0; i < event_count; ++i) {
+
+               pframes_t timestamp;
+               size_t size;
+               uint8_t const* buf;
+
+               port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
+
+               if (buf[0] == 0xfe) {
+                       /* throw away active sensing */
+                       continue;
+               }
+
+               parser.set_timestamp (now + timestamp);
+
+               /* During this parsing stage, signals will be emitted from the
+                * Parser, which will update anything connected to it.
+                *
+                * As of July 2018, this is only used by TransportMasters which
+                * read MIDI before the process() cycle really gets started.
+                */
+
+               if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
+                       /* normalize note on with velocity 0 to proper note off */
+                       parser.scanner (0x80 | (buf[0] & 0x0F));  /* note off */
+                       parser.scanner (buf[1]);
+                       parser.scanner (0x40);  /* default (off) velocity */
+               } else {
+                       for (size_t n = 0; n < size; ++n) {
+                               parser.scanner (buf[n]);
+                       }
+               }
+       }
+}
+
 void
 MidiPort::cycle_end (pframes_t /*nframes*/)
 {
-       _has_been_mixed_down = false;
+       _data_fetched_for_cycle = false;
 }
 
 void
 MidiPort::cycle_split ()
 {
-       _has_been_mixed_down = false;
+       _data_fetched_for_cycle = false;
 }
 
 void
@@ -253,16 +266,16 @@ MidiPort::flush_buffers (pframes_t nframes)
                        const Evoral::Event<MidiBuffer::TimeType> ev (*i, false);
 
 
-                       if (sends_output() && _trace_on) {
+                       if (sends_output() && _trace_parser) {
                                uint8_t const * const buf = ev.buffer();
                                const samplepos_t now = AudioEngine::instance()->sample_time_at_cycle_start();
 
-                               _self_parser.set_timestamp (now + ev.time());
+                               _trace_parser->set_timestamp (now + ev.time());
 
                                uint32_t limit = ev.size();
 
                                for (size_t n = 0; n < limit; ++n) {
-                                       _self_parser.scanner (buf[n]);
+                                       _trace_parser->scanner (buf[n]);
                                }
                        }
 
@@ -347,15 +360,9 @@ MidiPort::set_input_active (bool yn)
 }
 
 void
-MidiPort::set_always_parse (bool yn)
-{
-       _always_parse = yn;
-}
-
-void
-MidiPort::set_trace_on (bool yn)
+MidiPort::set_trace (MIDI::Parser * p)
 {
-       _trace_on = yn;
+       _trace_parser = p;
 }
 
 int
index cf1d90ac565ea082f961c01a6525247cab3f503c..df1b9d5441dfdea920c86d3403e3bee47206481b 100644 (file)
@@ -50,15 +50,9 @@ MidiPortManager::~MidiPortManager ()
        if (_scene_out) {
                AudioEngine::instance()->unregister_port (_scene_out);
        }
-       if (_mtc_input_port) {
-               AudioEngine::instance()->unregister_port (_mtc_input_port);
-       }
        if (_mtc_output_port) {
                AudioEngine::instance()->unregister_port (_mtc_output_port);
        }
-       if (_midi_clock_input_port) {
-               AudioEngine::instance()->unregister_port (_midi_clock_input_port);
-       }
        if (_midi_clock_output_port) {
                AudioEngine::instance()->unregister_port (_midi_clock_output_port);
        }
@@ -84,29 +78,16 @@ MidiPortManager::create_ports ()
        _scene_in  = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Scene in"), true);
        _scene_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Scene out"), true);
 
-       /* Now register ports used for sync (MTC and MIDI Clock)
+       /* Now register ports used to send positional sync data (MTC and MIDI Clock)
         */
 
        boost::shared_ptr<ARDOUR::Port> p;
 
-       p = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("MTC in"));
-       _mtc_input_port = boost::dynamic_pointer_cast<MidiPort> (p);
        p = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("MTC out"));
        _mtc_output_port= boost::dynamic_pointer_cast<MidiPort> (p);
 
-       p = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("MIDI Clock in"));
-       _midi_clock_input_port = boost::dynamic_pointer_cast<MidiPort> (p);
        p = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("MIDI Clock out"));
        _midi_clock_output_port= boost::dynamic_pointer_cast<MidiPort> (p);
-
-       /* These ports all need their incoming data handled in
-        * Port::cycle_start() and so ...
-        */
-
-       _mtc_input_port->set_always_parse (true);
-       _mtc_output_port->set_always_parse (true);
-       _midi_clock_input_port->set_always_parse (true);
-       _midi_clock_output_port->set_always_parse (true);
 }
 
 void
@@ -117,9 +98,7 @@ MidiPortManager::set_midi_port_states (const XMLNodeList&nodes)
        PortMap ports;
        const int version = 0;
 
-       ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port));
        ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port));
-       ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port));
        ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port));
        ports.insert (make_pair (_midi_in->name(), _midi_in));
        ports.insert (make_pair (_midi_out->name(), _midi_out));
@@ -149,9 +128,7 @@ MidiPortManager::get_midi_port_states () const
        PortMap ports;
        list<XMLNode*> s;
 
-       ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port));
        ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port));
-       ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port));
        ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port));
        ports.insert (make_pair (_midi_in->name(), _midi_in));
        ports.insert (make_pair (_midi_out->name(), _midi_out));
index 0f277a7f5550f1f99e6a14251278e8545b744d91..409fb9da6994bba8f89d0cf0ffa731f2c122e96a 100644 (file)
@@ -30,7 +30,7 @@
 #include "ardour/midi_buffer.h"
 #include "ardour/midi_port.h"
 #include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
 
 #include <glibmm/timer.h>
 
@@ -49,38 +49,35 @@ using namespace Timecode;
    recently received position (and without the direction of timecode reversing too), we
    will stop+locate+wait+chase.
 */
-const int MTC_Slave::sample_tolerance = 2;
-
-MTC_Slave::MTC_Slave (Session& s, MidiPort& p)
-       : session (s)
-       , port (&p)
+const int MTC_TransportMaster::sample_tolerance = 2;
+
+MTC_TransportMaster::MTC_TransportMaster (std::string const & name)
+       : TimecodeTransportMaster (name, MTC)
+       , can_notify_on_unknown_rate (true)
+       , mtc_frame (0)
+       , mtc_frame_dll (0)
+       , last_inbound_frame (0)
+       , window_begin (0)
+       , window_end (0)
+       , first_mtc_timestamp (0)
+       , did_reset_tc_format (false)
+       , reset_pending (0)
+       , reset_position (false)
+       , transport_direction (1)
+       , busy_guard1 (0)
+       , busy_guard2 (0)
+       , printed_timecode_warning (false)
 {
-       can_notify_on_unknown_rate = true;
-       did_reset_tc_format = false;
-       reset_pending = 0;
-       reset_position = false;
-       mtc_frame = 0;
-       mtc_frame_dll = 0;
-       engine_dll_initstate = 0;
-       busy_guard1 = busy_guard2 = 0;
-
-       last_mtc_fps_byte = session.get_mtc_timecode_bits ();
-       quarter_frame_duration = (double(session.samples_per_timecode_frame()) / 4.0);
-
-       mtc_timecode = session.config.get_timecode_format();
-       a3e_timecode = session.config.get_timecode_format();
-       printed_timecode_warning = false;
-
-       session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_Slave::parameter_changed, this, _1));
-       parse_timecode_offset();
-       reset (true);
+       if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
+               throw failed_constructor();
+       }
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("MTC registered %1\n", _port->name()));
 
-       port->self_parser().mtc_time.connect_same_thread (port_connections,  boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3));
-       port->self_parser().mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3));
-       port->self_parser().mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1));
+       init ();
 }
 
-MTC_Slave::~MTC_Slave()
+MTC_TransportMaster::~MTC_TransportMaster()
 {
        port_connections.drop_connections();
        config_connection.disconnect();
@@ -96,31 +93,69 @@ MTC_Slave::~MTC_Slave()
        }
 
        if (did_reset_tc_format) {
-               session.config.set_timecode_format (saved_tc_format);
+               _session->config.set_timecode_format (saved_tc_format);
        }
 }
 
 void
-MTC_Slave::rebind (MidiPort& p)
+MTC_TransportMaster::init ()
+{
+       reset (true);
+}
+
+void
+MTC_TransportMaster::set_session (Session *s)
 {
-       port_connections.drop_connections ();
+       config_connection.disconnect ();
+       port_connections.drop_connections();
+
+       _session = s;
+
+       if (_session) {
+
+               last_mtc_fps_byte = _session->get_mtc_timecode_bits ();
+               quarter_frame_duration = (double) (_session->samples_per_timecode_frame() / 4.0);
+               mtc_timecode = _session->config.get_timecode_format();
+               a3e_timecode = _session->config.get_timecode_format();
+
+               parse_timecode_offset ();
+               reset (true);
 
-       port = &p;
+               parser.mtc_time.connect_same_thread (port_connections,  boost::bind (&MTC_TransportMaster::update_mtc_time, this, _1, _2, _3));
+               parser.mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_qtr, this, _1, _2, _3));
+               parser.mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_status, this, _1));
 
+               _session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_TransportMaster::parameter_changed, this, _1));
+       }
 }
 
 void
-MTC_Slave::parse_timecode_offset() {
+MTC_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
+{
+       /* Read and parse incoming MIDI */
+
+       _midi_port->read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, parser, now);
+
+       if (session_pos) {
+               const samplepos_t current_pos = current.position + ((now - current.timestamp) * current.speed);
+               _current_delta = current_pos - *session_pos;
+       } else {
+               _current_delta = 0;
+       }
+}
+
+void
+MTC_TransportMaster::parse_timecode_offset() {
        Timecode::Time offset_tc;
-       Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc);
-       offset_tc.rate = session.timecode_frames_per_second();
-       offset_tc.drop = session.timecode_drop_frames();
-       session.timecode_to_sample(offset_tc, timecode_offset, false, false);
+       Timecode::parse_timecode_format (_session->config.get_slave_timecode_offset(), offset_tc);
+       offset_tc.rate = _session->timecode_frames_per_second();
+       offset_tc.drop = _session->timecode_drop_frames();
+       _session->timecode_to_sample(offset_tc, timecode_offset, false, false);
        timecode_negative_offset = offset_tc.negative;
 }
 
 void
-MTC_Slave::parameter_changed (std::string const & p)
+MTC_TransportMaster::parameter_changed (std::string const & p)
 {
        if (p == "slave-timecode-offset"
                        || p == "timecode-format"
@@ -129,47 +164,40 @@ MTC_Slave::parameter_changed (std::string const & p)
        }
 }
 
-bool
-MTC_Slave::give_slave_full_control_over_transport_speed() const
-{
-       return true; // DLL align to engine transport
-       // return false; // for Session-level computed varispeed
-}
-
 ARDOUR::samplecnt_t
-MTC_Slave::resolution () const
+MTC_TransportMaster::resolution () const
 {
        return (samplecnt_t) quarter_frame_duration * 4.0;
 }
 
 ARDOUR::samplecnt_t
-MTC_Slave::seekahead_distance () const
+MTC_TransportMaster::seekahead_distance () const
 {
        return quarter_frame_duration * 8 * transport_direction;
 }
 
 bool
-MTC_Slave::outside_window (samplepos_t pos) const
+MTC_TransportMaster::outside_window (samplepos_t pos) const
 {
        return ((pos < window_begin) || (pos > window_end));
 }
 
 
 bool
-MTC_Slave::locked () const
+MTC_TransportMaster::locked () const
 {
-       DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2 initstate %3\n", port->self_parser().mtc_locked(), last_inbound_frame, engine_dll_initstate));
-       return port->self_parser().mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0;
+       DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2\n", parser.mtc_locked(), last_inbound_frame));
+       return parser.mtc_locked() && last_inbound_frame !=0;
 }
 
 bool
-MTC_Slave::ok() const
+MTC_TransportMaster::ok() const
 {
        return true;
 }
 
 void
-MTC_Slave::queue_reset (bool reset_pos)
+MTC_TransportMaster::queue_reset (bool reset_pos)
 {
        Glib::Threads::Mutex::Lock lm (reset_lock);
        reset_pending++;
@@ -179,7 +207,7 @@ MTC_Slave::queue_reset (bool reset_pos)
 }
 
 void
-MTC_Slave::maybe_reset ()
+MTC_TransportMaster::maybe_reset ()
 {
        Glib::Threads::Mutex::Lock lm (reset_lock);
 
@@ -191,9 +219,10 @@ MTC_Slave::maybe_reset ()
 }
 
 void
-MTC_Slave::reset (bool with_position)
+MTC_TransportMaster::reset (bool with_position)
 {
-       DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC_Slave reset %1\n", with_position?"with position":"without position"));
+       DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC_TransportMaster reset %1\n", with_position?"with position":"without position"));
+
        if (with_position) {
                last_inbound_frame = 0;
                current.guard1++;
@@ -212,15 +241,14 @@ MTC_Slave::reset (bool with_position)
        window_begin = 0;
        window_end = 0;
        transport_direction = 1;
-       current_delta = 0;
-       ActiveChanged(false);
+       _current_delta = 0;
 }
 
 void
-MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
+MTC_TransportMaster::handle_locate (const MIDI::byte* mmc_tc)
 {
        MIDI::byte mtc[5];
-       DEBUG_TRACE (DEBUG::MTC, "MTC_Slave::handle_locate\n");
+       DEBUG_TRACE (DEBUG::MTC, "MTC_TransportMaster::handle_locate\n");
 
        mtc[4] = last_mtc_fps_byte;
        mtc[3] = mmc_tc[0] & 0xf; /* hrs only */
@@ -232,7 +260,7 @@ MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
 }
 
 void
-MTC_Slave::read_current (SafeTime *st) const
+MTC_TransportMaster::read_current (SafeTime *st) const
 {
        int tries = 0;
 
@@ -249,9 +277,9 @@ MTC_Slave::read_current (SafeTime *st) const
 }
 
 void
-MTC_Slave::init_mtc_dll(samplepos_t tme, double qtr)
+MTC_TransportMaster::init_mtc_dll(samplepos_t tme, double qtr)
 {
-       omega = 2.0 * M_PI * qtr / 2.0 / double(session.sample_rate());
+       const double omega = 2.0 * M_PI * qtr / 2.0 / double(_session->sample_rate());
        b = 1.4142135623730950488 * omega;
        c = omega * omega;
 
@@ -263,7 +291,7 @@ MTC_Slave::init_mtc_dll(samplepos_t tme, double qtr)
 
 /* called from MIDI parser */
 void
-MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, samplepos_t now)
+MTC_TransportMaster::update_mtc_qtr (Parser& p, int which_qtr, samplepos_t now)
 {
        busy_guard1++;
        const double qtr_d = quarter_frame_duration;
@@ -302,7 +330,7 @@ MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, samplepos_t now)
  * when a full TC has been received
  * OR on locate */
 void
-MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t now)
+MTC_TransportMaster::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t now)
 {
        busy_guard1++;
 
@@ -341,7 +369,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
                can_notify_on_unknown_rate = true;
                break;
        case MTC_30_FPS_DROP:
-               if (Config->get_timecode_source_2997()) {
+               if (fr2997()) {
                        tc_format = Timecode::timecode_2997000drop;
                        timecode.rate = (29970.0/1000.0);
                } else {
@@ -365,13 +393,13 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
                              << endmsg;
                        can_notify_on_unknown_rate = false;
                }
-               timecode.rate = session.timecode_frames_per_second();
-               timecode.drop = session.timecode_drop_frames();
+               timecode.rate = _session->timecode_frames_per_second();
+               timecode.drop = _session->timecode_drop_frames();
                reset_tc = false;
        }
 
        if (reset_tc) {
-               TimecodeFormat cur_timecode = session.config.get_timecode_format();
+               TimecodeFormat cur_timecode = _session->config.get_timecode_format();
                if (Config->get_timecode_sync_frame_rate()) {
                        /* enforce time-code */
                        if (!did_reset_tc_format) {
@@ -386,7 +414,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
                                                << endmsg;
                                }
                        }
-                       session.config.set_timecode_format (tc_format);
+                       _session->config.set_timecode_format (tc_format);
                } else {
                        /* only warn about TC mismatch */
                        if (mtc_timecode != tc_format) printed_timecode_warning = false;
@@ -414,11 +442,11 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
           consideration.
        */
 
-       quarter_frame_duration = (double(session.sample_rate()) / (double) timecode.rate / 4.0);
+       quarter_frame_duration = (double(_session->sample_rate()) / (double) timecode.rate / 4.0);
 
        Timecode::timecode_to_sample (timecode, mtc_frame, true, false,
-               double(session.sample_rate()),
-               session.config.get_subframes_per_frame(),
+               double(_session->sample_rate()),
+               _session->config.get_subframes_per_frame(),
                timecode_negative_offset, timecode_offset
                );
 
@@ -427,9 +455,9 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
 
        if (was_full || outside_window (mtc_frame)) {
                DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC %1 or outside window %2 MTC %3\n", was_full, outside_window (mtc_frame), mtc_frame));
-               session.set_requested_return_sample (-1);
-               session.request_transport_speed (0);
-               session.request_locate (mtc_frame, false);
+               _session->set_requested_return_sample (-1);
+               _session->request_transport_speed (0, TRS_MTC);
+               _session->request_locate (mtc_frame, false, TRS_MTC);
                update_mtc_status (MIDI::MTC_Stopped);
                reset (false);
                reset_window (mtc_frame);
@@ -449,9 +477,9 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
                long int mtc_off = (long) rint(7.0 * qtr);
 
                DEBUG_TRACE (DEBUG::MTC, string_compose ("new mtc_frame: %1 | MTC-FpT: %2 A3-FpT:%3\n",
-                                                        mtc_frame, (4.0*qtr), session.samples_per_timecode_frame()));
+                                                        mtc_frame, (4.0*qtr), _session->samples_per_timecode_frame()));
 
-               switch (port->self_parser().mtc_running()) {
+               switch (parser.mtc_running()) {
                case MTC_Backward:
                        mtc_frame -= mtc_off;
                        qtr *= -1.0;
@@ -470,7 +498,6 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
                                first_mtc_timestamp = now;
                                init_mtc_dll(mtc_frame, qtr);
                                mtc_frame_dll = mtc_frame;
-                               ActiveChanged (true); // emit signal
                        }
                        current.guard1++;
                        current.position = mtc_frame;
@@ -487,12 +514,12 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
 }
 
 void
-MTC_Slave::update_mtc_status (MIDI::MTC_Status status)
+MTC_TransportMaster::update_mtc_status (MIDI::MTC_Status status)
 {
        /* XXX !!! thread safety ... called from MIDI I/O context
         * on locate (via ::update_mtc_time())
         */
-       DEBUG_TRACE (DEBUG::MTC, string_compose("MTC_Slave::update_mtc_status - TID:%1 MTC:%2\n", pthread_name(), mtc_frame));
+       DEBUG_TRACE (DEBUG::MTC, string_compose("MTC_TransportMaster::update_mtc_status - TID:%1 MTC:%2\n", pthread_name(), mtc_frame));
        return; // why was this fn needed anyway ? it just messes up things -> use reset.
        busy_guard1++;
 
@@ -526,7 +553,7 @@ MTC_Slave::update_mtc_status (MIDI::MTC_Status status)
 }
 
 void
-MTC_Slave::reset_window (samplepos_t root)
+MTC_TransportMaster::reset_window (samplepos_t root)
 {
        /* if we're waiting for the master to catch us after seeking ahead, keep the window
           of acceptable MTC samples wide open. otherwise, shrink it down to just 2 video frames
@@ -535,7 +562,7 @@ MTC_Slave::reset_window (samplepos_t root)
 
        samplecnt_t const d = (quarter_frame_duration * 4 * sample_tolerance);
 
-       switch (port->self_parser().mtc_running()) {
+       switch (parser.mtc_running()) {
        case MTC_Forward:
                window_begin = root;
                transport_direction = 1;
@@ -561,144 +588,70 @@ MTC_Slave::reset_window (samplepos_t root)
        DEBUG_TRACE (DEBUG::MTC, string_compose ("reset MTC window @ %3, now %1 .. %2\n", window_begin, window_end, root));
 }
 
-void
-MTC_Slave::init_engine_dll (samplepos_t pos, samplepos_t inc)
-{
-       /* the bandwidth of the DLL is a trade-off,
-        * because the max-speed of the transport in ardour is
-        * limited to +-8.0, a larger bandwidth would cause oscillations
-        *
-        * But this is only really a problem if the user performs manual
-        * seeks while transport is running and slaved to MTC.
-        */
-       oe = 2.0 * M_PI * double(inc) / 2.0 / double(session.sample_rate());
-       be = 1.4142135623730950488 * oe;
-       ce = oe * oe;
-
-       ee2 = double(transport_direction * inc);
-       te0 = double(pos);
-       te1 = te0 + ee2;
-       DEBUG_TRACE (DEBUG::MTC, string_compose ("[re-]init Engine DLL %1 %2 %3\n", te0, te1, ee2));
-}
-
 /* main entry point from session_process.cc
 xo * in process callback context */
 bool
-MTC_Slave::speed_and_position (double& speed, samplepos_t& pos)
+MTC_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
 {
-       samplepos_t now = session.engine().sample_time_at_cycle_start();
-       samplepos_t sess_pos = session.transport_sample(); // corresponds to now
-       //sess_pos -= session.engine().samples_since_cycle_start();
-
        SafeTime last;
-       sampleoffset_t elapsed;
-       bool engine_dll_reinitialized = false;
+
+       if (!_collect) {
+               return false;
+       }
 
        read_current (&last);
 
-       DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 initstate %3 dir %4 tpos %5 now %6 last-in %7\n",
+       DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 dir %4 now %5 last-in %6\n",
                                                 last.timestamp,
                                                 last.speed,
-                                                engine_dll_initstate,
                                                 transport_direction,
-                                                sess_pos,
                                                 now,
                                                 last_inbound_frame));
 
-       /* re-init engine DLL here when state changed (direction, first_mtc_timestamp) */
        if (last.timestamp == 0) {
-               engine_dll_initstate = 0;
-       } else if (engine_dll_initstate != transport_direction && last.speed != 0) {
-               engine_dll_initstate = transport_direction;
-               init_engine_dll(last.position, session.engine().samples_per_cycle());
-               engine_dll_reinitialized = true;
-       }
-
-       if (last.timestamp == 0) {
-               speed = 0;
-               pos = session.transport_sample() ; // last.position;
-               DEBUG_TRACE (DEBUG::MTC, string_compose ("first call to MTC_Slave::speed_and_position, pos = %1\n", pos));
-               return true;
+               return false;
        }
 
-       /* no timecode for two samples - conclude that it's stopped */
        if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > labs(seekahead_distance())) {
-               speed = 0;
-               pos = last.position;
-               session.set_requested_return_sample (-1);
-               session.request_locate (pos, false);
-               session.request_transport_speed (0);
-               engine_dll_initstate = 0;
-               queue_reset (false);
-               ActiveChanged (false);
-               DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC not seen for 2 samples - reset pending, pos = %1\n", pos));
-               return false;
+               /* no timecode for two cycles - conclude that it's stopped */
+
+               if (!Config->get_transport_masters_just_roll_when_sync_lost()) {
+                       speed = 0;
+                       pos = last.position;
+                       _current_delta = 0;
+                       queue_reset (false);
+                       DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC not seen for 2 samples - reset pending, pos = %1\n", pos));
+                       return false;
+               }
        }
 
 
        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));
-       DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position eng-tme: %1 eng-pos: %2\n", now, sess_pos));
-
-       double speed_flt = last.speed; ///< MTC speed from MTC-quarter-frame DLL
-
-       /* interpolate position according to speed and time since last quarter-frame*/
-       if (speed_flt == 0.0f) {
-               elapsed = 0;
-       } else {
-               /* scale elapsed time by the current MTC speed */
-               elapsed = (samplecnt_t) rint (speed_flt * (now - last.timestamp));
-               if (give_slave_full_control_over_transport_speed() && !engine_dll_reinitialized) {
-                       /* there is an engine vs MTC position sample-delta.
-                        * This mostly due to quantization and rounding of (speed * nframes)
-                        * but can also due to the session-process not calling
-                        * speed_and_position() every cycle under some circumstances.
-                        * Thus we use an other DLL to align the engine and the MTC
-                        */
-
-                       /* update engine DLL and calculate speed */
-                       const double e = double (last.position + elapsed - sess_pos);
-                       te0 = te1;
-                       te1 += be * e + ee2;
-                       ee2 += ce * e;
-                       speed_flt = (te1 - te0) / double(session.engine().samples_per_cycle());
-                       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().samples_per_cycle() ));
-               }
-       }
 
-       pos = last.position + elapsed;
-       speed = speed_flt;
-
-       /* may happen if the user performs a seek in the timeline while slaved to running MTC
-        * engine-DLL can oscillate back before 0.
-        * also see note in MTC_Slave::init_engine_dll
-        */
-       if (!session.actively_recording()
-           && speed != 0
-           && ((pos < 0) || (labs(pos - sess_pos) > 3 * session.sample_rate()))) {
-               engine_dll_initstate = 0;
-               queue_reset (false);
-       }
+       speed = last.speed;
 
        /* provide a .1% deadzone to lock the speed */
-       if (fabs (speed - 1.0) <= 0.001)
-               speed = 1.0;
+       if (fabs (speed - 1.0) <= 0.001) {
+               speed = 1.0;
+       }
 
-       DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
-                                                speed, pos, last.position, elapsed,  pos - sess_pos));
+       pos =  last.position;
+       pos += (now - last.timestamp) * speed;
 
-       current_delta = (pos - sess_pos);
+       DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 | elapsed: %4\n",
+                                                speed, pos, last.position, (now - last.timestamp)));
 
        return true;
 }
 
 Timecode::TimecodeFormat
-MTC_Slave::apparent_timecode_format () const
+MTC_TransportMaster::apparent_timecode_format () const
 {
        return mtc_timecode;
 }
 
 std::string
-MTC_Slave::approximate_current_position() const
+MTC_TransportMaster::position_string() const
 {
        SafeTime last;
        read_current (&last);
@@ -707,22 +660,25 @@ MTC_Slave::approximate_current_position() const
        }
        return Timecode::timecode_format_sampletime(
                last.position,
-               double(session.sample_rate()),
+               double(_session->sample_rate()),
                Timecode::timecode_to_frames_per_second(mtc_timecode),
                Timecode::timecode_has_drop_frames(mtc_timecode));
 }
 
 std::string
-MTC_Slave::approximate_current_delta() const
+MTC_TransportMaster::delta_string () const
 {
        char delta[80];
        SafeTime last;
        read_current (&last);
+
+       delta[0] = '\0';
+
        if (last.timestamp == 0 || reset_pending) {
                snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
        } else {
                snprintf(delta, sizeof(delta), "\u0394<span foreground=\"green\" face=\"monospace\" >%s%s%" PRIi64 "</span>sm",
-                               LEADINGZERO(abs(current_delta)), PLUSMINUS(-current_delta), abs(current_delta));
+                               LEADINGZERO(abs(_current_delta)), PLUSMINUS(-_current_delta), abs(_current_delta));
        }
        return std::string(delta);
 }
index 27b414f945f2dd8c5655470aaa2296b9bf125404..3c31094daeedf3d78e855c22bd4590787ea644a3 100644 (file)
@@ -58,6 +58,7 @@ Port::Port (std::string const & n, DataType t, PortFlags f)
        : _name (n)
        , _flags (f)
        , _last_monitor (false)
+       , _externally_connected (0)
 {
        _private_playback_latency.min = 0;
        _private_playback_latency.max = 0;
@@ -82,8 +83,7 @@ Port::Port (std::string const & n, DataType t, PortFlags f)
 
        PortDrop.connect_same_thread (drop_connection, boost::bind (&Port::drop, this));
        PortSignalDrop.connect_same_thread (drop_connection, boost::bind (&Port::signal_drop, this));
-       port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection,
-                       boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
+       port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
 }
 
 /** Port destructor */
@@ -92,7 +92,6 @@ Port::~Port ()
        drop ();
 }
 
-
 std::string
 Port::pretty_name(bool fallback_to_name) const
 {
@@ -532,8 +531,7 @@ Port::reestablish ()
 
        reset ();
 
-       port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection,
-                       boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
+       port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
        return 0;
 }
 
@@ -583,15 +581,6 @@ Port::physically_connected () const
        return port_engine.physically_connected (_port_handle);
 }
 
-bool
-Port::externally_connected () const
-{
-       if (!_port_handle) {
-               return false;
-       }
-       return port_engine.externally_connected (_port_handle);
-}
-
 XMLNode&
 Port::get_state () const
 {
index 1cac85332b898ed184ec3b45c1a89250b2060c02..f5304f4961a59d4505e96fc51947f07269061dbe 100644 (file)
@@ -192,37 +192,51 @@ PortManager::port_is_physical (const std::string& portname) const
 void
 PortManager::filter_midi_ports (vector<string>& ports, MidiPortFlags include, MidiPortFlags exclude)
 {
+
        if (!include && !exclude) {
                return;
        }
 
-       for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
+       {
+               Glib::Threads::Mutex::Lock lm (midi_port_info_mutex);
 
-               PortManager::MidiPortInformation mpi = midi_port_information (*si);
+               fill_midi_port_info_locked ();
 
-               if (mpi.pretty_name.empty()) {
-                       /* no information !!! */
-                       ++si;
-                       continue;
-               }
+               for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
 
-               if (include) {
-                       if ((mpi.properties & include) != include) {
-                               /* properties do not include requested ones */
-                               si = ports.erase (si);
+                       MidiPortInfo::iterator x = midi_port_info.find (*si);
+
+                       if (x == midi_port_info.end()) {
+                               ++si;
                                continue;
                        }
-               }
 
-               if (exclude) {
-                       if ((mpi.properties & exclude)) {
-                               /* properties include ones to avoid */
-                               si = ports.erase (si);
+                       MidiPortInformation& mpi (x->second);
+
+                       if (mpi.pretty_name.empty()) {
+                               /* no information !!! */
+                               ++si;
                                continue;
                        }
-               }
 
-               ++si;
+                       if (include) {
+                               if ((mpi.properties & include) != include) {
+                                       /* properties do not include requested ones */
+                                       si = ports.erase (si);
+                                       continue;
+                               }
+                       }
+
+                       if (exclude) {
+                               if ((mpi.properties & exclude)) {
+                                       /* properties include ones to avoid */
+                                       si = ports.erase (si);
+                                       continue;
+                               }
+                       }
+
+                       ++si;
+               }
        }
 }
 
@@ -656,6 +670,20 @@ PortManager::connect_callback (const string& a, const string& b, bool conn)
                port_b = x->second;
        }
 
+       if (conn) {
+               if (port_a && !port_b) {
+                       port_a->increment_external_connections ();
+               } else if (port_b && !port_a) {
+                       port_b->increment_external_connections ();
+               }
+       } else {
+               if (port_a && !port_b) {
+                       port_a->decrement_external_connections ();
+               } else if (port_b && !port_a) {
+                       port_b->decrement_external_connections ();
+               }
+       }
+
        PortConnectedOrDisconnected (
                port_a, a,
                port_b, b,
@@ -1260,23 +1288,19 @@ PortManager::fill_midi_port_info_locked ()
                if (!ph) {
                        /* port info saved from some condition where this port
                         * existed, but no longer does (i.e. device unplugged
-                        * at present)
+                        * at present). We don't remove it from midi_port_info.
                         */
                        continue;
                }
 
-               if (!x->second.pretty_name.empty () && x->second.pretty_name != x->first) {
-                       /* name set in port info ... propagate */
-                       _backend->set_port_property (ph, "http://jackaudio.org/metadata/pretty-name", x->second.pretty_name, string());
-               } else {
-                       /* check with backend for pre-existing pretty name */
-                       string value;
-                       string type;
-                       if (0 == _backend->get_port_property (ph,
-                                                             "http://jackaudio.org/metadata/pretty-name",
-                                                             value, type)) {
-                               x->second.pretty_name = value;
-                       }
+               /* check with backend for pre-existing pretty name */
+               string value;
+               string type;
+
+               if (0 == _backend->get_port_property (ph,
+                                                     "http://jackaudio.org/metadata/pretty-name",
+                                                     value, type)) {
+                       x->second.pretty_name = value;
                }
        }
 
index 4651fa3341e43415695fe56f559f86039dea7688..c558be8b084b69fd57e1a39b7411b6707678a9ec 100644 (file)
@@ -36,6 +36,7 @@
 #include "ardour/port.h"
 #include "ardour/rc_configuration.h"
 #include "ardour/session_metadata.h"
+#include "ardour/transport_master_manager.h"
 #include "ardour/types_convert.h"
 
 #include "pbd/i18n.h"
@@ -66,12 +67,14 @@ RCConfiguration::RCConfiguration ()
 #undef  CONFIG_VARIABLE
 #undef  CONFIG_VARIABLE_SPECIAL
        _control_protocol_state (0)
+      , _transport_master_state (0)
 {
 }
 
 RCConfiguration::~RCConfiguration ()
 {
        delete _control_protocol_state;
+       delete _transport_master_state;
 }
 
 int
@@ -186,6 +189,7 @@ RCConfiguration::get_state ()
        }
 
        root->add_child_nocopy (ControlProtocolManager::instance().get_state());
+       root->add_child_nocopy (TransportMasterManager::instance().get_state());
 
        return *root;
 }
@@ -233,6 +237,8 @@ RCConfiguration::set_state (const XMLNode& root, int version)
                        SessionMetadata::Metadata()->set_state (*node, version);
                } else if (node->name() == ControlProtocolManager::state_node_name) {
                        _control_protocol_state = new XMLNode (*node);
+               } else if (node->name() == TransportMasterManager::state_node_name) {
+                       _transport_master_state = new XMLNode (*node);
                }
        }
 
index 8b3ad8af95e2ef327a1b5cdab9d42c547e559294..9bdf758a172148c8352d2b0b8890a0e56181210f 100644 (file)
 #include "ardour/session.h"
 #include "ardour/session_directory.h"
 #include "ardour/session_playlists.h"
-#include "ardour/slave.h"
 #include "ardour/smf_source.h"
-#include "ardour/slave.h"
 #include "ardour/solo_isolate_control.h"
 #include "ardour/source_factory.h"
 #include "ardour/speakers.h"
 #include "ardour/tempo.h"
 #include "ardour/ticker.h"
+#include "ardour/transport_master.h"
 #include "ardour/track.h"
 #include "ardour/types_convert.h"
 #include "ardour/user_bundle.h"
@@ -185,7 +184,6 @@ Session::Session (AudioEngine &eng,
        , _seek_counter (0)
        , _session_range_location (0)
        , _session_range_end_is_free (true)
-       , _slave (0)
        , _silent (false)
        , _remaining_latency_preroll (0)
        , _engine_speed (1.0)
@@ -195,7 +193,6 @@ Session::Session (AudioEngine &eng,
        , _signalled_varispeed (0)
        , _target_transport_speed (0.0)
        , auto_play_legal (false)
-       , _last_slave_transport_sample (0)
        , _requested_return_sample (-1)
        , current_block_size (0)
        , _worst_output_latency (0)
@@ -211,13 +208,8 @@ Session::Session (AudioEngine &eng,
        , _was_seamless (Config->get_seamless_loop ())
        , _under_nsm_control (false)
        , _xrun_count (0)
-       , delta_accumulator_cnt (0)
-       , average_slave_delta (1800) // !!! why 1800 ???
-       , average_dir (0)
-       , have_first_delta_accumulator (false)
-       , _slave_state (Stopped)
-       , _mtc_active (false)
-       , _ltc_active (false)
+       , transport_master_tracking_state (Stopped)
+       , master_wait_end (0)
        , post_export_sync (false)
        , post_export_position (0)
        , _exporting (false)
@@ -656,8 +648,6 @@ Session::destroy ()
        {
                Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
                ltc_tx_cleanup();
-               delete _slave;
-               _slave = 0;
        }
 
        /* disconnect from any and all signals that we are connected to */
@@ -671,7 +661,6 @@ Session::destroy ()
 
        /* remove I/O objects before unsetting the engine session */
        _click_io.reset ();
-       _ltc_input.reset ();
        _ltc_output.reset ();
 
        ControlProtocolManager::instance().drop_protocols ();
@@ -687,12 +676,6 @@ Session::destroy ()
        EngineStateController::instance()->remove_session();
 #endif
 
-       /* drop slave, if any. We don't use use_sync_source (0) because
-        * there's no reason to do all the other stuff that may happen
-        * when calling that method.
-        */
-       delete _slave;
-
        /* deregister all ports - there will be no process or any other
         * callbacks from the engine any more.
         */
@@ -891,21 +874,8 @@ Session::setup_ltc ()
 {
        XMLNode* child = 0;
 
-       _ltc_input.reset (new IO (*this, X_("LTC In"), IO::Input));
        _ltc_output.reset (new IO (*this, X_("LTC Out"), IO::Output));
 
-       if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC In"))) != 0) {
-               _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version);
-       } else {
-               {
-                       Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
-                       _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this);
-                       // TODO use auto-connect thread somehow (needs a route currently)
-                       // see note in Session::auto_connect_thread_run() why process lock is needed.
-                       reconnect_ltc_input ();
-               }
-       }
-
        if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC Out"))) != 0) {
                _ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version);
        } else {
@@ -921,7 +891,6 @@ Session::setup_ltc ()
         * IO style of NAME/TYPE-{in,out}N
         */
 
-       _ltc_input->nth (0)->set_name (X_("LTC-in"));
        _ltc_output->nth (0)->set_name (X_("LTC-out"));
 }
 
@@ -3003,40 +2972,6 @@ Session::reconnect_midi_scene_ports(bool inputs)
     }
 }
 
-void
-Session::reconnect_mtc_ports ()
-{
-       boost::shared_ptr<MidiPort> mtc_in_ptr = _midi_ports->mtc_input_port();
-
-       if (!mtc_in_ptr) {
-               return;
-       }
-
-       mtc_in_ptr->disconnect_all ();
-
-       std::vector<EngineStateController::MidiPortState> midi_port_states;
-       EngineStateController::instance()->get_physical_midi_input_states (midi_port_states);
-
-       std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
-
-       for (; state_iter != midi_port_states.end(); ++state_iter) {
-               if (state_iter->available && state_iter->mtc_in) {
-                       mtc_in_ptr->connect (state_iter->name);
-               }
-       }
-
-       if (!_midi_ports->mtc_input_port ()->connected () &&
-           config.get_external_sync () &&
-           (Config->get_sync_source () == MTC) ) {
-               config.set_external_sync (false);
-       }
-
-       if ( ARDOUR::Profile->get_trx () ) {
-               // Tracks need this signal to update timecode_source_dropdown
-               MtcOrLtcInputPortChanged (); //emit signal
-       }
-}
-
 void
 Session::reconnect_mmc_ports(bool inputs)
 {
@@ -7042,39 +6977,12 @@ Session::operation_in_progress (GQuark op) const
        return (find (_current_trans_quarks.begin(), _current_trans_quarks.end(), op) != _current_trans_quarks.end());
 }
 
-boost::shared_ptr<Port>
-Session::ltc_input_port () const
-{
-       assert (_ltc_input);
-       return _ltc_input->nth (0);
-}
-
 boost::shared_ptr<Port>
 Session::ltc_output_port () const
 {
        return _ltc_output ? _ltc_output->nth (0) : boost::shared_ptr<Port> ();
 }
 
-void
-Session::reconnect_ltc_input ()
-{
-       if (_ltc_input) {
-
-               string src = Config->get_ltc_source_port();
-
-               _ltc_input->disconnect (this);
-
-               if (src != _("None") && !src.empty())  {
-                       _ltc_input->nth (0)->connect (src);
-               }
-
-               if ( ARDOUR::Profile->get_trx () ) {
-                       // Tracks need this signal to update timecode_source_dropdown
-                       MtcOrLtcInputPortChanged (); //emit signal
-               }
-       }
-}
-
 void
 Session::reconnect_ltc_output ()
 {
index 5c4a65ad03b8991667501ff0a499b76d8eba8a34..a6a1c6bd7b8852fcfe851b069204df09a02b1443 100644 (file)
@@ -25,7 +25,7 @@
 #include "ardour/debug.h"
 #include "ardour/io.h"
 #include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
 
 #include "pbd/i18n.h"
 
@@ -68,7 +68,7 @@ Session::ltc_tx_initialize()
        ltc_enc_tcformat = config.get_timecode_format();
 
        ltc_tx_parse_offset();
-       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX init sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(ltc_enc_tcformat)));
+       DEBUG_TRACE (DEBUG::TXLTC, string_compose("LTC TX init sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(ltc_enc_tcformat)));
        ltc_encoder = ltc_encoder_create(nominal_sample_rate(),
                        timecode_to_frames_per_second(ltc_enc_tcformat),
                        TV_STANDARD(ltc_enc_tcformat), 0);
@@ -93,7 +93,7 @@ Session::ltc_tx_initialize()
 void
 Session::ltc_tx_cleanup()
 {
-       DEBUG_TRACE (DEBUG::LTC, "LTC TX cleanup\n");
+       DEBUG_TRACE (DEBUG::TXLTC, "cleanup\n");
        ltc_tx_connections.drop_connections ();
        free(ltc_enc_buf);
        ltc_enc_buf = NULL;
@@ -104,7 +104,7 @@ Session::ltc_tx_cleanup()
 void
 Session::ltc_tx_resync_latency()
 {
-       DEBUG_TRACE (DEBUG::LTC, "LTC TX resync latency\n");
+       DEBUG_TRACE (DEBUG::TXLTC, "resync latency\n");
        if (!deletion_in_progress()) {
                boost::shared_ptr<Port> ltcport = ltc_output_port();
                if (ltcport) {
@@ -116,7 +116,7 @@ Session::ltc_tx_resync_latency()
 void
 Session::ltc_tx_reset()
 {
-       DEBUG_TRACE (DEBUG::LTC, "LTC TX reset\n");
+       DEBUG_TRACE (DEBUG::TXLTC, "reset\n");
        assert (ltc_encoder);
        ltc_enc_pos = -9999; // force re-start
        ltc_buf_len = 0;
@@ -203,7 +203,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
        /* range from libltc (38..218) || - 128.0  -> (-90..90) */
        const float ltcvol = Config->get_ltc_output_volume()/(90.0); // pow(10, db/20.0)/(90.0);
 
-       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max));
+       DEBUG_TRACE (DEBUG::TXLTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max));
 
        /* all systems go. Now here's the plan:
         *
@@ -222,7 +222,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
        // (1) check fps
        TimecodeFormat cur_timecode = config.get_timecode_format();
        if (cur_timecode != ltc_enc_tcformat) {
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode)));
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode)));
                if (ltc_encoder_reinit(ltc_encoder, nominal_sample_rate(),
                                        timecode_to_frames_per_second(cur_timecode),
                                        TV_STANDARD(cur_timecode), 0
@@ -295,7 +295,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
        }
 
        if (SIGNUM(new_ltc_speed) != SIGNUM (ltc_speed)) {
-               DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport changed direction\n");
+               DEBUG_TRACE (DEBUG::TXLTC, "transport changed direction\n");
                ltc_tx_reset();
        }
 
@@ -315,13 +315,13 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                 * end_sample is calculated from 'samples_moved' which includes the interpolation.
                 * so we're good.
                 */
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed));
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed));
                speed_changed = true;
                ltc_encoder_set_filter(ltc_encoder, LTC_RISE_TIME(new_ltc_speed));
        }
 
        if (end_sample == start_sample || fabs(current_speed) < 0.1 ) {
-               DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport is not rolling or absolute-speed < 0.1\n");
+               DEBUG_TRACE (DEBUG::TXLTC, "transport is not rolling or absolute-speed < 0.1\n");
                /* keep repeating current sample
                 *
                 * an LTC generator must be able to continue generating LTC when Ardours transport is in stop
@@ -336,19 +336,19 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                        return;
                }
                if (start_sample != ltc_prev_cycle) {
-                       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample));
+                       DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample));
                        ltc_tx_reset();
                }
        }
 
        if (fabs(new_ltc_speed) > 10.0) {
-               DEBUG_TRACE (DEBUG::LTC, "LTC TX2: speed is out of bounds.\n");
+               DEBUG_TRACE (DEBUG::TXLTC, "speed is out of bounds.\n");
                ltc_tx_reset();
                return;
        }
 
        if (ltc_speed == 0 && new_ltc_speed != 0) {
-               DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport started rolling - reset\n");
+               DEBUG_TRACE (DEBUG::TXLTC, "transport started rolling - reset\n");
                ltc_tx_reset();
        }
 
@@ -374,21 +374,21 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                double oldbuflen = (double)(ltc_buf_len - ltc_buf_off);
                double newbuflen = (double)(ltc_buf_len - ltc_buf_off) * fabs(ltc_speed / new_ltc_speed);
 
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: bufOld %1 bufNew %2 | diff %3\n",
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: bufOld %1 bufNew %2 | diff %3\n",
                                        (ltc_buf_len - ltc_buf_off), newbuflen, newbuflen - oldbuflen
                                        ));
 
                double bufrspdiff = rint(newbuflen - oldbuflen);
 
                if (abs(bufrspdiff) > newbuflen || abs(bufrspdiff) > oldbuflen) {
-                       DEBUG_TRACE (DEBUG::LTC, "LTC TX2: resampling buffer would destroy information.\n");
+                       DEBUG_TRACE (DEBUG::TXLTC, "resampling buffer would destroy information.\n");
                        ltc_tx_reset();
                        poff = 0;
                } else if (bufrspdiff != 0 && newbuflen > oldbuflen) {
                        int incnt = 0;
                        double samples_to_insert = ceil(newbuflen - oldbuflen);
                        double avg_distance = newbuflen / samples_to_insert;
-                       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer insert: %1\n", samples_to_insert));
+                       DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer insert: %1\n", samples_to_insert));
 
                        for (int rp = ltc_buf_off; rp < ltc_buf_len - 1; ++rp) {
                                const int ro = rp - ltc_buf_off;
@@ -402,7 +402,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                        }
                } else if (bufrspdiff != 0 && newbuflen < oldbuflen) {
                        double samples_to_remove = ceil(oldbuflen - newbuflen);
-                       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer - remove: %1\n", samples_to_remove));
+                       DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer - remove: %1\n", samples_to_remove));
                        if (oldbuflen <= samples_to_remove) {
                                ltc_buf_off = ltc_buf_len= 0;
                        } else {
@@ -424,7 +424,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
 
        ltc_prev_cycle = start_sample;
        ltc_speed = new_ltc_speed;
-       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: transport speed %1.\n", ltc_speed));
+       DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: transport speed %1.\n", ltc_speed));
 
        // (3) bit/sample alignment
        Timecode::Time tc_start;
@@ -451,7 +451,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
        if (current_speed == 0) {
                soff = 0;
        }
-       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX3: A3cycle: %1 = A3tc: %2 +off: %3\n",
+       DEBUG_TRACE (DEBUG::TXLTC, string_compose("3: A3cycle: %1 = A3tc: %2 +off: %3\n",
                                cycle_start_sample, tc_sample_start, soff));
 
 
@@ -470,8 +470,8 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
         */
        double maxdiff;
 
-       if (config.get_external_sync() && slave()) {
-               maxdiff = slave()->resolution();
+       if (transport_master_is_external()) {
+               maxdiff = transport_master()->resolution();
        } else {
                maxdiff = ceil(fabs(ltc_speed))*2.0;
                if (nominal_sample_rate() != sample_rate()) {
@@ -482,10 +482,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                }
        }
 
-       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n",
+       DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n",
                                ltc_enc_pos, ltc_enc_cnt, poff, (ltc_buf_len - ltc_buf_off), poff, ltc_enc_byte));
 
-       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc-pos: %1  | d: %2\n",
+       DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc-pos: %1  | d: %2\n",
                                ltc_enc_pos + ltc_enc_cnt - poff,
                                rint(ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_sample
                                ));
@@ -515,7 +515,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                ltc_encoder_set_frame(ltc_encoder, &ltcframe);
 
 
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff));
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff));
 
                int32_t cyc_off;
                if (soff < 0 || soff >= fptcf) {
@@ -546,7 +546,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                        }
                }
 
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart encoder: soff %1 byte %2 cycoff %3\n",
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart encoder: soff %1 byte %2 cycoff %3\n",
                                        soff, ltc_enc_byte, cyc_off));
 
                if ( (ltc_speed < 0 && ltc_enc_byte !=9 ) || (ltc_speed >= 0 && ltc_enc_byte !=0 ) ) {
@@ -565,14 +565,14 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
 
                ltc_enc_pos = tc_sample_start % wrap24h;
 
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart @ %1 + %2 - %3 |  byte %4\n",
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart @ %1 + %2 - %3 |  byte %4\n",
                                        ltc_enc_pos, ltc_enc_cnt, cyc_off, ltc_enc_byte));
        }
        else if (ltc_speed != 0 && (fptcf / ltc_speed / 80) > 3 ) {
                /* reduce (low freq) jitter.
                 * The granularity of the LTC encoder speed is 1 byte =
                 * (samples-per-timecode-sample / 10) audio-samples.
-                * Thus, tiny speed changes [as produced by some slaves]
+                * Thus, tiny speed changes [as produced by some transport masters]
                 * may not have any effect in the cycle when they occur,
                 * but they will add up over time.
                 *
@@ -593,7 +593,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
        // (6) encode and output
        while (1) {
 #ifdef LTC_GEN_TXDBUG
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.1 @%1  [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.1 @%1  [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
 #endif
                // (6a) send remaining buffer
                while ((ltc_buf_off < ltc_buf_len) && (txf < nframes)) {
@@ -602,11 +602,11 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                        out[txf++] = val;
                }
 #ifdef LTC_GEN_TXDBUG
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.2 @%1  [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.2 @%1  [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
 #endif
 
                if (txf >= nframes) {
-                       DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n",
+                       DEBUG_TRACE (DEBUG::TXLTC, string_compose("7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n",
                                                ltc_enc_pos, ltc_buf_off, ltc_buf_len, ltc_enc_byte, ltc_speed, nframes, txf));
                        break;
                }
@@ -635,7 +635,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                        memset(&ltc_enc_buf[ltc_buf_len], 127, enc_samples * sizeof(ltcsnd_sample_t));
                } else {
                        if (ltc_encoder_encode_byte(ltc_encoder, ltc_enc_byte, (ltc_speed==0)?1.0:(1.0/ltc_speed))) {
-                               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoder error byte %1\n", ltc_enc_byte));
+                               DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoder error byte %1\n", ltc_enc_byte));
                                ltc_encoder_buffer_flush(ltc_encoder);
                                ltc_tx_reset();
                                return;
@@ -644,10 +644,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                }
 
 #ifdef LTC_GEN_FRAMEDBUG
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed));
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed));
 #endif
                if (enc_samples <=0) {
-                       DEBUG_TRACE (DEBUG::LTC, "LTC TX6.3 encoder empty buffer.\n");
+                       DEBUG_TRACE (DEBUG::TXLTC, "6.3 encoder empty buffer.\n");
                        ltc_encoder_buffer_flush(ltc_encoder);
                        ltc_tx_reset();
                        return;
@@ -677,7 +677,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
                        }
                }
 #ifdef LTC_GEN_FRAMEDBUG
-               DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed));
+               DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed));
 #endif
        }
 
index d0e75fbb6eae9e4c4c197e53ac1c8a823caaea32..87b9e5e8617c44755a8f46c089151199ec88f5e3 100644 (file)
@@ -45,7 +45,7 @@
 #include "ardour/midi_ui.h"
 #include "ardour/profile.h"
 #include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
 #include "ardour/ticker.h"
 
 #include "pbd/i18n.h"
@@ -306,9 +306,9 @@ Session::mmc_locate (MIDI::MachineControl &/*mmc*/, const MIDI::byte* mmc_tc)
           of an MTC slave to become out of date. Catch this.
        */
 
-       MTC_Slave* mtcs = dynamic_cast<MTC_Slave*> (_slave);
+       boost::shared_ptr<MTC_TransportMaster> mtcs = boost::dynamic_pointer_cast<MTC_TransportMaster> (transport_master());
 
-       if (mtcs != 0) {
+       if (mtcs) {
                // cerr << "Locate *with* MTC slave\n";
                mtcs->handle_locate (mmc_tc);
        } else {
@@ -402,7 +402,7 @@ Session::send_full_time_code (samplepos_t const t, MIDI::pframes_t nframes)
        if (_engine.freewheeling() || !Config->get_send_mtc()) {
                return 0;
        }
-       if (_slave && !_slave->locked()) {
+       if (!transport_master()->locked()) {
                return 0;
        }
 
@@ -486,7 +486,7 @@ Session::send_midi_time_code_for_cycle (samplepos_t start_sample, samplepos_t en
                // cerr << "(MTC) Not sending MTC\n";
                return 0;
        }
-       if (_slave && !_slave->locked()) {
+       if (!transport_master()->locked()) {
                return 0;
        }
 
@@ -707,21 +707,12 @@ Session::midi_clock_output_port () const
        return _midi_ports->midi_clock_output_port ();
 }
 
-boost::shared_ptr<MidiPort>
-Session::midi_clock_input_port () const
-{
-       return _midi_ports->midi_clock_input_port ();
-}
+
 boost::shared_ptr<MidiPort>
 Session::mtc_output_port () const
 {
        return _midi_ports->mtc_output_port ();
 }
-boost::shared_ptr<MidiPort>
-Session::mtc_input_port () const
-{
-       return _midi_ports->mtc_input_port ();
-}
 
 void
 Session::midi_track_presentation_info_changed (PropertyChange const& what_changed, boost::weak_ptr<MidiTrack> mt)
index eeb8cb871e905cefaf6d654a3ee26a2d35979132..04f2d3f77e7edb978731cfa07d0e9ae6004aad06 100644 (file)
@@ -38,7 +38,8 @@
 #include "ardour/process_thread.h"
 #include "ardour/scene_changer.h"
 #include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
+#include "ardour/transport_master_manager.h"
 #include "ardour/ticker.h"
 #include "ardour/types.h"
 #include "ardour/vca.h"
@@ -65,6 +66,7 @@ Session::process (pframes_t nframes)
 
        if (processing_blocked()) {
                _silent = true;
+               cerr << "%%%%%%%%%%%%%% session process blocked\n";
                return;
        }
 
@@ -251,6 +253,23 @@ Session::get_track_statistics ()
        }
 }
 
+bool
+Session::compute_audible_delta (samplepos_t& pos_and_delta) const
+{
+       if (_transport_speed == 0.0 || _count_in_samples > 0 || _remaining_latency_preroll > 0) {
+               /* cannot compute audible delta, because the session is
+                  generating silence that does not correspond to the timeline,
+                  but is instead filling playback buffers to manage latency
+                  alignment.
+               */
+               DEBUG_TRACE (DEBUG::Slave, string_compose ("still adjusting for latency (%1) and/or count-in (%2) or stopped %1\n", _remaining_latency_preroll, _count_in_samples, _transport_speed));
+               return false;
+       }
+
+       pos_and_delta -= _transport_sample;
+       return true;
+}
+
 /** Process callback used when the auditioner is not active */
 void
 Session::process_with_events (pframes_t nframes)
@@ -285,7 +304,6 @@ Session::process_with_events (pframes_t nframes)
                immediate_events.pop_front ();
                process_event (ev);
        }
-
        /* only count-in when going to roll at speed 1.0 */
        if (_transport_speed != 1.0 && _count_in_samples > 0) {
                _count_in_samples = 0;
@@ -296,6 +314,8 @@ Session::process_with_events (pframes_t nframes)
 
        assert (_count_in_samples == 0 || _remaining_latency_preroll == 0 || _count_in_samples == _remaining_latency_preroll);
 
+       DEBUG_TRACE (DEBUG::Transport, string_compose ("Running count in/latency preroll of %1 & %2\n", _count_in_samples, _remaining_latency_preroll));
+
        while (_count_in_samples > 0 || _remaining_latency_preroll > 0) {
                samplecnt_t ns;
 
@@ -440,8 +460,9 @@ Session::process_with_events (pframes_t nframes)
                        return;
                }
 
-               if (!_exporting && _slave) {
-                       if (!follow_slave (nframes)) {
+               if (!_exporting && config.get_external_sync()) {
+                       if (!follow_transport_master (nframes)) {
+                               ltc_tx_send_time_code_for_cycle (_transport_sample, end_sample, _target_transport_speed, _transport_speed, nframes);
                                return;
                        }
                }
@@ -546,308 +567,16 @@ Session::process_with_events (pframes_t nframes)
        }
 }
 
-void
-Session::reset_slave_state ()
-{
-       average_slave_delta = 1800;
-       delta_accumulator_cnt = 0;
-       have_first_delta_accumulator = false;
-       _slave_state = Stopped;
-       DiskReader::set_no_disk_output (false);
-}
-
 bool
 Session::transport_locked () const
 {
-       Slave* sl = _slave;
-
-       if (!locate_pending() && (!config.get_external_sync() || (sl && sl->ok() && sl->locked()))) {
-               return true;
-       }
-
-       return false;
-}
-
-bool
-Session::follow_slave (pframes_t nframes)
-{
-       double slave_speed;
-       samplepos_t slave_transport_sample;
-       samplecnt_t this_delta;
-       int dir;
-
-       if (!_slave->ok()) {
-               stop_transport ();
-               config.set_external_sync (false);
-               goto noroll;
-       }
-
-       _slave->speed_and_position (slave_speed, slave_transport_sample);
-
-       DEBUG_TRACE (DEBUG::Slave, string_compose ("Slave position %1 speed %2\n", slave_transport_sample, slave_speed));
-
-       if (!_slave->locked()) {
-               DEBUG_TRACE (DEBUG::Slave, "slave not locked\n");
-               goto noroll;
-       }
-
-       if (slave_transport_sample > _transport_sample) {
-               this_delta = slave_transport_sample - _transport_sample;
-               dir = 1;
-       } else {
-               this_delta = _transport_sample - slave_transport_sample;
-               dir = -1;
-       }
-
-       if (_slave->starting()) {
-               slave_speed = 0.0f;
-       }
-
-       if (_slave->is_always_synced() ||
-                       (Config->get_timecode_source_is_synced() && (dynamic_cast<TimecodeSlave*>(_slave)) != 0)
-                       ) {
-
-               /* if the TC source is synced, then we assume that its
-                  speed is binary: 0.0 or 1.0
-               */
-
-               if (slave_speed != 0.0f) {
-                       slave_speed = 1.0f;
-               }
-
-       } else {
-
-               /* if we are chasing and the average delta between us and the
-                  master gets too big, we want to switch to silent
-                  motion. so keep track of that here.
-               */
-
-               if (_slave_state == Running) {
-                       calculate_moving_average_of_slave_delta(dir, abs(this_delta));
-               }
-       }
-
-       track_slave_state (slave_speed, slave_transport_sample, this_delta);
-
-       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave state %1 @ %2 speed %3 cur delta %4 avg delta %5\n",
-                                                  _slave_state, slave_transport_sample, slave_speed, this_delta, average_slave_delta));
-
-
-       if (_slave_state == Running && !_slave->is_always_synced() && !(Config->get_timecode_source_is_synced() && (dynamic_cast<TimecodeSlave*>(_slave)) != 0)) {
-
-               /* may need to varispeed to sync with slave */
-
-               if (_transport_speed != 0.0f) {
-
-                       /*
-                          note that average_dir is +1 or -1
-                       */
-
-                       float delta;
-
-                       if (average_slave_delta == 0) {
-                               delta = this_delta;
-                               delta *= dir;
-                       } else {
-                               delta = average_slave_delta;
-                               delta *= average_dir;
-                       }
-
-#ifndef NDEBUG
-                       if (slave_speed != 0.0) {
-                               DEBUG_TRACE (DEBUG::Slave, string_compose ("delta = %1 speed = %2 ts = %3 M@%4 S@%5 avgdelta %6\n",
-                                                                          (int) (dir * this_delta),
-                                                                          slave_speed,
-                                                                          _transport_speed,
-                                                                          _transport_sample,
-                                                                          slave_transport_sample,
-                                                                          average_slave_delta));
-                       }
-#endif
-
-                       if (_slave->give_slave_full_control_over_transport_speed()) {
-                               set_transport_speed (slave_speed, 0, false, false);
-                               //std::cout << "set speed = " << slave_speed << "\n";
-                       } else {
-                               float adjusted_speed = slave_speed + (1.5 * (delta /  float(_current_sample_rate)));
-                               request_transport_speed (adjusted_speed);
-                               DEBUG_TRACE (DEBUG::Slave, string_compose ("adjust using %1 towards %2 ratio %3 current %4 slave @ %5\n",
-                                                                          delta, adjusted_speed, adjusted_speed/slave_speed, _transport_speed,
-                                                                          slave_speed));
-                       }
-
-                       if (!actively_recording() && (samplecnt_t) average_slave_delta > _slave->resolution()) {
-                               DEBUG_TRACE (DEBUG::Slave, string_compose ("average slave delta %1 greater than slave resolution %2 => no disk output\n", average_slave_delta, _slave->resolution()));
-                               /* run routes as normal, but no disk output */
-                               DiskReader::set_no_disk_output (true);
-                               return true;
-                       }
-
-                       if (!have_first_delta_accumulator) {
-                               DEBUG_TRACE (DEBUG::Slave, "waiting for first slave delta accumulator to be ready, no disk output\n");
-                               /* run routes as normal, but no disk output */
-                               DiskReader::set_no_disk_output (true);
-                               return true;
-                       }
-               }
-       }
-
-
-       if (!have_first_delta_accumulator) {
-               DEBUG_TRACE (DEBUG::Slave, "still waiting to compute slave delta, no disk output\n");
-               DiskReader::set_no_disk_output (true);
-       } else {
-               DiskReader::set_no_disk_output (false);
-       }
-
-       if ((_slave_state == Running) && (0 == (post_transport_work () & ~PostTransportSpeed))) {
-               /* speed is set, we're locked, and good to go */
+       if (!locate_pending() && (!config.get_external_sync() || (transport_master()->ok() && transport_master()->locked()))) {
                return true;
        }
 
-  noroll:
-       /* don't move at all */
-       DEBUG_TRACE (DEBUG::Slave, "no roll\n")
-       no_roll (nframes);
        return false;
 }
 
-void
-Session::calculate_moving_average_of_slave_delta (int dir, samplecnt_t this_delta)
-{
-       if (delta_accumulator_cnt >= delta_accumulator_size) {
-               have_first_delta_accumulator = true;
-               delta_accumulator_cnt = 0;
-       }
-
-       if (delta_accumulator_cnt != 0 || this_delta < _current_sample_rate) {
-               delta_accumulator[delta_accumulator_cnt++] = (samplecnt_t) dir *  (samplecnt_t) this_delta;
-       }
-
-       if (have_first_delta_accumulator) {
-               average_slave_delta = 0L;
-               for (int i = 0; i < delta_accumulator_size; ++i) {
-                       average_slave_delta += delta_accumulator[i];
-               }
-               average_slave_delta /= (int32_t) delta_accumulator_size;
-               if (average_slave_delta < 0L) {
-                       average_dir = -1;
-                       average_slave_delta = average_slave_delta;
-               } else {
-                       average_dir = 1;
-               }
-       }
-}
-
-void
-Session::track_slave_state (float slave_speed, samplepos_t slave_transport_sample, samplecnt_t /*this_delta*/)
-{
-       if (slave_speed != 0.0f) {
-
-               /* slave is running */
-
-               switch (_slave_state) {
-               case Stopped:
-                       if (_slave->requires_seekahead()) {
-                               slave_wait_end = slave_transport_sample + _slave->seekahead_distance ();
-                               DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, but running, requires seekahead to %1\n", slave_wait_end));
-                               /* we can call locate() here because we are in process context */
-                               locate (slave_wait_end, false, false);
-                               _slave_state = Waiting;
-
-                       } else {
-
-                               DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped -> running at %1\n", slave_transport_sample));
-
-                               memset (delta_accumulator, 0, sizeof (int32_t) * delta_accumulator_size);
-                               average_slave_delta = 0L;
-
-                               Location* al = _locations->auto_loop_location();
-
-                               if (al && play_loop && (slave_transport_sample < al->start() || slave_transport_sample > al->end())) {
-                                       // cancel looping
-                                       request_play_loop(false);
-                               }
-
-                               if (slave_transport_sample != _transport_sample) {
-                                       DEBUG_TRACE (DEBUG::Slave, string_compose ("require locate to run. eng: %1 -> sl: %2\n", _transport_sample, slave_transport_sample));
-                                       locate (slave_transport_sample, false, false);
-                               }
-                               _slave_state = Running;
-                       }
-                       break;
-
-               case Waiting:
-               default:
-                       break;
-               }
-
-               if (_slave_state == Waiting) {
-
-                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave waiting at %1\n", slave_transport_sample));
-
-                       if (slave_transport_sample >= slave_wait_end) {
-
-                               DEBUG_TRACE (DEBUG::Slave, string_compose ("slave start at %1 vs %2\n", slave_transport_sample, _transport_sample));
-
-                               _slave_state = Running;
-
-                               /* now perform a "micro-seek" within the disk buffers to realign ourselves
-                                  precisely with the master.
-                               */
-
-
-                               bool ok = true;
-                               samplecnt_t sample_delta = slave_transport_sample - _transport_sample;
-
-                               boost::shared_ptr<RouteList> rl = routes.reader();
-                               for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-                                       boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-                                       if (tr && !tr->can_internal_playback_seek (sample_delta)) {
-                                               ok = false;
-                                               break;
-                                       }
-                               }
-
-                               if (ok) {
-                                       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-                                               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-                                               if (tr) {
-                                                       tr->internal_playback_seek (sample_delta);
-                                               }
-                                       }
-                                       _transport_sample += sample_delta;
-
-                               } else {
-                                       cerr << "cannot micro-seek\n";
-                                       /* XXX what? */
-                               }
-                       }
-               }
-
-               if (_slave_state == Running && _transport_speed == 0.0f) {
-                       DEBUG_TRACE (DEBUG::Slave, "slave starts transport\n");
-                       start_transport ();
-               }
-
-       } else { // slave_speed is 0
-
-               /* slave has stopped */
-
-               if (_transport_speed != 0.0f) {
-                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", slave_speed, slave_transport_sample, _transport_sample));
-                       stop_transport ();
-               }
-
-               if (slave_transport_sample != _transport_sample) {
-                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, move to %1\n", slave_transport_sample));
-                       force_locate (slave_transport_sample, false);
-               }
-
-               reset_slave_state();
-       }
-}
-
 void
 Session::process_without_events (pframes_t nframes)
 {
@@ -859,21 +588,22 @@ Session::process_without_events (pframes_t nframes)
                return;
        }
 
-       if (!_exporting && _slave) {
-               if (!follow_slave (nframes)) {
+       if (!_exporting && config.get_external_sync()) {
+               if (!follow_transport_master (nframes)) {
                        ltc_tx_send_time_code_for_cycle (_transport_sample, _transport_sample, 0, 0 , nframes);
                        return;
                }
        }
 
+       assert (_transport_speed == 0 || _transport_speed == 1.0 || _transport_speed == -1.0);
+
        if (_transport_speed == 0) {
                no_roll (nframes);
                return;
+       } else {
+               samples_moved = (samplecnt_t) nframes;
        }
 
-       assert (_transport_speed == 1.f || _transport_speed == -1.f);
-       samples_moved = (samplecnt_t) nframes * _transport_speed;
-
        if (!_exporting && !timecode_transmission_suspended()) {
                send_midi_time_code_for_cycle (_transport_sample, _transport_sample + samples_moved, nframes);
        }
@@ -1130,6 +860,10 @@ Session::process_event (SessionEvent* ev)
                set_transport_speed (ev->speed, ev->target_sample, ev->yes_or_no, ev->second_yes_or_no, ev->third_yes_or_no);
                break;
 
+       case SessionEvent::SetTransportMaster:
+               TransportMasterManager::instance().set_current (ev->transport_master);
+               break;
+
        case SessionEvent::PunchIn:
                // cerr << "PunchIN at " << transport_sample() << endl;
                if (config.get_punch_in() && record_status() == Enabled) {
@@ -1176,11 +910,6 @@ Session::process_event (SessionEvent* ev)
                overwrite_some_buffers (static_cast<Track*>(ev->ptr));
                break;
 
-       case SessionEvent::SetSyncSource:
-               DEBUG_TRACE (DEBUG::Slave, "seen request for new slave\n");
-               use_sync_source (ev->slave);
-               break;
-
        case SessionEvent::Audition:
                set_audition (ev->region);
                // drop reference to region
@@ -1234,7 +963,7 @@ Session::compute_stop_limit () const
                return max_samplepos;
        }
 
-       if (_slave) {
+       if (config.get_external_sync()) {
                return max_samplepos;
        }
 
@@ -1330,3 +1059,146 @@ Session::emit_thread_run ()
        }
        pthread_mutex_unlock (&_rt_emit_mutex);
 }
+
+bool
+Session::follow_transport_master (pframes_t nframes)
+{
+       TransportMasterManager& tmm (TransportMasterManager::instance());
+
+       double slave_speed;
+       samplepos_t slave_transport_sample;
+       sampleoffset_t delta;
+
+       if (tmm.master_invalid_this_cycle()) {
+               DEBUG_TRACE (DEBUG::Slave, "session told not to use the transport master this cycle\n");
+               goto noroll;
+       }
+
+       slave_speed = tmm.get_current_speed_in_process_context();
+       slave_transport_sample = tmm.get_current_position_in_process_context ();
+       delta = _transport_sample - slave_transport_sample;
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("session at %1, master at %2, delta: %3 res: %4\n", _transport_sample, slave_transport_sample, delta, tmm.current()->resolution()));
+
+       track_transport_master (slave_speed, slave_transport_sample);
+
+       if (transport_master_tracking_state == Running) {
+
+               if (!actively_recording() && fabs (delta) > tmm.current()->resolution()) {
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("average slave delta %1 greater than slave resolution %2\n", delta, tmm.current()->resolution()));
+                       if (micro_locate (-delta) != 0) {
+                               DEBUG_TRACE (DEBUG::Slave, "micro-locate didn't work, set no disk output true\n");
+
+                               /* run routes as normal, but no disk output */
+                               DiskReader::set_no_disk_output (true);
+                       }
+                       return true;
+               }
+
+               if (transport_master_tracking_state == Running) {
+                       /* speed is set, we're locked, and good to go */
+                       DiskReader::set_no_disk_output (false);
+                       return true;
+               }
+       }
+
+  noroll:
+       /* don't move at all */
+       DEBUG_TRACE (DEBUG::Slave, "no roll\n")
+       no_roll (nframes);
+       return false;
+}
+
+void
+Session::track_transport_master (float slave_speed, samplepos_t slave_transport_sample)
+{
+       boost::shared_ptr<TransportMaster> master (TransportMasterManager::instance().current());
+
+       assert (master);
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("session has master tracking state as %1\n", transport_master_tracking_state));
+
+       if (slave_speed != 0.0f) {
+
+               /* slave is running */
+
+               switch (transport_master_tracking_state) {
+               case Stopped:
+                       if (master->requires_seekahead()) {
+                               master_wait_end = slave_transport_sample + master->seekahead_distance ();
+                               DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, but running, requires seekahead to %1\n", master_wait_end));
+                               /* we can call locate() here because we are in process context */
+                               if (micro_locate (master_wait_end - _transport_sample) != 0) {
+                                       locate (master_wait_end, false, false);
+                               }
+                               transport_master_tracking_state = Waiting;
+
+                       } else {
+
+                               DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped -> running at %1\n", slave_transport_sample));
+
+                               if (slave_transport_sample != _transport_sample) {
+                                       DEBUG_TRACE (DEBUG::Slave, string_compose ("require locate to run. eng: %1 -> sl: %2\n", _transport_sample, slave_transport_sample));
+                                       if (micro_locate (slave_transport_sample - _transport_sample) != 0) {
+                                               locate (slave_transport_sample, false, false);
+                                       }
+                               }
+                               transport_master_tracking_state = Running;
+                       }
+                       break;
+
+               case Waiting:
+               default:
+                       break;
+               }
+
+               if (transport_master_tracking_state == Waiting) {
+
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave waiting at %1\n", slave_transport_sample));
+
+                       if (slave_transport_sample >= master_wait_end) {
+
+                               DEBUG_TRACE (DEBUG::Slave, string_compose ("slave start at %1 vs %2\n", slave_transport_sample, _transport_sample));
+
+                               transport_master_tracking_state = Running;
+
+                               /* now perform a "micro-seek" within the disk buffers to realign ourselves
+                                  precisely with the master.
+                               */
+
+                               if (micro_locate (slave_transport_sample - _transport_sample) != 0) {
+                                       cerr << "cannot micro-seek\n";
+                                       /* XXX what? */
+                               }
+                       }
+               }
+
+               if (transport_master_tracking_state == Running && _transport_speed == 0.0f) {
+                       DEBUG_TRACE (DEBUG::Slave, "slave starts transport\n");
+                       start_transport ();
+               }
+
+       } else { // slave_speed is 0
+
+               /* slave has stopped */
+
+               if (_transport_speed != 0.0f) {
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", slave_speed, slave_transport_sample, _transport_sample));
+                       stop_transport ();
+               }
+
+               if (slave_transport_sample != _transport_sample) {
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, move to %1\n", slave_transport_sample));
+                       force_locate (slave_transport_sample, false);
+               }
+
+               reset_slave_state();
+       }
+}
+
+void
+Session::reset_slave_state ()
+{
+       transport_master_tracking_state = Stopped;
+       DiskReader::set_no_disk_output (false);
+}
index 4b0a0d9e925caf6fcd599b3481b68f8e3896f717..1335c554cb286890d3ee14d9effdc4b2354dcdb6 100644 (file)
 #include "ardour/template_utils.h"
 #include "ardour/tempo.h"
 #include "ardour/ticker.h"
+#include "ardour/transport_master_manager.h"
 #include "ardour/types_convert.h"
 #include "ardour/user_bundle.h"
 #include "ardour/vca.h"
@@ -1479,12 +1480,7 @@ Session::state (bool save_template, snapshot_t snapshot_type, bool only_used_ass
                gain_child->add_child_nocopy (_click_gain->get_state ());
        }
 
-       if (_ltc_input) {
-               XMLNode* ltc_input_child = node->add_child ("LTC-In");
-               ltc_input_child->add_child_nocopy (_ltc_input->get_state ());
-       }
-
-       if (_ltc_input) {
+       if (_ltc_output) {
                XMLNode* ltc_output_child = node->add_child ("LTC-Out");
                ltc_output_child->add_child_nocopy (_ltc_output->get_state ());
        }
@@ -1523,8 +1519,7 @@ Session::state (bool save_template, snapshot_t snapshot_type, bool only_used_ass
 XMLNode&
 Session::get_control_protocol_state ()
 {
-       ControlProtocolManager& cpm (ControlProtocolManager::instance());
-       return cpm.get_state();
+       return ControlProtocolManager::instance().get_state ();
 }
 
 int
@@ -4103,11 +4098,7 @@ Session::config_changed (std::string p, bool ours)
                first_file_data_format_reset = false;
 
        } else if (p == "external-sync") {
-               if (!config.get_external_sync()) {
-                       drop_sync_source ();
-               } else {
-                       switch_to_sync_source (Config->get_sync_source());
-               }
+               request_sync_source (TransportMasterManager::instance().master_by_type (Config->get_sync_source()));
        }  else if (p == "denormal-model") {
                setup_fpu ();
        } else if (p == "history-depth") {
@@ -4138,8 +4129,6 @@ Session::config_changed (std::string p, bool ours)
                last_timecode_valid = false;
        } else if (p == "playback-buffer-seconds") {
                AudioSource::allocate_working_buffers (sample_rate());
-       } else if (p == "ltc-source-port") {
-               reconnect_ltc_input ();
        } else if (p == "ltc-sink-port") {
                reconnect_ltc_output ();
        } else if (p == "timecode-generator-offset") {
index b69565f3849741d07b703062ff2e3eb15c72871b..95daf7495bfe22332b19f1791938ac7a81616de7 100644 (file)
@@ -47,7 +47,8 @@
 #include "ardour/profile.h"
 #include "ardour/scene_changer.h"
 #include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
+#include "ardour/transport_master_manager.h"
 #include "ardour/tempo.h"
 #include "ardour/operations.h"
 #include "ardour/vca.h"
@@ -78,33 +79,34 @@ Session::add_post_transport_work (PostTransportWork ptw)
        error << "Could not set post transport work! Crazy thread madness, call the programmers" << endmsg;
 }
 
-void
-Session::request_sync_source (Slave* new_slave)
+bool
+Session::should_ignore_transport_request (TransportRequestSource src, TransportRequestType type) const
 {
-       SessionEvent* ev = new SessionEvent (SessionEvent::SetSyncSource, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
-       bool seamless;
-
-       seamless = Config->get_seamless_loop ();
-
-       if (dynamic_cast<Engine_Slave*>(new_slave)) {
-               /* JACK cannot support seamless looping at present */
-               Config->set_seamless_loop (false);
-       } else {
-               /* reset to whatever the value was before we last switched slaves */
-               Config->set_seamless_loop (_was_seamless);
+       if (config.get_external_sync()) {
+               if (TransportMasterManager::instance().current()->allow_request (src, type)) {
+                       return false;
+               } else {
+                       return true;
+               }
        }
+       return false;
+}
 
-       /* save value of seamless from before the switch */
-       _was_seamless = seamless;
-
-       ev->slave = new_slave;
-       DEBUG_TRACE (DEBUG::Slave, "sent request for new slave\n");
+void
+Session::request_sync_source (boost::shared_ptr<TransportMaster> tm)
+{
+       SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportMaster, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
+       ev->transport_master = tm;
+       DEBUG_TRACE (DEBUG::Slave, "sent request for new transport master\n");
        queue_event (ev);
 }
 
 void
-Session::request_transport_speed (double speed, bool as_default)
+Session::request_transport_speed (double speed, bool as_default, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TR_Speed)) {
+               return;
+       }
        SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed);
        ev->third_yes_or_no = as_default; // as_default
        DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport speed = %1 as default = %2\n", speed, as_default));
@@ -116,8 +118,12 @@ Session::request_transport_speed (double speed, bool as_default)
  *  be used by callers who are varying transport speed but don't ever want to stop it.
  */
 void
-Session::request_transport_speed_nonzero (double speed, bool as_default)
+Session::request_transport_speed_nonzero (double speed, bool as_default, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TransportRequestType (TR_Speed|TR_Start))) {
+               return;
+       }
+
        if (speed == 0) {
                speed = DBL_EPSILON;
        }
@@ -126,16 +132,24 @@ Session::request_transport_speed_nonzero (double speed, bool as_default)
 }
 
 void
-Session::request_stop (bool abort, bool clear_state)
+Session::request_stop (bool abort, bool clear_state, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TR_Stop)) {
+               return;
+       }
+
        SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, audible_sample(), 0.0, abort, clear_state);
        DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport stop, audible %3 transport %4 abort = %1, clear state = %2\n", abort, clear_state, audible_sample(), _transport_sample));
        queue_event (ev);
 }
 
 void
-Session::request_locate (samplepos_t target_sample, bool with_roll)
+Session::request_locate (samplepos_t target_sample, bool with_roll, TransportRequestSource origin)
 {
+       if (should_ignore_transport_request (origin, TR_Locate)) {
+               return;
+       }
+
        SessionEvent *ev = new SessionEvent (with_roll ? SessionEvent::LocateRoll : SessionEvent::Locate, SessionEvent::Add, SessionEvent::Immediate, target_sample, 0, false);
        DEBUG_TRACE (DEBUG::Transport, string_compose ("Request locate to %1\n", target_sample));
        queue_event (ev);
@@ -190,7 +204,7 @@ Session::request_count_in_record ()
 void
 Session::request_play_loop (bool yn, bool change_transport_roll)
 {
-       if (_slave && yn) {
+       if (transport_master_is_external() && yn) {
                // don't attempt to loop when not using Internal Transport
                // see also gtk2_ardour/ardour_ui_options.cc parameter_changed()
                return;
@@ -287,22 +301,22 @@ Session::solo_selection ( StripableList &list, bool new_state  )
                _soloSelection = list;
        else
                _soloSelection.clear();
-       
+
        boost::shared_ptr<RouteList> rl = get_routes();
+
        for (ARDOUR::RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
 
                if ( !(*i)->is_track() ) {
                        continue;
                }
-               
+
                boost::shared_ptr<Stripable> s (*i);
 
                bool found = (std::find(list.begin(), list.end(), s) != list.end());
                if ( new_state && found ) {
-                       
+
                        solo_list->push_back (s->solo_control());
-                       
+
                        //must invalidate playlists on selected tracks, so only selected regions get heard
                        boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (*i);
                        if (track) {
@@ -369,7 +383,7 @@ Session::realtime_stop (bool abort, bool clear_state)
        if ( solo_selection_active() ) {
                solo_selection ( _soloSelection, false );
        }
-       
+
        /* if we're going to clear loop state, then force disabling record BUT only if we're not doing latched rec-enable */
        disable_record (true, (!Config->get_latched_record_enable() && clear_state));
 
@@ -487,10 +501,6 @@ Session::butler_transport_work ()
                }
        }
 
-       if (ptw & PostTransportSpeed) {
-               non_realtime_set_speed ();
-       }
-
        if (ptw & PostTransportReverse) {
 
                clear_clicks();
@@ -546,18 +556,6 @@ Session::butler_transport_work ()
        DEBUG_TRACE (DEBUG::Transport, string_compose (X_("Butler transport work all done after %1 usecs @ %2 trw = %3\n"), g_get_monotonic_time() - before, _transport_sample, _butler->transport_work_requested()));
 }
 
-void
-Session::non_realtime_set_speed ()
-{
-       boost::shared_ptr<RouteList> rl = routes.reader();
-       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-               if (tr) {
-                       tr->non_realtime_speed_change ();
-               }
-       }
-}
-
 void
 Session::non_realtime_overwrite (int on_entry, bool& finished)
 {
@@ -926,7 +924,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
                // need to queue this in the next RT cycle
                _send_timecode_update = true;
 
-               if (!dynamic_cast<MTC_Slave*>(_slave)) {
+               if (transport_master()->type() == MTC) {
                        send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop));
 
                        /* This (::non_realtime_stop()) gets called by main
@@ -947,7 +945,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
                 *
                 * save state only if there's no slave or if it's not yet locked.
                 */
-               if (!_slave || !_slave->locked()) {
+               if (!transport_master_is_external() || !transport_master()->locked()) {
                        DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: requests save\n"));
                        SaveSessionRequested (_current_snapshot_name);
                        saved = true;
@@ -1122,7 +1120,7 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
                double sp;
                samplepos_t pos;
 
-               _slave->speed_and_position (sp, pos);
+               transport_master()->speed_and_position (sp, pos, 0);
 
                if (target_sample != pos) {
 
@@ -1168,6 +1166,8 @@ Session::micro_locate (samplecnt_t distance)
                }
        }
 
+       DEBUG_TRACE (DEBUG::Transport, string_compose ("micro-locate by %1\n", distance));
+
        for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
                boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
                if (tr) {
@@ -1611,7 +1611,7 @@ Session::start_transport ()
        if (!_engine.freewheeling()) {
                Timecode::Time time;
                timecode_time_subframes (_transport_sample, time);
-               if (!dynamic_cast<MTC_Slave*>(_slave)) {
+               if (transport_master()->type() == MTC) {
                        send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay));
                }
 
@@ -1721,164 +1721,6 @@ Session::reset_rf_scale (samplecnt_t motion)
        }
 }
 
-void
-Session::mtc_status_changed (bool yn)
-{
-       g_atomic_int_set (&_mtc_active, yn);
-       MTCSyncStateChanged( yn );
-}
-
-void
-Session::ltc_status_changed (bool yn)
-{
-       g_atomic_int_set (&_ltc_active, yn);
-       LTCSyncStateChanged( yn );
-}
-
-void
-Session::use_sync_source (Slave* new_slave)
-{
-       /* Runs in process() context */
-
-       if (!_slave && !new_slave) {
-               return;
-       }
-
-       bool non_rt_required = false;
-
-       /* XXX this deletion is problematic because we're in RT context */
-
-       delete _slave;
-       _slave = new_slave;
-
-
-       /* slave change, reset any DiskIO block on disk output because it is no
-          longer valid with a new slave.
-       */
-       DiskReader::set_no_disk_output (false);
-
-       MTC_Slave* mtc_slave = dynamic_cast<MTC_Slave*>(_slave);
-       if (mtc_slave) {
-               mtc_slave->ActiveChanged.connect_same_thread (mtc_status_connection, boost::bind (&Session::mtc_status_changed, this, _1));
-               MTCSyncStateChanged(mtc_slave->locked() );
-       } else {
-               if (g_atomic_int_get (&_mtc_active) ){
-                       g_atomic_int_set (&_mtc_active, 0);
-                       MTCSyncStateChanged( false );
-               }
-               mtc_status_connection.disconnect ();
-       }
-
-       LTC_Slave* ltc_slave = dynamic_cast<LTC_Slave*> (_slave);
-       if (ltc_slave) {
-               ltc_slave->ActiveChanged.connect_same_thread (ltc_status_connection, boost::bind (&Session::ltc_status_changed, this, _1));
-               LTCSyncStateChanged (ltc_slave->locked() );
-       } else {
-               if (g_atomic_int_get (&_ltc_active) ){
-                       g_atomic_int_set (&_ltc_active, 0);
-                       LTCSyncStateChanged( false );
-               }
-               ltc_status_connection.disconnect ();
-       }
-
-       DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave));
-
-       // need to queue this for next process() cycle
-       _send_timecode_update = true;
-
-       boost::shared_ptr<RouteList> rl = routes.reader();
-       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-               if (tr && !tr->is_private_route()) {
-                       tr->set_slaved (_slave != 0);
-               }
-       }
-
-       if (non_rt_required) {
-               add_post_transport_work (PostTransportSpeed);
-               _butler->schedule_transport_work ();
-       }
-
-       set_dirty();
-}
-
-void
-Session::drop_sync_source ()
-{
-       request_sync_source (0);
-}
-
-void
-Session::switch_to_sync_source (SyncSource src)
-{
-       Slave* new_slave;
-
-       DEBUG_TRACE (DEBUG::Slave, string_compose ("Setting up sync source %1\n", enum_2_string (src)));
-
-       switch (src) {
-       case MTC:
-               if (_slave && dynamic_cast<MTC_Slave*>(_slave)) {
-                       return;
-               }
-
-               try {
-                       new_slave = new MTC_Slave (*this, *_midi_ports->mtc_input_port());
-               }
-
-               catch (failed_constructor& err) {
-                       return;
-               }
-               break;
-
-       case LTC:
-               if (_slave && dynamic_cast<LTC_Slave*>(_slave)) {
-                       return;
-               }
-
-               try {
-                       new_slave = new LTC_Slave (*this);
-               }
-
-               catch (failed_constructor& err) {
-                       return;
-               }
-
-               break;
-
-       case MIDIClock:
-               if (_slave && dynamic_cast<MIDIClock_Slave*>(_slave)) {
-                       return;
-               }
-
-               try {
-                       new_slave = new MIDIClock_Slave (*this, *_midi_ports->midi_clock_input_port(), 24);
-               }
-
-               catch (failed_constructor& err) {
-                       return;
-               }
-               break;
-
-       case Engine:
-               if (_slave && dynamic_cast<Engine_Slave*>(_slave)) {
-                       return;
-               }
-
-               if (config.get_video_pullup() != 0.0f) {
-                       return;
-               }
-
-               new_slave = new Engine_Slave (*AudioEngine::instance());
-               break;
-
-       default:
-               new_slave = 0;
-               break;
-       };
-
-       request_sync_source (new_slave);
-}
-
 void
 Session::unset_play_range ()
 {
@@ -2112,3 +1954,91 @@ Session::timecode_transmission_suspended () const
 {
        return g_atomic_int_get (&_suspend_timecode_transmission) == 1;
 }
+
+boost::shared_ptr<TransportMaster>
+Session::transport_master() const
+{
+       return TransportMasterManager::instance().current();
+}
+
+bool
+Session::transport_master_is_external () const
+{
+       return config.get_external_sync();
+}
+
+void
+Session::sync_source_changed (SyncSource type, samplepos_t pos, pframes_t cycle_nframes)
+{
+       /* Runs in process() context */
+
+       boost::shared_ptr<TransportMaster> master = TransportMasterManager::instance().current();
+
+       /* save value of seamless from before the switch */
+       _was_seamless = Config->get_seamless_loop ();
+
+       if (type == Engine) {
+               /* JACK cannot support seamless looping at present */
+               Config->set_seamless_loop (false);
+       } else {
+               /* reset to whatever the value was before we last switched slaves */
+               Config->set_seamless_loop (_was_seamless);
+       }
+
+       if (master->can_loop()) {
+               request_play_loop (false);
+       } else if (master->has_loop()) {
+               request_play_loop (true);
+       }
+
+       /* slave change, reset any DiskIO block on disk output because it is no
+          longer valid with a new slave.
+       */
+
+       DiskReader::set_no_disk_output (false);
+
+#if 0
+       we should not be treating specific transport masters as special cases because there maybe > 1 of a particular type
+
+       boost::shared_ptr<MTC_TransportMaster> mtc_master = boost::dynamic_pointer_cast<MTC_TransportMaster> (master);
+
+       if (mtc_master) {
+               mtc_master->ActiveChanged.connect_same_thread (mtc_status_connection, boost::bind (&Session::mtc_status_changed, this, _1));
+               MTCSyncStateChanged(mtc_master->locked() );
+       } else {
+               if (g_atomic_int_compare_and_exchange (&_mtc_active, 1, 0)) {
+                       MTCSyncStateChanged( false );
+               }
+               mtc_status_connection.disconnect ();
+       }
+
+       boost::shared_ptr<LTC_TransportMaster> ltc_master = boost::dynamic_pointer_cast<LTC_TransportMaster> (master);
+
+       if (ltc_master) {
+               ltc_master->ActiveChanged.connect_same_thread (ltc_status_connection, boost::bind (&Session::ltc_status_changed, this, _1));
+               LTCSyncStateChanged (ltc_master->locked() );
+       } else {
+               if (g_atomic_int_compare_and_exchange (&_ltc_active, 1, 0)) {
+                       LTCSyncStateChanged( false );
+               }
+               ltc_status_connection.disconnect ();
+       }
+#endif
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", master));
+
+       // need to queue this for next process() cycle
+       _send_timecode_update = true;
+
+       boost::shared_ptr<RouteList> rl = routes.reader();
+       const bool externally_slaved = transport_master_is_external();
+
+       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+               if (tr && !tr->is_private_route()) {
+                       tr->set_slaved (externally_slaved);
+               }
+       }
+
+       set_dirty();
+}
index 78b5a209ccb865c4b9290f90781ddba69e4300fa..4f22892b5ab2594549ed6eb1788ef0980589dc87 100644 (file)
@@ -524,12 +524,6 @@ Track::non_realtime_locate (samplepos_t p)
        Route::non_realtime_locate (p);
 }
 
-void
-Track::non_realtime_speed_change ()
-{
-       _disk_reader->non_realtime_speed_change ();
-}
-
 int
 Track::overwrite_existing_buffers ()
 {
diff --git a/libs/ardour/transport_master.cc b/libs/ardour/transport_master.cc
new file mode 100644 (file)
index 0000000..284a0a8
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+    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.
+
+ */
+
+#include <vector>
+
+#include "pbd/i18n.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/midi_port.h"
+#include "ardour/session.h"
+#include "ardour/transport_master.h"
+#include "ardour/transport_master_manager.h"
+#include "ardour/utils.h"
+
+using namespace ARDOUR;
+
+const std::string TransportMaster::state_node_name = X_("TransportMaster");
+
+
+TransportMaster::TransportMaster (SyncSource t, std::string const & name)
+       : _type (t)
+       , _name (name)
+       , _session (0)
+       , _connected (false)
+       , _current_delta (0)
+       , _collect (true)
+       , _pending_collect (true)
+       , _request_mask (TransportRequestType (0))
+       , _sclock_synced (false)
+{
+       ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect_same_thread (port_connection, boost::bind (&TransportMaster::connection_handler, this, _1, _2, _3, _4, _5));
+       ARDOUR::AudioEngine::instance()->Running.connect_same_thread (backend_connection, boost::bind (&TransportMaster::check_backend, this));
+}
+
+TransportMaster::~TransportMaster()
+{
+       delete _session;
+}
+
+bool
+TransportMaster::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+{
+       if (!_port) {
+               return false;
+       }
+
+       const std::string fqn = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (_port->name());
+
+       if (fqn == name1 || fqn == name2) {
+
+               /* it's about us */
+
+               if (yn) {
+                       _connected = true;
+               } else {
+                       _connected = false;
+               }
+
+               return true;
+       }
+
+       return false;
+}
+
+bool
+TransportMaster::check_collect()
+{
+       if (!_connected) {
+               return false;
+       }
+
+       /* XXX should probably use boost::atomic something or other here */
+
+       if (_pending_collect != _collect) {
+               if (_pending_collect) {
+                       init ();
+               } else {
+                       if (TransportMasterManager::instance().current().get() == this) {
+                               if (_session) {
+                                       _session->config.set_external_sync (false);
+                               }
+                       }
+               }
+               std::cerr << name() << " pc = " << _pending_collect << " c = " << _collect << std::endl;
+               _collect = _pending_collect;
+       }
+
+       return _collect;
+}
+
+void
+TransportMaster::set_collect (bool yn)
+{
+       _pending_collect = yn;
+}
+
+void
+TransportMaster::set_sample_clock_synced (bool yn)
+{
+       _sclock_synced = yn;
+}
+
+void
+TransportMaster::set_session (Session* s)
+{
+       _session = s;
+}
+
+int
+TransportMaster::set_state (XMLNode const & node, int /* version */)
+{
+       if (!node.get_property (X_("collect"), _collect)) {
+               _collect = false;
+       }
+
+       if (!node.get_property (X_("clock-synced"), _sclock_synced)) {
+               _sclock_synced = false;
+       }
+
+       TimecodeTransportMaster* ttm = dynamic_cast<TimecodeTransportMaster*> (this);
+       if (ttm) {
+               bool val;
+               node.get_property (X_("fr2997"), val);
+               ttm->set_fr2997 (val);
+       }
+
+       XMLNode* pnode = node.child (X_("Port"));
+
+       if (pnode) {
+               XMLNodeList const & children = pnode->children();
+               for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
+
+                       XMLProperty const *prop;
+
+                       if ((*ci)->name() == X_("Connection")) {
+                               if ((prop = (*ci)->property (X_("other"))) == 0) {
+                                       continue;
+                               }
+                               _port->connect (prop->value());
+                       }
+               }
+       }
+
+       return 0;
+}
+
+XMLNode&
+TransportMaster::get_state ()
+{
+       XMLNode* node = new XMLNode (state_node_name);
+       node->set_property (X_("type"), _type);
+       node->set_property (X_("name"), _name);
+       node->set_property (X_("collect"), _collect);
+       node->set_property (X_("clock-synced"), _sclock_synced);
+
+       TimecodeTransportMaster* ttm = dynamic_cast<TimecodeTransportMaster*> (this);
+       if (ttm) {
+               node->set_property (X_("fr2997"), ttm->fr2997());
+       }
+
+       if (_port) {
+               std::vector<std::string> connections;
+
+               XMLNode* pnode = new XMLNode (X_("Port"));
+
+               if (_port->get_connections (connections)) {
+
+                       std::vector<std::string>::const_iterator ci;
+                       std::sort (connections.begin(), connections.end());
+
+                       for (ci = connections.begin(); ci != connections.end(); ++ci) {
+
+                               /* if its a connection to our own port,
+                                  return only the port name, not the
+                                  whole thing. this allows connections
+                                  to be re-established even when our
+                                  client name is different.
+                               */
+
+                               XMLNode* cnode = new XMLNode (X_("Connection"));
+
+                               cnode->set_property (X_("other"), AudioEngine::instance()->make_port_name_relative (*ci));
+                               pnode->add_child_nocopy (*cnode);
+                       }
+               }
+
+               node->add_child_nocopy (*pnode);
+       }
+
+       return *node;
+}
+
+boost::shared_ptr<TransportMaster>
+TransportMaster::factory (XMLNode const & node)
+{
+       if (node.name() != TransportMaster::state_node_name) {
+               return boost::shared_ptr<TransportMaster>();
+       }
+
+       SyncSource type;
+       std::string name;
+
+       if (!node.get_property (X_("type"), type)) {
+               return boost::shared_ptr<TransportMaster>();
+       }
+
+       if (!node.get_property (X_("name"), name)) {
+               return boost::shared_ptr<TransportMaster>();
+       }
+
+       return factory (type, name);
+}
+
+boost::shared_ptr<TransportMaster>
+TransportMaster::factory (SyncSource type, std::string const& name)
+{
+       /* XXX need to count existing sources of a given type */
+
+       switch (type) {
+       case MTC:
+               return boost::shared_ptr<TransportMaster> (new MTC_TransportMaster (sync_source_to_string (type)));
+       case LTC:
+               return boost::shared_ptr<TransportMaster> (new LTC_TransportMaster (sync_source_to_string (type)));
+       case MIDIClock:
+               return boost::shared_ptr<TransportMaster> (new MIDIClock_TransportMaster (sync_source_to_string (type)));
+       case Engine:
+               return boost::shared_ptr<TransportMaster> (new Engine_TransportMaster (*AudioEngine::instance()));
+       default:
+               break;
+       }
+
+       return boost::shared_ptr<TransportMaster>();
+}
+
+boost::shared_ptr<Port>
+TransportMasterViaMIDI::create_midi_port (std::string const & port_name)
+{
+       boost::shared_ptr<Port> p;
+
+       if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name)) == 0) {
+               return boost::shared_ptr<Port> ();
+       }
+
+       _midi_port = boost::dynamic_pointer_cast<MidiPort> (p);
+
+       return p;
+}
+
+bool
+TransportMaster::allow_request (TransportRequestSource src, TransportRequestType type) const
+{
+       return _request_mask & type;
+}
+
+void
+TransportMaster::set_request_mask (TransportRequestType t)
+{
+       _request_mask = t;
+}
+
+void
+TimecodeTransportMaster::set_fr2997 (bool yn)
+{
+       _fr2997 = yn;
+}
diff --git a/libs/ardour/transport_master_manager.cc b/libs/ardour/transport_master_manager.cc
new file mode 100644 (file)
index 0000000..e69aebe
--- /dev/null
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2018 Paul Davis (paul@linuxaudiosystems.com)
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "pbd/i18n.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/debug.h"
+#include "ardour/disk_reader.h"
+#include "ardour/session.h"
+#include "ardour/transport_master_manager.h"
+
+#if __cplusplus > 199711L
+#define local_signbit(x) std::signbit (x)
+#else
+#define local_signbit(x) ((((__int64*)(&z))*) & 0x8000000000000000)
+#endif
+
+using namespace ARDOUR;
+using namespace PBD;
+
+const std::string TransportMasterManager::state_node_name = X_("TransportMasters");
+TransportMasterManager* TransportMasterManager::_instance = 0;
+
+TransportMasterManager::TransportMasterManager()
+       : _master_speed (0)
+       , _master_position (0)
+       , _current_master (0)
+       , _session (0)
+       , _master_invalid_this_cycle (false)
+       , master_dll_initstate (0)
+{
+}
+
+TransportMasterManager::~TransportMasterManager ()
+{
+       clear ();
+}
+
+int
+TransportMasterManager::init ()
+{
+       try {
+               /* setup default transport masters. Most people will never need any
+                  others
+               */
+               add (Engine, X_("JACK Transport"));
+               add (MTC, X_("MTC"));
+               add (LTC, X_("LTC"));
+               add (MIDIClock, X_("MIDI Clock"));
+       } catch (...) {
+               return -1;
+       }
+
+       _current_master = _transport_masters.back();
+
+       return 0;
+}
+
+void
+TransportMasterManager::set_session (Session* s)
+{
+       /* Called by AudioEngine in process context, synchronously with it's
+        * own "adoption" of the Session. The call will occur before the first
+        * call to ::pre_process_transport_masters().
+        */
+
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       config_connection.disconnect ();
+
+       _session = s;
+
+       for (TransportMasters::iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
+               (*tm)->set_session (s);
+       }
+
+       if (_session) {
+               _session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&TransportMasterManager::parameter_changed, this, _1));
+       }
+
+}
+
+void
+TransportMasterManager::parameter_changed (std::string const & what)
+{
+       if (what == "external-sync") {
+               if (!_session->config.get_external_sync()) {
+                       /* disabled */
+                       DiskReader::set_no_disk_output (false);
+               }
+       }
+}
+
+TransportMasterManager&
+TransportMasterManager::instance()
+{
+       if (!_instance) {
+               _instance = new TransportMasterManager();
+       }
+       return *_instance;
+}
+
+// Called from AudioEngine::process_callback() BEFORE Session::process() is called. Each transport master has processed any incoming data for this cycle,
+// and this method computes the transport speed that Ardour should use to get into and remain in sync with the master.
+//
+double
+TransportMasterManager::pre_process_transport_masters (pframes_t nframes, samplepos_t now)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
+               return 1.0;
+       }
+
+       boost::optional<samplepos_t> session_pos;
+
+       if (_session) {
+               session_pos = _session->audible_sample();
+       }
+
+       if (Config->get_run_all_transport_masters_always()) {
+               for (TransportMasters::iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
+                       if ((*tm)->check_collect()) {
+                               (*tm)->pre_process (nframes, now, session_pos);
+                       }
+               }
+       }
+
+       if (!_session) {
+               return 1.0;
+       }
+
+       /* if we're not running ALL transport masters, but still have a current
+        * one, then we should run that one all the time so that we know
+        * precisely where it is when we starting chasing it ...
+        */
+
+       if (!Config->get_run_all_transport_masters_always() && _current_master) {
+               _current_master->pre_process (nframes, now, session_pos);
+       }
+
+       if (!_session->config.get_external_sync()) {
+               DEBUG_TRACE (DEBUG::Slave, string_compose ("no external sync, use session actual speed of %1\n", _session->actual_speed() ? _session->actual_speed() : 1.0));
+               return _session->actual_speed () ? _session->actual_speed() : 1.0;
+       }
+
+       /* --- NOT REACHED UNLESS CHASING (i.e. _session->config.get_external_sync() is true ------*/
+
+       if (!_current_master->ok()) {
+               /* stop */
+               _session->request_transport_speed (0.0, false, _current_master->request_type());
+               DEBUG_TRACE (DEBUG::Slave, "no roll2 - master has failed\n");
+               _master_invalid_this_cycle = true;
+               return 1.0;
+       }
+
+       if (!_current_master->locked()) {
+               DEBUG_TRACE (DEBUG::Slave, "no roll4 - not locked\n");
+               _master_invalid_this_cycle = true;
+               return 1.0;
+       }
+
+       double engine_speed;
+
+       if (!_current_master->speed_and_position (_master_speed, _master_position, now)) {
+               return 1.0;
+       }
+
+       if (_master_speed != 0.0) {
+
+               samplepos_t delta = _master_position;
+
+               if (_session->compute_audible_delta (delta)) {
+
+                       if (master_dll_initstate == 0) {
+
+                               init_transport_master_dll (_master_speed, _master_position);
+                               // _master_invalid_this_cycle = true;
+                               DEBUG_TRACE (DEBUG::Slave, "no roll3 - still initializing master DLL\n");
+                               master_dll_initstate = _master_speed > 0.0 ? 1 : -1;
+
+                               return 1.0;
+                       }
+
+                       /* compute delta or "error" between the computed master_position for
+                        * this cycle and the current session position.
+                        *
+                        * Remember: ::speed_and_position() is being called in process context
+                        * but returns the predicted speed+position for the start of this process cycle,
+                        * not just the most recent timestamp received by the current master object.
+                        */
+
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("master DLL: delta = %1 (%2 vs %3) res: %4\n", delta, _master_position, _session->transport_sample(), _current_master->resolution()));
+
+                       if (delta > _current_master->resolution()) {
+
+                               // init_transport_master_dll (_master_speed, _master_position);
+
+                               if (!_session->actively_recording()) {
+                                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave delta %1 greater than slave resolution %2 => no disk output\n", delta, _current_master->resolution()));
+                                       /* run routes as normal, but no disk output */
+                                       DiskReader::set_no_disk_output (true);
+                               } else {
+                                       DiskReader::set_no_disk_output (false);
+                               }
+                       } else {
+                               DiskReader::set_no_disk_output (false);
+                       }
+
+                       /* inject DLL with new data */
+
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("feed master DLL t0 %1 t1 %2 e %3 %4 e2 %5 sess %6\n", t0, t1, delta, _master_position, e2, _session->transport_sample()));
+
+                       const double e = delta;
+
+                       t0 = t1;
+                       t1 += b * e + e2;
+                       e2 += c * e;
+
+                       engine_speed = (t1 - t0) / nframes;
+
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("slave @ %1 speed %2 cur delta %3 matching speed %4\n", _master_position, _master_speed, delta, engine_speed));
+
+                       /* provide a .1% deadzone to lock the speed */
+                       if (fabs (engine_speed - 1.0) <= 0.001) {
+                               engine_speed = 1.0;
+                       }
+
+                       if (_current_master->sample_clock_synced() && engine_speed != 0.0f) {
+
+                               /* if the master is synced to our audio interface via word-clock or similar, then we assume that its speed is binary: 0.0 or 1.0
+                                  (since our sample clock cannot change with respect to it).
+                               */
+                               if (engine_speed > 0.0) {
+                                       engine_speed = 1.0;
+                               } else if (engine_speed < 0.0) {
+                                       engine_speed = -1.0;
+                               }
+                       }
+
+                       /* speed is set, we're locked, and good to go */
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("%1: computed speed-to-follow-master as %2\n", _current_master->name(), engine_speed));
+
+               } else {
+
+                       /* session has not finished with latency compensation yet, so we cannot compute the
+                          difference between the master and the session.
+                       */
+                       engine_speed = 1.0;
+               }
+
+       } else {
+
+               engine_speed = 1.0;
+       }
+
+       _master_invalid_this_cycle = false;
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("computed resampling ratio as %1 with position = %2 and speed = %3\n", engine_speed, _master_position, _master_speed));
+
+       return engine_speed;
+}
+
+
+void
+TransportMasterManager::init_transport_master_dll (double speed, samplepos_t pos)
+{
+       /* the bandwidth of the DLL is a trade-off,
+        * because the max-speed of the transport in ardour is
+        * limited to +-8.0, a larger bandwidth would cause oscillations
+        *
+        * But this is only really a problem if the user performs manual
+        * seeks while transport is running and slaved to some timecode-y master.
+        */
+
+       AudioEngine* ae = AudioEngine::instance();
+
+       double const omega = 2.0 * M_PI * double(ae->samples_per_cycle()) / 2.0 / double(ae->sample_rate());
+       b = 1.4142135623730950488 * omega;
+       c = omega * omega;
+
+       const int direction = (speed >= 0.0 ? 1 : -1);
+
+       master_dll_initstate = direction;
+
+       e2 = double (direction * ae->samples_per_cycle());
+       t0 = double (pos);
+       t1 = t0 + e2;
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("[re-]init ENGINE DLL %1 %2 %3 from %4 %5\n", t0,  t1, e2, speed, pos));
+}
+
+int
+TransportMasterManager::add (SyncSource type, std::string const & name)
+{
+       int ret = 0;
+       boost::shared_ptr<TransportMaster> tm;
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               tm = TransportMaster::factory (type, name);
+               ret = add_locked (tm);
+       }
+
+       if (ret == 0) {
+               Added (tm);
+       }
+
+       return ret;
+}
+
+int
+TransportMasterManager::add_locked (boost::shared_ptr<TransportMaster> tm)
+{
+       if (!tm) {
+               return -1;
+       }
+
+       for (TransportMasters::const_iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
+               if ((*t)->name() == tm->name()) {
+                       error << string_compose (_("There is already a transport master named \"%1\" - not duplicated"), tm->name()) << endmsg;
+                       return -1;
+               }
+       }
+
+       _transport_masters.push_back (tm);
+       return 0;
+}
+
+int
+TransportMasterManager::remove (std::string const & name)
+{
+       int ret = -1;
+       boost::shared_ptr<TransportMaster> tm;
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+
+               for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
+                       if ((*t)->name() == name) {
+                               tm = *t;
+                               _transport_masters.erase (t);
+                               ret = 0;
+                               break;
+                       }
+               }
+       }
+
+       if (ret == 0 && tm) {
+               Removed (tm);
+       }
+
+       return -1;
+}
+
+int
+TransportMasterManager::set_current_locked (boost::shared_ptr<TransportMaster> c)
+{
+       if (find (_transport_masters.begin(), _transport_masters.end(), c) == _transport_masters.end()) {
+               warning << string_compose (X_("programming error: attempt to use unknown transport master named \"%1\"\n"), c->name());
+               return -1;
+       }
+
+       _current_master = c;
+       _master_speed = 0;
+       _master_position = 0;
+
+       master_dll_initstate = 0;
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("current transport master set to %1\n", c->name()));
+
+       return 0;
+}
+
+int
+TransportMasterManager::set_current (boost::shared_ptr<TransportMaster> c)
+{
+       int ret = -1;
+       boost::shared_ptr<TransportMaster> old (_current_master);
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               ret = set_current_locked (c);
+       }
+
+       if (ret == 0) {
+               CurrentChanged (old, _current_master); // EMIT SIGNAL
+       }
+
+       return ret;
+}
+
+int
+TransportMasterManager::set_current (SyncSource ss)
+{
+       int ret = -1;
+       boost::shared_ptr<TransportMaster> old (_current_master);
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+
+               for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
+                       if ((*t)->type() == ss) {
+                               ret = set_current_locked (*t);
+                               break;
+                       }
+               }
+       }
+
+       if (ret == 0) {
+               CurrentChanged (old, _current_master); // EMIT SIGNAL
+       }
+
+       return ret;
+}
+
+
+int
+TransportMasterManager::set_current (std::string const & str)
+{
+       int ret = -1;
+       boost::shared_ptr<TransportMaster> old (_current_master);
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+
+               for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
+                       if ((*t)->name() == str) {
+                               ret = set_current_locked (*t);
+                               break;
+                       }
+               }
+       }
+
+       if (ret == 0) {
+               CurrentChanged (old, _current_master); // EMIT SIGNAL
+       }
+
+       return ret;
+}
+
+
+void
+TransportMasterManager::clear ()
+{
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               _transport_masters.clear ();
+       }
+
+       Removed (boost::shared_ptr<TransportMaster>());
+}
+
+int
+TransportMasterManager::set_state (XMLNode const & node, int version)
+{
+       assert (node.name() == state_node_name);
+
+       XMLNodeList const & children = node.children();
+
+
+       if (!children.empty()) {
+               _transport_masters.clear ();
+       }
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+
+
+               for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
+
+                       boost::shared_ptr<TransportMaster> tm = TransportMaster::factory (**c);
+
+                       if (add_locked (tm)) {
+                               continue;
+                       }
+
+                       /* we know it is the last thing added to the list of masters */
+
+                       _transport_masters.back()->set_state (**c, version);
+               }
+       }
+
+       std::string current_master;
+       if (node.get_property (X_("current"), current_master)) {
+               set_current (current_master);
+       } else {
+               set_current (MTC);
+       }
+
+       return 0;
+}
+
+XMLNode&
+TransportMasterManager::get_state ()
+{
+       XMLNode* node = new XMLNode (state_node_name);
+
+       node->set_property (X_("current"), _current_master->name());
+
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
+               node->add_child_nocopy ((*t)->get_state());
+       }
+
+       return *node;
+}
+
+boost::shared_ptr<TransportMaster>
+TransportMasterManager::master_by_type (SyncSource src) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       for (TransportMasters::const_iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
+               if ((*tm)->type() == src) {
+                       return *tm;
+               }
+       }
+
+       return boost::shared_ptr<TransportMaster> ();
+}
index ec360687209aa979ab7c9a16d4567a79a57ed344..efc305325aea009eaf0e566d73672300978fb2b6 100644 (file)
@@ -219,7 +219,6 @@ libardour_sources = [
         'session_time.cc',
         'session_transport.cc',
         'sidechain.cc',
-        'slave.cc',
         'slavable.cc',
         'slavable_automation_control.cc',
         'smf_source.cc',
@@ -247,6 +246,8 @@ libardour_sources = [
         'track.cc',
         'transient_detector.cc',
         'transform.cc',
+        'transport_master.cc',
+        'transport_master_manager.cc',
         'transpose.cc',
         'unknown_processor.cc',
         'user_bundle.cc',
index 3b1dd470523228632a733e1bdfd63a1fbbb5a67c..0ccef6e66f8a51b23f8eb8912d20e9712461dce0 100644 (file)
@@ -42,6 +42,7 @@ typedef PBD::Signal2<void,Parser &, pitchbend_t>     PitchBendSignal;
 typedef PBD::Signal3<void,Parser &, uint16_t, int>   RPNSignal;
 typedef PBD::Signal3<void,Parser &, uint16_t, float> RPNValueSignal;
 typedef PBD::Signal3<void,Parser &, byte *, size_t>  Signal;
+typedef PBD::Signal4<void,Parser &, byte *, size_t, samplecnt_t> AnySignal;
 
 class LIBMIDIPP_API Parser {
  public:
@@ -86,7 +87,7 @@ class LIBMIDIPP_API Parser {
        Signal                mtc;
        Signal                raw_preparse;
        Signal                raw_postparse;
-       Signal                any;
+       AnySignal             any;
        Signal                sysex;
        Signal                mmc;
        Signal                position;
@@ -147,7 +148,7 @@ class LIBMIDIPP_API Parser {
 
        std::ostream *trace_stream;
        std::string trace_prefix;
-       void trace_event (Parser &p, byte *msg, size_t len);
+       void trace_event (Parser &p, byte *msg, size_t len, samplecnt_t);
        PBD::ScopedConnection trace_connection;
 
        size_t message_counter[256];
index 9866d41632f948aa0bfab565f916c884bf596ed9..599f8764f15d8835012bc5b83f084db4d88b77c6 100644 (file)
@@ -136,7 +136,7 @@ Parser::~Parser ()
 }
 
 void
-Parser::trace_event (Parser &, MIDI::byte *msg, size_t len)
+Parser::trace_event (Parser &, MIDI::byte *msg, size_t len, samplecnt_t /*when*/)
 {
        eventType type;
        ostream *o;
@@ -313,7 +313,7 @@ Parser::trace (bool onoff, ostream *o, const string &prefix)
        if (onoff) {
                trace_stream = o;
                trace_prefix = prefix;
-               any.connect_same_thread (trace_connection, boost::bind (&Parser::trace_event, this, _1, _2, _3));
+               any.connect_same_thread (trace_connection, boost::bind (&Parser::trace_event, this, _1, _2, _3, _4));
        } else {
                trace_prefix = "";
                trace_stream = 0;
@@ -440,7 +440,7 @@ Parser::scanner (unsigned char inbyte)
                                        }
                                }
                                if (!_offline) {
-                                       any (*this, msgbuf, msgindex);
+                                       any (*this, msgbuf, msgindex, _timestamp);
                                }
                        }
                }
@@ -572,7 +572,7 @@ Parser::realtime_msg(unsigned char inbyte)
                break;
        }
 
-       any (*this, &inbyte, 1);
+       any (*this, &inbyte, 1, _timestamp);
 }
 
 
@@ -661,7 +661,7 @@ Parser::system_msg (unsigned char inbyte)
 
        // all these messages will be sent via any()
        // when they are complete.
-       // any (*this, &inbyte, 1);
+       // any (*this, &inbyte, 1, _timestamp);
 }
 
 void
@@ -764,7 +764,7 @@ Parser::signal (MIDI::byte *msg, size_t len)
                break;
        }
 
-       any (*this, msg, len);
+       any (*this, msg, len, _timestamp);
 }
 
 bool