summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/ardour/ardour/debug.h1
-rw-r--r--libs/ardour/ardour/disk_io.h4
-rw-r--r--libs/ardour/ardour/midi_buffer.h2
-rw-r--r--libs/ardour/ardour/midi_port.h32
-rw-r--r--libs/ardour/ardour/midiport_manager.h6
-rw-r--r--libs/ardour/ardour/port.h6
-rw-r--r--libs/ardour/ardour/rc_configuration.h2
-rw-r--r--libs/ardour/ardour/rc_configuration_vars.h13
-rw-r--r--libs/ardour/ardour/session.h81
-rw-r--r--libs/ardour/ardour/session_event.h14
-rw-r--r--libs/ardour/ardour/slave.h24
-rw-r--r--libs/ardour/ardour/track.h2
-rw-r--r--libs/ardour/ardour/transport_master.h546
-rw-r--r--libs/ardour/ardour/transport_master_manager.h114
-rw-r--r--libs/ardour/ardour/types.h1356
-rw-r--r--libs/ardour/audio_port.cc15
-rw-r--r--libs/ardour/audioengine.cc29
-rw-r--r--libs/ardour/debug.cc1
-rw-r--r--libs/ardour/disk_io.cc16
-rw-r--r--libs/ardour/engine_slave.cc87
-rw-r--r--libs/ardour/enums.cc12
-rw-r--r--libs/ardour/globals.cc16
-rw-r--r--libs/ardour/ltc_slave.cc514
-rw-r--r--libs/ardour/midi_clock_slave.cc408
-rw-r--r--libs/ardour/midi_port.cc189
-rw-r--r--libs/ardour/midiport_manager.cc25
-rw-r--r--libs/ardour/mtc_slave.cc332
-rw-r--r--libs/ardour/port.cc17
-rw-r--r--libs/ardour/port_manager.cc86
-rw-r--r--libs/ardour/rc_configuration.cc6
-rw-r--r--libs/ardour/session.cc98
-rw-r--r--libs/ardour/session_ltc.cc68
-rw-r--r--libs/ardour/session_midi.cc21
-rw-r--r--libs/ardour/session_process.cc488
-rw-r--r--libs/ardour/session_state.cc19
-rw-r--r--libs/ardour/session_transport.cc346
-rw-r--r--libs/ardour/track.cc6
-rw-r--r--libs/ardour/transport_master.cc281
-rw-r--r--libs/ardour/transport_master_manager.cc537
-rw-r--r--libs/ardour/wscript3
-rw-r--r--libs/midi++2/midi++/parser.h5
-rw-r--r--libs/midi++2/parser.cc12
42 files changed, 3533 insertions, 2307 deletions
diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h
index e91e0edf51..069840a149 100644
--- a/libs/ardour/ardour/debug.h
+++ b/libs/ardour/ardour/debug.h
@@ -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;
diff --git a/libs/ardour/ardour/disk_io.h b/libs/ardour/ardour/disk_io.h
index 4f9eb2257e..0f79930654 100644
--- a/libs/ardour/ardour/disk_io.h
+++ b/libs/ardour/ardour/disk_io.h
@@ -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;
diff --git a/libs/ardour/ardour/midi_buffer.h b/libs/ardour/ardour/midi_buffer.h
index 509b60f12d..7793f7e0bd 100644
--- a/libs/ardour/ardour/midi_buffer.h
+++ b/libs/ardour/ardour/midi_buffer.h
@@ -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);
diff --git a/libs/ardour/ardour/midi_port.h b/libs/ardour/ardour/midi_port.h
index e23914c4ce..4b453b80bc 100644
--- a/libs/ardour/ardour/midi_port.h
+++ b/libs/ardour/ardour/midi_port.h
@@ -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
diff --git a/libs/ardour/ardour/midiport_manager.h b/libs/ardour/ardour/midiport_manager.h
index 2fb5d5a57a..69b603723f 100644
--- a/libs/ardour/ardour/midiport_manager.h
+++ b/libs/ardour/ardour/midiport_manager.h
@@ -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 ();
diff --git a/libs/ardour/ardour/port.h b/libs/ardour/ardour/port.h
index 99a2b60cc3..9ab850c86b 100644
--- a/libs/ardour/ardour/port.h
+++ b/libs/ardour/ardour/port.h
@@ -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
diff --git a/libs/ardour/ardour/rc_configuration.h b/libs/ardour/ardour/rc_configuration.h
index a644d76250..f3b98c7774 100644
--- a/libs/ardour/ardour/rc_configuration.h
+++ b/libs/ardour/ardour/rc_configuration.h
@@ -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 */
diff --git a/libs/ardour/ardour/rc_configuration_vars.h b/libs/ardour/ardour/rc_configuration_vars.h
index b7ee95e9e5..7ffc8879e2 100644
--- a/libs/ardour/ardour/rc_configuration_vars.h
+++ b/libs/ardour/ardour/rc_configuration_vars.h
@@ -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)
diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h
index c4c7458670..6e7ec93dec 100644
--- a/libs/ardour/ardour/session.h
+++ b/libs/ardour/ardour/session.h
@@ -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;
diff --git a/libs/ardour/ardour/session_event.h b/libs/ardour/ardour/session_event.h
index aaa254e003..37e229acfb 100644
--- a/libs/ardour/ardour/session_event.h
+++ b/libs/ardour/ardour/session_event.h
@@ -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);
diff --git a/libs/ardour/ardour/slave.h b/libs/ardour/ardour/slave.h
index 155e295cbf..2a05268c77 100644
--- a/libs/ardour/ardour/slave.h
+++ b/libs/ardour/ardour/slave.h
@@ -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;
diff --git a/libs/ardour/ardour/track.h b/libs/ardour/ardour/track.h
index 2ec5a0f4aa..b6abece1f0 100644
--- a/libs/ardour/ardour/track.h
+++ b/libs/ardour/ardour/track.h
@@ -140,7 +140,7 @@ public:
int can_internal_playback_seek (samplecnt_t);
int internal_playback_seek (samplecnt_t);
void non_realtime_locate (samplepos_t);
- void non_realtime_speed_change ();
+ void realtime_handle_transport_stopped ();
int overwrite_existing_buffers ();
samplecnt_t get_captured_samples (uint32_t n = 0) const;
void transport_looped (samplepos_t);
diff --git a/libs/ardour/ardour/transport_master.h b/libs/ardour/ardour/transport_master.h
new file mode 100644
index 0000000000..9da0378fb1
--- /dev/null
+++ b/libs/ardour/ardour/transport_master.h
@@ -0,0 +1,546 @@
+/*
+ Copyright (C) 2002 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_transport_master_h__
+#define __ardour_transport_master_h__
+
+#include <vector>
+
+#include <boost/atomic.hpp>
+#include <boost/optional.hpp>
+
+#include <glibmm/threads.h>
+
+#include <ltc.h>
+
+#include "pbd/signals.h"
+
+#include "temporal/time.h"
+
+#include "ardour/libardour_visibility.h"
+#include "ardour/types.h"
+#include "midi++/parser.h"
+#include "midi++/types.h"
+
+
+/* used for delta_string(): */
+#define PLUSMINUS(A) ( ((A)<0) ? "-" : (((A)>0) ? "+" : "\u00B1") )
+#define LEADINGZERO(A) ( (A)<10 ? " " : (A)<100 ? " " : (A)<1000 ? " " : "" )
+
+namespace ARDOUR {
+
+class TempoMap;
+class Session;
+class AudioEngine;
+class Location;
+class MidiPort;
+class AudioPort;
+class Port;
+
+
+/**
+ * @class TransportMaster
+ *
+ * @brief The TransportMaster interface can be used to sync ARDOURs tempo to an external source
+ * like MTC, MIDI Clock, etc. as well as a single internal pseudo master we
+ * call "UI" because it is controlled from any of the user interfaces for
+ * Ardour (GUI, control surfaces, OSC, etc.)
+ *
+ */
+class LIBARDOUR_API TransportMaster {
+ public:
+
+ TransportMaster (SyncSource t, std::string const & name);
+ virtual ~TransportMaster();
+
+ static boost::shared_ptr<TransportMaster> factory (SyncSource, std::string const &);
+ static boost::shared_ptr<TransportMaster> factory (XMLNode const &);
+
+ virtual void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>) = 0;
+
+ /**
+ * This is the most important function to implement:
+ * Each process cycle, Session::follow_slave will call this method.
+ * and after the method call they should
+ *
+ * Session::follow_slave will then try to follow the given
+ * <em>position</em> using a delay locked loop (DLL),
+ * starting with the first given transport speed.
+ * If the values of speed and position contradict each other,
+ * ARDOUR will always follow the position and disregard the speed.
+ * Although, a correct speed is important so that ARDOUR
+ * can sync to the master time source quickly.
+ *
+ * For background information on delay locked loops,
+ * see http://www.kokkinizita.net/papers/usingdll.pdf
+ *
+ * The method has the following precondition:
+ * <ul>
+ * <li>
+ * TransportMaster::ok() should return true, otherwise playback will stop
+ * immediately and the method will not be called
+ * </li>
+ * <li>
+ * when the references speed and position are passed into the TransportMaster
+ * they are uninitialized
+ * </li>
+ * </ul>
+ *
+ * After the method call the following postconditions should be met:
+ * <ul>
+ * <li>
+ * The first position value on transport start should be 0,
+ * otherwise ARDOUR will try to locate to the new position
+ * rather than move to it
+ * </li>
+ * <li>
+ * the references speed and position should be assigned
+ * to the TransportMasters current requested transport speed
+ * and transport position.
+ * </li>
+ * <li>
+ * TransportMaster::resolution() should be greater than the maximum distance of
+ * ARDOURs transport position to the slaves requested transport position.
+ * </li>
+ * <li>TransportMaster::locked() should return true, otherwise Session::no_roll will be called</li>
+ * <li>TransportMaster::starting() should be false, otherwise the transport will not move until it becomes true</li> *
+ * </ul>
+ *
+ * @param speed - The transport speed requested
+ * @param position - The transport position requested
+ * @return - The return value is currently ignored (see Session::follow_slave)
+ */
+ virtual bool speed_and_position (double& speed, samplepos_t& position, samplepos_t now) = 0;
+
+ /**
+ * reports to ARDOUR whether the TransportMaster is currently synced to its external
+ * time source.
+ *
+ * @return - when returning false, the transport will stop rolling
+ */
+ virtual bool locked() const = 0;
+
+ /**
+ * reports to ARDOUR whether the slave is in a sane state
+ *
+ * @return - when returning false, the transport will be stopped and the slave
+ * disconnected from ARDOUR.
+ */
+ virtual bool ok() const = 0;
+
+ /**
+ * reports to ARDOUR whether the slave is in the process of starting
+ * to roll
+ *
+ * @return - when returning false, transport will not move until this method returns true
+ */
+ virtual bool starting() const { return false; }
+
+ /**
+ * @return - the timing resolution of the TransportMaster - If the distance of ARDOURs transport
+ * to the slave becomes greater than the resolution, sound will stop
+ */
+ virtual samplecnt_t resolution() const = 0;
+
+ /**
+ * @return - when returning true, ARDOUR will wait for seekahead_distance() before transport
+ * starts rolling
+ */
+ virtual bool requires_seekahead () const = 0;
+
+ /**
+ * @return the number of samples that this slave wants to seek ahead. Relevant
+ * only if requires_seekahead() returns true.
+ */
+
+ virtual samplecnt_t seekahead_distance() const { return 0; }
+
+ /**
+ * @return - when returning true, ARDOUR will use transport speed 1.0 no matter what
+ * the slave returns
+ */
+ virtual bool sample_clock_synced() const { return _sclock_synced; }
+ virtual void set_sample_clock_synced (bool);
+
+ /**
+ * @return - current time-delta between engine and sync-source
+ */
+ virtual std::string delta_string() const { return ""; }
+
+ sampleoffset_t current_delta() const { return _current_delta; }
+
+ /* this is intended to be used by a UI and polled from a timeout. it should
+ return a string describing the current position of the TC source. it
+ should NOT do any computation, but should use a cached value
+ of the TC source position.
+ */
+ virtual std::string position_string() const = 0;
+
+ virtual bool can_loop() const { return false; }
+
+ virtual Location* loop_location() const { return 0; }
+ bool has_loop() const { return loop_location() != 0; }
+
+ SyncSource type() const { return _type; }
+ TransportRequestSource request_type() const {
+ switch (_type) {
+ case Engine: /* also JACK */
+ return TRS_Engine;
+ case MTC:
+ return TRS_MTC;
+ case LTC:
+ return TRS_LTC;
+ case MIDIClock:
+ break;
+ }
+ return TRS_MIDIClock;
+ }
+
+ std::string name() const { return _name; }
+ void set_name (std::string const &);
+
+ int set_state (XMLNode const &, int);
+ XMLNode& get_state();
+
+ static const std::string state_node_name;
+
+ virtual void set_session (Session*);
+
+ boost::shared_ptr<Port> port() const { return _port; }
+
+ bool check_collect();
+ virtual void set_collect (bool);
+ bool collect() const { return _collect; }
+
+ /* called whenever the manager starts collecting (processing) this
+ transport master. Typically will re-initialize any state used to
+ deal with incoming data.
+ */
+ virtual void init() = 0;
+
+ virtual void check_backend() {}
+ virtual bool allow_request (TransportRequestSource, TransportRequestType) const;
+
+ TransportRequestType request_mask() const { return _request_mask; }
+ void set_request_mask (TransportRequestType);
+ protected:
+ SyncSource _type;
+ std::string _name;
+ Session* _session;
+ bool _connected;
+ sampleoffset_t _current_delta;
+ bool _collect;
+ bool _pending_collect;
+ TransportRequestType _request_mask; /* lists transport requests still accepted when we're in control */
+ bool _sclock_synced;
+
+ /* DLL - chase incoming data */
+
+ int transport_direction;
+ int dll_initstate;
+
+ double t0;
+ double t1;
+ double e2;
+ double b, c;
+
+ boost::shared_ptr<Port> _port;
+
+ PBD::ScopedConnection port_connection;
+ bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
+
+ PBD::ScopedConnection backend_connection;
+};
+
+struct LIBARDOUR_API SafeTime {
+ volatile int guard1;
+ samplepos_t position;
+ samplepos_t timestamp;
+ double speed;
+ volatile int guard2;
+
+ SafeTime() {
+ guard1 = 0;
+ position = 0;
+ timestamp = 0;
+ speed = 0;
+ guard2 = 0;
+ }
+};
+
+/** a helper class for any TransportMaster that receives its input via a MIDI
+ * port
+ */
+class LIBARDOUR_API TransportMasterViaMIDI {
+ public:
+ boost::shared_ptr<MidiPort> midi_port() const { return _midi_port; }
+ boost::shared_ptr<Port> create_midi_port (std::string const & port_name);
+
+ protected:
+ TransportMasterViaMIDI () {};
+
+ MIDI::Parser parser;
+ boost::shared_ptr<MidiPort> _midi_port;
+};
+
+class LIBARDOUR_API TimecodeTransportMaster : public TransportMaster {
+ public:
+ TimecodeTransportMaster (std::string const & name, SyncSource type) : TransportMaster (type, name) {}
+
+ virtual Timecode::TimecodeFormat apparent_timecode_format() const = 0;
+ samplepos_t timecode_offset;
+ bool timecode_negative_offset;
+
+ bool fr2997() const { return _fr2997; }
+ void set_fr2997 (bool);
+
+ private:
+ bool _fr2997;
+
+};
+
+class LIBARDOUR_API MTC_TransportMaster : public TimecodeTransportMaster, public TransportMasterViaMIDI {
+ public:
+ MTC_TransportMaster (std::string const &);
+ ~MTC_TransportMaster ();
+
+ void set_session (Session*);
+
+ void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
+
+ bool speed_and_position (double&, samplepos_t&, samplepos_t);
+
+ bool locked() const;
+ bool ok() const;
+ void handle_locate (const MIDI::byte*);
+
+ samplecnt_t resolution () const;
+ bool requires_seekahead () const { return false; }
+ samplecnt_t seekahead_distance() const;
+ void init ();
+
+ Timecode::TimecodeFormat apparent_timecode_format() const;
+ std::string position_string() const;
+ std::string delta_string() const;
+
+ private:
+ PBD::ScopedConnectionList port_connections;
+ PBD::ScopedConnection config_connection;
+ bool can_notify_on_unknown_rate;
+
+ static const int sample_tolerance;
+
+ SafeTime current;
+ samplepos_t mtc_frame; /* current time */
+ double mtc_frame_dll;
+ samplepos_t last_inbound_frame; /* when we got it; audio clocked */
+ MIDI::byte last_mtc_fps_byte;
+ samplepos_t window_begin;
+ samplepos_t window_end;
+ samplepos_t first_mtc_timestamp;
+ bool did_reset_tc_format;
+ Timecode::TimecodeFormat saved_tc_format;
+ Glib::Threads::Mutex reset_lock;
+ uint32_t reset_pending;
+ bool reset_position;
+ int transport_direction;
+ int busy_guard1;
+ int busy_guard2;
+
+ double speedup_due_to_tc_mismatch;
+ double quarter_frame_duration;
+ Timecode::TimecodeFormat mtc_timecode;
+ Timecode::TimecodeFormat a3e_timecode;
+ Timecode::Time timecode;
+ bool printed_timecode_warning;
+
+ void reset (bool with_pos);
+ void queue_reset (bool with_pos);
+ void maybe_reset ();
+
+ void update_mtc_qtr (MIDI::Parser&, int, samplepos_t);
+ void update_mtc_time (const MIDI::byte *, bool, samplepos_t);
+ void update_mtc_status (MIDI::MTC_Status);
+ void read_current (SafeTime *) const;
+ void reset_window (samplepos_t);
+ bool outside_window (samplepos_t) const;
+ void init_mtc_dll(samplepos_t, double);
+ void parse_timecode_offset();
+ void parameter_changed(std::string const & p);
+};
+
+class LIBARDOUR_API LTC_TransportMaster : public TimecodeTransportMaster {
+public:
+ LTC_TransportMaster (std::string const &);
+ ~LTC_TransportMaster ();
+
+ void set_session (Session*);
+
+ void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
+ bool speed_and_position (double&, samplepos_t&, samplepos_t);
+
+ bool locked() const;
+ bool ok() const;
+
+ samplecnt_t resolution () const;
+ bool requires_seekahead () const { return false; }
+ samplecnt_t seekahead_distance () const { return 0; }
+ void init ();
+
+ Timecode::TimecodeFormat apparent_timecode_format() const;
+ std::string position_string() const;
+ std::string delta_string() const;
+
+ private:
+ void parse_ltc(const pframes_t, const Sample* const, const samplecnt_t);
+ void process_ltc(samplepos_t const);
+ void init_dll (samplepos_t, int32_t);
+ bool detect_discontinuity(LTCFrameExt *, int, bool);
+ bool detect_ltc_fps(int, bool);
+ bool equal_ltc_sample_time(LTCFrame *a, LTCFrame *b);
+ void reset (bool with_ts = true);
+ void resync_xrun();
+ void resync_latency();
+ void parse_timecode_offset();
+ void parameter_changed(std::string const & p);
+
+ bool did_reset_tc_format;
+ Timecode::TimecodeFormat saved_tc_format;
+
+ LTCDecoder * decoder;
+ double samples_per_ltc_frame;
+ Timecode::Time timecode;
+ LTCFrameExt prev_sample;
+ bool fps_detected;
+
+ samplecnt_t monotonic_cnt;
+ samplecnt_t last_timestamp;
+ samplecnt_t last_ltc_sample;
+ double ltc_speed;
+ int delayedlocked;
+
+ int ltc_detect_fps_cnt;
+ int ltc_detect_fps_max;
+ bool printed_timecode_warning;
+ bool sync_lock_broken;
+ Timecode::TimecodeFormat ltc_timecode;
+ Timecode::TimecodeFormat a3e_timecode;
+ double samples_per_timecode_frame;
+
+ PBD::ScopedConnectionList port_connections;
+ PBD::ScopedConnection config_connection;
+ LatencyRange ltc_slave_latency;
+};
+
+class LIBARDOUR_API MIDIClock_TransportMaster : public TransportMaster, public TransportMasterViaMIDI {
+ public:
+ MIDIClock_TransportMaster (std::string const & name, int ppqn = 24);
+
+ /// Constructor for unit tests
+ ~MIDIClock_TransportMaster ();
+
+ void set_session (Session*);
+
+ void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
+
+ void rebind (MidiPort&);
+ bool speed_and_position (double&, samplepos_t&, samplepos_t);
+
+ bool locked() const;
+ bool ok() const;
+ bool starting() const;
+
+ samplecnt_t resolution () const;
+ bool requires_seekahead () const { return false; }
+ void init ();
+
+ std::string position_string() const;
+ std::string delta_string() const;
+
+ float bpm() const { return _bpm; }
+
+ protected:
+ PBD::ScopedConnectionList port_connections;
+
+ /// pulses per quarter note for one MIDI clock sample (default 24)
+ int ppqn;
+
+ /// the duration of one ppqn in sample time
+ double one_ppqn_in_samples;
+
+ /// the timestamp of the first MIDI clock message
+ samplepos_t first_timestamp;
+
+ /// the time stamp and should-be transport position of the last inbound MIDI clock message
+ samplepos_t last_timestamp;
+ double should_be_position;
+
+ /// the number of midi clock messages received (zero-based)
+ /// since start
+ long midi_clock_count;
+
+ /// a DLL to track MIDI clock
+
+ double _speed;
+ bool _running;
+ double _bpm;
+
+ void reset ();
+ void start (MIDI::Parser& parser, samplepos_t timestamp);
+ void contineu (MIDI::Parser& parser, samplepos_t timestamp);
+ void stop (MIDI::Parser& parser, samplepos_t timestamp);
+ void position (MIDI::Parser& parser, MIDI::byte* message, size_t size);
+ // we can't use continue because it is a C++ keyword
+ void calculate_one_ppqn_in_samples_at(samplepos_t time);
+ samplepos_t calculate_song_position(uint16_t song_position_in_sixteenth_notes);
+ void calculate_filter_coefficients (double qpm);
+ void update_midi_clock (MIDI::Parser& parser, samplepos_t timestamp);
+ void read_current (SafeTime *) const;
+};
+
+class LIBARDOUR_API Engine_TransportMaster : public TransportMaster
+{
+ public:
+ Engine_TransportMaster (AudioEngine&);
+ ~Engine_TransportMaster ();
+
+ void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
+ bool speed_and_position (double& speed, samplepos_t& pos, samplepos_t);
+
+ bool starting() const { return _starting; }
+ bool locked() const;
+ bool ok() const;
+ samplecnt_t resolution () const { return 1; }
+ bool requires_seekahead () const { return false; }
+ bool sample_clock_synced() const { return true; }
+ void init ();
+ void check_backend();
+ bool allow_request (TransportRequestSource, TransportRequestType) const;
+
+ std::string position_string() const;
+ std::string delta_string() const;
+
+ private:
+ AudioEngine& engine;
+ bool _starting;
+};
+
+} /* namespace */
+
+#endif /* __ardour_transport_master_h__ */
diff --git a/libs/ardour/ardour/transport_master_manager.h b/libs/ardour/ardour/transport_master_manager.h
new file mode 100644
index 0000000000..4c4159cb6f
--- /dev/null
+++ b/libs/ardour/ardour/transport_master_manager.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__ */
diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h
index 366e303186..635b78334b 100644
--- a/libs/ardour/ardour/types.h
+++ b/libs/ardour/ardour/types.h
@@ -53,696 +53,712 @@ typedef int intptr_t;
namespace ARDOUR {
- class Source;
- class AudioSource;
- class Route;
- class Region;
- class Stripable;
- class VCA;
- class AutomationControl;
- class SlavableAutomationControl;
-
- typedef float Sample;
- typedef float pan_t;
- typedef float gain_t;
- typedef uint32_t layer_t;
- typedef uint64_t microseconds_t;
- typedef uint32_t pframes_t;
-
- /* rebind Temporal position types into ARDOUR namespace */
- typedef Temporal::samplecnt_t samplecnt_t;
- typedef Temporal::samplepos_t samplepos_t;
- typedef Temporal::sampleoffset_t sampleoffset_t;
-
- static const layer_t max_layer = UINT32_MAX;
-
- // a set of (time) intervals: first of pair is the offset of the start within the region, second is the offset of the end
- typedef std::list<std::pair<sampleoffset_t, sampleoffset_t> > AudioIntervalResult;
- // associate a set of intervals with regions (e.g. for silence detection)
- typedef std::map<boost::shared_ptr<ARDOUR::Region>,AudioIntervalResult> AudioIntervalMap;
-
- typedef std::list<boost::shared_ptr<Region> > RegionList;
-
- struct IOChange {
-
- enum Type {
- NoChange = 0,
- ConfigurationChanged = 0x1,
- ConnectionsChanged = 0x2
- } type;
-
- IOChange () : type (NoChange) {}
- IOChange (Type t) : type (t) {}
-
- /** channel count of IO before a ConfigurationChanged, if appropriate */
- ARDOUR::ChanCount before;
- /** channel count of IO after a ConfigurationChanged, if appropriate */
- ARDOUR::ChanCount after;
- };
-
- /* policies for inserting/pasting material where overlaps
- might be an issue.
- */
-
- enum InsertMergePolicy {
- InsertMergeReject, ///< no overlaps allowed
- InsertMergeRelax, ///< we just don't care about overlaps
- InsertMergeReplace, ///< replace old with new
- InsertMergeTruncateExisting, ///< shorten existing to avoid overlap
- InsertMergeTruncateAddition, ///< shorten new to avoid overlap
- InsertMergeExtend ///< extend new (or old) to the range of old+new
- };
-
- /** See evoral/Parameter.hpp
- *
- * When you add things here, you REALLY SHOULD add a case clause to
- * the constructor of ParameterDescriptor, unless the Controllables
- * that the enum refers to are completely standard (0-1.0 range, 0.0 as
- * normal, non-toggled, non-enumerated). Anything else needs to be
- * added there so that things that try to represent them can do so
- * with as much information as possible.
- */
- enum AutomationType {
- NullAutomation,
- GainAutomation,
- PanAzimuthAutomation,
- PanElevationAutomation,
- PanWidthAutomation,
- PanFrontBackAutomation,
- PanLFEAutomation,
- PluginAutomation,
- PluginPropertyAutomation,
- SoloAutomation,
- SoloIsolateAutomation,
- SoloSafeAutomation,
- MuteAutomation,
- MidiCCAutomation,
- MidiPgmChangeAutomation,
- MidiPitchBenderAutomation,
- MidiChannelPressureAutomation,
- MidiNotePressureAutomation,
- MidiSystemExclusiveAutomation,
- FadeInAutomation,
- FadeOutAutomation,
- EnvelopeAutomation,
- RecEnableAutomation,
- RecSafeAutomation,
- TrimAutomation,
- PhaseAutomation,
- MonitoringAutomation,
- BusSendLevel,
- BusSendEnable,
-
- /* used only by Controllable Descriptor to access send parameters */
-
- SendLevelAutomation,
- SendEnableAutomation,
- SendAzimuthAutomation,
- };
-
- enum AutoState {
- Off = 0x00,
- Write = 0x01,
- Touch = 0x02,
- Play = 0x04,
- Latch = 0x08
- };
-
- std::string auto_state_to_string (AutoState);
- AutoState string_to_auto_state (std::string);
-
- enum AlignStyle {
- CaptureTime,
- ExistingMaterial
- };
-
- enum AlignChoice {
- UseCaptureTime,
- UseExistingMaterial,
- Automatic
- };
-
- enum MeterPoint {
- MeterInput,
- MeterPreFader,
- MeterPostFader,
- MeterOutput,
- MeterCustom
- };
-
- enum DiskIOPoint {
- DiskIOPreFader, /* after the trim control, but before other processors */
- DiskIOPostFader, /* before the main outs, after other processors */
- DiskIOCustom, /* up to the user. Caveat Emptor! */
- };
-
- enum MeterType {
- MeterMaxSignal = 0x0001,
- MeterMaxPeak = 0x0002,
- MeterPeak = 0x0004,
- MeterKrms = 0x0008,
- MeterK20 = 0x0010,
- MeterK14 = 0x0020,
- MeterIEC1DIN = 0x0040,
- MeterIEC1NOR = 0x0080,
- MeterIEC2BBC = 0x0100,
- MeterIEC2EBU = 0x0200,
- MeterVU = 0x0400,
- MeterK12 = 0x0800,
- MeterPeak0dB = 0x1000,
- MeterMCP = 0x2000
- };
-
- enum TrackMode {
- Normal,
- NonLayered,
- Destructive
- };
-
- enum NoteMode {
- Sustained,
- Percussive
- };
-
- enum ChannelMode {
- AllChannels = 0, ///< Pass through all channel information unmodified
- FilterChannels, ///< Ignore events on certain channels
- ForceChannel ///< Force all events to a certain channel
- };
-
- enum ColorMode {
- MeterColors = 0,
- ChannelColors,
- TrackColor
- };
-
- enum RoundMode {
- RoundDownMaybe = -2, ///< Round down only if necessary
- RoundDownAlways = -1, ///< Always round down, even if on a division
- RoundNearest = 0, ///< Round to nearest
- RoundUpAlways = 1, ///< Always round up, even if on a division
- RoundUpMaybe = 2 ///< Round up only if necessary
- };
-
- enum SnapPref {
- SnapToAny_Visual = 0, /**< Snap to the editor's visual snap
- * (incoprorating snap prefs and the current zoom scaling)
- * this defines the behavior for visual mouse drags, for example */
-
- SnapToGrid_Scaled = 1, /**< Snap to the selected grid quantization with visual scaling.
- * Ignores other snap preferences (markers, regions, etc)
- * this defines the behavior for nudging the playhead to next/prev grid, for example */
-
- SnapToGrid_Unscaled = 2, /**< Snap to the selected grid quantization.
- * If one is selected, and ignore any visual scaling
- * this is the behavior for automated processes like "snap regions to grid"
- * but note that midi quantization uses its own mechanism, not the grid */
- };
-
- class AnyTime {
- public:
- enum Type {
- Timecode,
- BBT,
- Samples,
- Seconds
- };
-
- Type type;
-
- Timecode::Time timecode;
- Timecode::BBT_Time bbt;
-
- union {
- samplecnt_t samples;
- double seconds;
- };
-
- AnyTime() { type = Samples; samples = 0; }
-
- bool operator== (AnyTime const & other) const {
- if (type != other.type) { return false; }
-
- switch (type) {
- case Timecode:
- return timecode == other.timecode;
- case BBT:
- return bbt == other.bbt;
- case Samples:
- return samples == other.samples;
- case Seconds:
- return seconds == other.seconds;
- }
- return false; // get rid of warning
- }
-
- bool not_zero() const
- {
- switch (type) {
- case Timecode:
- return timecode.hours != 0 || timecode.minutes != 0 ||
- timecode.seconds != 0 || timecode.frames != 0;
- case BBT:
- return bbt.bars != 0 || bbt.beats != 0 || bbt.ticks != 0;
- case Samples:
- return samples != 0;
- case Seconds:
- return seconds != 0;
- }
-
- abort(); /* NOTREACHED */
- return false;
- }
- };
-
- /* used for translating audio samples to an exact musical position using a note divisor.
- an exact musical position almost never falls exactly on an audio sample, but for sub-sample
- musical accuracy we need to derive exact musical locations from a sample position
- the division follows TempoMap::exact_beat_at_sample().
- division
- -1 musical location is the bar closest to sample
- 0 musical location is the musical position of the sample
- 1 musical location is the BBT beat closest to sample
- n musical location is the quarter-note division n closest to sample
- */
- struct MusicSample {
- samplepos_t sample;
- int32_t division;
-
- MusicSample (samplepos_t f, int32_t d) : sample (f), division (d) {}
-
- void set (samplepos_t f, int32_t d) {sample = f; division = d; }
-
- MusicSample operator- (MusicSample other) { return MusicSample (sample - other.sample, 0); }
- };
-
- /* XXX: slightly unfortunate that there is this and Evoral::Range<>,
- but this has a uint32_t id which Evoral::Range<> does not.
- */
- struct AudioRange {
- samplepos_t start;
- samplepos_t end;
- uint32_t id;
-
- AudioRange (samplepos_t s, samplepos_t e, uint32_t i) : start (s), end (e) , id (i) {}
-
- samplecnt_t length() const { return end - start + 1; }
-
- bool operator== (const AudioRange& other) const {
- return start == other.start && end == other.end && id == other.id;
- }
-
- bool equal (const AudioRange& other) const {
- return start == other.start && end == other.end;
- }
+class Source;
+class AudioSource;
+class Route;
+class Region;
+class Stripable;
+class VCA;
+class AutomationControl;
+class SlavableAutomationControl;
+
+typedef float Sample;
+typedef float pan_t;
+typedef float gain_t;
+typedef uint32_t layer_t;
+typedef uint64_t microseconds_t;
+typedef uint32_t pframes_t;
+
+/* rebind Temporal position types into ARDOUR namespace */
+typedef Temporal::samplecnt_t samplecnt_t;
+typedef Temporal::samplepos_t samplepos_t;
+typedef Temporal::sampleoffset_t sampleoffset_t;
+
+static const layer_t max_layer = UINT32_MAX;
+
+// a set of (time) intervals: first of pair is the offset of the start within the region, second is the offset of the end
+typedef std::list<std::pair<sampleoffset_t, sampleoffset_t> > AudioIntervalResult;
+// associate a set of intervals with regions (e.g. for silence detection)
+typedef std::map<boost::shared_ptr<ARDOUR::Region>,AudioIntervalResult> AudioIntervalMap;
+
+typedef std::list<boost::shared_ptr<Region> > RegionList;
+
+struct IOChange {
+
+ enum Type {
+ NoChange = 0,
+ ConfigurationChanged = 0x1,
+ ConnectionsChanged = 0x2
+ } type;
+
+ IOChange () : type (NoChange) {}
+ IOChange (Type t) : type (t) {}
+
+ /** channel count of IO before a ConfigurationChanged, if appropriate */
+ ARDOUR::ChanCount before;
+ /** channel count of IO after a ConfigurationChanged, if appropriate */
+ ARDOUR::ChanCount after;
+};
+
+/* policies for inserting/pasting material where overlaps
+ might be an issue.
+*/
- Evoral::OverlapType coverage (samplepos_t s, samplepos_t e) const {
- return Evoral::coverage (start, end, s, e);
+enum InsertMergePolicy {
+ InsertMergeReject, ///< no overlaps allowed
+ InsertMergeRelax, ///< we just don't care about overlaps
+ InsertMergeReplace, ///< replace old with new
+ InsertMergeTruncateExisting, ///< shorten existing to avoid overlap
+ InsertMergeTruncateAddition, ///< shorten new to avoid overlap
+ InsertMergeExtend ///< extend new (or old) to the range of old+new
+};
+
+/** See evoral/Parameter.hpp
+ *
+ * When you add things here, you REALLY SHOULD add a case clause to
+ * the constructor of ParameterDescriptor, unless the Controllables
+ * that the enum refers to are completely standard (0-1.0 range, 0.0 as
+ * normal, non-toggled, non-enumerated). Anything else needs to be
+ * added there so that things that try to represent them can do so
+ * with as much information as possible.
+ */
+enum AutomationType {
+ NullAutomation,
+ GainAutomation,
+ PanAzimuthAutomation,
+ PanElevationAutomation,
+ PanWidthAutomation,
+ PanFrontBackAutomation,
+ PanLFEAutomation,
+ PluginAutomation,
+ PluginPropertyAutomation,
+ SoloAutomation,
+ SoloIsolateAutomation,
+ SoloSafeAutomation,
+ MuteAutomation,
+ MidiCCAutomation,
+ MidiPgmChangeAutomation,
+ MidiPitchBenderAutomation,
+ MidiChannelPressureAutomation,
+ MidiNotePressureAutomation,
+ MidiSystemExclusiveAutomation,
+ FadeInAutomation,
+ FadeOutAutomation,
+ EnvelopeAutomation,
+ RecEnableAutomation,
+ RecSafeAutomation,
+ TrimAutomation,
+ PhaseAutomation,
+ MonitoringAutomation,
+ BusSendLevel,
+ BusSendEnable,
+
+ /* used only by Controllable Descriptor to access send parameters */
+
+ SendLevelAutomation,
+ SendEnableAutomation,
+ SendAzimuthAutomation,
+};
+
+enum AutoState {
+ Off = 0x00,
+ Write = 0x01,
+ Touch = 0x02,
+ Play = 0x04,
+ Latch = 0x08
+};
+
+std::string auto_state_to_string (AutoState);
+AutoState string_to_auto_state (std::string);
+
+enum AlignStyle {
+ CaptureTime,
+ ExistingMaterial
+};
+
+enum AlignChoice {
+ UseCaptureTime,
+ UseExistingMaterial,
+ Automatic
+};
+
+enum MeterPoint {
+ MeterInput,
+ MeterPreFader,
+ MeterPostFader,
+ MeterOutput,
+ MeterCustom
+};
+
+enum DiskIOPoint {
+ DiskIOPreFader, /* after the trim control, but before other processors */
+ DiskIOPostFader, /* before the main outs, after other processors */
+ DiskIOCustom, /* up to the user. Caveat Emptor! */
+};
+
+enum MeterType {
+ MeterMaxSignal = 0x0001,
+ MeterMaxPeak = 0x0002,
+ MeterPeak = 0x0004,
+ MeterKrms = 0x0008,
+ MeterK20 = 0x0010,
+ MeterK14 = 0x0020,
+ MeterIEC1DIN = 0x0040,
+ MeterIEC1NOR = 0x0080,
+ MeterIEC2BBC = 0x0100,
+ MeterIEC2EBU = 0x0200,
+ MeterVU = 0x0400,
+ MeterK12 = 0x0800,
+ MeterPeak0dB = 0x1000,
+ MeterMCP = 0x2000
+};
+
+enum TrackMode {
+ Normal,
+ NonLayered,
+ Destructive
+};
+
+enum NoteMode {
+ Sustained,
+ Percussive
+};
+
+enum ChannelMode {
+ AllChannels = 0, ///< Pass through all channel information unmodified
+ FilterChannels, ///< Ignore events on certain channels
+ ForceChannel ///< Force all events to a certain channel
+};
+
+enum ColorMode {
+ MeterColors = 0,
+ ChannelColors,
+ TrackColor
+};
+
+enum RoundMode {
+ RoundDownMaybe = -2, ///< Round down only if necessary
+ RoundDownAlways = -1, ///< Always round down, even if on a division
+ RoundNearest = 0, ///< Round to nearest
+ RoundUpAlways = 1, ///< Always round up, even if on a division
+ RoundUpMaybe = 2 ///< Round up only if necessary
+};
+
+enum SnapPref {
+ SnapToAny_Visual = 0, /**< Snap to the editor's visual snap
+ * (incoprorating snap prefs and the current zoom scaling)
+ * this defines the behavior for visual mouse drags, for example */
+
+ SnapToGrid_Scaled = 1, /**< Snap to the selected grid quantization with visual scaling.
+ * Ignores other snap preferences (markers, regions, etc)
+ * this defines the behavior for nudging the playhead to next/prev grid, for example */
+
+ SnapToGrid_Unscaled = 2, /**< Snap to the selected grid quantization.
+ * If one is selected, and ignore any visual scaling
+ * this is the behavior for automated processes like "snap regions to grid"
+ * but note that midi quantization uses its own mechanism, not the grid */
+};
+
+class AnyTime {
+ public:
+ enum Type {
+ Timecode,
+ BBT,
+ Samples,
+ Seconds
+ };
+
+ Type type;
+
+ Timecode::Time timecode;
+ Timecode::BBT_Time bbt;
+
+ union {
+ samplecnt_t samples;
+ double seconds;
+ };
+
+ AnyTime() { type = Samples; samples = 0; }
+
+ bool operator== (AnyTime const & other) const {
+ if (type != other.type) { return false; }
+
+ switch (type) {
+ case Timecode:
+ return timecode == other.timecode;
+ case BBT:
+ return bbt == other.bbt;
+ case Samples:
+ return samples == other.samples;
+ case Seconds:
+ return seconds == other.seconds;
}
- };
-
- struct MusicRange {
- Timecode::BBT_Time start;
- Timecode::BBT_Time end;
- uint32_t id;
-
- MusicRange (Timecode::BBT_Time& s, Timecode::BBT_Time& e, uint32_t i)
- : start (s), end (e), id (i) {}
-
- bool operator== (const MusicRange& other) const {
- return start == other.start && end == other.end && id == other.id;
+ return false; // get rid of warning
+ }
+
+ bool not_zero() const
+ {
+ switch (type) {
+ case Timecode:
+ return timecode.hours != 0 || timecode.minutes != 0 ||
+ timecode.seconds != 0 || timecode.frames != 0;
+ case BBT:
+ return bbt.bars != 0 || bbt.beats != 0 || bbt.ticks != 0;
+ case Samples:
+ return samples != 0;
+ case Seconds:
+ return seconds != 0;
}
- bool equal (const MusicRange& other) const {
- return start == other.start && end == other.end;
- }
- };
-
- /*
- Slowest = 6.6dB/sec falloff at update rate of 40ms
- Slow = 6.8dB/sec falloff at update rate of 40ms
- */
-
- enum MeterFalloff {
- MeterFalloffOff = 0,
- MeterFalloffSlowest = 1,
- MeterFalloffSlow = 2,
- MeterFalloffSlowish = 3,
- MeterFalloffModerate = 4,
- MeterFalloffMedium = 5,
- MeterFalloffFast = 6,
- MeterFalloffFaster = 7,
- MeterFalloffFastest = 8,
- };
-
- enum MeterHold {
- MeterHoldOff = 0,
- MeterHoldShort = 40,
- MeterHoldMedium = 100,
- MeterHoldLong = 200
- };
-
- enum EditMode {
- Slide,
- Splice,
- Ripple,
- Lock
- };
-
- enum RegionSelectionAfterSplit {
- None = 0,
- NewlyCreatedLeft = 1, // bit 0
- NewlyCreatedRight = 2, // bit 1
- NewlyCreatedBoth = 3,
- Existing = 4, // bit 2
- ExistingNewlyCreatedLeft = 5,
- ExistingNewlyCreatedRight = 6,
- ExistingNewlyCreatedBoth = 7
- };
-
- enum RegionPoint {
- Start,
- End,
- SyncPoint
- };
-
- enum Placement {
- PreFader,
- PostFader
- };
-
- enum MonitorModel {
- HardwareMonitoring, ///< JACK does monitoring
- SoftwareMonitoring, ///< Ardour does monitoring
- ExternalMonitoring ///< we leave monitoring to the audio hardware
- };
-
- enum MonitorChoice {
- MonitorAuto = 0,
- MonitorInput = 0x1,
- MonitorDisk = 0x2,
- MonitorCue = 0x3,
- };
-
- enum MonitorState {
- MonitoringSilence = 0x1,
- MonitoringInput = 0x2,
- MonitoringDisk = 0x4,
- MonitoringCue = 0x6,
- };
-
- enum MeterState {
- MeteringInput, ///< meter the input IO, regardless of what is going through the route
- MeteringRoute ///< meter what is going through the route
- };
-
- enum VUMeterStandard {
- MeteringVUfrench, // 0VU = -2dBu
- MeteringVUamerican, // 0VU = 0dBu
- MeteringVUstandard, // 0VU = +4dBu
- MeteringVUeight // 0VU = +8dBu
- };
-
- enum MeterLineUp {
- MeteringLineUp24,
- MeteringLineUp20,
- MeteringLineUp18,
- MeteringLineUp15
- };
-
- enum PFLPosition {
- /** PFL signals come from before pre-fader processors */
- PFLFromBeforeProcessors,
- /** PFL signals come pre-fader but after pre-fader processors */
- PFLFromAfterProcessors
- };
-
- enum AFLPosition {
- /** AFL signals come post-fader and before post-fader processors */
- AFLFromBeforeProcessors,
- /** AFL signals come post-fader but after post-fader processors */
- AFLFromAfterProcessors
- };
-
- enum ClockDeltaMode {
- NoDelta,
- DeltaEditPoint,
- DeltaOriginMarker
- };
-
- enum DenormalModel {
- DenormalNone,
- DenormalFTZ,
- DenormalDAZ,
- DenormalFTZDAZ
- };
-
- enum LayerModel {
- LaterHigher,
- Manual
- };
-
- enum ListenPosition {
- AfterFaderListen,
- PreFaderListen
- };
-
- enum AutoConnectOption {
- ManualConnect = 0x0,
- AutoConnectPhysical = 0x1,
- AutoConnectMaster = 0x2
- };
-
- enum TracksAutoNamingRule {
- UseDefaultNames = 0x1,
- NameAfterDriver = 0x2
- };
-
- enum SampleFormat {
- FormatFloat = 0,
- FormatInt24,
- FormatInt16
- };
-
- int format_data_width (ARDOUR::SampleFormat);
-
- enum CDMarkerFormat {
- CDMarkerNone,
- CDMarkerCUE,
- CDMarkerTOC,
- MP4Chaps
- };
-
- enum HeaderFormat {
- BWF,
- WAVE,
- WAVE64,
- CAF,
- AIFF,
- iXML,
- RF64,
- RF64_WAV,
- MBWF,
- };
+ abort(); /* NOTREACHED */
+ return false;
+ }
+};
+
+/* used for translating audio samples to an exact musical position using a note divisor.
+ an exact musical position almost never falls exactly on an audio sample, but for sub-sample
+ musical accuracy we need to derive exact musical locations from a sample position
+ the division follows TempoMap::exact_beat_at_sample().
+ division
+ -1 musical location is the bar closest to sample
+ 0 musical location is the musical position of the sample
+ 1 musical location is the BBT beat closest to sample
+ n musical location is the quarter-note division n closest to sample
+*/
+struct MusicSample {
+ samplepos_t sample;
+ int32_t division;
- struct PeakData {
- typedef Sample PeakDatum;
+ MusicSample (samplepos_t f, int32_t d) : sample (f), division (d) {}
- PeakDatum min;
- PeakDatum max;
- };
+ void set (samplepos_t f, int32_t d) {sample = f; division = d; }
- enum RunContext {
- ButlerContext = 0,
- TransportContext,
- ExportContext
- };
+ MusicSample operator- (MusicSample other) { return MusicSample (sample - other.sample, 0); }
+};
- enum SyncSource {
- /* These are "synonyms". It is important for JACK to be first
- both here and in enums.cc, so that the string "JACK" is
- correctly recognized in older session and preference files.
- */
- JACK = 0,
- Engine = 0,
- MTC,
- MIDIClock,
- LTC
- };
-
- enum ShuttleBehaviour {
- Sprung,
- Wheel
- };
-
- enum ShuttleUnits {
- Percentage,
- Semitones
- };
+/* XXX: slightly unfortunate that there is this and Evoral::Range<>,
+ but this has a uint32_t id which Evoral::Range<> does not.
+*/
+struct AudioRange {
+ samplepos_t start;
+ samplepos_t end;
+ uint32_t id;
- typedef std::vector<boost::shared_ptr<Source> > SourceList;
+ AudioRange (samplepos_t s, samplepos_t e, uint32_t i) : start (s), end (e) , id (i) {}
- enum SrcQuality {
- SrcBest,
- SrcGood,
- SrcQuick,
- SrcFast,
- SrcFastest
- };
+ samplecnt_t length() const { return end - start + 1; }
- typedef std::list<samplepos_t> AnalysisFeatureList;
+ bool operator== (const AudioRange& other) const {
+ return start == other.start && end == other.end && id == other.id;
+ }
- typedef std::list<boost::shared_ptr<Route> > RouteList;
- typedef std::list<boost::shared_ptr<Stripable> > StripableList;
- typedef std::list<boost::weak_ptr <Route> > WeakRouteList;
- typedef std::list<boost::weak_ptr <Stripable> > WeakStripableList;
- typedef std::list<boost::shared_ptr<AutomationControl> > ControlList;
- typedef std::list<boost::shared_ptr<SlavableAutomationControl> > SlavableControlList;
- typedef std::set <boost::shared_ptr<AutomationControl> > AutomationControlSet;
+ bool equal (const AudioRange& other) const {
+ return start == other.start && end == other.end;
+ }
- typedef std::list<boost::shared_ptr<VCA> > VCAList;
+ Evoral::OverlapType coverage (samplepos_t s, samplepos_t e) const {
+ return Evoral::coverage (start, end, s, e);
+ }
+};
- class Bundle;
- typedef std::vector<boost::shared_ptr<Bundle> > BundleList;
+struct MusicRange {
+ Timecode::BBT_Time start;
+ Timecode::BBT_Time end;
+ uint32_t id;
- enum RegionEquivalence {
- Exact,
- Enclosed,
- Overlap
- };
-
- enum WaveformScale {
- Linear,
- Logarithmic
- };
+ MusicRange (Timecode::BBT_Time& s, Timecode::BBT_Time& e, uint32_t i)
+ : start (s), end (e), id (i) {}
- enum WaveformShape {
- Traditional,
- Rectified
- };
+ bool operator== (const MusicRange& other) const {
+ return start == other.start && end == other.end && id == other.id;
+ }
- struct CleanupReport {
- std::vector<std::string> paths;
- size_t space;
- };
+ bool equal (const MusicRange& other) const {
+ return start == other.start && end == other.end;
+ }
+};
- enum PositionLockStyle {
- AudioTime,
- MusicTime
- };
+/*
+ Slowest = 6.6dB/sec falloff at update rate of 40ms
+ Slow = 6.8dB/sec falloff at update rate of 40ms
+*/
- /** A struct used to describe changes to processors in a route.
- * This is useful because objects that respond to a change in processors
- * can optimise what work they do based on details of what has changed.
+enum MeterFalloff {
+ MeterFalloffOff = 0,
+ MeterFalloffSlowest = 1,
+ MeterFalloffSlow = 2,
+ MeterFalloffSlowish = 3,
+ MeterFalloffModerate = 4,
+ MeterFalloffMedium = 5,
+ MeterFalloffFast = 6,
+ MeterFalloffFaster = 7,
+ MeterFalloffFastest = 8,
+};
+
+enum MeterHold {
+ MeterHoldOff = 0,
+ MeterHoldShort = 40,
+ MeterHoldMedium = 100,
+ MeterHoldLong = 200
+};
+
+enum EditMode {
+ Slide,
+ Splice,
+ Ripple,
+ Lock
+};
+
+enum RegionSelectionAfterSplit {
+ None = 0,
+ NewlyCreatedLeft = 1, // bit 0
+ NewlyCreatedRight = 2, // bit 1
+ NewlyCreatedBoth = 3,
+ Existing = 4, // bit 2
+ ExistingNewlyCreatedLeft = 5,
+ ExistingNewlyCreatedRight = 6,
+ ExistingNewlyCreatedBoth = 7
+};
+
+enum RegionPoint {
+ Start,
+ End,
+ SyncPoint
+};
+
+enum Placement {
+ PreFader,
+ PostFader
+};
+
+enum MonitorModel {
+ HardwareMonitoring, ///< JACK does monitoring
+ SoftwareMonitoring, ///< Ardour does monitoring
+ ExternalMonitoring ///< we leave monitoring to the audio hardware
+};
+
+enum MonitorChoice {
+ MonitorAuto = 0,
+ MonitorInput = 0x1,
+ MonitorDisk = 0x2,
+ MonitorCue = 0x3,
+};
+
+enum MonitorState {
+ MonitoringSilence = 0x1,
+ MonitoringInput = 0x2,
+ MonitoringDisk = 0x4,
+ MonitoringCue = 0x6,
+};
+
+enum MeterState {
+ MeteringInput, ///< meter the input IO, regardless of what is going through the route
+ MeteringRoute ///< meter what is going through the route
+};
+
+enum VUMeterStandard {
+ MeteringVUfrench, // 0VU = -2dBu
+ MeteringVUamerican, // 0VU = 0dBu
+ MeteringVUstandard, // 0VU = +4dBu
+ MeteringVUeight // 0VU = +8dBu
+};
+
+enum MeterLineUp {
+ MeteringLineUp24,
+ MeteringLineUp20,
+ MeteringLineUp18,
+ MeteringLineUp15
+};
+
+enum PFLPosition {
+ /** PFL signals come from before pre-fader processors */
+ PFLFromBeforeProcessors,
+ /** PFL signals come pre-fader but after pre-fader processors */
+ PFLFromAfterProcessors
+};
+
+enum AFLPosition {
+ /** AFL signals come post-fader and before post-fader processors */
+ AFLFromBeforeProcessors,
+ /** AFL signals come post-fader but after post-fader processors */
+ AFLFromAfterProcessors
+};
+
+enum ClockDeltaMode {
+ NoDelta,
+ DeltaEditPoint,
+ DeltaOriginMarker
+};
+
+enum DenormalModel {
+ DenormalNone,
+ DenormalFTZ,
+ DenormalDAZ,
+ DenormalFTZDAZ
+};
+
+enum LayerModel {
+ LaterHigher,
+ Manual
+};
+
+enum ListenPosition {
+ AfterFaderListen,
+ PreFaderListen
+};
+
+enum AutoConnectOption {
+ ManualConnect = 0x0,
+ AutoConnectPhysical = 0x1,
+ AutoConnectMaster = 0x2
+};
+
+enum TracksAutoNamingRule {
+ UseDefaultNames = 0x1,
+ NameAfterDriver = 0x2
+};
+
+enum SampleFormat {
+ FormatFloat = 0,
+ FormatInt24,
+ FormatInt16
+};
+
+int format_data_width (ARDOUR::SampleFormat);
+
+enum CDMarkerFormat {
+ CDMarkerNone,
+ CDMarkerCUE,
+ CDMarkerTOC,
+ MP4Chaps
+};
+
+enum HeaderFormat {
+ BWF,
+ WAVE,
+ WAVE64,
+ CAF,
+ AIFF,
+ iXML,
+ RF64,
+ RF64_WAV,
+ MBWF,
+};
+
+struct PeakData {
+ typedef Sample PeakDatum;
+
+ PeakDatum min;
+ PeakDatum max;
+};
+
+enum RunContext {
+ ButlerContext = 0,
+ TransportContext,
+ ExportContext
+};
+
+enum SyncSource {
+ /* The first two are "synonyms". It is important for JACK to be first
+ both here and in enums.cc, so that the string "JACK" is
+ correctly recognized in older session and preference files.
*/
- struct RouteProcessorChange {
- enum Type {
- GeneralChange = 0x0,
- MeterPointChange = 0x1,
- RealTimeChange = 0x2
- };
-
- RouteProcessorChange () : type (GeneralChange), meter_visibly_changed (true)
- {}
-
- RouteProcessorChange (Type t) : type (t), meter_visibly_changed (true)
- {}
-
- RouteProcessorChange (Type t, bool m) : type (t), meter_visibly_changed (m)
- {}
-
- /** type of change; "GeneralChange" means anything could have changed */
- Type type;
- /** true if, when a MeterPointChange has occurred, the change is visible to the user */
- bool meter_visibly_changed;
- };
-
- struct BusProfile {
- uint32_t master_out_channels; /* how many channels for the master bus, 0: no master bus */
- };
-
- enum FadeShape {
- FadeLinear,
- FadeFast,
- FadeSlow,
- FadeConstantPower,
- FadeSymmetric,
- };
-
- enum TransportState {
- /* these values happen to match the constants used by JACK but
- this equality cannot be assumed.
- */
- TransportStopped = 0,
- TransportRolling = 1,
- TransportLooping = 2,
- TransportStarting = 3,
- };
-
- enum PortFlags {
- /* these values happen to match the constants used by JACK but
- this equality cannot be assumed.
- */
- IsInput = 0x1,
- IsOutput = 0x2,
- IsPhysical = 0x4,
- CanMonitor = 0x8,
- IsTerminal = 0x10,
-
- /* non-JACK related flags */
- Hidden = 0x20,
- Shadow = 0x40
- };
-
- enum MidiPortFlags {
- MidiPortMusic = 0x1,
- MidiPortControl = 0x2,
- MidiPortSelection = 0x4,
- MidiPortVirtual = 0x8
- };
-
- struct LatencyRange {
- uint32_t min; //< samples
- uint32_t max; //< samples
- };
-
- enum BufferingPreset {
- Small,
- Medium,
- Large,
- Custom,
- };
-
- enum AutoReturnTarget {
- LastLocate = 0x1,
- RangeSelectionStart = 0x2,
- Loop = 0x4,
- RegionSelectionStart = 0x8,
- };
-
- enum PlaylistDisposition {
- CopyPlaylist,
- NewPlaylist,
- SharePlaylist
- };
-
- enum MidiTrackNameSource {
- SMFTrackNumber,
- SMFTrackName,
- SMFInstrumentName
- };
-
- enum MidiTempoMapDisposition {
- SMFTempoIgnore,
- SMFTempoUse,
- };
-
- struct CaptureInfo {
- samplepos_t start;
- samplecnt_t samples;
- };
-
- typedef std::vector<CaptureInfo*> CaptureInfos;
+ JACK = 0,
+ Engine = 0,
+ MTC,
+ MIDIClock,
+ LTC,
+};
+
+enum TransportRequestSource {
+ TRS_Engine,
+ TRS_MTC,
+ TRS_MIDIClock,
+ TRS_LTC,
+ TRS_MMC,
+ TRS_UI,
+};
+
+enum TransportRequestType {
+ TR_Stop = 0x1,
+ TR_Start = 0x2,
+ TR_Speed = 0x4,
+ TR_Locate = 0x8
+};
+
+enum ShuttleBehaviour {
+ Sprung,
+ Wheel
+};
+
+enum ShuttleUnits {
+ Percentage,
+ Semitones
+};
+
+typedef std::vector<boost::shared_ptr<Source> > SourceList;
+
+enum SrcQuality {
+ SrcBest,
+ SrcGood,
+ SrcQuick,
+ SrcFast,
+ SrcFastest
+};
+
+typedef std::list<samplepos_t> AnalysisFeatureList;
+
+typedef std::list<boost::shared_ptr<Route> > RouteList;
+typedef std::list<boost::shared_ptr<Stripable> > StripableList;
+typedef std::list<boost::weak_ptr <Route> > WeakRouteList;
+typedef std::list<boost::weak_ptr <Stripable> > WeakStripableList;
+typedef std::list<boost::shared_ptr<AutomationControl> > ControlList;
+typedef std::list<boost::shared_ptr<SlavableAutomationControl> > SlavableControlList;
+typedef std::set <boost::shared_ptr<AutomationControl> > AutomationControlSet;
+
+typedef std::list<boost::shared_ptr<VCA> > VCAList;
+
+class Bundle;
+typedef std::vector<boost::shared_ptr<Bundle> > BundleList;
+
+enum RegionEquivalence {
+ Exact,
+ Enclosed,
+ Overlap
+};
+
+enum WaveformScale {
+ Linear,
+ Logarithmic
+};
+
+enum WaveformShape {
+ Traditional,
+ Rectified
+};
+
+struct CleanupReport {
+ std::vector<std::string> paths;
+ size_t space;
+};
+
+enum PositionLockStyle {
+ AudioTime,
+ MusicTime
+};
+
+/** A struct used to describe changes to processors in a route.
+ * This is useful because objects that respond to a change in processors
+ * can optimise what work they do based on details of what has changed.
+ */
+struct RouteProcessorChange {
+ enum Type {
+ GeneralChange = 0x0,
+ MeterPointChange = 0x1,
+ RealTimeChange = 0x2
+ };
+
+ RouteProcessorChange () : type (GeneralChange), meter_visibly_changed (true)
+ {}
+
+ RouteProcessorChange (Type t) : type (t), meter_visibly_changed (true)
+ {}
+
+ RouteProcessorChange (Type t, bool m) : type (t), meter_visibly_changed (m)
+ {}
+
+ /** type of change; "GeneralChange" means anything could have changed */
+ Type type;
+ /** true if, when a MeterPointChange has occurred, the change is visible to the user */
+ bool meter_visibly_changed;
+};
+
+struct BusProfile {
+ uint32_t master_out_channels; /* how many channels for the master bus, 0: no master bus */
+};
+
+enum FadeShape {
+ FadeLinear,
+ FadeFast,
+ FadeSlow,
+ FadeConstantPower,
+ FadeSymmetric,
+};
+
+enum TransportState {
+ /* these values happen to match the constants used by JACK but
+ this equality cannot be assumed.
+ */
+ TransportStopped = 0,
+ TransportRolling = 1,
+ TransportLooping = 2,
+ TransportStarting = 3,
+};
+
+enum PortFlags {
+ /* these values happen to match the constants used by JACK but
+ this equality cannot be assumed.
+ */
+ IsInput = 0x1,
+ IsOutput = 0x2,
+ IsPhysical = 0x4,
+ CanMonitor = 0x8,
+ IsTerminal = 0x10,
+
+ /* non-JACK related flags */
+ Hidden = 0x20,
+ Shadow = 0x40
+};
+
+enum MidiPortFlags {
+ MidiPortMusic = 0x1,
+ MidiPortControl = 0x2,
+ MidiPortSelection = 0x4,
+ MidiPortVirtual = 0x8
+};
+
+struct LatencyRange {
+ uint32_t min; //< samples
+ uint32_t max; //< samples
+};
+
+enum BufferingPreset {
+ Small,
+ Medium,
+ Large,
+ Custom,
+};
+
+enum AutoReturnTarget {
+ LastLocate = 0x1,
+ RangeSelectionStart = 0x2,
+ Loop = 0x4,
+ RegionSelectionStart = 0x8,
+};
+
+enum PlaylistDisposition {
+ CopyPlaylist,
+ NewPlaylist,
+ SharePlaylist
+};
+
+enum MidiTrackNameSource {
+ SMFTrackNumber,
+ SMFTrackName,
+ SMFInstrumentName
+};
+
+enum MidiTempoMapDisposition {
+ SMFTempoIgnore,
+ SMFTempoUse,
+};
+
+struct CaptureInfo {
+ samplepos_t start;
+ samplecnt_t samples;
+};
+
+typedef std::vector<CaptureInfo*> CaptureInfos;
} // namespace ARDOUR
diff --git a/libs/ardour/audio_port.cc b/libs/ardour/audio_port.cc
index 44e0298f1d..1a6c30dcf2 100644
--- a/libs/ardour/audio_port.cc
+++ b/libs/ardour/audio_port.cc
@@ -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());
}
diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc
index 83919bbe6a..91f58d44ad 100644
--- a/libs/ardour/audioengine.cc
+++ b/libs/ardour/audioengine.cc
@@ -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 */
diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc
index a578bd40ff..3eced3063b 100644
--- a/libs/ardour/debug.cc
+++ b/libs/ardour/debug.cc
@@ -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");
diff --git a/libs/ardour/disk_io.cc b/libs/ardour/disk_io.cc
index decacc850f..427f364404 100644
--- a/libs/ardour/disk_io.cc
+++ b/libs/ardour/disk_io.cc
@@ -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)
{
diff --git a/libs/ardour/engine_slave.cc b/libs/ardour/engine_slave.cc
index 7ac767c3e8..1031370557 100644
--- a/libs/ardour/engine_slave.cc
+++ b/libs/ardour/engine_slave.cc
@@ -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)
+{
+ check_backend ();
+}
+
+Engine_TransportMaster::~Engine_TransportMaster ()
{
- double x;
- samplepos_t p;
- /* call this to initialize things */
- speed_and_position (x, p);
}
-Engine_Slave::~Engine_Slave ()
+void
+Engine_TransportMaster::init ()
{
}
+void
+Engine_TransportMaster::check_backend()
+{
+ if (AudioEngine::instance()->current_backend_name() == X_("JACK")) {
+ _connected = true;
+ } else {
+ _connected = false;
+ }
+}
+
bool
-Engine_Slave::locked() const
+Engine_TransportMaster::locked() const
{
return true;
}
bool
-Engine_Slave::ok() const
+Engine_TransportMaster::ok() const
{
return true;
}
+void
+Engine_TransportMaster::pre_process (pframes_t, samplepos_t, boost::optional<samplepos_t>)
+{
+ /* nothing to do */
+}
+
bool
-Engine_Slave::speed_and_position (double& sp, samplepos_t& position)
+Engine_TransportMaster::speed_and_position (double& sp, samplepos_t& position, samplepos_t /* now */)
{
boost::shared_ptr<AudioBackend> backend = engine.current_backend();
- if (backend) {
- _starting = backend->speed_and_position (sp, position);
- } else {
- _starting = false;
+ /* 3rd argument (now) doesn't matter here because we're always being
+ * called synchronously with the engine.
+ */
+
+ if (backend && backend->speed_and_position (sp, position)) {
+ return true;
+ }
+
+ _current_delta = 0;
+
+ return false;
+}
+
+std::string
+Engine_TransportMaster::position_string () const
+{
+ if (_session) {
+ return to_string (_session->audible_sample());
+ }
+
+ return std::string();
+}
+
+std::string
+Engine_TransportMaster::delta_string () const
+{
+ return string ("0");
+}
+
+bool
+Engine_TransportMaster::allow_request (TransportRequestSource src, TransportRequestType type) const
+{
+ if (_session) {
+ if (_session->config.get_jack_time_master()) {
+ return true;
+ } else {
+ return false;
+ }
}
return true;
diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc
index 1173a99b07..e90f07fe12 100644
--- a/libs/ardour/enums.cc
+++ b/libs/ardour/enums.cc
@@ -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);
diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc
index fb5be2d4ac..a806b693b4 100644
--- a/libs/ardour/globals.cc
+++ b/libs/ardour/globals.cc
@@ -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;
diff --git a/libs/ardour/ltc_slave.cc b/libs/ardour/ltc_slave.cc
index 9baa3276ef..b1b4f71734 100644
--- a/libs/ardour/ltc_slave.cc
+++ b/libs/ardour/ltc_slave.cc
@@ -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() )
-
-LTC_Slave::LTC_Slave (Session& s)
- : session (s)
+#define ENGINE AudioEngine::instance()
+#define FLYWHEEL_TIMEOUT ( 1 * ENGINE->sample_rate() )
+
+/* XXX USE Config->get_ltc_input */
+
+LTC_TransportMaster::LTC_TransportMaster (std::string const & name)
+ : TimecodeTransportMaster (name, LTC)
+ , did_reset_tc_format (false)
+ , decoder (0)
+ , samples_per_ltc_frame (0)
+ , fps_detected (false)
+ , monotonic_cnt (0)
+ , last_timestamp (0)
+ , last_ltc_sample (0)
+ , delayedlocked (10)
+ , ltc_detect_fps_cnt (0)
+ , ltc_detect_fps_max (0)
+ , sync_lock_broken (false)
{
- samples_per_ltc_frame = session.samples_per_timecode_frame();
- timecode.rate = session.timecode_frames_per_second();
- timecode.drop = session.timecode_drop_frames();
+ if ((_port = AudioEngine::instance()->register_input_port (DataType::AUDIO, string_compose ("%1 in", _name))) == 0) {
+ throw failed_constructor();
+ }
- did_reset_tc_format = false;
- delayedlocked = 10;
- monotonic_cnt = 0;
- fps_detected=false;
- sync_lock_broken = false;
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("LTC registered %1\n", _port->name()));
- ltc_timecode = session.config.get_timecode_format();
- a3e_timecode = session.config.get_timecode_format();
- printed_timecode_warning = false;
- ltc_detect_fps_cnt = ltc_detect_fps_max = 0;
memset(&prev_sample, 0, sizeof(LTCFrameExt));
- decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);
-
- session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&LTC_Slave::parameter_changed, this, _1));
- parse_timecode_offset();
- reset();
resync_latency();
- session.Xrun.connect_same_thread (port_connections, boost::bind (&LTC_Slave::resync_xrun, this));
- session.engine().GraphReordered.connect_same_thread (port_connections, boost::bind (&LTC_Slave::resync_latency, this));
+
+ AudioEngine::instance()->Xrun.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_xrun, this));
+ AudioEngine::instance()->GraphReordered.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_latency, this));
+}
+
+void
+LTC_TransportMaster::init ()
+{
+ reset (true);
+}
+
+void
+LTC_TransportMaster::set_session (Session *s)
+{
+ config_connection.disconnect ();
+ _session = s;
+
+ if (_session) {
+
+ samples_per_ltc_frame = _session->samples_per_timecode_frame();
+ timecode.rate = _session->timecode_frames_per_second();
+ timecode.drop = _session->timecode_drop_frames();
+ printed_timecode_warning = false;
+ ltc_timecode = _session->config.get_timecode_format();
+ a3e_timecode = _session->config.get_timecode_format();
+
+ if (Config->get_use_session_timecode_format() && _session) {
+ samples_per_timecode_frame = _session->samples_per_timecode_frame();
+ }
+
+ if (decoder) {
+ ltc_decoder_free (decoder);
+ }
+
+ decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);
+
+ parse_timecode_offset();
+ reset();
+
+ _session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&LTC_TransportMaster::parameter_changed, this, _1));
+ }
}
-LTC_Slave::~LTC_Slave()
+LTC_TransportMaster::~LTC_TransportMaster()
{
port_connections.drop_connections();
config_connection.disconnect();
if (did_reset_tc_format) {
- session.config.set_timecode_format (saved_tc_format);
+ _session->config.set_timecode_format (saved_tc_format);
}
ltc_decoder_free(decoder);
}
void
-LTC_Slave::parse_timecode_offset() {
+LTC_TransportMaster::parse_timecode_offset() {
Timecode::Time offset_tc;
- Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc);
- offset_tc.rate = session.timecode_frames_per_second();
- offset_tc.drop = session.timecode_drop_frames();
- session.timecode_to_sample(offset_tc, timecode_offset, false, false);
+ Timecode::parse_timecode_format(_session->config.get_slave_timecode_offset(), offset_tc);
+ offset_tc.rate = _session->timecode_frames_per_second();
+ offset_tc.drop = _session->timecode_drop_frames();
+ _session->timecode_to_sample(offset_tc, timecode_offset, false, false);
timecode_negative_offset = offset_tc.negative;
}
void
-LTC_Slave::parameter_changed (std::string const & p)
+LTC_TransportMaster::parameter_changed (std::string const & p)
{
if (p == "slave-timecode-offset"
|| p == "timecode-format"
@@ -104,65 +145,61 @@ LTC_Slave::parameter_changed (std::string const & p)
}
ARDOUR::samplecnt_t
-LTC_Slave::resolution () const
+LTC_TransportMaster::resolution () const
{
- return (samplecnt_t) (session.sample_rate() / 1000);
+ return (samplecnt_t) (ENGINE->sample_rate() / 1000);
}
bool
-LTC_Slave::locked () const
+LTC_TransportMaster::locked () const
{
return (delayedlocked < 5);
}
bool
-LTC_Slave::ok() const
+LTC_TransportMaster::ok() const
{
return true;
}
void
-LTC_Slave::resync_xrun()
+LTC_TransportMaster::resync_xrun()
{
DEBUG_TRACE (DEBUG::LTC, "LTC resync_xrun()\n");
- engine_dll_initstate = 0;
sync_lock_broken = false;
}
void
-LTC_Slave::resync_latency()
+LTC_TransportMaster::resync_latency()
{
DEBUG_TRACE (DEBUG::LTC, "LTC resync_latency()\n");
- engine_dll_initstate = 0;
sync_lock_broken = false;
- if (!session.deletion_in_progress() && session.ltc_output_io()) { /* check if Port exits */
- boost::shared_ptr<Port> ltcport = session.ltc_input_port();
- ltcport->get_connected_latency_range (ltc_slave_latency, false);
+ if (!_port) {
+ _port->get_connected_latency_range (ltc_slave_latency, false);
}
}
void
-LTC_Slave::reset (bool with_ts)
+LTC_TransportMaster::reset (bool with_ts)
{
DEBUG_TRACE (DEBUG::LTC, "LTC reset()\n");
if (with_ts) {
last_timestamp = 0;
- current_delta = 0;
+ _current_delta = 0;
}
transport_direction = 0;
ltc_speed = 0;
- engine_dll_initstate = 0;
sync_lock_broken = false;
-
- ActiveChanged (false); /* EMIT SIGNAL */
+ monotonic_cnt = 0;
}
void
-LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
+LTC_TransportMaster::parse_ltc (const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
{
pframes_t i;
unsigned char sound[8192];
+
if (nframes > 8192) {
/* TODO warn once or wrap, loop conversion below
* does jack/A3 support > 8192 spp anyway?
@@ -171,32 +208,33 @@ LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, co
}
for (i = 0; i < nframes; i++) {
- const int snd=(int)rint((127.0*in[i])+128.0);
+ const int snd=(int) rint ((127.0*in[i])+128.0);
sound[i] = (unsigned char) (snd&0xff);
}
- ltc_decoder_write(decoder, sound, nframes, posinfo);
+
+ ltc_decoder_write (decoder, sound, nframes, posinfo);
+
return;
}
bool
-LTC_Slave::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
- if ( a->frame_units != b->frame_units
- || a->frame_tens != b->frame_tens
- || a->dfbit != b->dfbit
- || a->secs_units != b->secs_units
- || a->secs_tens != b->secs_tens
- || a->mins_units != b->mins_units
- || a->mins_tens != b->mins_tens
- || a->hours_units != b->hours_units
- || a->hours_tens != b->hours_tens
- ) {
+LTC_TransportMaster::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
+ if (a->frame_units != b->frame_units ||
+ a->frame_tens != b->frame_tens ||
+ a->dfbit != b->dfbit ||
+ a->secs_units != b->secs_units ||
+ a->secs_tens != b->secs_tens ||
+ a->mins_units != b->mins_units ||
+ a->mins_tens != b->mins_tens ||
+ a->hours_units != b->hours_units ||
+ a->hours_tens != b->hours_tens) {
return false;
}
return true;
}
bool
-LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
+LTC_TransportMaster::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
bool discontinuity_detected = false;
if (fuzzy && (
@@ -221,7 +259,7 @@ LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
}
bool
-LTC_Slave::detect_ltc_fps(int frameno, bool df)
+LTC_TransportMaster::detect_ltc_fps(int frameno, bool df)
{
bool fps_changed = false;
double detected_fps = 0;
@@ -236,7 +274,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
detected_fps = ltc_detect_fps_max + 1;
if (df) {
/* LTC df -> indicates fractional framerate */
- if (Config->get_timecode_source_2997()) {
+ if (fr2997()) {
detected_fps = detected_fps * 999.0 / 1000.0;
} else {
detected_fps = detected_fps * 1000.0 / 1001.0;
@@ -256,7 +294,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
if (detected_fps != 0 && (detected_fps != timecode.rate || df != timecode.drop)) {
timecode.rate = detected_fps;
timecode.drop = df;
- samples_per_ltc_frame = double(session.sample_rate()) / timecode.rate;
+ samples_per_ltc_frame = double(_session->sample_rate()) / timecode.rate;
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC reset to FPS: %1%2 ; audio-samples per LTC: %3\n",
detected_fps, df?"df":"ndf", samples_per_ltc_frame));
fps_changed=true;
@@ -264,7 +302,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
/* poll and check session TC */
TimecodeFormat tc_format = apparent_timecode_format();
- TimecodeFormat cur_timecode = session.config.get_timecode_format();
+ TimecodeFormat cur_timecode = _session->config.get_timecode_format();
if (Config->get_timecode_sync_frame_rate()) {
/* enforce time-code */
@@ -279,7 +317,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
Timecode::timecode_format_name(tc_format))
<< endmsg;
}
- session.config.set_timecode_format (tc_format);
+ _session->config.set_timecode_format (tc_format);
}
} else {
/* only warn about TC mismatch */
@@ -299,45 +337,63 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
ltc_timecode = tc_format;
a3e_timecode = cur_timecode;
+ if (Config->get_use_session_timecode_format() && _session) {
+ samples_per_timecode_frame = _session->samples_per_timecode_frame();
+ } else {
+ samples_per_timecode_frame = ENGINE->sample_rate() / Timecode::timecode_to_frames_per_second (ltc_timecode);
+ }
+
return fps_changed;
}
void
-LTC_Slave::process_ltc(samplepos_t const /*now*/)
+LTC_TransportMaster::process_ltc(samplepos_t const now)
{
LTCFrameExt sample;
- enum LTC_TV_STANDARD tv_standard = LTC_TV_625_50;
- while (ltc_decoder_read(decoder, &sample)) {
+ LTC_TV_STANDARD tv_standard = LTC_TV_625_50;
+
+ while (ltc_decoder_read (decoder, &sample)) {
+
SMPTETimecode stime;
- ltc_frame_to_time(&stime, &sample.ltc, 0);
+ ltc_frame_to_time (&stime, &sample.ltc, 0);
timecode.negative = false;
timecode.subframes = 0;
/* set timecode.rate and timecode.drop: */
- bool ltc_is_static = equal_ltc_sample_time(&prev_sample.ltc, &sample.ltc);
- if (detect_discontinuity(&sample, ceil(timecode.rate), !fps_detected)) {
- if (fps_detected) { ltc_detect_fps_cnt = ltc_detect_fps_max = 0; }
- fps_detected=false;
+ const bool ltc_is_stationary = equal_ltc_sample_time (&prev_sample.ltc, &sample.ltc);
+
+ if (detect_discontinuity (&sample, ceil(timecode.rate), !fps_detected)) {
+
+ if (fps_detected) {
+ ltc_detect_fps_cnt = ltc_detect_fps_max = 0;
+ }
+
+ fps_detected = false;
}
- if (!ltc_is_static && detect_ltc_fps(stime.frame, (sample.ltc.dfbit)? true : false)) {
+ if (!ltc_is_stationary && detect_ltc_fps (stime.frame, (sample.ltc.dfbit)? true : false)) {
reset();
fps_detected=true;
}
-#if 0 // Devel/Debug
- fprintf(stdout, "LTC %02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
- stime.hours,
- stime.mins,
- stime.secs,
- (sample.ltc.dfbit) ? '.' : ':',
- stime.frame,
- sample.off_start,
- sample.off_end,
- sample.reverse ? " R" : " "
- );
+#ifndef NDEBUG
+ if (DEBUG_ENABLED (DEBUG::LTC)) {
+ /* use fprintf for simpler correct formatting of times
+ */
+ fprintf (stderr, "LTC@%ld %02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
+ now,
+ stime.hours,
+ stime.mins,
+ stime.secs,
+ (sample.ltc.dfbit) ? '.' : ':',
+ stime.frame,
+ sample.off_start,
+ sample.off_end,
+ sample.reverse ? " R" : " "
+ );
+ }
#endif
/* when a full LTC sample is decoded, the timecode the LTC sample
@@ -367,13 +423,13 @@ LTC_Slave::process_ltc(samplepos_t const /*now*/)
ltc_frame_increment(&sample.ltc, fps_i, tv_standard, 0);
ltc_frame_to_time(&stime, &sample.ltc, 0);
transport_direction = 1;
- sample.off_start -= ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
- sample.off_end -= ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
+ sample.off_start -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
+ sample.off_end -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
} else {
ltc_frame_decrement(&sample.ltc, fps_i, tv_standard, 0);
int off = sample.off_end - sample.off_start;
- sample.off_start += off - ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
- sample.off_end += off - ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
+ sample.off_start += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
+ sample.off_end += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
transport_direction = -1;
}
@@ -382,236 +438,194 @@ LTC_Slave::process_ltc(samplepos_t const /*now*/)
timecode.seconds = stime.secs;
timecode.frames = stime.frame;
- /* map LTC timecode to session TC setting */
- samplepos_t ltc_frame; ///< audio-sample corresponding to LTC sample
- Timecode::timecode_to_sample (timecode, ltc_frame, true, false,
- double(session.sample_rate()),
- session.config.get_subframes_per_frame(),
- timecode_negative_offset, timecode_offset
- );
+ samplepos_t ltc_sample; // audio-sample corresponding to position of LTC frame
+
+ if (_session && Config->get_use_session_timecode_format()) {
+ Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), _session->config.get_subframes_per_frame(), timecode_negative_offset, timecode_offset);
+ } else {
+ Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), 100, timecode_negative_offset, timecode_offset);
+ }
- ltc_frame += ltc_slave_latency.max;
+ ltc_sample += ltc_slave_latency.max;
+
+ /* This LTC frame spans sample time between sample.off_start .. sample.off_end
+ *
+ * NOTE: these sample times are NOT the ones that LTC is representing. They are
+ * derived our own audioengine's monotonic audio clock.
+ *
+ * So we expect the next frame to span sample.off_end+1 and ... <don't care for now>.
+ * That isn't the time we will necessarily receive the LTC frame, but the decoder
+ * should tell us that its span begins there.
+ *
+ */
samplepos_t cur_timestamp = sample.off_end + 1;
- DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC F: %1 LF: %2 N: %3 L: %4\n", ltc_frame, last_ltc_sample, cur_timestamp, last_timestamp));
- if (sample.off_end + 1 <= last_timestamp || last_timestamp == 0) {
+
+ DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC S: %1 LS: %2 N: %3 L: %4\n", ltc_sample, last_ltc_sample, cur_timestamp, last_timestamp));
+
+ if (cur_timestamp <= last_timestamp || last_timestamp == 0) {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: UNCHANGED: %1\n", ltc_speed));
} else {
- ltc_speed = double(ltc_frame - last_ltc_sample) / double(cur_timestamp - last_timestamp);
+ ltc_speed = double (ltc_sample - last_ltc_sample) / double (cur_timestamp - last_timestamp);
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: %1\n", ltc_speed));
}
- if (fabs(ltc_speed) > 10.0) {
+ if (fabs (ltc_speed) > 10.0) {
ltc_speed = 0;
}
- last_timestamp = sample.off_end + 1;
- last_ltc_sample = ltc_frame;
- } /* end foreach decoded LTC sample */
-}
+ last_timestamp = cur_timestamp;
+ last_ltc_sample = ltc_sample;
-void
-LTC_Slave::init_engine_dll (samplepos_t pos, int32_t inc)
-{
- double omega = 2.0 * M_PI * double(inc) / double(session.sample_rate());
- b = 1.4142135623730950488 * omega;
- c = omega * omega;
-
- e2 = double(ltc_speed * inc);
- t0 = double(pos);
- t1 = t0 + e2;
- DEBUG_TRACE (DEBUG::LTC, string_compose ("[re-]init Engine DLL %1 %2 %3\n", t0, t1, e2));
+ } /* end foreach decoded LTC sample */
}
-/* main entry point from session_process.cc
- * called from process callback context
- * so it is OK to use get_buffer()
- */
bool
-LTC_Slave::speed_and_position (double& speed, samplepos_t& pos)
+LTC_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
{
- bool engine_init_called = false;
- samplepos_t now = session.engine().sample_time_at_cycle_start();
- samplepos_t sess_pos = session.transport_sample(); // corresponds to now
- samplecnt_t nframes = session.engine().samples_per_cycle();
+ if (!_collect || last_timestamp == 0) {
+ return false;
+ }
- Sample* in;
+ /* XXX these are not atomics and maybe modified in a thread other other than the one
+ that is executing this.
+ */
- boost::shared_ptr<Port> ltcport = session.ltc_input_port();
+ speed = ltc_speed;
- in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (ltcport->port_handle(), nframes);
+ /* provide a .1% deadzone to lock the speed */
+ if (fabs (speed - 1.0) <= 0.001) {
+ speed = 1.0;
+ }
+ if (speed != 0 && delayedlocked == 0 && fabs(speed) != 1.0) {
+ sync_lock_broken = true;
+ DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 based on %2\n", speed, ltc_speed));
+ }
+
+ pos = last_ltc_sample;
+ pos += (now - last_timestamp) * speed;
+
+ return true;
+}
+
+void
+LTC_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
+{
+ Sample* in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (_port->port_handle(), nframes);
sampleoffset_t skip = now - (monotonic_cnt + nframes);
monotonic_cnt = now;
- DEBUG_TRACE (DEBUG::LTC, string_compose ("speed_and_position - TID:%1 | latency: %2 | skip %3\n", pthread_name(), ltc_slave_latency.max, skip));
+
+ DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process - TID:%1 | latency: %2 | skip %3 | session ? %4| last %5 | dir %6 | sp %7\n",
+ pthread_name(), ltc_slave_latency.max, skip, (_session ? 'y' : 'n'), last_timestamp, transport_direction, ltc_speed));
if (last_timestamp == 0) {
- engine_dll_initstate = 0;
- if (delayedlocked < 10) ++delayedlocked;
- } else if (engine_dll_initstate != transport_direction && ltc_speed != 0) {
+ if (delayedlocked < 10) {
+ ++delayedlocked;
+ }
- ActiveChanged (true); /* EMIT SIGNAL */
+ } else if (ltc_speed != 0) {
- engine_dll_initstate = transport_direction;
- init_engine_dll(last_ltc_sample + rint(ltc_speed * double(2 * nframes + now - last_timestamp)),
- session.engine().samples_per_cycle());
- engine_init_called = true;
}
- if (in) {
- DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC Process eng-tme: %1 eng-pos: %2\n", now, sess_pos));
- /* when the jack-graph changes and if ardour performs
- * locates, the audioengine is stopped (skipping samples) while
- * jack [time] moves along.
- */
- if (skip > 0) {
- DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples. Feeding silence to LTC parser.\n", skip));
- if (skip >= 8192) skip = 8192;
- unsigned char sound[8192];
- memset(sound, 0x80, sizeof(char) * skip);
- ltc_decoder_write(decoder, sound, nframes, now);
- } else if (skip != 0) {
- /* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
- DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
- reset();
- }
-
- parse_ltc(nframes, in, now);
- process_ltc(now);
+ DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process with audio clock time: %1\n", now));
+
+ /* if the audioengine failed to take the process lock, it won't
+ call this method, and time will appear to skip. Reset the
+ LTC decoder's state by giving it some silence.
+ */
+
+ if (skip > 0) {
+ DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples. Feeding silence to LTC parser.\n", skip));
+ if (skip >= 8192) skip = 8192;
+ unsigned char sound[8192];
+ memset (sound, 0x80, sizeof(char) * skip);
+ ltc_decoder_write (decoder, sound, nframes, now);
+ } else if (skip != 0) {
+ /* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
+ DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
+ reset();
}
+ /* Now feed the incoming LTC signal into the decoder */
+
+ parse_ltc (nframes, in, now);
+
+ /* and pull out actual LTC frame data */
+
+ process_ltc (now);
+
if (last_timestamp == 0) {
DEBUG_TRACE (DEBUG::LTC, "last timestamp == 0\n");
- speed = 0;
- pos = session.transport_sample();
- return true;
+ return;
} else if (ltc_speed != 0) {
- if (delayedlocked > 1) delayedlocked--;
- else if (current_delta == 0) delayedlocked = 0;
+ DEBUG_TRACE (DEBUG::LTC, string_compose ("speed non-zero (%1)\n", ltc_speed));
+ if (delayedlocked > 1) {
+ delayedlocked--;
+ } else if (_current_delta == 0) {
+ delayedlocked = 0;
+ }
}
- if (abs(now - last_timestamp) > FLYWHEEL_TIMEOUT) {
+ if (abs (now - last_timestamp) > FLYWHEEL_TIMEOUT) {
DEBUG_TRACE (DEBUG::LTC, "flywheel timeout\n");
reset();
- speed = 0;
- pos = session.transport_sample();
- ActiveChanged (false); /* EMIT SIGNAL */
- return true;
- }
+ /* don't change position from last known */
- /* it take 2 cycles from naught to rolling.
- * during these to initial cycles the speed == 0
- *
- * the first cycle:
- * DEBUG::Slave: slave stopped, move to NNN
- * DEBUG::Transport: Request forced locate to NNN
- * DEBUG::Slave: slave state 0 @ NNN speed 0 cur delta VERY-LARGE-DELTA avg delta 1800
- * DEBUG::Slave: silent motion
- * DEBUG::Transport: realtime stop @ NNN
- * DEBUG::Transport: Butler transport work, todo = PostTransportStop,PostTransportLocate,PostTransportClearSubstate
- *
- * [engine skips samples to locate, jack time keeps rolling on]
- *
- * the second cycle:
- *
- * DEBUG::LTC: [re-]init Engine DLL
- * DEBUG::Slave: slave stopped, move to NNN+
- * ...
- *
- * we need to seek two cycles ahead: 2 * nframes
- */
- if (engine_dll_initstate == 0) {
- DEBUG_TRACE (DEBUG::LTC, "engine DLL not initialized. ltc_speed\n");
- speed = 0;
- pos = last_ltc_sample + rint(ltc_speed * double(2 * nframes + now - last_timestamp));
- return true;
+ return;
}
- /* interpolate position according to speed and time since last LTC-sample*/
- double speed_flt = ltc_speed;
- double elapsed = (now - last_timestamp) * speed_flt;
-
- if (!engine_init_called) {
- const double e = elapsed + double (last_ltc_sample - sess_pos);
- t0 = t1;
- t1 += b * e + e2;
- e2 += c * e;
- speed_flt = (t1 - t0) / double(session.engine().samples_per_cycle());
- DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, speed_flt, e2 - session.engine().samples_per_cycle() ));
+ if (session_pos) {
+ const samplepos_t current_pos = last_ltc_sample + ((now - last_timestamp) * ltc_speed);
+ _current_delta = current_pos - *session_pos;
} else {
- DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC adjusting elapsed (no DLL) from %1 by %2\n", elapsed, (2 * nframes * ltc_speed)));
- speed_flt = 0;
- elapsed += 2.0 * nframes * ltc_speed; /* see note above */
- }
-
- pos = last_ltc_sample + rint(elapsed);
- speed = speed_flt;
- current_delta = (pos - sess_pos);
-
- if (((pos < 0) || (labs(current_delta) > 2 * session.sample_rate()))) {
- DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC large drift: %1\n", current_delta));
- reset();
- speed = 0;
- return true;
- }
-
- DEBUG_TRACE (DEBUG::LTC, string_compose ("LTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
- speed, pos, last_ltc_sample, elapsed, current_delta));
-
- /* provide a .1% deadzone to lock the speed */
- if (fabs(speed - 1.0) <= 0.001) {
- speed = 1.0;
- }
-
- if (speed != 0 && delayedlocked == 0 && fabs(speed) != 1.0) {
- sync_lock_broken = true;
- DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 %2\n", speed, ltc_speed));
+ _current_delta = 0;
}
-
- return true;
}
Timecode::TimecodeFormat
-LTC_Slave::apparent_timecode_format () const
+LTC_TransportMaster::apparent_timecode_format () const
{
if (timecode.rate == 24 && !timecode.drop)
return timecode_24;
else if (timecode.rate == 25 && !timecode.drop)
return timecode_25;
else if (rint(timecode.rate * 100) == 2997 && !timecode.drop)
- return (Config->get_timecode_source_2997() ? timecode_2997000 : timecode_2997);
+ return (fr2997() ? timecode_2997000 : timecode_2997);
else if (rint(timecode.rate * 100) == 2997 && timecode.drop)
- return (Config->get_timecode_source_2997() ? timecode_2997000drop : timecode_2997drop);
+ return (fr2997() ? timecode_2997000drop : timecode_2997drop);
else if (timecode.rate == 30 && timecode.drop)
return timecode_2997drop; // timecode_30drop; // LTC counting to 30 samples w/DF *means* 29.97 df
else if (timecode.rate == 30 && !timecode.drop)
return timecode_30;
/* XXX - unknown timecode format */
- return session.config.get_timecode_format();
+ return _session->config.get_timecode_format();
}
std::string
-LTC_Slave::approximate_current_position() const
+LTC_TransportMaster::position_string() const
{
- if (last_timestamp == 0) {
+ if (!_collect || last_timestamp == 0) {
return " --:--:--:--";
}
return Timecode::timecode_format_time(timecode);
}
std::string
-LTC_Slave::approximate_current_delta() const
+LTC_TransportMaster::delta_string() const
{
char delta[80];
- if (last_timestamp == 0 || engine_dll_initstate == 0) {
- snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
+
+ if (!_collect || last_timestamp == 0) {
+ snprintf (delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
} else if ((monotonic_cnt - last_timestamp) > 2 * samples_per_ltc_frame) {
- snprintf(delta, sizeof(delta), "%s", _("flywheel"));
+ snprintf (delta, sizeof(delta), "%s", _("flywheel"));
} else {
- snprintf(delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
+ snprintf (delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
sync_lock_broken ? "red" : "green",
- LEADINGZERO(::llabs(current_delta)), PLUSMINUS(-current_delta), ::llabs(current_delta));
+ LEADINGZERO(::llabs(_current_delta)), PLUSMINUS(-_current_delta), ::llabs(_current_delta));
}
- return std::string(delta);
+
+ return delta;
}
diff --git a/libs/ardour/midi_clock_slave.cc b/libs/ardour/midi_clock_slave.cc
index 273c397733..93ca9059ef 100644
--- a/libs/ardour/midi_clock_slave.cc
+++ b/libs/ardour/midi_clock_slave.cc
@@ -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
+#define ENGINE AudioEngine::instance()
+
+MIDIClock_TransportMaster::MIDIClock_TransportMaster (std::string const & name, int ppqn)
+ : TransportMaster (MIDIClock, name)
+ , ppqn (ppqn)
+ , last_timestamp (0)
+ , should_be_position (0)
+ , midi_clock_count (0)
+ , _speed (0)
+ , _running (false)
+ , _bpm (0)
{
- session = (ISlaveSessionProxy *) new SlaveSessionProxy(s);
- rebind (p);
- reset ();
+ if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
+ throw failed_constructor();
+ }
}
-MIDIClock_Slave::MIDIClock_Slave (ISlaveSessionProxy* session_proxy, int ppqn)
- : session(session_proxy)
- , ppqn (ppqn)
- , bandwidth (2.0 / 60.0) // 1 BpM = 1 / 60 Hz
+MIDIClock_TransportMaster::~MIDIClock_TransportMaster()
{
- reset ();
+ port_connections.drop_connections ();
}
-MIDIClock_Slave::~MIDIClock_Slave()
+void
+MIDIClock_TransportMaster::init ()
{
- delete session;
+ midi_clock_count = 0;
+ last_timestamp = 0;
}
void
-MIDIClock_Slave::rebind (MidiPort& port)
+MIDIClock_TransportMaster::set_session (Session *session)
{
- DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port.name()));
+ port_connections.drop_connections();
+ _session = session;
- port_connections.drop_connections ();
+ /* only connect to signals if we have a proxy, because otherwise we
+ * cannot interpet incoming data (no tempo map etc.)
+ */
- port.self_parser().timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::update_midi_clock, this, _1, _2));
- port.self_parser().start.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::start, this, _1, _2));
- port.self_parser().contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::contineu, this, _1, _2));
- port.self_parser().stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::stop, this, _1, _2));
- port.self_parser().position.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::position, this, _1, _2, 3));
+ if (_session) {
+ parser.timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::update_midi_clock, this, _1, _2));
+ parser.start.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::start, this, _1, _2));
+ parser.contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::contineu, this, _1, _2));
+ parser.stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::stop, this, _1, _2));
+ parser.position.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::position, this, _1, _2, 3));
+ reset ();
+ }
+}
+
+bool
+MIDIClock_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
+{
+ if (!_running || !_collect) {
+ return false;
+ }
+
+ if (fabs (_speed - 1.0) < 0.001) {
+ speed = 1.0;
+ } else {
+ speed = _speed;
+ }
+
+ pos = should_be_position;
+ pos += (now - last_timestamp) * _speed;
+
+ return true;
}
void
-MIDIClock_Slave::calculate_one_ppqn_in_samples_at(samplepos_t time)
+MIDIClock_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
{
- const double samples_per_quarter_note = session->tempo_map().samples_per_quarter_note_at (time, session->sample_rate());
+ /* Read and parse incoming MIDI */
+
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("preprocess with lt = %1 @ %2, running ? %3\n", last_timestamp, now, _running));
+
+ _midi_port->read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, parser, now);
+
+ /* no clock messages ever, or no clock messages for 1/4 second ? conclude that its stopped */
+
+ if (!last_timestamp || (now > last_timestamp && ((now - last_timestamp) > (ENGINE->sample_rate() / 4)))) {
+ _speed = 0.0;
+ _bpm = 0.0;
+ last_timestamp = 0;
+ _running = false;
+ _current_delta = 0;
+ midi_clock_count = 0;
+
+ DEBUG_TRACE (DEBUG::MidiClock, "No MIDI Clock messages received for some time, stopping!\n");
+ return;
+ }
+
+ if (!_running && midi_clock_count == 0 && session_pos) {
+ should_be_position = *session_pos;
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("set sbp to %1\n", should_be_position));
+ }
+
+ if (session_pos) {
+ const samplepos_t current_pos = should_be_position + ((now - last_timestamp) * _speed);
+ _current_delta = current_pos - *session_pos;
+ } else {
+ _current_delta = 0;
+ }
+
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("speed_and_position: speed %1 should-be %2 transport %3 \n", _speed, should_be_position, _session->transport_sample()));
+}
+
+void
+MIDIClock_TransportMaster::calculate_one_ppqn_in_samples_at(samplepos_t time)
+{
+ const double samples_per_quarter_note = _session->tempo_map().samples_per_quarter_note_at (time, ENGINE->sample_rate());
one_ppqn_in_samples = samples_per_quarter_note / double (ppqn);
// DEBUG_TRACE (DEBUG::MidiClock, string_compose ("at %1, one ppqn = %2\n", time, one_ppqn_in_samples));
}
ARDOUR::samplepos_t
-MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
+MIDIClock_TransportMaster::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
{
samplepos_t song_position_samples = 0;
for (uint16_t i = 1; i <= song_position_in_sixteenth_notes; ++i) {
@@ -102,81 +174,129 @@ MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_not
}
void
-MIDIClock_Slave::calculate_filter_coefficients()
+MIDIClock_TransportMaster::calculate_filter_coefficients (double qpm)
{
- // omega = 2 * PI * Bandwidth / MIDI clock sample frequency in Hz
- omega = 2.0 * M_PI * bandwidth * one_ppqn_in_samples / session->sample_rate();
- b = 1.4142135623730950488 * omega;
+ /* Paul says: I don't understand this computation of bandwidth
+ */
+
+ const double bandwidth = 2.0 / qpm;
+
+ /* Frequency of the clock messages is ENGINE->sample_rate() / * one_ppqn_in_samples, per second or in Hz */
+ const double freq = (double) ENGINE->sample_rate() / one_ppqn_in_samples;
+
+ const double omega = 2.0 * M_PI * bandwidth / freq;
+ b = 1.4142135623730950488 * omega; // sqrt (2.0) * omega
c = omega * omega;
+
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("DLL coefficients: bw:%1 omega:%2 b:%3 c:%4\n", bandwidth, omega, b, c));
}
void
-MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
+MIDIClock_TransportMaster::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
{
- // some pieces of hardware send MIDI Clock all the time
- if ( (!_starting) && (!_started) ) {
- return;
- }
-
- pframes_t cycle_offset = timestamp - session->sample_time_at_cycle_start();
+ samplepos_t elapsed_since_start = timestamp - first_timestamp;
+ double e = 0;
- calculate_one_ppqn_in_samples_at(should_be_position);
+ calculate_one_ppqn_in_samples_at (should_be_position);
- samplepos_t elapsed_since_start = timestamp - first_timestamp;
- double error = 0;
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("clock count %1, sbp %2\n", midi_clock_count, should_be_position));
- if (_starting || last_timestamp == 0) {
- midi_clock_count = 0;
+ if (midi_clock_count == 0) {
+ /* second 0xf8 message after start/reset has arrived */
first_timestamp = timestamp;
- elapsed_since_start = should_be_position;
+ last_timestamp = timestamp;
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("first clock message after start received @ %1\n", timestamp));
- // calculate filter coefficients
- calculate_filter_coefficients();
+ midi_clock_count++;
- // initialize DLL
- e2 = double(one_ppqn_in_samples) / double(session->sample_rate());
- t0 = double(elapsed_since_start) / double(session->sample_rate());
- t1 = t0 + e2;
+ should_be_position += one_ppqn_in_samples;
+
+ } else if (midi_clock_count == 1) {
+
+ /* second 0xf8 message has arrived. we can now estimate QPM
+ * (quarters per minute, and fully initialize the DLL
+ */
+
+ e = timestamp - last_timestamp;
+
+ const samplecnt_t samples_per_quarter = e * 24;
+ _bpm = (ENGINE->sample_rate() * 60.0) / samples_per_quarter;
+
+ calculate_filter_coefficients (_bpm);
+
+ /* finish DLL initialization */
+
+ t0 = timestamp;
+ e2 = e;
+ t1 = t0 + e2; /* timestamp we predict for the next 0xf8 clock message */
- // let ardour go after first MIDI Clock Event
- _starting = false;
- } else {
midi_clock_count++;
- should_be_position += one_ppqn_in_samples;
- calculate_filter_coefficients();
-
- // calculate loop error
- // we use session->transport_sample() instead of t1 here
- // because t1 is used to calculate the transport speed,
- // so the loop will compensate for accumulating rounding errors
- error = (double(should_be_position) - (double(session->transport_sample()) + double(cycle_offset)));
- e = error / double(session->sample_rate());
- current_delta = error;
-
- // update DLL
+ should_be_position += one_ppqn_in_samples;
+
+ } else {
+
+ /* 3rd or later MIDI clock message. We can now compute actual
+ * speed (and tempo) with the DLL
+ */
+
+ e = timestamp - t1; // error between actual time of arrival of clock message and our predicted time
t0 = t1;
t1 += b * e + e2;
e2 += c * e;
+
+ const double samples_per_quarter = (timestamp - last_timestamp) * 24.0;
+ const double instantaneous_bpm = (ENGINE->sample_rate() * 60.0) / samples_per_quarter;
+ const double lpf_coeff = 0.05;
+
+ const double predicted_clock_interval_in_samples = (t1 - t0);
+
+ /* _speed is relative to session tempo map */
+
+ _speed = predicted_clock_interval_in_samples / one_ppqn_in_samples;
+
+ /* _bpm (really, _qpm) is absolute */
+
+ /* detect substantial changes in apparent tempo (defined as a
+ * change of more than 20% of the current tempo.
+ */
+
+ if (fabs (instantaneous_bpm - _bpm) > (0.20 * _bpm)) {
+ _bpm = instantaneous_bpm;
+ } else {
+ _bpm += lpf_coeff * (instantaneous_bpm - _bpm);
+ }
+
+ calculate_filter_coefficients (_bpm);
+
+ // need at least two clock events to compute speed
+
+ if (!_running) {
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("start mclock running with speed = %1\n", (t1 - t0) / one_ppqn_in_samples));
+ _running = true;
+ }
+
+ midi_clock_count++;
+ should_be_position += one_ppqn_in_samples;
}
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("clock #%1 @ %2 should-be %3 transport %4 error %5 appspeed %6 "
- "read-delta %7 should-be delta %8 t1-t0 %9 t0 %10 t1 %11 framerate %12 engine %13\n",
+ "read-delta %7 should-be delta %8 t1-t0 %9 t0 %10 t1 %11 framerate %12 engine %13 running %14\n",
midi_clock_count, // #
elapsed_since_start, // @
should_be_position, // should-be
- session->transport_sample(), // transport
- error, // error
- ((t1 - t0) * session->sample_rate()) / one_ppqn_in_samples, // appspeed
+ _session->transport_sample(), // transport
+ e, // error
+ (t1 - t0) / one_ppqn_in_samples, // appspeed
timestamp - last_timestamp, // read delta
one_ppqn_in_samples, // should-be delta
- (t1 - t0) * session->sample_rate(), // t1-t0
- t0 * session->sample_rate(), // t0
- t1 * session->sample_rate(), // t1
- session->sample_rate(), // framerate
- session->sample_time()
+ (t1 - t0), // t1-t0
+ t0, // t0
+ t1, // t1
+ ENGINE->sample_rate(), // framerate
+ ENGINE->sample_time(),
+ _running
));
@@ -184,57 +304,47 @@ MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
}
void
-MIDIClock_Slave::start (Parser& /*parser*/, samplepos_t timestamp)
+MIDIClock_TransportMaster::start (Parser& /*parser*/, samplepos_t timestamp)
{
- DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave got start message at time %1 engine time %2 transport_sample %3\n", timestamp, session->sample_time(), session->transport_sample()));
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_TransportMaster got start message at time %1 engine time %2 transport_sample %3\n", timestamp, ENGINE->sample_time(), _session->transport_sample()));
- if (!_started) {
+ if (!_running) {
reset();
-
- _started = true;
- _starting = true;
-
- should_be_position = session->transport_sample();
+ _running = true;
+ should_be_position = _session->transport_sample();
}
}
void
-MIDIClock_Slave::reset ()
+MIDIClock_TransportMaster::reset ()
{
- DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MidiClock_Slave reset(): calculated filter bandwidth is %1 for period size %2\n", bandwidth, session->samples_per_cycle()));
+ DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MidiClock Master reset(): calculated filter for period size %2\n", ENGINE->samples_per_cycle()));
- should_be_position = session->transport_sample();
+ should_be_position = _session->transport_sample();
+ _speed = 0;
last_timestamp = 0;
- _starting = true;
- _started = true;
-
- // session->request_locate(0, false);
- current_delta = 0;
+ _running = false;
+ _current_delta = 0;
}
void
-MIDIClock_Slave::contineu (Parser& /*parser*/, samplepos_t /*timestamp*/)
+MIDIClock_TransportMaster::contineu (Parser& /*parser*/, samplepos_t /*timestamp*/)
{
- DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_Slave got continue message\n");
+ DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_TransportMaster got continue message\n");
- if (!_started) {
- _starting = true;
- _started = true;
- }
+ _running = true;
}
-
void
-MIDIClock_Slave::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
+MIDIClock_TransportMaster::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
{
- DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_Slave got stop message\n");
+ DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_TransportMaster got stop message\n");
- if (_started || _starting) {
- _starting = false;
- _started = false;
- // locate to last MIDI clock position
- session->request_transport_speed(0.0);
+ if (_running) {
+ _running = false;
+ _speed = 0;
+ last_timestamp = 0;
// we need to go back to the last MIDI beat (6 ppqn)
// and lets hope the tempo didnt change in the meantime :)
@@ -243,24 +353,19 @@ MIDIClock_Slave::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
// that is the position of the last MIDI Clock
// message and that is probably what the master
// expects where we are right now
- samplepos_t stop_position = should_be_position;
-
+ //
// find out the last MIDI beat: go back #midi_clocks mod 6
// and lets hope the tempo didnt change in those last 6 beats :)
- stop_position -= (midi_clock_count % 6) * one_ppqn_in_samples;
-
- session->request_locate(stop_position, false);
- should_be_position = stop_position;
- last_timestamp = 0;
+ should_be_position -= (midi_clock_count % 6) * one_ppqn_in_samples;
}
}
void
-MIDIClock_Slave::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
+MIDIClock_TransportMaster::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
{
- // we are note supposed to get position messages while we are running
+ // we are not supposed to get position messages while we are running
// so lets be robust and ignore those
- if (_started || _starting) {
+ if (_running) {
return;
}
@@ -274,102 +379,51 @@ MIDIClock_Slave::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Song Position: %1 samples: %2\n", position_in_sixteenth_notes, position_in_samples));
- session->request_locate(position_in_samples, false);
- should_be_position = position_in_samples;
+ should_be_position = position_in_samples;
last_timestamp = 0;
}
bool
-MIDIClock_Slave::locked () const
+MIDIClock_TransportMaster::locked () const
{
return true;
}
bool
-MIDIClock_Slave::ok() const
+MIDIClock_TransportMaster::ok() const
{
return true;
}
bool
-MIDIClock_Slave::starting() const
+MIDIClock_TransportMaster::starting() const
{
return false;
}
-bool
-MIDIClock_Slave::stop_if_no_more_clock_events(samplepos_t& pos, samplepos_t now)
-{
- /* no timecode for 1/4 second ? conclude that its stopped */
- if (last_timestamp &&
- now > last_timestamp &&
- now - last_timestamp > session->sample_rate() / 4) {
- DEBUG_TRACE (DEBUG::MidiClock, "No MIDI Clock samples received for some time, stopping!\n");
- pos = should_be_position;
- session->request_transport_speed (0);
- session->request_locate (should_be_position, false);
- return true;
- } else {
- return false;
- }
-}
-
-bool
-MIDIClock_Slave::speed_and_position (double& speed, samplepos_t& pos)
-{
- if (!_started || _starting) {
- speed = 0.0;
- pos = should_be_position;
- return true;
- }
-
- samplepos_t engine_now = session->sample_time();
-
- if (stop_if_no_more_clock_events(pos, engine_now)) {
- return false;
- }
-
- // calculate speed
- speed = ((t1 - t0) * session->sample_rate()) / one_ppqn_in_samples;
-
- // provide a 0.1% deadzone to lock the speed
- if (fabs(speed - 1.0) <= 0.001)
- speed = 1.0;
-
- // calculate position
- if (engine_now > last_timestamp) {
- // we are in between MIDI clock messages
- // so we interpolate position according to speed
- samplecnt_t elapsed = engine_now - last_timestamp;
- pos = (samplepos_t) (should_be_position + double(elapsed) * speed);
- } else {
- // A new MIDI clock message has arrived this cycle
- pos = should_be_position;
- }
-
- DEBUG_TRACE (DEBUG::MidiClock, string_compose ("speed_and_position: speed %1 should-be %2 transport %3 \n", speed, pos, session->transport_sample()));
-
- return true;
-}
-
ARDOUR::samplecnt_t
-MIDIClock_Slave::resolution() const
+MIDIClock_TransportMaster::resolution() const
{
// one beat
return (samplecnt_t) one_ppqn_in_samples * ppqn;
}
std::string
-MIDIClock_Slave::approximate_current_delta() const
+MIDIClock_TransportMaster::position_string () const
+{
+ return std::string();
+}
+
+std::string
+MIDIClock_TransportMaster::delta_string() const
{
char delta[80];
- if (last_timestamp == 0 || _starting) {
+ if (last_timestamp == 0 || starting()) {
snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
} else {
snprintf(delta, sizeof(delta), "\u0394<span foreground=\"green\" face=\"monospace\" >%s%s%" PRIi64 "</span>sm",
- LEADINGZERO(abs(current_delta)), PLUSMINUS(-current_delta), abs(current_delta));
+ LEADINGZERO(abs(_current_delta)), PLUSMINUS(-_current_delta), abs(_current_delta));
}
return std::string(delta);
}
-
diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc
index dfc1c37e87..f8259c8917 100644
--- a/libs/ardour/midi_port.cc
+++ b/libs/ardour/midi_port.cc
@@ -37,11 +37,10 @@ using namespace PBD;
MidiPort::MidiPort (const std::string& name, PortFlags flags)
: Port (name, DataType::MIDI, flags)
- , _has_been_mixed_down (false)
, _resolve_required (false)
, _input_active (true)
- , _always_parse (false)
- , _trace_on (false)
+ , _trace_parser (0)
+ , _data_fetched_for_cycle (false)
{
_buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI));
}
@@ -57,10 +56,13 @@ MidiPort::~MidiPort()
}
void
-MidiPort::cycle_start (pframes_t nframes)
+MidiPort::parse_input (pframes_t nframes, MIDI::Parser& parser)
{
- samplepos_t now = AudioEngine::instance()->sample_time_at_cycle_start();
+}
+void
+MidiPort::cycle_start (pframes_t nframes)
+{
Port::cycle_start (nframes);
_buffer->clear ();
@@ -69,22 +71,8 @@ MidiPort::cycle_start (pframes_t nframes)
port_engine.midi_clear (port_engine.get_buffer (_port_handle, nframes));
}
- if (_always_parse || (receives_input() && _trace_on)) {
- MidiBuffer& mb (get_midi_buffer (nframes));
-
- /* dump incoming MIDI to parser */
-
- for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) {
- uint8_t* buf = (*b).buffer();
-
- _self_parser.set_timestamp (now + (*b).time());
-
- uint32_t limit = (*b).size();
-
- for (size_t n = 0; n < limit; ++n) {
- _self_parser.scanner (buf[n]);
- }
- }
+ if (receives_input() && _trace_parser) {
+ read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, *_trace_parser, AudioEngine::instance()->sample_time_at_cycle_start());
}
if (inbound_midi_filter) {
@@ -101,80 +89,64 @@ MidiPort::cycle_start (pframes_t nframes)
}
-Buffer&
-MidiPort::get_buffer (pframes_t nframes)
-{
- return get_midi_buffer (nframes);
-}
-
MidiBuffer &
MidiPort::get_midi_buffer (pframes_t nframes)
{
- if (_has_been_mixed_down) {
+ if (_data_fetched_for_cycle) {
return *_buffer;
}
- if (receives_input ()) {
-
- if (_input_active) {
+ if (receives_input () && _input_active) {
- void* buffer = port_engine.get_buffer (_port_handle, nframes);
- const pframes_t event_count = port_engine.get_midi_event_count (buffer);
+ void* buffer = port_engine.get_buffer (_port_handle, nframes);
+ const pframes_t event_count = port_engine.get_midi_event_count (buffer);
- /* suck all relevant MIDI events from the MIDI port buffer
- into our MidiBuffer
- */
+ /* suck all MIDI events for this cycle of nframes from
+ the MIDI port buffer into our MidiBuffer.
+ */
- for (pframes_t i = 0; i < event_count; ++i) {
+ for (pframes_t i = 0; i < event_count; ++i) {
- pframes_t timestamp;
- size_t size;
- uint8_t const* buf;
+ pframes_t timestamp;
+ size_t size;
+ uint8_t const* buf;
- port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
+ port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
- if (buf[0] == 0xfe) {
- /* throw away active sensing */
- continue;
- }
+ if (buf[0] == 0xfe) {
+ /* throw away active sensing */
+ continue;
+ }
- timestamp = floor (timestamp * _speed_ratio);
+ timestamp = floor (timestamp * _speed_ratio);
- /* check that the event is in the acceptable time range */
- if ((timestamp < (_global_port_buffer_offset)) ||
- (timestamp >= (_global_port_buffer_offset + nframes))) {
- // XXX this is normal after a split cycles:
- // The engine buffer contains the data for the complete cycle, but
- // only the part after _global_port_buffer_offset is needed.
+ /* check that the event is in the acceptable time range */
+ if ((timestamp < (_global_port_buffer_offset)) ||
+ (timestamp >= (_global_port_buffer_offset + nframes))) {
+ // XXX this is normal after a split cycles:
+ // The engine buffer contains the data for the complete cycle, but
+ // only the part after _global_port_buffer_offset is needed.
#ifndef NDEBUG
- cerr << "Dropping incoming MIDI at time " << timestamp << "; offset="
- << _global_port_buffer_offset << " limit="
- << (_global_port_buffer_offset + nframes)
- << " = (" << _global_port_buffer_offset
- << " + " << nframes
- << ")\n";
+ cerr << "Dropping incoming MIDI at time " << timestamp << "; offset="
+ << _global_port_buffer_offset << " limit="
+ << (_global_port_buffer_offset + nframes)
+ << " = (" << _global_port_buffer_offset
+ << " + " << nframes
+ << ")\n";
#endif
- continue;
- }
-
- /* adjust timestamp to match current cycle */
- timestamp -= _global_port_buffer_offset;
- assert (timestamp < nframes);
-
- if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
- /* normalize note on with velocity 0 to proper note off */
- uint8_t ev[3];
- ev[0] = 0x80 | (buf[0] & 0x0F); /* note off */
- ev[1] = buf[1];
- ev[2] = 0x40; /* default velocity */
- _buffer->push_back (timestamp, size, ev);
- } else {
- _buffer->push_back (timestamp, size, buf);
- }
+ continue;
}
- } else {
- _buffer->silence (nframes);
+ if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
+ /* normalize note on with velocity 0 to proper note off */
+ uint8_t ev[3];
+ ev[0] = 0x80 | (buf[0] & 0x0F); /* note off */
+ ev[1] = buf[1];
+ ev[2] = 0x40; /* default velocity */
+ _buffer->push_back (timestamp, size, ev);
+ } else {
+ _buffer->push_back (timestamp, size, buf);
+ }
}
} else {
@@ -182,22 +154,63 @@ MidiPort::get_midi_buffer (pframes_t nframes)
}
if (nframes) {
- _has_been_mixed_down = true;
+ _data_fetched_for_cycle = true;
}
return *_buffer;
}
void
+MidiPort::read_and_parse_entire_midi_buffer_with_no_speed_adjustment (pframes_t nframes, MIDI::Parser& parser, samplepos_t now)
+{
+ void* buffer = port_engine.get_buffer (_port_handle, nframes);
+ const pframes_t event_count = port_engine.get_midi_event_count (buffer);
+
+ for (pframes_t i = 0; i < event_count; ++i) {
+
+ pframes_t timestamp;
+ size_t size;
+ uint8_t const* buf;
+
+ port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
+
+ if (buf[0] == 0xfe) {
+ /* throw away active sensing */
+ continue;
+ }
+
+ parser.set_timestamp (now + timestamp);
+
+ /* During this parsing stage, signals will be emitted from the
+ * Parser, which will update anything connected to it.
+ *
+ * As of July 2018, this is only used by TransportMasters which
+ * read MIDI before the process() cycle really gets started.
+ */
+
+ if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
+ /* normalize note on with velocity 0 to proper note off */
+ parser.scanner (0x80 | (buf[0] & 0x0F)); /* note off */
+ parser.scanner (buf[1]);
+ parser.scanner (0x40); /* default (off) velocity */
+ } else {
+ for (size_t n = 0; n < size; ++n) {
+ parser.scanner (buf[n]);
+ }
+ }
+ }
+}
+
+void
MidiPort::cycle_end (pframes_t /*nframes*/)
{
- _has_been_mixed_down = false;
+ _data_fetched_for_cycle = false;
}
void
MidiPort::cycle_split ()
{
- _has_been_mixed_down = false;
+ _data_fetched_for_cycle = false;
}
void
@@ -253,16 +266,16 @@ MidiPort::flush_buffers (pframes_t nframes)
const Evoral::Event<MidiBuffer::TimeType> ev (*i, false);
- if (sends_output() && _trace_on) {
+ if (sends_output() && _trace_parser) {
uint8_t const * const buf = ev.buffer();
const samplepos_t now = AudioEngine::instance()->sample_time_at_cycle_start();
- _self_parser.set_timestamp (now + ev.time());
+ _trace_parser->set_timestamp (now + ev.time());
uint32_t limit = ev.size();
for (size_t n = 0; n < limit; ++n) {
- _self_parser.scanner (buf[n]);
+ _trace_parser->scanner (buf[n]);
}
}
@@ -347,15 +360,9 @@ MidiPort::set_input_active (bool yn)
}
void
-MidiPort::set_always_parse (bool yn)
-{
- _always_parse = yn;
-}
-
-void
-MidiPort::set_trace_on (bool yn)
+MidiPort::set_trace (MIDI::Parser * p)
{
- _trace_on = yn;
+ _trace_parser = p;
}
int
diff --git a/libs/ardour/midiport_manager.cc b/libs/ardour/midiport_manager.cc
index cf1d90ac56..df1b9d5441 100644
--- a/libs/ardour/midiport_manager.cc
+++ b/libs/ardour/midiport_manager.cc
@@ -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));
diff --git a/libs/ardour/mtc_slave.cc b/libs/ardour/mtc_slave.cc
index 0f277a7f55..409fb9da69 100644
--- a/libs/ardour/mtc_slave.cc
+++ b/libs/ardour/mtc_slave.cc
@@ -30,7 +30,7 @@
#include "ardour/midi_buffer.h"
#include "ardour/midi_port.h"
#include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
#include <glibmm/timer.h>
@@ -49,38 +49,35 @@ using namespace Timecode;
recently received position (and without the direction of timecode reversing too), we
will stop+locate+wait+chase.
*/
-const int MTC_Slave::sample_tolerance = 2;
-
-MTC_Slave::MTC_Slave (Session& s, MidiPort& p)
- : session (s)
- , port (&p)
+const int MTC_TransportMaster::sample_tolerance = 2;
+
+MTC_TransportMaster::MTC_TransportMaster (std::string const & name)
+ : TimecodeTransportMaster (name, MTC)
+ , can_notify_on_unknown_rate (true)
+ , mtc_frame (0)
+ , mtc_frame_dll (0)
+ , last_inbound_frame (0)
+ , window_begin (0)
+ , window_end (0)
+ , first_mtc_timestamp (0)
+ , did_reset_tc_format (false)
+ , reset_pending (0)
+ , reset_position (false)
+ , transport_direction (1)
+ , busy_guard1 (0)
+ , busy_guard2 (0)
+ , printed_timecode_warning (false)
{
- can_notify_on_unknown_rate = true;
- did_reset_tc_format = false;
- reset_pending = 0;
- reset_position = false;
- mtc_frame = 0;
- mtc_frame_dll = 0;
- engine_dll_initstate = 0;
- busy_guard1 = busy_guard2 = 0;
-
- last_mtc_fps_byte = session.get_mtc_timecode_bits ();
- quarter_frame_duration = (double(session.samples_per_timecode_frame()) / 4.0);
-
- mtc_timecode = session.config.get_timecode_format();
- a3e_timecode = session.config.get_timecode_format();
- printed_timecode_warning = false;
-
- session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_Slave::parameter_changed, this, _1));
- parse_timecode_offset();
- reset (true);
+ if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
+ throw failed_constructor();
+ }
+
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("MTC registered %1\n", _port->name()));
- port->self_parser().mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3));
- port->self_parser().mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3));
- port->self_parser().mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1));
+ init ();
}
-MTC_Slave::~MTC_Slave()
+MTC_TransportMaster::~MTC_TransportMaster()
{
port_connections.drop_connections();
config_connection.disconnect();
@@ -96,31 +93,69 @@ MTC_Slave::~MTC_Slave()
}
if (did_reset_tc_format) {
- session.config.set_timecode_format (saved_tc_format);
+ _session->config.set_timecode_format (saved_tc_format);
}
}
void
-MTC_Slave::rebind (MidiPort& p)
+MTC_TransportMaster::init ()
+{
+ reset (true);
+}
+
+void
+MTC_TransportMaster::set_session (Session *s)
{
- port_connections.drop_connections ();
+ config_connection.disconnect ();
+ port_connections.drop_connections();
+
+ _session = s;
+
+ if (_session) {
+
+ last_mtc_fps_byte = _session->get_mtc_timecode_bits ();
+ quarter_frame_duration = (double) (_session->samples_per_timecode_frame() / 4.0);
+ mtc_timecode = _session->config.get_timecode_format();
+ a3e_timecode = _session->config.get_timecode_format();
+
+ parse_timecode_offset ();
+ reset (true);
- port = &p;
+ parser.mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_time, this, _1, _2, _3));
+ parser.mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_qtr, this, _1, _2, _3));
+ parser.mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_status, this, _1));
+ _session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_TransportMaster::parameter_changed, this, _1));
+ }
}
void
-MTC_Slave::parse_timecode_offset() {
+MTC_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
+{
+ /* Read and parse incoming MIDI */
+
+ _midi_port->read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, parser, now);
+
+ if (session_pos) {
+ const samplepos_t current_pos = current.position + ((now - current.timestamp) * current.speed);
+ _current_delta = current_pos - *session_pos;
+ } else {
+ _current_delta = 0;
+ }
+}
+
+void
+MTC_TransportMaster::parse_timecode_offset() {
Timecode::Time offset_tc;
- Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc);
- offset_tc.rate = session.timecode_frames_per_second();
- offset_tc.drop = session.timecode_drop_frames();
- session.timecode_to_sample(offset_tc, timecode_offset, false, false);
+ Timecode::parse_timecode_format (_session->config.get_slave_timecode_offset(), offset_tc);
+ offset_tc.rate = _session->timecode_frames_per_second();
+ offset_tc.drop = _session->timecode_drop_frames();
+ _session->timecode_to_sample(offset_tc, timecode_offset, false, false);
timecode_negative_offset = offset_tc.negative;
}
void
-MTC_Slave::parameter_changed (std::string const & p)
+MTC_TransportMaster::parameter_changed (std::string const & p)
{
if (p == "slave-timecode-offset"
|| p == "timecode-format"
@@ -129,47 +164,40 @@ MTC_Slave::parameter_changed (std::string const & p)
}
}
-bool
-MTC_Slave::give_slave_full_control_over_transport_speed() const
-{
- return true; // DLL align to engine transport
- // return false; // for Session-level computed varispeed
-}
-
ARDOUR::samplecnt_t
-MTC_Slave::resolution () const
+MTC_TransportMaster::resolution () const
{
return (samplecnt_t) quarter_frame_duration * 4.0;
}
ARDOUR::samplecnt_t
-MTC_Slave::seekahead_distance () const
+MTC_TransportMaster::seekahead_distance () const
{
return quarter_frame_duration * 8 * transport_direction;
}
bool
-MTC_Slave::outside_window (samplepos_t pos) const
+MTC_TransportMaster::outside_window (samplepos_t pos) const
{
return ((pos < window_begin) || (pos > window_end));
}
bool
-MTC_Slave::locked () const
+MTC_TransportMaster::locked () const
{
- DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2 initstate %3\n", port->self_parser().mtc_locked(), last_inbound_frame, engine_dll_initstate));
- return port->self_parser().mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0;
+ DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2\n", parser.mtc_locked(), last_inbound_frame));
+ return parser.mtc_locked() && last_inbound_frame !=0;
}
bool
-MTC_Slave::ok() const
+MTC_TransportMaster::ok() const
{
return true;
}
void
-MTC_Slave::queue_reset (bool reset_pos)
+MTC_TransportMaster::queue_reset (bool reset_pos)
{
Glib::Threads::Mutex::Lock lm (reset_lock);
reset_pending++;
@@ -179,7 +207,7 @@ MTC_Slave::queue_reset (bool reset_pos)
}
void
-MTC_Slave::maybe_reset ()
+MTC_TransportMaster::maybe_reset ()
{
Glib::Threads::Mutex::Lock lm (reset_lock);
@@ -191,9 +219,10 @@ MTC_Slave::maybe_reset ()
}
void
-MTC_Slave::reset (bool with_position)
+MTC_TransportMaster::reset (bool with_position)
{
- DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC_Slave reset %1\n", with_position?"with position":"without position"));
+ DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC_TransportMaster reset %1\n", with_position?"with position":"without position"));
+
if (with_position) {
last_inbound_frame = 0;
current.guard1++;
@@ -212,15 +241,14 @@ MTC_Slave::reset (bool with_position)
window_begin = 0;
window_end = 0;
transport_direction = 1;
- current_delta = 0;
- ActiveChanged(false);
+ _current_delta = 0;
}
void
-MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
+MTC_TransportMaster::handle_locate (const MIDI::byte* mmc_tc)
{
MIDI::byte mtc[5];
- DEBUG_TRACE (DEBUG::MTC, "MTC_Slave::handle_locate\n");
+ DEBUG_TRACE (DEBUG::MTC, "MTC_TransportMaster::handle_locate\n");
mtc[4] = last_mtc_fps_byte;
mtc[3] = mmc_tc[0] & 0xf; /* hrs only */
@@ -232,7 +260,7 @@ MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
}
void
-MTC_Slave::read_current (SafeTime *st) const
+MTC_TransportMaster::read_current (SafeTime *st) const
{
int tries = 0;
@@ -249,9 +277,9 @@ MTC_Slave::read_current (SafeTime *st) const
}
void
-MTC_Slave::init_mtc_dll(samplepos_t tme, double qtr)
+MTC_TransportMaster::init_mtc_dll(samplepos_t tme, double qtr)
{
- omega = 2.0 * M_PI * qtr / 2.0 / double(session.sample_rate());
+ const double omega = 2.0 * M_PI * qtr / 2.0 / double(_session->sample_rate());
b = 1.4142135623730950488 * omega;
c = omega * omega;
@@ -263,7 +291,7 @@ MTC_Slave::init_mtc_dll(samplepos_t tme, double qtr)
/* called from MIDI parser */
void
-MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, samplepos_t now)
+MTC_TransportMaster::update_mtc_qtr (Parser& p, int which_qtr, samplepos_t now)
{
busy_guard1++;
const double qtr_d = quarter_frame_duration;
@@ -302,7 +330,7 @@ MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, samplepos_t now)
* when a full TC has been received
* OR on locate */
void
-MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t now)
+MTC_TransportMaster::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t now)
{
busy_guard1++;
@@ -341,7 +369,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
can_notify_on_unknown_rate = true;
break;
case MTC_30_FPS_DROP:
- if (Config->get_timecode_source_2997()) {
+ if (fr2997()) {
tc_format = Timecode::timecode_2997000drop;
timecode.rate = (29970.0/1000.0);
} else {
@@ -365,13 +393,13 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
<< endmsg;
can_notify_on_unknown_rate = false;
}
- timecode.rate = session.timecode_frames_per_second();
- timecode.drop = session.timecode_drop_frames();
+ timecode.rate = _session->timecode_frames_per_second();
+ timecode.drop = _session->timecode_drop_frames();
reset_tc = false;
}
if (reset_tc) {
- TimecodeFormat cur_timecode = session.config.get_timecode_format();
+ TimecodeFormat cur_timecode = _session->config.get_timecode_format();
if (Config->get_timecode_sync_frame_rate()) {
/* enforce time-code */
if (!did_reset_tc_format) {
@@ -386,7 +414,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
<< endmsg;
}
}
- session.config.set_timecode_format (tc_format);
+ _session->config.set_timecode_format (tc_format);
} else {
/* only warn about TC mismatch */
if (mtc_timecode != tc_format) printed_timecode_warning = false;
@@ -414,11 +442,11 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
consideration.
*/
- quarter_frame_duration = (double(session.sample_rate()) / (double) timecode.rate / 4.0);
+ quarter_frame_duration = (double(_session->sample_rate()) / (double) timecode.rate / 4.0);
Timecode::timecode_to_sample (timecode, mtc_frame, true, false,
- double(session.sample_rate()),
- session.config.get_subframes_per_frame(),
+ double(_session->sample_rate()),
+ _session->config.get_subframes_per_frame(),
timecode_negative_offset, timecode_offset
);
@@ -427,9 +455,9 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
if (was_full || outside_window (mtc_frame)) {
DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC %1 or outside window %2 MTC %3\n", was_full, outside_window (mtc_frame), mtc_frame));
- session.set_requested_return_sample (-1);
- session.request_transport_speed (0);
- session.request_locate (mtc_frame, false);
+ _session->set_requested_return_sample (-1);
+ _session->request_transport_speed (0, TRS_MTC);
+ _session->request_locate (mtc_frame, false, TRS_MTC);
update_mtc_status (MIDI::MTC_Stopped);
reset (false);
reset_window (mtc_frame);
@@ -449,9 +477,9 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
long int mtc_off = (long) rint(7.0 * qtr);
DEBUG_TRACE (DEBUG::MTC, string_compose ("new mtc_frame: %1 | MTC-FpT: %2 A3-FpT:%3\n",
- mtc_frame, (4.0*qtr), session.samples_per_timecode_frame()));
+ mtc_frame, (4.0*qtr), _session->samples_per_timecode_frame()));
- switch (port->self_parser().mtc_running()) {
+ switch (parser.mtc_running()) {
case MTC_Backward:
mtc_frame -= mtc_off;
qtr *= -1.0;
@@ -470,7 +498,6 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
first_mtc_timestamp = now;
init_mtc_dll(mtc_frame, qtr);
mtc_frame_dll = mtc_frame;
- ActiveChanged (true); // emit signal
}
current.guard1++;
current.position = mtc_frame;
@@ -487,12 +514,12 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
}
void
-MTC_Slave::update_mtc_status (MIDI::MTC_Status status)
+MTC_TransportMaster::update_mtc_status (MIDI::MTC_Status status)
{
/* XXX !!! thread safety ... called from MIDI I/O context
* on locate (via ::update_mtc_time())
*/
- DEBUG_TRACE (DEBUG::MTC, string_compose("MTC_Slave::update_mtc_status - TID:%1 MTC:%2\n", pthread_name(), mtc_frame));
+ DEBUG_TRACE (DEBUG::MTC, string_compose("MTC_TransportMaster::update_mtc_status - TID:%1 MTC:%2\n", pthread_name(), mtc_frame));
return; // why was this fn needed anyway ? it just messes up things -> use reset.
busy_guard1++;
@@ -526,7 +553,7 @@ MTC_Slave::update_mtc_status (MIDI::MTC_Status status)
}
void
-MTC_Slave::reset_window (samplepos_t root)
+MTC_TransportMaster::reset_window (samplepos_t root)
{
/* if we're waiting for the master to catch us after seeking ahead, keep the window
of acceptable MTC samples wide open. otherwise, shrink it down to just 2 video frames
@@ -535,7 +562,7 @@ MTC_Slave::reset_window (samplepos_t root)
samplecnt_t const d = (quarter_frame_duration * 4 * sample_tolerance);
- switch (port->self_parser().mtc_running()) {
+ switch (parser.mtc_running()) {
case MTC_Forward:
window_begin = root;
transport_direction = 1;
@@ -561,144 +588,70 @@ MTC_Slave::reset_window (samplepos_t root)
DEBUG_TRACE (DEBUG::MTC, string_compose ("reset MTC window @ %3, now %1 .. %2\n", window_begin, window_end, root));
}
-void
-MTC_Slave::init_engine_dll (samplepos_t pos, samplepos_t inc)
-{
- /* the bandwidth of the DLL is a trade-off,
- * because the max-speed of the transport in ardour is
- * limited to +-8.0, a larger bandwidth would cause oscillations
- *
- * But this is only really a problem if the user performs manual
- * seeks while transport is running and slaved to MTC.
- */
- oe = 2.0 * M_PI * double(inc) / 2.0 / double(session.sample_rate());
- be = 1.4142135623730950488 * oe;
- ce = oe * oe;
-
- ee2 = double(transport_direction * inc);
- te0 = double(pos);
- te1 = te0 + ee2;
- DEBUG_TRACE (DEBUG::MTC, string_compose ("[re-]init Engine DLL %1 %2 %3\n", te0, te1, ee2));
-}
-
/* main entry point from session_process.cc
xo * in process callback context */
bool
-MTC_Slave::speed_and_position (double& speed, samplepos_t& pos)
+MTC_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
{
- samplepos_t now = session.engine().sample_time_at_cycle_start();
- samplepos_t sess_pos = session.transport_sample(); // corresponds to now
- //sess_pos -= session.engine().samples_since_cycle_start();
-
SafeTime last;
- sampleoffset_t elapsed;
- bool engine_dll_reinitialized = false;
+
+ if (!_collect) {
+ return false;
+ }
read_current (&last);
- DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 initstate %3 dir %4 tpos %5 now %6 last-in %7\n",
+ DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 dir %4 now %5 last-in %6\n",
last.timestamp,
last.speed,
- engine_dll_initstate,
transport_direction,
- sess_pos,
now,
last_inbound_frame));
- /* re-init engine DLL here when state changed (direction, first_mtc_timestamp) */
if (last.timestamp == 0) {
- engine_dll_initstate = 0;
- } else if (engine_dll_initstate != transport_direction && last.speed != 0) {
- engine_dll_initstate = transport_direction;
- init_engine_dll(last.position, session.engine().samples_per_cycle());
- engine_dll_reinitialized = true;
- }
-
- if (last.timestamp == 0) {
- speed = 0;
- pos = session.transport_sample() ; // last.position;
- DEBUG_TRACE (DEBUG::MTC, string_compose ("first call to MTC_Slave::speed_and_position, pos = %1\n", pos));
- return true;
+ return false;
}
- /* no timecode for two samples - conclude that it's stopped */
if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > labs(seekahead_distance())) {
- speed = 0;
- pos = last.position;
- session.set_requested_return_sample (-1);
- session.request_locate (pos, false);
- session.request_transport_speed (0);
- engine_dll_initstate = 0;
- queue_reset (false);
- ActiveChanged (false);
- DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC not seen for 2 samples - reset pending, pos = %1\n", pos));
- return false;
+ /* no timecode for two cycles - conclude that it's stopped */
+
+ if (!Config->get_transport_masters_just_roll_when_sync_lost()) {
+ speed = 0;
+ pos = last.position;
+ _current_delta = 0;
+ queue_reset (false);
+ DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC not seen for 2 samples - reset pending, pos = %1\n", pos));
+ return false;
+ }
}
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position mtc-tme: %1 mtc-pos: %2 mtc-spd: %3\n", last.timestamp, last.position, last.speed));
- DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position eng-tme: %1 eng-pos: %2\n", now, sess_pos));
-
- double speed_flt = last.speed; ///< MTC speed from MTC-quarter-frame DLL
-
- /* interpolate position according to speed and time since last quarter-frame*/
- if (speed_flt == 0.0f) {
- elapsed = 0;
- } else {
- /* scale elapsed time by the current MTC speed */
- elapsed = (samplecnt_t) rint (speed_flt * (now - last.timestamp));
- if (give_slave_full_control_over_transport_speed() && !engine_dll_reinitialized) {
- /* there is an engine vs MTC position sample-delta.
- * This mostly due to quantization and rounding of (speed * nframes)
- * but can also due to the session-process not calling
- * speed_and_position() every cycle under some circumstances.
- * Thus we use an other DLL to align the engine and the MTC
- */
-
- /* update engine DLL and calculate speed */
- const double e = double (last.position + elapsed - sess_pos);
- te0 = te1;
- te1 += be * e + ee2;
- ee2 += ce * e;
- speed_flt = (te1 - te0) / double(session.engine().samples_per_cycle());
- DEBUG_TRACE (DEBUG::MTC, string_compose ("engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", te0, te1, e, speed_flt, ee2 - session.engine().samples_per_cycle() ));
- }
- }
- pos = last.position + elapsed;
- speed = speed_flt;
-
- /* may happen if the user performs a seek in the timeline while slaved to running MTC
- * engine-DLL can oscillate back before 0.
- * also see note in MTC_Slave::init_engine_dll
- */
- if (!session.actively_recording()
- && speed != 0
- && ((pos < 0) || (labs(pos - sess_pos) > 3 * session.sample_rate()))) {
- engine_dll_initstate = 0;
- queue_reset (false);
- }
+ speed = last.speed;
/* provide a .1% deadzone to lock the speed */
- if (fabs (speed - 1.0) <= 0.001)
- speed = 1.0;
+ if (fabs (speed - 1.0) <= 0.001) {
+ speed = 1.0;
+ }
- DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
- speed, pos, last.position, elapsed, pos - sess_pos));
+ pos = last.position;
+ pos += (now - last.timestamp) * speed;
- current_delta = (pos - sess_pos);
+ DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 | elapsed: %4\n",
+ speed, pos, last.position, (now - last.timestamp)));
return true;
}
Timecode::TimecodeFormat
-MTC_Slave::apparent_timecode_format () const
+MTC_TransportMaster::apparent_timecode_format () const
{
return mtc_timecode;
}
std::string
-MTC_Slave::approximate_current_position() const
+MTC_TransportMaster::position_string() const
{
SafeTime last;
read_current (&last);
@@ -707,22 +660,25 @@ MTC_Slave::approximate_current_position() const
}
return Timecode::timecode_format_sampletime(
last.position,
- double(session.sample_rate()),
+ double(_session->sample_rate()),
Timecode::timecode_to_frames_per_second(mtc_timecode),
Timecode::timecode_has_drop_frames(mtc_timecode));
}
std::string
-MTC_Slave::approximate_current_delta() const
+MTC_TransportMaster::delta_string () const
{
char delta[80];
SafeTime last;
read_current (&last);
+
+ delta[0] = '\0';
+
if (last.timestamp == 0 || reset_pending) {
snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
} else {
snprintf(delta, sizeof(delta), "\u0394<span foreground=\"green\" face=\"monospace\" >%s%s%" PRIi64 "</span>sm",
- LEADINGZERO(abs(current_delta)), PLUSMINUS(-current_delta), abs(current_delta));
+ LEADINGZERO(abs(_current_delta)), PLUSMINUS(-_current_delta), abs(_current_delta));
}
return std::string(delta);
}
diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc
index 27b414f945..3c31094dae 100644
--- a/libs/ardour/port.cc
+++ b/libs/ardour/port.cc
@@ -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
{
diff --git a/libs/ardour/port_manager.cc b/libs/ardour/port_manager.cc
index 1cac85332b..f5304f4961 100644
--- a/libs/ardour/port_manager.cc
+++ b/libs/ardour/port_manager.cc
@@ -192,37 +192,51 @@ PortManager::port_is_physical (const std::string& portname) const
void
PortManager::filter_midi_ports (vector<string>& ports, MidiPortFlags include, MidiPortFlags exclude)
{
+
if (!include && !exclude) {
return;
}
- for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
+ {
+ Glib::Threads::Mutex::Lock lm (midi_port_info_mutex);
- PortManager::MidiPortInformation mpi = midi_port_information (*si);
+ fill_midi_port_info_locked ();
- if (mpi.pretty_name.empty()) {
- /* no information !!! */
- ++si;
- continue;
- }
+ for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
- if (include) {
- if ((mpi.properties & include) != include) {
- /* properties do not include requested ones */
- si = ports.erase (si);
+ MidiPortInfo::iterator x = midi_port_info.find (*si);
+
+ if (x == midi_port_info.end()) {
+ ++si;
continue;
}
- }
- if (exclude) {
- if ((mpi.properties & exclude)) {
- /* properties include ones to avoid */
- si = ports.erase (si);
+ MidiPortInformation& mpi (x->second);
+
+ if (mpi.pretty_name.empty()) {
+ /* no information !!! */
+ ++si;
continue;
}
- }
- ++si;
+ if (include) {
+ if ((mpi.properties & include) != include) {
+ /* properties do not include requested ones */
+ si = ports.erase (si);
+ continue;
+ }
+ }
+
+ if (exclude) {
+ if ((mpi.properties & exclude)) {
+ /* properties include ones to avoid */
+ si = ports.erase (si);
+ continue;
+ }
+ }
+
+ ++si;
+ }
}
}
@@ -656,6 +670,20 @@ PortManager::connect_callback (const string& a, const string& b, bool conn)
port_b = x->second;
}
+ if (conn) {
+ if (port_a && !port_b) {
+ port_a->increment_external_connections ();
+ } else if (port_b && !port_a) {
+ port_b->increment_external_connections ();
+ }
+ } else {
+ if (port_a && !port_b) {
+ port_a->decrement_external_connections ();
+ } else if (port_b && !port_a) {
+ port_b->decrement_external_connections ();
+ }
+ }
+
PortConnectedOrDisconnected (
port_a, a,
port_b, b,
@@ -1260,23 +1288,19 @@ PortManager::fill_midi_port_info_locked ()
if (!ph) {
/* port info saved from some condition where this port
* existed, but no longer does (i.e. device unplugged
- * at present)
+ * at present). We don't remove it from midi_port_info.
*/
continue;
}
- if (!x->second.pretty_name.empty () && x->second.pretty_name != x->first) {
- /* name set in port info ... propagate */
- _backend->set_port_property (ph, "http://jackaudio.org/metadata/pretty-name", x->second.pretty_name, string());
- } else {
- /* check with backend for pre-existing pretty name */
- string value;
- string type;
- if (0 == _backend->get_port_property (ph,
- "http://jackaudio.org/metadata/pretty-name",
- value, type)) {
- x->second.pretty_name = value;
- }
+ /* check with backend for pre-existing pretty name */
+ string value;
+ string type;
+
+ if (0 == _backend->get_port_property (ph,
+ "http://jackaudio.org/metadata/pretty-name",
+ value, type)) {
+ x->second.pretty_name = value;
}
}
diff --git a/libs/ardour/rc_configuration.cc b/libs/ardour/rc_configuration.cc
index 4651fa3341..c558be8b08 100644
--- a/libs/ardour/rc_configuration.cc
+++ b/libs/ardour/rc_configuration.cc
@@ -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);
}
}
diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc
index 8b3ad8af95..9bdf758a17 100644
--- a/libs/ardour/session.cc
+++ b/libs/ardour/session.cc
@@ -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"));
}
@@ -3004,40 +2973,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)
{
if (inputs ) { // get all enabled midi input ports
@@ -7043,39 +6978,12 @@ Session::operation_in_progress (GQuark op) const
}
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 ()
{
if (_ltc_output) {
diff --git a/libs/ardour/session_ltc.cc b/libs/ardour/session_ltc.cc
index 5c4a65ad03..a6a1c6bd7b 100644
--- a/libs/ardour/session_ltc.cc
+++ b/libs/ardour/session_ltc.cc
@@ -25,7 +25,7 @@
#include "ardour/debug.h"
#include "ardour/io.h"
#include "ardour/session.h"
-#include "ardour/slave.h"
+#include "ardour/transport_master.h"
#include "pbd/i18n.h"
@@ -68,7 +68,7 @@ Session::ltc_tx_initialize()
ltc_enc_tcformat = config.get_timecode_format();
ltc_tx_parse_offset();
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX init sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(ltc_enc_tcformat)));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("LTC TX init sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(ltc_enc_tcformat)));
ltc_encoder = ltc_encoder_create(nominal_sample_rate(),
timecode_to_frames_per_second(ltc_enc_tcformat),
TV_STANDARD(ltc_enc_tcformat), 0);
@@ -93,7 +93,7 @@ Session::ltc_tx_initialize()
void
Session::ltc_tx_cleanup()
{
- DEBUG_TRACE (DEBUG::LTC, "LTC TX cleanup\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "cleanup\n");
ltc_tx_connections.drop_connections ();
free(ltc_enc_buf);
ltc_enc_buf = NULL;
@@ -104,7 +104,7 @@ Session::ltc_tx_cleanup()
void
Session::ltc_tx_resync_latency()
{
- DEBUG_TRACE (DEBUG::LTC, "LTC TX resync latency\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "resync latency\n");
if (!deletion_in_progress()) {
boost::shared_ptr<Port> ltcport = ltc_output_port();
if (ltcport) {
@@ -116,7 +116,7 @@ Session::ltc_tx_resync_latency()
void
Session::ltc_tx_reset()
{
- DEBUG_TRACE (DEBUG::LTC, "LTC TX reset\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "reset\n");
assert (ltc_encoder);
ltc_enc_pos = -9999; // force re-start
ltc_buf_len = 0;
@@ -203,7 +203,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
/* range from libltc (38..218) || - 128.0 -> (-90..90) */
const float ltcvol = Config->get_ltc_output_volume()/(90.0); // pow(10, db/20.0)/(90.0);
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max));
/* all systems go. Now here's the plan:
*
@@ -222,7 +222,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
// (1) check fps
TimecodeFormat cur_timecode = config.get_timecode_format();
if (cur_timecode != ltc_enc_tcformat) {
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode)));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode)));
if (ltc_encoder_reinit(ltc_encoder, nominal_sample_rate(),
timecode_to_frames_per_second(cur_timecode),
TV_STANDARD(cur_timecode), 0
@@ -295,7 +295,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
if (SIGNUM(new_ltc_speed) != SIGNUM (ltc_speed)) {
- DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport changed direction\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "transport changed direction\n");
ltc_tx_reset();
}
@@ -315,13 +315,13 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
* end_sample is calculated from 'samples_moved' which includes the interpolation.
* so we're good.
*/
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed));
speed_changed = true;
ltc_encoder_set_filter(ltc_encoder, LTC_RISE_TIME(new_ltc_speed));
}
if (end_sample == start_sample || fabs(current_speed) < 0.1 ) {
- DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport is not rolling or absolute-speed < 0.1\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "transport is not rolling or absolute-speed < 0.1\n");
/* keep repeating current sample
*
* an LTC generator must be able to continue generating LTC when Ardours transport is in stop
@@ -336,19 +336,19 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
return;
}
if (start_sample != ltc_prev_cycle) {
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample));
ltc_tx_reset();
}
}
if (fabs(new_ltc_speed) > 10.0) {
- DEBUG_TRACE (DEBUG::LTC, "LTC TX2: speed is out of bounds.\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "speed is out of bounds.\n");
ltc_tx_reset();
return;
}
if (ltc_speed == 0 && new_ltc_speed != 0) {
- DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport started rolling - reset\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "transport started rolling - reset\n");
ltc_tx_reset();
}
@@ -374,21 +374,21 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
double oldbuflen = (double)(ltc_buf_len - ltc_buf_off);
double newbuflen = (double)(ltc_buf_len - ltc_buf_off) * fabs(ltc_speed / new_ltc_speed);
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: bufOld %1 bufNew %2 | diff %3\n",
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: bufOld %1 bufNew %2 | diff %3\n",
(ltc_buf_len - ltc_buf_off), newbuflen, newbuflen - oldbuflen
));
double bufrspdiff = rint(newbuflen - oldbuflen);
if (abs(bufrspdiff) > newbuflen || abs(bufrspdiff) > oldbuflen) {
- DEBUG_TRACE (DEBUG::LTC, "LTC TX2: resampling buffer would destroy information.\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "resampling buffer would destroy information.\n");
ltc_tx_reset();
poff = 0;
} else if (bufrspdiff != 0 && newbuflen > oldbuflen) {
int incnt = 0;
double samples_to_insert = ceil(newbuflen - oldbuflen);
double avg_distance = newbuflen / samples_to_insert;
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer insert: %1\n", samples_to_insert));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer insert: %1\n", samples_to_insert));
for (int rp = ltc_buf_off; rp < ltc_buf_len - 1; ++rp) {
const int ro = rp - ltc_buf_off;
@@ -402,7 +402,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
} else if (bufrspdiff != 0 && newbuflen < oldbuflen) {
double samples_to_remove = ceil(oldbuflen - newbuflen);
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer - remove: %1\n", samples_to_remove));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer - remove: %1\n", samples_to_remove));
if (oldbuflen <= samples_to_remove) {
ltc_buf_off = ltc_buf_len= 0;
} else {
@@ -424,7 +424,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
ltc_prev_cycle = start_sample;
ltc_speed = new_ltc_speed;
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: transport speed %1.\n", ltc_speed));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: transport speed %1.\n", ltc_speed));
// (3) bit/sample alignment
Timecode::Time tc_start;
@@ -451,7 +451,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
if (current_speed == 0) {
soff = 0;
}
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX3: A3cycle: %1 = A3tc: %2 +off: %3\n",
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("3: A3cycle: %1 = A3tc: %2 +off: %3\n",
cycle_start_sample, tc_sample_start, soff));
@@ -470,8 +470,8 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
*/
double maxdiff;
- if (config.get_external_sync() && slave()) {
- maxdiff = slave()->resolution();
+ if (transport_master_is_external()) {
+ maxdiff = transport_master()->resolution();
} else {
maxdiff = ceil(fabs(ltc_speed))*2.0;
if (nominal_sample_rate() != sample_rate()) {
@@ -482,10 +482,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
}
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n",
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n",
ltc_enc_pos, ltc_enc_cnt, poff, (ltc_buf_len - ltc_buf_off), poff, ltc_enc_byte));
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc-pos: %1 | d: %2\n",
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc-pos: %1 | d: %2\n",
ltc_enc_pos + ltc_enc_cnt - poff,
rint(ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_sample
));
@@ -515,7 +515,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
ltc_encoder_set_frame(ltc_encoder, &ltcframe);
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff));
int32_t cyc_off;
if (soff < 0 || soff >= fptcf) {
@@ -546,7 +546,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
}
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart encoder: soff %1 byte %2 cycoff %3\n",
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart encoder: soff %1 byte %2 cycoff %3\n",
soff, ltc_enc_byte, cyc_off));
if ( (ltc_speed < 0 && ltc_enc_byte !=9 ) || (ltc_speed >= 0 && ltc_enc_byte !=0 ) ) {
@@ -565,14 +565,14 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
ltc_enc_pos = tc_sample_start % wrap24h;
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart @ %1 + %2 - %3 | byte %4\n",
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart @ %1 + %2 - %3 | byte %4\n",
ltc_enc_pos, ltc_enc_cnt, cyc_off, ltc_enc_byte));
}
else if (ltc_speed != 0 && (fptcf / ltc_speed / 80) > 3 ) {
/* reduce (low freq) jitter.
* The granularity of the LTC encoder speed is 1 byte =
* (samples-per-timecode-sample / 10) audio-samples.
- * Thus, tiny speed changes [as produced by some slaves]
+ * Thus, tiny speed changes [as produced by some transport masters]
* may not have any effect in the cycle when they occur,
* but they will add up over time.
*
@@ -593,7 +593,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
// (6) encode and output
while (1) {
#ifdef LTC_GEN_TXDBUG
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.1 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.1 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
#endif
// (6a) send remaining buffer
while ((ltc_buf_off < ltc_buf_len) && (txf < nframes)) {
@@ -602,11 +602,11 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
out[txf++] = val;
}
#ifdef LTC_GEN_TXDBUG
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.2 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.2 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
#endif
if (txf >= nframes) {
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n",
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n",
ltc_enc_pos, ltc_buf_off, ltc_buf_len, ltc_enc_byte, ltc_speed, nframes, txf));
break;
}
@@ -635,7 +635,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
memset(&ltc_enc_buf[ltc_buf_len], 127, enc_samples * sizeof(ltcsnd_sample_t));
} else {
if (ltc_encoder_encode_byte(ltc_encoder, ltc_enc_byte, (ltc_speed==0)?1.0:(1.0/ltc_speed))) {
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoder error byte %1\n", ltc_enc_byte));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoder error byte %1\n", ltc_enc_byte));
ltc_encoder_buffer_flush(ltc_encoder);
ltc_tx_reset();
return;
@@ -644,10 +644,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
#ifdef LTC_GEN_FRAMEDBUG
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed));
#endif
if (enc_samples <=0) {
- DEBUG_TRACE (DEBUG::LTC, "LTC TX6.3 encoder empty buffer.\n");
+ DEBUG_TRACE (DEBUG::TXLTC, "6.3 encoder empty buffer.\n");
ltc_encoder_buffer_flush(ltc_encoder);
ltc_tx_reset();
return;
@@ -677,7 +677,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
}
#ifdef LTC_GEN_FRAMEDBUG
- DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed));
+ DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed));
#endif
}
diff --git a/libs/ardour/session_midi.cc b/libs/ardour/session_midi.cc
index d0e75fbb6e..87b9e5e861 100644
--- a/libs/ardour/session_midi.cc
+++ b/libs/ardour/session_midi.cc
@@ -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)
diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc
index eeb8cb871e..04f2d3f77e 100644
--- a/libs/ardour/session_process.cc
+++ b/libs/ardour/session_process.cc
@@ -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,309 +567,17 @@ Session::process_with_events (pframes_t nframes)
}
}
-void
-Session::reset_slave_state ()
-{
- average_slave_delta = 1800;
- delta_accumulator_cnt = 0;
- have_first_delta_accumulator = false;
- _slave_state = Stopped;
- DiskReader::set_no_disk_output (false);
-}
-
bool
Session::transport_locked () const
{
- Slave* sl = _slave;
-
- if (!locate_pending() && (!config.get_external_sync() || (sl && sl->ok() && sl->locked()))) {
- return true;
- }
-
- return false;
-}
-
-bool
-Session::follow_slave (pframes_t nframes)
-{
- double slave_speed;
- samplepos_t slave_transport_sample;
- samplecnt_t this_delta;
- int dir;
-
- if (!_slave->ok()) {
- stop_transport ();
- config.set_external_sync (false);
- goto noroll;
- }
-
- _slave->speed_and_position (slave_speed, slave_transport_sample);
-
- DEBUG_TRACE (DEBUG::Slave, string_compose ("Slave position %1 speed %2\n", slave_transport_sample, slave_speed));
-
- if (!_slave->locked()) {
- DEBUG_TRACE (DEBUG::Slave, "slave not locked\n");
- goto noroll;
- }
-
- if (slave_transport_sample > _transport_sample) {
- this_delta = slave_transport_sample - _transport_sample;
- dir = 1;
- } else {
- this_delta = _transport_sample - slave_transport_sample;
- dir = -1;
- }
-
- if (_slave->starting()) {
- slave_speed = 0.0f;
- }
-
- if (_slave->is_always_synced() ||
- (Config->get_timecode_source_is_synced() && (dynamic_cast<TimecodeSlave*>(_slave)) != 0)
- ) {
-
- /* if the TC source is synced, then we assume that its
- speed is binary: 0.0 or 1.0
- */
-
- if (slave_speed != 0.0f) {
- slave_speed = 1.0f;
- }
-
- } else {
-
- /* if we are chasing and the average delta between us and the
- master gets too big, we want to switch to silent
- motion. so keep track of that here.
- */
-
- if (_slave_state == Running) {
- calculate_moving_average_of_slave_delta(dir, abs(this_delta));
- }
- }
-
- track_slave_state (slave_speed, slave_transport_sample, this_delta);
-
- DEBUG_TRACE (DEBUG::Slave, string_compose ("slave state %1 @ %2 speed %3 cur delta %4 avg delta %5\n",
- _slave_state, slave_transport_sample, slave_speed, this_delta, average_slave_delta));
-
-
- if (_slave_state == Running && !_slave->is_always_synced() && !(Config->get_timecode_source_is_synced() && (dynamic_cast<TimecodeSlave*>(_slave)) != 0)) {
-
- /* may need to varispeed to sync with slave */
-
- if (_transport_speed != 0.0f) {
-
- /*
- note that average_dir is +1 or -1
- */
-
- float delta;
-
- if (average_slave_delta == 0) {
- delta = this_delta;
- delta *= dir;
- } else {
- delta = average_slave_delta;
- delta *= average_dir;
- }
-
-#ifndef NDEBUG
- if (slave_speed != 0.0) {
- DEBUG_TRACE (DEBUG::Slave, string_compose ("delta = %1 speed = %2 ts = %3 M@%4 S@%5 avgdelta %6\n",
- (int) (dir * this_delta),
- slave_speed,
- _transport_speed,
- _transport_sample,
- slave_transport_sample,
- average_slave_delta));
- }
-#endif
-
- if (_slave->give_slave_full_control_over_transport_speed()) {
- set_transport_speed (slave_speed, 0, false, false);
- //std::cout << "set speed = " << slave_speed << "\n";
- } else {
- float adjusted_speed = slave_speed + (1.5 * (delta / float(_current_sample_rate)));
- request_transport_speed (adjusted_speed);
- DEBUG_TRACE (DEBUG::Slave, string_compose ("adjust using %1 towards %2 ratio %3 current %4 slave @ %5\n",
- delta, adjusted_speed, adjusted_speed/slave_speed, _transport_speed,
- slave_speed));
- }
-
- if (!actively_recording() && (samplecnt_t) average_slave_delta > _slave->resolution()) {
- DEBUG_TRACE (DEBUG::Slave, string_compose ("average slave delta %1 greater than slave resolution %2 => no disk output\n", average_slave_delta, _slave->resolution()));
- /* run routes as normal, but no disk output */
- DiskReader::set_no_disk_output (true);
- return true;
- }
-
- if (!have_first_delta_accumulator) {
- DEBUG_TRACE (DEBUG::Slave, "waiting for first slave delta accumulator to be ready, no disk output\n");
- /* run routes as normal, but no disk output */
- DiskReader::set_no_disk_output (true);
- return true;
- }
- }
- }
-
-
- if (!have_first_delta_accumulator) {
- DEBUG_TRACE (DEBUG::Slave, "still waiting to compute slave delta, no disk output\n");
- DiskReader::set_no_disk_output (true);
- } else {
- DiskReader::set_no_disk_output (false);
- }
-
- if ((_slave_state == Running) && (0 == (post_transport_work () & ~PostTransportSpeed))) {
- /* speed is set, we're locked, and good to go */
+ if (!locate_pending() && (!config.get_external_sync() || (transport_master()->ok() && transport_master()->locked()))) {
return true;
}
- noroll:
- /* don't move at all */
- DEBUG_TRACE (DEBUG::Slave, "no roll\n")
- no_roll (nframes);
return false;
}
void
-Session::calculate_moving_average_of_slave_delta (int dir, samplecnt_t this_delta)
-{
- if (delta_accumulator_cnt >= delta_accumulator_size) {
- have_first_delta_accumulator = true;
- delta_accumulator_cnt = 0;
- }
-
- if (delta_accumulator_cnt != 0 || this_delta < _current_sample_rate) {
- delta_accumulator[delta_accumulator_cnt++] = (samplecnt_t) dir * (samplecnt_t) this_delta;
- }
-
- if (have_first_delta_accumulator) {
- average_slave_delta = 0L;
- for (int i = 0; i < delta_accumulator_size; ++i) {
- average_slave_delta += delta_accumulator[i];
- }
- average_slave_delta /= (int32_t) delta_accumulator_size;
- if (average_slave_delta < 0L) {
- average_dir = -1;
- average_slave_delta = average_slave_delta;
- } else {
- average_dir = 1;
- }
- }
-}
-
-void
-Session::track_slave_state (float slave_speed, samplepos_t slave_transport_sample, samplecnt_t /*this_delta*/)
-{
- if (slave_speed != 0.0f) {
-
- /* slave is running */
-
- switch (_slave_state) {
- case Stopped:
- if (_slave->requires_seekahead()) {
- slave_wait_end = slave_transport_sample + _slave->seekahead_distance ();
- DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, but running, requires seekahead to %1\n", slave_wait_end));
- /* we can call locate() here because we are in process context */
- locate (slave_wait_end, false, false);
- _slave_state = Waiting;
-
- } else {
-
- DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped -> running at %1\n", slave_transport_sample));
-
- memset (delta_accumulator, 0, sizeof (int32_t) * delta_accumulator_size);
- average_slave_delta = 0L;
-
- Location* al = _locations->auto_loop_location();
-
- if (al && play_loop && (slave_transport_sample < al->start() || slave_transport_sample > al->end())) {
- // cancel looping
- request_play_loop(false);
- }
-
- if (slave_transport_sample != _transport_sample) {
- DEBUG_TRACE (DEBUG::Slave, string_compose ("require locate to run. eng: %1 -> sl: %2\n", _transport_sample, slave_transport_sample));
- locate (slave_transport_sample, false, false);
- }
- _slave_state = Running;
- }
- break;
-
- case Waiting:
- default:
- break;
- }
-
- if (_slave_state == Waiting) {
-
- DEBUG_TRACE (DEBUG::Slave, string_compose ("slave waiting at %1\n", slave_transport_sample));
-
- if (slave_transport_sample >= slave_wait_end) {
-
- DEBUG_TRACE (DEBUG::Slave, string_compose ("slave start at %1 vs %2\n", slave_transport_sample, _transport_sample));
-
- _slave_state = Running;
-
- /* now perform a "micro-seek" within the disk buffers to realign ourselves
- precisely with the master.
- */
-
-
- bool ok = true;
- samplecnt_t sample_delta = slave_transport_sample - _transport_sample;
-
- boost::shared_ptr<RouteList> rl = routes.reader();
- for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr && !tr->can_internal_playback_seek (sample_delta)) {
- ok = false;
- break;
- }
- }
-
- if (ok) {
- for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr) {
- tr->internal_playback_seek (sample_delta);
- }
- }
- _transport_sample += sample_delta;
-
- } else {
- cerr << "cannot micro-seek\n";
- /* XXX what? */
- }
- }
- }
-
- if (_slave_state == Running && _transport_speed == 0.0f) {
- DEBUG_TRACE (DEBUG::Slave, "slave starts transport\n");
- start_transport ();
- }
-
- } else { // slave_speed is 0
-
- /* slave has stopped */
-
- if (_transport_speed != 0.0f) {
- DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", slave_speed, slave_transport_sample, _transport_sample));
- stop_transport ();
- }
-
- if (slave_transport_sample != _transport_sample) {
- DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, move to %1\n", slave_transport_sample));
- force_locate (slave_transport_sample, false);
- }
-
- reset_slave_state();
- }
-}
-
-void
Session::process_without_events (pframes_t nframes)
{
bool session_needs_butler = false;
@@ -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);
+}
diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc
index 4b0a0d9e92..1335c554cb 100644
--- a/libs/ardour/session_state.cc
+++ b/libs/ardour/session_state.cc
@@ -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") {
diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc
index b69565f384..95daf7495b 100644
--- a/libs/ardour/session_transport.cc
+++ b/libs/ardour/session_transport.cc
@@ -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();
@@ -547,18 +557,6 @@ Session::butler_transport_work ()
}
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)
{
boost::shared_ptr<RouteList> rl = routes.reader();
@@ -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));
}
@@ -1722,164 +1722,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 ()
{
_play_range = false;
@@ -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();
+}
diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc
index 78b5a209cc..4f22892b5a 100644
--- a/libs/ardour/track.cc
+++ b/libs/ardour/track.cc
@@ -524,12 +524,6 @@ Track::non_realtime_locate (samplepos_t p)
Route::non_realtime_locate (p);
}
-void
-Track::non_realtime_speed_change ()
-{
- _disk_reader->non_realtime_speed_change ();
-}
-
int
Track::overwrite_existing_buffers ()
{
diff --git a/libs/ardour/transport_master.cc b/libs/ardour/transport_master.cc
new file mode 100644
index 0000000000..284a0a8537
--- /dev/null
+++ b/libs/ardour/transport_master.cc
@@ -0,0 +1,281 @@
+/*
+ Copyright (C) 2002 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+#include <vector>
+
+#include "pbd/i18n.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/midi_port.h"
+#include "ardour/session.h"
+#include "ardour/transport_master.h"
+#include "ardour/transport_master_manager.h"
+#include "ardour/utils.h"
+
+using namespace ARDOUR;
+
+const std::string TransportMaster::state_node_name = X_("TransportMaster");
+
+
+TransportMaster::TransportMaster (SyncSource t, std::string const & name)
+ : _type (t)
+ , _name (name)
+ , _session (0)
+ , _connected (false)
+ , _current_delta (0)
+ , _collect (true)
+ , _pending_collect (true)
+ , _request_mask (TransportRequestType (0))
+ , _sclock_synced (false)
+{
+ ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect_same_thread (port_connection, boost::bind (&TransportMaster::connection_handler, this, _1, _2, _3, _4, _5));
+ ARDOUR::AudioEngine::instance()->Running.connect_same_thread (backend_connection, boost::bind (&TransportMaster::check_backend, this));
+}
+
+TransportMaster::~TransportMaster()
+{
+ delete _session;
+}
+
+bool
+TransportMaster::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+{
+ if (!_port) {
+ return false;
+ }
+
+ const std::string fqn = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (_port->name());
+
+ if (fqn == name1 || fqn == name2) {
+
+ /* it's about us */
+
+ if (yn) {
+ _connected = true;
+ } else {
+ _connected = false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool
+TransportMaster::check_collect()
+{
+ if (!_connected) {
+ return false;
+ }
+
+ /* XXX should probably use boost::atomic something or other here */
+
+ if (_pending_collect != _collect) {
+ if (_pending_collect) {
+ init ();
+ } else {
+ if (TransportMasterManager::instance().current().get() == this) {
+ if (_session) {
+ _session->config.set_external_sync (false);
+ }
+ }
+ }
+ std::cerr << name() << " pc = " << _pending_collect << " c = " << _collect << std::endl;
+ _collect = _pending_collect;
+ }
+
+ return _collect;
+}
+
+void
+TransportMaster::set_collect (bool yn)
+{
+ _pending_collect = yn;
+}
+
+void
+TransportMaster::set_sample_clock_synced (bool yn)
+{
+ _sclock_synced = yn;
+}
+
+void
+TransportMaster::set_session (Session* s)
+{
+ _session = s;
+}
+
+int
+TransportMaster::set_state (XMLNode const & node, int /* version */)
+{
+ if (!node.get_property (X_("collect"), _collect)) {
+ _collect = false;
+ }
+
+ if (!node.get_property (X_("clock-synced"), _sclock_synced)) {
+ _sclock_synced = false;
+ }
+
+ TimecodeTransportMaster* ttm = dynamic_cast<TimecodeTransportMaster*> (this);
+ if (ttm) {
+ bool val;
+ node.get_property (X_("fr2997"), val);
+ ttm->set_fr2997 (val);
+ }
+
+ XMLNode* pnode = node.child (X_("Port"));
+
+ if (pnode) {
+ XMLNodeList const & children = pnode->children();
+ for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
+
+ XMLProperty const *prop;
+
+ if ((*ci)->name() == X_("Connection")) {
+ if ((prop = (*ci)->property (X_("other"))) == 0) {
+ continue;
+ }
+ _port->connect (prop->value());
+ }
+ }
+ }
+
+ return 0;
+}
+
+XMLNode&
+TransportMaster::get_state ()
+{
+ XMLNode* node = new XMLNode (state_node_name);
+ node->set_property (X_("type"), _type);
+ node->set_property (X_("name"), _name);
+ node->set_property (X_("collect"), _collect);
+ node->set_property (X_("clock-synced"), _sclock_synced);
+
+ TimecodeTransportMaster* ttm = dynamic_cast<TimecodeTransportMaster*> (this);
+ if (ttm) {
+ node->set_property (X_("fr2997"), ttm->fr2997());
+ }
+
+ if (_port) {
+ std::vector<std::string> connections;
+
+ XMLNode* pnode = new XMLNode (X_("Port"));
+
+ if (_port->get_connections (connections)) {
+
+ std::vector<std::string>::const_iterator ci;
+ std::sort (connections.begin(), connections.end());
+
+ for (ci = connections.begin(); ci != connections.end(); ++ci) {
+
+ /* if its a connection to our own port,
+ return only the port name, not the
+ whole thing. this allows connections
+ to be re-established even when our
+ client name is different.
+ */
+
+ XMLNode* cnode = new XMLNode (X_("Connection"));
+
+ cnode->set_property (X_("other"), AudioEngine::instance()->make_port_name_relative (*ci));
+ pnode->add_child_nocopy (*cnode);
+ }
+ }
+
+ node->add_child_nocopy (*pnode);
+ }
+
+ return *node;
+}
+
+boost::shared_ptr<TransportMaster>
+TransportMaster::factory (XMLNode const & node)
+{
+ if (node.name() != TransportMaster::state_node_name) {
+ return boost::shared_ptr<TransportMaster>();
+ }
+
+ SyncSource type;
+ std::string name;
+
+ if (!node.get_property (X_("type"), type)) {
+ return boost::shared_ptr<TransportMaster>();
+ }
+
+ if (!node.get_property (X_("name"), name)) {
+ return boost::shared_ptr<TransportMaster>();
+ }
+
+ return factory (type, name);
+}
+
+boost::shared_ptr<TransportMaster>
+TransportMaster::factory (SyncSource type, std::string const& name)
+{
+ /* XXX need to count existing sources of a given type */
+
+ switch (type) {
+ case MTC:
+ return boost::shared_ptr<TransportMaster> (new MTC_TransportMaster (sync_source_to_string (type)));
+ case LTC:
+ return boost::shared_ptr<TransportMaster> (new LTC_TransportMaster (sync_source_to_string (type)));
+ case MIDIClock:
+ return boost::shared_ptr<TransportMaster> (new MIDIClock_TransportMaster (sync_source_to_string (type)));
+ case Engine:
+ return boost::shared_ptr<TransportMaster> (new Engine_TransportMaster (*AudioEngine::instance()));
+ default:
+ break;
+ }
+
+ return boost::shared_ptr<TransportMaster>();
+}
+
+boost::shared_ptr<Port>
+TransportMasterViaMIDI::create_midi_port (std::string const & port_name)
+{
+ boost::shared_ptr<Port> p;
+
+ if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name)) == 0) {
+ return boost::shared_ptr<Port> ();
+ }
+
+ _midi_port = boost::dynamic_pointer_cast<MidiPort> (p);
+
+ return p;
+}
+
+bool
+TransportMaster::allow_request (TransportRequestSource src, TransportRequestType type) const
+{
+ return _request_mask & type;
+}
+
+void
+TransportMaster::set_request_mask (TransportRequestType t)
+{
+ _request_mask = t;
+}
+
+void
+TimecodeTransportMaster::set_fr2997 (bool yn)
+{
+ _fr2997 = yn;
+}
diff --git a/libs/ardour/transport_master_manager.cc b/libs/ardour/transport_master_manager.cc
new file mode 100644
index 0000000000..e69aebe77d
--- /dev/null
+++ b/libs/ardour/transport_master_manager.cc
@@ -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> ();
+}
diff --git a/libs/ardour/wscript b/libs/ardour/wscript
index ec36068720..efc305325a 100644
--- a/libs/ardour/wscript
+++ b/libs/ardour/wscript
@@ -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',
diff --git a/libs/midi++2/midi++/parser.h b/libs/midi++2/midi++/parser.h
index 3b1dd47052..0ccef6e66f 100644
--- a/libs/midi++2/midi++/parser.h
+++ b/libs/midi++2/midi++/parser.h
@@ -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];
diff --git a/libs/midi++2/parser.cc b/libs/midi++2/parser.cc
index 9866d41632..599f8764f1 100644
--- a/libs/midi++2/parser.cc
+++ b/libs/midi++2/parser.cc
@@ -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