new transport slave/master implementation, libs/ edition
This commit is contained in:
parent
7390b88c2b
commit
e6915e01de
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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__ */
|
|
@ -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__ */
|
File diff suppressed because it is too large
Load Diff
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -20,47 +20,104 @@
|
|||
#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)
|
||||
{
|
||||
double x;
|
||||
samplepos_t p;
|
||||
/* call this to initialize things */
|
||||
speed_and_position (x, p);
|
||||
check_backend ();
|
||||
}
|
||||
|
||||
Engine_Slave::~Engine_Slave ()
|
||||
Engine_TransportMaster::~Engine_TransportMaster ()
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
#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;
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
#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() )
|
||||
#define ENGINE AudioEngine::instance()
|
||||
#define FLYWHEEL_TIMEOUT ( 1 * ENGINE->sample_rate() )
|
||||
|
||||
LTC_Slave::LTC_Slave (Session& s)
|
||||
: session (s)
|
||||
/* 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 (<C_Slave::parameter_changed, this, _1));
|
||||
parse_timecode_offset();
|
||||
reset();
|
||||
resync_latency();
|
||||
session.Xrun.connect_same_thread (port_connections, boost::bind (<C_Slave::resync_xrun, this));
|
||||
session.engine().GraphReordered.connect_same_thread (port_connections, boost::bind (<C_Slave::resync_latency, this));
|
||||
|
||||
AudioEngine::instance()->Xrun.connect_same_thread (port_connections, boost::bind (<C_TransportMaster::resync_xrun, this));
|
||||
AudioEngine::instance()->GraphReordered.connect_same_thread (port_connections, boost::bind (<C_TransportMaster::resync_latency, this));
|
||||
}
|
||||
|
||||
LTC_Slave::~LTC_Slave()
|
||||
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 (<C_TransportMaster::parameter_changed, this, _1));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
ltc_frame += ltc_slave_latency.max;
|
||||
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_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;
|
||||
last_timestamp = cur_timestamp;
|
||||
last_ltc_sample = ltc_sample;
|
||||
|
||||
} /* end foreach decoded 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));
|
||||
}
|
||||
|
||||
/* 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();
|
||||
|
||||
Sample* in;
|
||||
|
||||
boost::shared_ptr<Port> ltcport = session.ltc_input_port();
|
||||
|
||||
in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (ltcport->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));
|
||||
|
||||
if (last_timestamp == 0) {
|
||||
engine_dll_initstate = 0;
|
||||
if (delayedlocked < 10) ++delayedlocked;
|
||||
} else if (engine_dll_initstate != transport_direction && ltc_speed != 0) {
|
||||
|
||||
ActiveChanged (true); /* EMIT SIGNAL */
|
||||
|
||||
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 (!_collect || last_timestamp == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
/* XXX these are not atomics and maybe modified in a thread other other than the one
|
||||
that is executing this.
|
||||
*/
|
||||
|
||||
parse_ltc(nframes, in, now);
|
||||
process_ltc(now);
|
||||
}
|
||||
|
||||
if (last_timestamp == 0) {
|
||||
DEBUG_TRACE (DEBUG::LTC, "last timestamp == 0\n");
|
||||
speed = 0;
|
||||
pos = session.transport_sample();
|
||||
return true;
|
||||
} else if (ltc_speed != 0) {
|
||||
if (delayedlocked > 1) delayedlocked--;
|
||||
else if (current_delta == 0) delayedlocked = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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() ));
|
||||
} 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));
|
||||
speed = ltc_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;
|
||||
}
|
||||
|
||||
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));
|
||||
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 ("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) {
|
||||
if (delayedlocked < 10) {
|
||||
++delayedlocked;
|
||||
}
|
||||
|
||||
} else if (ltc_speed != 0) {
|
||||
|
||||
}
|
||||
|
||||
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");
|
||||
return;
|
||||
} else if (ltc_speed != 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) {
|
||||
DEBUG_TRACE (DEBUG::LTC, "flywheel timeout\n");
|
||||
reset();
|
||||
/* don't change position from last known */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (session_pos) {
|
||||
const samplepos_t current_pos = last_ltc_sample + ((now - last_timestamp) * ltc_speed);
|
||||
_current_delta = current_pos - *session_pos;
|
||||
} else {
|
||||
_current_delta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -29,11 +29,13 @@
|
|||
|
||||
#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
|
||||
{
|
||||
session = (ISlaveSessionProxy *) new SlaveSessionProxy(s);
|
||||
rebind (p);
|
||||
reset ();
|
||||
}
|
||||
#define ENGINE AudioEngine::instance()
|
||||
|
||||
MIDIClock_Slave::MIDIClock_Slave (ISlaveSessionProxy* session_proxy, int ppqn)
|
||||
: session(session_proxy)
|
||||
MIDIClock_TransportMaster::MIDIClock_TransportMaster (std::string const & name, int ppqn)
|
||||
: TransportMaster (MIDIClock, name)
|
||||
, ppqn (ppqn)
|
||||
, bandwidth (2.0 / 60.0) // 1 BpM = 1 / 60 Hz
|
||||
, last_timestamp (0)
|
||||
, should_be_position (0)
|
||||
, midi_clock_count (0)
|
||||
, _speed (0)
|
||||
, _running (false)
|
||||
, _bpm (0)
|
||||
{
|
||||
reset ();
|
||||
if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
|
||||
throw failed_constructor();
|
||||
}
|
||||
}
|
||||
|
||||
MIDIClock_Slave::~MIDIClock_Slave()
|
||||
MIDIClock_TransportMaster::~MIDIClock_TransportMaster()
|
||||
{
|
||||
delete session;
|
||||
}
|
||||
|
||||
void
|
||||
MIDIClock_Slave::rebind (MidiPort& port)
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port.name()));
|
||||
|
||||
port_connections.drop_connections ();
|
||||
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
MIDIClock_Slave::calculate_one_ppqn_in_samples_at(samplepos_t time)
|
||||
MIDIClock_TransportMaster::init ()
|
||||
{
|
||||
const double samples_per_quarter_note = session->tempo_map().samples_per_quarter_note_at (time, session->sample_rate());
|
||||
midi_clock_count = 0;
|
||||
last_timestamp = 0;
|
||||
}
|
||||
|
||||
void
|
||||
MIDIClock_TransportMaster::set_session (Session *session)
|
||||
{
|
||||
port_connections.drop_connections();
|
||||
_session = session;
|
||||
|
||||
/* only connect to signals if we have a proxy, because otherwise we
|
||||
* cannot interpet incoming data (no tempo map etc.)
|
||||
*/
|
||||
|
||||
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_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
|
||||
{
|
||||
/* 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();
|
||||
|
||||
calculate_one_ppqn_in_samples_at(should_be_position);
|
||||
|
||||
samplepos_t elapsed_since_start = timestamp - first_timestamp;
|
||||
double error = 0;
|
||||
double e = 0;
|
||||
|
||||
if (_starting || last_timestamp == 0) {
|
||||
midi_clock_count = 0;
|
||||
calculate_one_ppqn_in_samples_at (should_be_position);
|
||||
|
||||
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("clock count %1, sbp %2\n", midi_clock_count, should_be_position));
|
||||
|
||||
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();
|
||||
|
||||
// initialize DLL
|
||||
e2 = double(one_ppqn_in_samples) / double(session->sample_rate());
|
||||
t0 = double(elapsed_since_start) / double(session->sample_rate());
|
||||
t1 = t0 + e2;
|
||||
|
||||
// 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;
|
||||
should_be_position += one_ppqn_in_samples;
|
||||
|
||||
// update DLL
|
||||
} 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 */
|
||||
|
||||
midi_clock_count++;
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
@ -56,11 +55,14 @@ MidiPort::~MidiPort()
|
|||
delete _buffer;
|
||||
}
|
||||
|
||||
void
|
||||
MidiPort::parse_input (pframes_t nframes, MIDI::Parser& parser)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
MidiPort::cycle_start (pframes_t nframes)
|
||||
{
|
||||
samplepos_t now = AudioEngine::instance()->sample_time_at_cycle_start();
|
||||
|
||||
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 (receives_input () && _input_active) {
|
||||
|
||||
if (_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 MIDI events for this cycle of nframes from
|
||||
the MIDI port buffer into our MidiBuffer.
|
||||
*/
|
||||
|
||||
/* suck all relevant MIDI events 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;
|
||||
}
|
||||
|
||||
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.
|
||||
#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";
|
||||
#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);
|
||||
}
|
||||
if (buf[0] == 0xfe) {
|
||||
/* throw away active sensing */
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
_buffer->silence (nframes);
|
||||
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.
|
||||
#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";
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
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)
|
||||
MidiPort::set_trace (MIDI::Parser * p)
|
||||
{
|
||||
_always_parse = yn;
|
||||
}
|
||||
|
||||
void
|
||||
MidiPort::set_trace_on (bool yn)
|
||||
{
|
||||
_trace_on = yn;
|
||||
_trace_parser = p;
|
||||
}
|
||||
|
||||
int
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
const int MTC_TransportMaster::sample_tolerance = 2;
|
||||
|
||||
MTC_Slave::MTC_Slave (Session& s, MidiPort& p)
|
||||
: session (s)
|
||||
, port (&p)
|
||||
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;
|
||||
if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
|
||||
throw failed_constructor();
|
||||
}
|
||||
|
||||
last_mtc_fps_byte = session.get_mtc_timecode_bits ();
|
||||
quarter_frame_duration = (double(session.samples_per_timecode_frame()) / 4.0);
|
||||
DEBUG_TRACE (DEBUG::Slave, string_compose ("MTC registered %1\n", _port->name()));
|
||||
|
||||
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);
|
||||
|
||||
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 ()
|
||||
{
|
||||
port_connections.drop_connections ();
|
||||
|
||||
port = &p;
|
||||
|
||||
reset (true);
|
||||
}
|
||||
|
||||
void
|
||||
MTC_Slave::parse_timecode_offset() {
|
||||
MTC_TransportMaster::set_session (Session *s)
|
||||
{
|
||||
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);
|
||||
|
||||
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_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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > labs(seekahead_distance())) {
|
||||
/* 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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 ();
|
||||
|
||||
for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
|
||||
|
||||
MidiPortInfo::iterator x = midi_port_info.find (*si);
|
||||
|
||||
if (x == midi_port_info.end()) {
|
||||
++si;
|
||||
continue;
|
||||
}
|
||||
|
||||
MidiPortInformation& mpi (x->second);
|
||||
|
||||
if (mpi.pretty_name.empty()) {
|
||||
/* no information !!! */
|
||||
++si;
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (mpi.pretty_name.empty()) {
|
||||
/* no information !!! */
|
||||
++si;
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,14 +99,13 @@
|
|||
#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 ()
|
||||
{
|
||||
|
|
|
@ -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, <cframe);
|
||||
|
||||
|
||||
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(<c_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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()))) {
|
||||
if (!locate_pending() && (!config.get_external_sync() || (transport_master()->ok() && transport_master()->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 */
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@
|
|||
#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") {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 ()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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> ();
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue