From e6915e01de2e2167c3384c6c8f2408f763971616 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 18 Sep 2018 18:51:59 -0400 Subject: new transport slave/master implementation, libs/ edition --- libs/ardour/ardour/debug.h | 1 + libs/ardour/ardour/disk_io.h | 4 - libs/ardour/ardour/midi_buffer.h | 2 + libs/ardour/ardour/midi_port.h | 32 +- libs/ardour/ardour/midiport_manager.h | 6 +- libs/ardour/ardour/port.h | 6 +- libs/ardour/ardour/rc_configuration.h | 2 + libs/ardour/ardour/rc_configuration_vars.h | 13 +- libs/ardour/ardour/session.h | 81 +- libs/ardour/ardour/session_event.h | 14 +- libs/ardour/ardour/slave.h | 24 +- libs/ardour/ardour/track.h | 2 +- libs/ardour/ardour/transport_master.h | 546 ++++++++++ libs/ardour/ardour/transport_master_manager.h | 114 +++ libs/ardour/ardour/types.h | 1356 +++++++++++++------------ libs/ardour/audio_port.cc | 15 +- libs/ardour/audioengine.cc | 29 +- libs/ardour/debug.cc | 1 + libs/ardour/disk_io.cc | 16 - libs/ardour/engine_slave.cc | 87 +- libs/ardour/enums.cc | 12 +- libs/ardour/globals.cc | 16 +- libs/ardour/ltc_slave.cc | 514 +++++----- libs/ardour/midi_clock_slave.cc | 408 ++++---- libs/ardour/midi_port.cc | 189 ++-- libs/ardour/midiport_manager.cc | 25 +- libs/ardour/mtc_slave.cc | 332 +++--- libs/ardour/port.cc | 17 +- libs/ardour/port_manager.cc | 86 +- libs/ardour/rc_configuration.cc | 6 + libs/ardour/session.cc | 98 +- libs/ardour/session_ltc.cc | 68 +- libs/ardour/session_midi.cc | 21 +- libs/ardour/session_process.cc | 488 ++++----- libs/ardour/session_state.cc | 19 +- libs/ardour/session_transport.cc | 346 +++---- libs/ardour/track.cc | 6 - libs/ardour/transport_master.cc | 281 +++++ libs/ardour/transport_master_manager.cc | 537 ++++++++++ libs/ardour/wscript | 3 +- libs/midi++2/midi++/parser.h | 5 +- libs/midi++2/parser.cc | 12 +- 42 files changed, 3533 insertions(+), 2307 deletions(-) create mode 100644 libs/ardour/ardour/transport_master.h create mode 100644 libs/ardour/ardour/transport_master_manager.h create mode 100644 libs/ardour/transport_master.cc create mode 100644 libs/ardour/transport_master_manager.cc 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& 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 MidiFilter; void set_inbound_filter (MidiFilter); int add_shadow_port (std::string const &, MidiFilter); boost::shared_ptr 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 _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 scene_input_port() const { return boost::dynamic_pointer_cast(_scene_in); } boost::shared_ptr scene_output_port() const { return boost::dynamic_pointer_cast(_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 mtc_input_port() const { return _mtc_input_port; } boost::shared_ptr mtc_output_port() const { return _mtc_output_port; } - boost::shared_ptr midi_clock_input_port() const { return _midi_clock_input_port; } boost::shared_ptr 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 _scene_out; /* synchronously handled ports: ARDOUR::MidiPort */ - boost::shared_ptr _mtc_input_port; boost::shared_ptr _mtc_output_port; - boost::shared_ptr _midi_clock_input_port; boost::shared_ptr _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 MonitorInputChanged; static PBD::Signal2,boost::shared_ptr > 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 IOConnectionsComplete; - /* Timecode status signals */ - PBD::Signal1 MTCSyncStateChanged; - PBD::Signal1 LTCSyncStateChanged; - /* Record status signals */ PBD::Signal0 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); 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 StartTimeChanged; static PBD::Signal1 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(&_mtc_active)); } - bool synced_to_ltc () const { return config.get_external_sync() && Config->get_sync_source() == LTC && g_atomic_int_get (const_cast(&_ltc_active)); } + void request_sync_source (boost::shared_ptr); + 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 playlists; void send_mmc_locate (samplepos_t); @@ -1189,22 +1176,16 @@ public: /* synchronous MIDI ports used for synchronization */ boost::shared_ptr midi_clock_output_port () const; - boost::shared_ptr midi_clock_input_port () const; boost::shared_ptr mtc_output_port () const; - boost::shared_ptr mtc_input_port () const; - boost::shared_ptr ltc_input_port() const; boost::shared_ptr ltc_output_port() const; - boost::shared_ptr ltc_input_io() { return _ltc_input; } boost::shared_ptr 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 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 _ltc_input; boost::shared_ptr _ltc_output; boost::shared_ptr _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 music_range; boost::shared_ptr region; + boost::shared_ptr 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 + +#include +#include + +#include + +#include + +#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 factory (SyncSource, std::string const &); + static boost::shared_ptr factory (XMLNode const &); + + virtual void pre_process (pframes_t nframes, samplepos_t now, boost::optional) = 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 + * position 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: + *
    + *
  • + * TransportMaster::ok() should return true, otherwise playback will stop + * immediately and the method will not be called + *
  • + *
  • + * when the references speed and position are passed into the TransportMaster + * they are uninitialized + *
  • + *
+ * + * After the method call the following postconditions should be met: + *
    + *
  • + * 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 + *
  • + *
  • + * the references speed and position should be assigned + * to the TransportMasters current requested transport speed + * and transport position. + *
  • + *
  • + * TransportMaster::resolution() should be greater than the maximum distance of + * ARDOURs transport position to the slaves requested transport position. + *
  • + *
  • TransportMaster::locked() should return true, otherwise Session::no_roll will be called
  • + *
  • TransportMaster::starting() should be false, otherwise the transport will not move until it becomes true
  • * + *
+ * + * @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() 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; + + PBD::ScopedConnection port_connection; + bool connection_handler (boost::weak_ptr, std::string name1, boost::weak_ptr, 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 midi_port() const { return _midi_port; } + boost::shared_ptr create_midi_port (std::string const & port_name); + + protected: + TransportMasterViaMIDI () {}; + + MIDI::Parser parser; + boost::shared_ptr _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); + + 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); + 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); + + 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); + 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 + +#include + +#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 > TransportMasters; + + int add (SyncSource type, std::string const & name); + int remove (std::string const & name); + void clear (); + + PBD::Signal1 > Added; + PBD::Signal1 > 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 current() const { return _current_master; } + int set_current (boost::shared_ptr); + int set_current (SyncSource); + int set_current (std::string const &); + + PBD::Signal2, boost::shared_ptr > 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 master_by_type (SyncSource src) const; + boost::shared_ptr 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 _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); + double compute_matching_master_speed (pframes_t nframes, samplepos_t, bool& locate_required); + int set_current_locked (boost::shared_ptr); + + 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 > AudioIntervalResult; - // associate a set of intervals with regions (e.g. for silence detection) - typedef std::map,AudioIntervalResult> AudioIntervalMap; - - typedef std::list > 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 > AudioIntervalResult; +// associate a set of intervals with regions (e.g. for silence detection) +typedef std::map,AudioIntervalResult> AudioIntervalMap; + +typedef std::list > 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 > 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 AnalysisFeatureList; + bool operator== (const AudioRange& other) const { + return start == other.start && end == other.end && id == other.id; + } - typedef std::list > RouteList; - typedef std::list > StripableList; - typedef std::list > WeakRouteList; - typedef std::list > WeakStripableList; - typedef std::list > ControlList; - typedef std::list > SlavableControlList; - typedef std::set > AutomationControlSet; + bool equal (const AudioRange& other) const { + return start == other.start && end == other.end; + } - typedef std::list > VCAList; + Evoral::OverlapType coverage (samplepos_t s, samplepos_t e) const { + return Evoral::coverage (start, end, s, e); + } +}; - class Bundle; - typedef std::vector > 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 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 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 > SourceList; + +enum SrcQuality { + SrcBest, + SrcGood, + SrcQuick, + SrcFast, + SrcFastest +}; + +typedef std::list AnalysisFeatureList; + +typedef std::list > RouteList; +typedef std::list > StripableList; +typedef std::list > WeakRouteList; +typedef std::list > WeakStripableList; +typedef std::list > ControlList; +typedef std::list > SlavableControlList; +typedef std::set > AutomationControlSet; + +typedef std::list > VCAList; + +class Bundle; +typedef std::vector > BundleList; + +enum RegionEquivalence { + Exact, + Enclosed, + Overlap +}; + +enum WaveformScale { + Linear, + Logarithmic +}; + +enum WaveformShape { + Traditional, + Rectified +}; + +struct CleanupReport { + std::vector 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 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 #include +#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) +{ + /* 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 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 #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 (<C_Slave::parameter_changed, this, _1)); - parse_timecode_offset(); - reset(); resync_latency(); - session.Xrun.connect_same_thread (port_connections, boost::bind (<C_Slave::resync_xrun, this)); - session.engine().GraphReordered.connect_same_thread (port_connections, boost::bind (<C_Slave::resync_latency, this)); + + AudioEngine::instance()->Xrun.connect_same_thread (port_connections, boost::bind (<C_TransportMaster::resync_xrun, this)); + AudioEngine::instance()->GraphReordered.connect_same_thread (port_connections, boost::bind (<C_TransportMaster::resync_latency, this)); +} + +void +LTC_TransportMaster::init () +{ + reset (true); +} + +void +LTC_TransportMaster::set_session (Session *s) +{ + config_connection.disconnect (); + _session = s; + + if (_session) { + + samples_per_ltc_frame = _session->samples_per_timecode_frame(); + timecode.rate = _session->timecode_frames_per_second(); + timecode.drop = _session->timecode_drop_frames(); + printed_timecode_warning = false; + ltc_timecode = _session->config.get_timecode_format(); + a3e_timecode = _session->config.get_timecode_format(); + + if (Config->get_use_session_timecode_format() && _session) { + samples_per_timecode_frame = _session->samples_per_timecode_frame(); + } + + if (decoder) { + ltc_decoder_free (decoder); + } + + decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/); + + parse_timecode_offset(); + reset(); + + _session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (<C_TransportMaster::parameter_changed, this, _1)); + } } -LTC_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 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 ... . + * 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 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 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%s%s%lldsm", + snprintf (delta, sizeof(delta), "\u0394%s%s%lldsm", 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 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%s%s%" PRIi64 "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 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 p; - p = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("MTC in")); - _mtc_input_port = boost::dynamic_pointer_cast (p); p = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("MTC out")); _mtc_output_port= boost::dynamic_pointer_cast (p); - p = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("MIDI Clock in")); - _midi_clock_input_port = boost::dynamic_pointer_cast (p); p = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("MIDI Clock out")); _midi_clock_output_port= boost::dynamic_pointer_cast (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 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 @@ -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 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%s%s%" PRIi64 "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& ports, MidiPortFlags include, MidiPortFlags exclude) { + if (!include && !exclude) { return; } - for (vector::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::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")); } @@ -3003,40 +2972,6 @@ Session::reconnect_midi_scene_ports(bool inputs) } } -void -Session::reconnect_mtc_ports () -{ - boost::shared_ptr mtc_in_ptr = _midi_ports->mtc_input_port(); - - if (!mtc_in_ptr) { - return; - } - - mtc_in_ptr->disconnect_all (); - - std::vector midi_port_states; - EngineStateController::instance()->get_physical_midi_input_states (midi_port_states); - - std::vector::iterator state_iter = midi_port_states.begin(); - - for (; state_iter != midi_port_states.end(); ++state_iter) { - if (state_iter->available && state_iter->mtc_in) { - mtc_in_ptr->connect (state_iter->name); - } - } - - if (!_midi_ports->mtc_input_port ()->connected () && - config.get_external_sync () && - (Config->get_sync_source () == MTC) ) { - config.set_external_sync (false); - } - - if ( ARDOUR::Profile->get_trx () ) { - // Tracks need this signal to update timecode_source_dropdown - MtcOrLtcInputPortChanged (); //emit signal - } -} - void Session::reconnect_mmc_ports(bool inputs) { @@ -7042,39 +6977,12 @@ Session::operation_in_progress (GQuark op) const return (find (_current_trans_quarks.begin(), _current_trans_quarks.end(), op) != _current_trans_quarks.end()); } -boost::shared_ptr -Session::ltc_input_port () const -{ - assert (_ltc_input); - return _ltc_input->nth (0); -} - boost::shared_ptr Session::ltc_output_port () const { return _ltc_output ? _ltc_output->nth (0) : boost::shared_ptr (); } -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 () { 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 ltcport = ltc_output_port(); if (ltcport) { @@ -116,7 +116,7 @@ Session::ltc_tx_resync_latency() void Session::ltc_tx_reset() { - DEBUG_TRACE (DEBUG::LTC, "LTC TX reset\n"); + DEBUG_TRACE (DEBUG::TXLTC, "reset\n"); assert (ltc_encoder); ltc_enc_pos = -9999; // force re-start ltc_buf_len = 0; @@ -203,7 +203,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t /* range from libltc (38..218) || - 128.0 -> (-90..90) */ const float ltcvol = Config->get_ltc_output_volume()/(90.0); // pow(10, db/20.0)/(90.0); - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max)); /* all systems go. Now here's the plan: * @@ -222,7 +222,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t // (1) check fps TimecodeFormat cur_timecode = config.get_timecode_format(); if (cur_timecode != ltc_enc_tcformat) { - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode))); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode))); if (ltc_encoder_reinit(ltc_encoder, nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode), TV_STANDARD(cur_timecode), 0 @@ -295,7 +295,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t } if (SIGNUM(new_ltc_speed) != SIGNUM (ltc_speed)) { - DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport changed direction\n"); + DEBUG_TRACE (DEBUG::TXLTC, "transport changed direction\n"); ltc_tx_reset(); } @@ -315,13 +315,13 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t * end_sample is calculated from 'samples_moved' which includes the interpolation. * so we're good. */ - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed)); speed_changed = true; ltc_encoder_set_filter(ltc_encoder, LTC_RISE_TIME(new_ltc_speed)); } if (end_sample == start_sample || fabs(current_speed) < 0.1 ) { - DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport is not rolling or absolute-speed < 0.1\n"); + DEBUG_TRACE (DEBUG::TXLTC, "transport is not rolling or absolute-speed < 0.1\n"); /* keep repeating current sample * * an LTC generator must be able to continue generating LTC when Ardours transport is in stop @@ -336,19 +336,19 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t return; } if (start_sample != ltc_prev_cycle) { - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample)); ltc_tx_reset(); } } if (fabs(new_ltc_speed) > 10.0) { - DEBUG_TRACE (DEBUG::LTC, "LTC TX2: speed is out of bounds.\n"); + DEBUG_TRACE (DEBUG::TXLTC, "speed is out of bounds.\n"); ltc_tx_reset(); return; } if (ltc_speed == 0 && new_ltc_speed != 0) { - DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport started rolling - reset\n"); + DEBUG_TRACE (DEBUG::TXLTC, "transport started rolling - reset\n"); ltc_tx_reset(); } @@ -374,21 +374,21 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t double oldbuflen = (double)(ltc_buf_len - ltc_buf_off); double newbuflen = (double)(ltc_buf_len - ltc_buf_off) * fabs(ltc_speed / new_ltc_speed); - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: bufOld %1 bufNew %2 | diff %3\n", + DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: bufOld %1 bufNew %2 | diff %3\n", (ltc_buf_len - ltc_buf_off), newbuflen, newbuflen - oldbuflen )); double bufrspdiff = rint(newbuflen - oldbuflen); if (abs(bufrspdiff) > newbuflen || abs(bufrspdiff) > oldbuflen) { - DEBUG_TRACE (DEBUG::LTC, "LTC TX2: resampling buffer would destroy information.\n"); + DEBUG_TRACE (DEBUG::TXLTC, "resampling buffer would destroy information.\n"); ltc_tx_reset(); poff = 0; } else if (bufrspdiff != 0 && newbuflen > oldbuflen) { int incnt = 0; double samples_to_insert = ceil(newbuflen - oldbuflen); double avg_distance = newbuflen / samples_to_insert; - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer insert: %1\n", samples_to_insert)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer insert: %1\n", samples_to_insert)); for (int rp = ltc_buf_off; rp < ltc_buf_len - 1; ++rp) { const int ro = rp - ltc_buf_off; @@ -402,7 +402,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t } } else if (bufrspdiff != 0 && newbuflen < oldbuflen) { double samples_to_remove = ceil(oldbuflen - newbuflen); - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer - remove: %1\n", samples_to_remove)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer - remove: %1\n", samples_to_remove)); if (oldbuflen <= samples_to_remove) { ltc_buf_off = ltc_buf_len= 0; } else { @@ -424,7 +424,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t ltc_prev_cycle = start_sample; ltc_speed = new_ltc_speed; - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: transport speed %1.\n", ltc_speed)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: transport speed %1.\n", ltc_speed)); // (3) bit/sample alignment Timecode::Time tc_start; @@ -451,7 +451,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t if (current_speed == 0) { soff = 0; } - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX3: A3cycle: %1 = A3tc: %2 +off: %3\n", + DEBUG_TRACE (DEBUG::TXLTC, string_compose("3: A3cycle: %1 = A3tc: %2 +off: %3\n", cycle_start_sample, tc_sample_start, soff)); @@ -470,8 +470,8 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t */ double maxdiff; - if (config.get_external_sync() && slave()) { - maxdiff = slave()->resolution(); + if (transport_master_is_external()) { + maxdiff = transport_master()->resolution(); } else { maxdiff = ceil(fabs(ltc_speed))*2.0; if (nominal_sample_rate() != sample_rate()) { @@ -482,10 +482,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t } } - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n", + DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n", ltc_enc_pos, ltc_enc_cnt, poff, (ltc_buf_len - ltc_buf_off), poff, ltc_enc_byte)); - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc-pos: %1 | d: %2\n", + DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc-pos: %1 | d: %2\n", ltc_enc_pos + ltc_enc_cnt - poff, rint(ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_sample )); @@ -515,7 +515,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t ltc_encoder_set_frame(ltc_encoder, <cframe); - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff)); int32_t cyc_off; if (soff < 0 || soff >= fptcf) { @@ -546,7 +546,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t } } - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart encoder: soff %1 byte %2 cycoff %3\n", + DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart encoder: soff %1 byte %2 cycoff %3\n", soff, ltc_enc_byte, cyc_off)); if ( (ltc_speed < 0 && ltc_enc_byte !=9 ) || (ltc_speed >= 0 && ltc_enc_byte !=0 ) ) { @@ -565,14 +565,14 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t ltc_enc_pos = tc_sample_start % wrap24h; - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart @ %1 + %2 - %3 | byte %4\n", + DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart @ %1 + %2 - %3 | byte %4\n", ltc_enc_pos, ltc_enc_cnt, cyc_off, ltc_enc_byte)); } else if (ltc_speed != 0 && (fptcf / ltc_speed / 80) > 3 ) { /* reduce (low freq) jitter. * The granularity of the LTC encoder speed is 1 byte = * (samples-per-timecode-sample / 10) audio-samples. - * Thus, tiny speed changes [as produced by some slaves] + * Thus, tiny speed changes [as produced by some transport masters] * may not have any effect in the cycle when they occur, * but they will add up over time. * @@ -593,7 +593,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t // (6) encode and output while (1) { #ifdef LTC_GEN_TXDBUG - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.1 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.1 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len)); #endif // (6a) send remaining buffer while ((ltc_buf_off < ltc_buf_len) && (txf < nframes)) { @@ -602,11 +602,11 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t out[txf++] = val; } #ifdef LTC_GEN_TXDBUG - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.2 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.2 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len)); #endif if (txf >= nframes) { - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n", + DEBUG_TRACE (DEBUG::TXLTC, string_compose("7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n", ltc_enc_pos, ltc_buf_off, ltc_buf_len, ltc_enc_byte, ltc_speed, nframes, txf)); break; } @@ -635,7 +635,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t memset(<c_enc_buf[ltc_buf_len], 127, enc_samples * sizeof(ltcsnd_sample_t)); } else { if (ltc_encoder_encode_byte(ltc_encoder, ltc_enc_byte, (ltc_speed==0)?1.0:(1.0/ltc_speed))) { - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoder error byte %1\n", ltc_enc_byte)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoder error byte %1\n", ltc_enc_byte)); ltc_encoder_buffer_flush(ltc_encoder); ltc_tx_reset(); return; @@ -644,10 +644,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t } #ifdef LTC_GEN_FRAMEDBUG - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed)); #endif if (enc_samples <=0) { - DEBUG_TRACE (DEBUG::LTC, "LTC TX6.3 encoder empty buffer.\n"); + DEBUG_TRACE (DEBUG::TXLTC, "6.3 encoder empty buffer.\n"); ltc_encoder_buffer_flush(ltc_encoder); ltc_tx_reset(); return; @@ -677,7 +677,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t } } #ifdef LTC_GEN_FRAMEDBUG - DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed)); + DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed)); #endif } 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 (_slave); + boost::shared_ptr mtcs = boost::dynamic_pointer_cast (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 -Session::midi_clock_input_port () const -{ - return _midi_ports->midi_clock_input_port (); -} + boost::shared_ptr Session::mtc_output_port () const { return _midi_ports->mtc_output_port (); } -boost::shared_ptr -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 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,308 +567,16 @@ Session::process_with_events (pframes_t nframes) } } -void -Session::reset_slave_state () -{ - average_slave_delta = 1800; - delta_accumulator_cnt = 0; - have_first_delta_accumulator = false; - _slave_state = Stopped; - DiskReader::set_no_disk_output (false); -} - bool Session::transport_locked () const { - Slave* sl = _slave; - - if (!locate_pending() && (!config.get_external_sync() || (sl && sl->ok() && sl->locked()))) { - return true; - } - - return false; -} - -bool -Session::follow_slave (pframes_t nframes) -{ - double slave_speed; - samplepos_t slave_transport_sample; - samplecnt_t this_delta; - int dir; - - if (!_slave->ok()) { - stop_transport (); - config.set_external_sync (false); - goto noroll; - } - - _slave->speed_and_position (slave_speed, slave_transport_sample); - - DEBUG_TRACE (DEBUG::Slave, string_compose ("Slave position %1 speed %2\n", slave_transport_sample, slave_speed)); - - if (!_slave->locked()) { - DEBUG_TRACE (DEBUG::Slave, "slave not locked\n"); - goto noroll; - } - - if (slave_transport_sample > _transport_sample) { - this_delta = slave_transport_sample - _transport_sample; - dir = 1; - } else { - this_delta = _transport_sample - slave_transport_sample; - dir = -1; - } - - if (_slave->starting()) { - slave_speed = 0.0f; - } - - if (_slave->is_always_synced() || - (Config->get_timecode_source_is_synced() && (dynamic_cast(_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(_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 rl = routes.reader(); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*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 tr = boost::dynamic_pointer_cast (*i); - if (tr) { - tr->internal_playback_seek (sample_delta); - } - } - _transport_sample += sample_delta; - - } else { - cerr << "cannot micro-seek\n"; - /* XXX what? */ - } - } - } - - if (_slave_state == Running && _transport_speed == 0.0f) { - DEBUG_TRACE (DEBUG::Slave, "slave starts transport\n"); - start_transport (); - } - - } else { // slave_speed is 0 - - /* slave has stopped */ - - if (_transport_speed != 0.0f) { - DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", slave_speed, slave_transport_sample, _transport_sample)); - stop_transport (); - } - - if (slave_transport_sample != _transport_sample) { - DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, move to %1\n", slave_transport_sample)); - force_locate (slave_transport_sample, false); - } - - reset_slave_state(); - } -} - void Session::process_without_events (pframes_t nframes) { @@ -859,21 +588,22 @@ Session::process_without_events (pframes_t nframes) return; } - if (!_exporting && _slave) { - if (!follow_slave (nframes)) { + if (!_exporting && config.get_external_sync()) { + if (!follow_transport_master (nframes)) { ltc_tx_send_time_code_for_cycle (_transport_sample, _transport_sample, 0, 0 , nframes); return; } } + assert (_transport_speed == 0 || _transport_speed == 1.0 || _transport_speed == -1.0); + if (_transport_speed == 0) { no_roll (nframes); return; + } else { + samples_moved = (samplecnt_t) nframes; } - assert (_transport_speed == 1.f || _transport_speed == -1.f); - samples_moved = (samplecnt_t) nframes * _transport_speed; - if (!_exporting && !timecode_transmission_suspended()) { send_midi_time_code_for_cycle (_transport_sample, _transport_sample + samples_moved, nframes); } @@ -1130,6 +860,10 @@ Session::process_event (SessionEvent* ev) set_transport_speed (ev->speed, ev->target_sample, ev->yes_or_no, ev->second_yes_or_no, ev->third_yes_or_no); break; + case SessionEvent::SetTransportMaster: + TransportMasterManager::instance().set_current (ev->transport_master); + break; + case SessionEvent::PunchIn: // cerr << "PunchIN at " << transport_sample() << endl; if (config.get_punch_in() && record_status() == Enabled) { @@ -1176,11 +910,6 @@ Session::process_event (SessionEvent* ev) overwrite_some_buffers (static_cast(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 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(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 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 rl = get_routes(); - + for (ARDOUR::RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { if ( !(*i)->is_track() ) { continue; } - + boost::shared_ptr 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 = boost::dynamic_pointer_cast (*i); if (track) { @@ -369,7 +383,7 @@ Session::realtime_stop (bool abort, bool clear_state) if ( solo_selection_active() ) { solo_selection ( _soloSelection, false ); } - + /* if we're going to clear loop state, then force disabling record BUT only if we're not doing latched rec-enable */ disable_record (true, (!Config->get_latched_record_enable() && clear_state)); @@ -487,10 +501,6 @@ Session::butler_transport_work () } } - if (ptw & PostTransportSpeed) { - non_realtime_set_speed (); - } - if (ptw & PostTransportReverse) { clear_clicks(); @@ -546,18 +556,6 @@ Session::butler_transport_work () DEBUG_TRACE (DEBUG::Transport, string_compose (X_("Butler transport work all done after %1 usecs @ %2 trw = %3\n"), g_get_monotonic_time() - before, _transport_sample, _butler->transport_work_requested())); } -void -Session::non_realtime_set_speed () -{ - boost::shared_ptr rl = routes.reader(); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - if (tr) { - tr->non_realtime_speed_change (); - } - } -} - void Session::non_realtime_overwrite (int on_entry, bool& finished) { @@ -926,7 +924,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) // need to queue this in the next RT cycle _send_timecode_update = true; - if (!dynamic_cast(_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 tr = boost::dynamic_pointer_cast (*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(_slave)) { + if (transport_master()->type() == MTC) { send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); } @@ -1721,164 +1721,6 @@ Session::reset_rf_scale (samplecnt_t motion) } } -void -Session::mtc_status_changed (bool yn) -{ - g_atomic_int_set (&_mtc_active, yn); - MTCSyncStateChanged( yn ); -} - -void -Session::ltc_status_changed (bool yn) -{ - g_atomic_int_set (&_ltc_active, yn); - LTCSyncStateChanged( yn ); -} - -void -Session::use_sync_source (Slave* new_slave) -{ - /* Runs in process() context */ - - if (!_slave && !new_slave) { - return; - } - - bool non_rt_required = false; - - /* XXX this deletion is problematic because we're in RT context */ - - delete _slave; - _slave = new_slave; - - - /* slave change, reset any DiskIO block on disk output because it is no - longer valid with a new slave. - */ - DiskReader::set_no_disk_output (false); - - MTC_Slave* mtc_slave = dynamic_cast(_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 (_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 rl = routes.reader(); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*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(_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(_slave)) { - return; - } - - try { - new_slave = new LTC_Slave (*this); - } - - catch (failed_constructor& err) { - return; - } - - break; - - case MIDIClock: - if (_slave && dynamic_cast(_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(_slave)) { - return; - } - - if (config.get_video_pullup() != 0.0f) { - return; - } - - new_slave = new Engine_Slave (*AudioEngine::instance()); - break; - - default: - new_slave = 0; - break; - }; - - request_sync_source (new_slave); -} - void Session::unset_play_range () { @@ -2112,3 +1954,91 @@ Session::timecode_transmission_suspended () const { return g_atomic_int_get (&_suspend_timecode_transmission) == 1; } + +boost::shared_ptr +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 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_master = boost::dynamic_pointer_cast (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_master = boost::dynamic_pointer_cast (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 rl = routes.reader(); + const bool externally_slaved = transport_master_is_external(); + + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*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 + +#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, std::string name1, boost::weak_ptr, 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 (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 (this); + if (ttm) { + node->set_property (X_("fr2997"), ttm->fr2997()); + } + + if (_port) { + std::vector connections; + + XMLNode* pnode = new XMLNode (X_("Port")); + + if (_port->get_connections (connections)) { + + std::vector::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::factory (XMLNode const & node) +{ + if (node.name() != TransportMaster::state_node_name) { + return boost::shared_ptr(); + } + + SyncSource type; + std::string name; + + if (!node.get_property (X_("type"), type)) { + return boost::shared_ptr(); + } + + if (!node.get_property (X_("name"), name)) { + return boost::shared_ptr(); + } + + return factory (type, name); +} + +boost::shared_ptr +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 (new MTC_TransportMaster (sync_source_to_string (type))); + case LTC: + return boost::shared_ptr (new LTC_TransportMaster (sync_source_to_string (type))); + case MIDIClock: + return boost::shared_ptr (new MIDIClock_TransportMaster (sync_source_to_string (type))); + case Engine: + return boost::shared_ptr (new Engine_TransportMaster (*AudioEngine::instance())); + default: + break; + } + + return boost::shared_ptr(); +} + +boost::shared_ptr +TransportMasterViaMIDI::create_midi_port (std::string const & port_name) +{ + boost::shared_ptr p; + + if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name)) == 0) { + return boost::shared_ptr (); + } + + _midi_port = boost::dynamic_pointer_cast (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 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 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 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 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 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 c) +{ + int ret = -1; + boost::shared_ptr 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 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 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()); +} + +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 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 +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 (); +} 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 PitchBendSignal; typedef PBD::Signal3 RPNSignal; typedef PBD::Signal3 RPNValueSignal; typedef PBD::Signal3 Signal; +typedef PBD::Signal4 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 -- cgit v1.2.3