new transport slave/master implementation, libs/ edition

This commit is contained in:
Paul Davis 2018-09-18 18:51:59 -04:00
parent 7390b88c2b
commit e6915e01de
42 changed files with 3561 additions and 2335 deletions

View File

@ -44,6 +44,7 @@ namespace PBD {
LIBARDOUR_API extern DebugBits Destruction;
LIBARDOUR_API extern DebugBits MTC;
LIBARDOUR_API extern DebugBits LTC;
LIBARDOUR_API extern DebugBits TXLTC;
LIBARDOUR_API extern DebugBits Transport;
LIBARDOUR_API extern DebugBits Slave;
LIBARDOUR_API extern DebugBits SessionEvents;

View File

@ -81,9 +81,6 @@ public:
virtual void non_realtime_locate (samplepos_t);
void non_realtime_speed_change ();
bool realtime_speed_change ();
virtual void punch_in() {}
virtual void punch_out() {}
@ -118,7 +115,6 @@ protected:
uint32_t i_am_the_modifier;
double _actual_speed;
double _target_speed;
bool _seek_required;
bool _slaved;
bool in_set_state;
samplepos_t playback_sample;

View File

@ -48,6 +48,8 @@ public:
void copy(const MidiBuffer& copy);
void copy(MidiBuffer const * const);
void skip_to (TimeType when);
bool push_back(const Evoral::Event<TimeType>& event);
bool push_back(TimeType time, size_t size, const uint8_t* data);

View File

@ -24,6 +24,7 @@
#include "midi++/parser.h"
#include "ardour/port.h"
#include "ardour/midi_buffer.h"
namespace ARDOUR {
@ -51,19 +52,20 @@ class LIBARDOUR_API MidiPort : public Port {
bool input_active() const { return _input_active; }
void set_input_active (bool yn);
Buffer& get_buffer (pframes_t nframes);
Buffer& get_buffer (pframes_t nframes) {
return get_midi_buffer (nframes);
}
MidiBuffer& get_midi_buffer (pframes_t nframes);
void set_always_parse (bool yn);
void set_trace_on (bool yn);
void set_trace (MIDI::Parser* trace_parser);
typedef boost::function<bool(MidiBuffer&,MidiBuffer&)> MidiFilter;
void set_inbound_filter (MidiFilter);
int add_shadow_port (std::string const &, MidiFilter);
boost::shared_ptr<MidiPort> shadow_port() const { return _shadow_port; }
MIDI::Parser& self_parser() { return _self_parser; }
void read_and_parse_entire_midi_buffer_with_no_speed_adjustment (pframes_t nframes, MIDI::Parser& parser, samplepos_t now);
protected:
friend class PortManager;
@ -72,29 +74,17 @@ class LIBARDOUR_API MidiPort : public Port {
private:
MidiBuffer* _buffer;
bool _has_been_mixed_down;
bool _resolve_required;
bool _input_active;
bool _always_parse;
bool _trace_on;
MidiFilter inbound_midi_filter;
boost::shared_ptr<MidiPort> _shadow_port;
MidiFilter shadow_midi_filter;
/* Naming this is tricky. AsyncMIDIPort inherits (for now, aug 2013) from
* both MIDI::Port, which has _parser, and this (ARDOUR::MidiPort). We
* need parsing support in this object, independently of what the
* MIDI::Port/AsyncMIDIPort stuff does. Rather than risk errors coming
* from not explicitly naming which _parser we want, we will call this
* _self_parser for now.
*
* Ultimately, MIDI::Port should probably go away or be fully integrated
* into this object, somehow.
*/
MIDI::Parser _self_parser;
MIDI::Parser* _trace_parser;
bool _data_fetched_for_cycle;
void resolve_notes (void* buffer, samplepos_t when);
void pull_input (pframes_t nframes, bool adjust_speed);
void parse_input (pframes_t nframes, MIDI::Parser& parser);
};
} // namespace ARDOUR

View File

@ -62,13 +62,11 @@ class LIBARDOUR_API MidiPortManager {
boost::shared_ptr<ARDOUR::Port> scene_input_port() const { return boost::dynamic_pointer_cast<MidiPort>(_scene_in); }
boost::shared_ptr<ARDOUR::Port> scene_output_port() const { return boost::dynamic_pointer_cast<MidiPort>(_scene_out); }
/* Ports used for synchronization. These have their I/O handled inside the
/* Ports used to send synchronization. These have their output handled inside the
* process callback.
*/
boost::shared_ptr<MidiPort> mtc_input_port() const { return _mtc_input_port; }
boost::shared_ptr<MidiPort> mtc_output_port() const { return _mtc_output_port; }
boost::shared_ptr<MidiPort> midi_clock_input_port() const { return _midi_clock_input_port; }
boost::shared_ptr<MidiPort> midi_clock_output_port() const { return _midi_clock_output_port; }
void set_midi_port_states (const XMLNodeList&);
@ -86,9 +84,7 @@ class LIBARDOUR_API MidiPortManager {
boost::shared_ptr<Port> _scene_out;
/* synchronously handled ports: ARDOUR::MidiPort */
boost::shared_ptr<MidiPort> _mtc_input_port;
boost::shared_ptr<MidiPort> _mtc_output_port;
boost::shared_ptr<MidiPort> _midi_clock_input_port;
boost::shared_ptr<MidiPort> _midi_clock_output_port;
void create_ports ();

View File

@ -123,7 +123,10 @@ public:
virtual void realtime_locate () {}
bool physically_connected () const;
bool externally_connected () const;
uint32_t externally_connected () const { return _externally_connected; }
void increment_external_connections() { _externally_connected++; }
void decrement_external_connections() { if (_externally_connected) _externally_connected--; }
PBD::Signal1<void,bool> MonitorInputChanged;
static PBD::Signal2<void,boost::shared_ptr<Port>,boost::shared_ptr<Port> > PostDisconnect;
@ -170,6 +173,7 @@ private:
std::string _name; ///< port short name
PortFlags _flags; ///< flags
bool _last_monitor;
uint32_t _externally_connected;
/** ports that we are connected to, kept so that we can
reconnect to the backend when required

View File

@ -55,6 +55,7 @@ class LIBARDOUR_API RCConfiguration : public PBD::Configuration
XMLNode * instant_xml (const std::string& str);
XMLNode* control_protocol_state () { return _control_protocol_state; }
XMLNode* transport_master_state () { return _transport_master_state; }
/* define accessor methods */
@ -83,6 +84,7 @@ class LIBARDOUR_API RCConfiguration : public PBD::Configuration
#undef CONFIG_VARIABLE_SPECIAL
XMLNode* _control_protocol_state;
XMLNode* _transport_master_state;
};
/* XXX: rename this */

View File

@ -45,6 +45,11 @@ CONFIG_VARIABLE (bool, strict_io, "strict-io", true)
/* Naming */
CONFIG_VARIABLE (TracksAutoNamingRule, tracks_auto_naming, "tracks-auto-naming", UseDefaultNames)
/* Transport Masters (all) */
CONFIG_VARIABLE (bool, transport_masters_just_roll_when_sync_lost, "transport-masters-just-roll-when-sync-lost", false)
CONFIG_VARIABLE (bool, midi_clock_sets_tempo, "midi-clock-sets-tempo", true)
/* MIDI and MIDI related */
CONFIG_VARIABLE (bool, trace_midi_input, "trace-midi-input", false)
@ -63,15 +68,11 @@ CONFIG_VARIABLE (bool, midi_input_follows_selection, "midi-input-follows-selecti
/* Timecode and related */
CONFIG_VARIABLE (bool, run_all_transport_masters_always, "run-all-transport-masters-always", true)
CONFIG_VARIABLE (bool, use_session_timecode_format, "use-session-timecode-format", true)
CONFIG_VARIABLE (int, mtc_qf_speed_tolerance, "mtc-qf-speed-tolerance", 5)
CONFIG_VARIABLE (bool, timecode_sync_frame_rate, "timecode-sync-frame-rate", true)
#ifdef USE_TRACKS_CODE_FEATURES
CONFIG_VARIABLE (bool, timecode_source_is_synced, "timecode-source-is-synced", true)
#else
CONFIG_VARIABLE (bool, timecode_source_is_synced, "timecode-source-is-synced", false)
#endif
CONFIG_VARIABLE (bool, timecode_source_2997, "timecode-source-2997", false)
#ifdef USE_TRACKS_CODE_FEATURES
CONFIG_VARIABLE (SyncSource, sync_source, "sync-source", MTC)
#else
CONFIG_VARIABLE (SyncSource, sync_source, "sync-source", Engine)

View File

@ -149,11 +149,12 @@ class SceneChanger;
class SessionDirectory;
class SessionMetadata;
class SessionPlaylists;
class Slave;
class Source;
class Speakers;
class TempoMap;
class TransportMaster;
class Track;
class UI_TransportMaster;
class VCAManager;
class WindowsVSTPlugin;
@ -358,10 +359,6 @@ public:
PBD::Signal0<void> IOConnectionsComplete;
/* Timecode status signals */
PBD::Signal1<void, bool> MTCSyncStateChanged;
PBD::Signal1<void, bool> LTCSyncStateChanged;
/* Record status signals */
PBD::Signal0<void> RecordStateChanged; /* signals changes in recording state (i.e. are we recording) */
@ -415,8 +412,8 @@ public:
void request_roll_at_and_return (samplepos_t start, samplepos_t return_to);
void request_bounded_roll (samplepos_t start, samplepos_t end);
void request_stop (bool abort = false, bool clear_state = false);
void request_locate (samplepos_t sample, bool with_roll = false);
void request_stop (bool abort = false, bool clear_state = false, TransportRequestSource origin = TRS_UI);
void request_locate (samplepos_t sample, bool with_roll = false, TransportRequestSource origin = TRS_UI);
void request_play_loop (bool yn, bool leave_rolling = false);
bool get_play_loop () const { return play_loop; }
@ -426,8 +423,8 @@ public:
void goto_start (bool and_roll = false);
void use_rf_shuttle_speed ();
void allow_auto_play (bool yn);
void request_transport_speed (double speed, bool as_default = true);
void request_transport_speed_nonzero (double, bool as_default = true);
void request_transport_speed (double speed, bool as_default = true, TransportRequestSource origin = TRS_UI);
void request_transport_speed_nonzero (double, bool as_default = true, TransportRequestSource origin = TRS_UI);
void request_overwrite_buffer (boost::shared_ptr<Route>);
void adjust_playback_buffering();
void adjust_capture_buffering();
@ -687,6 +684,7 @@ public:
samplepos_t requested_return_sample() const { return _requested_return_sample; }
void set_requested_return_sample(samplepos_t return_to);
bool compute_audible_delta (samplepos_t& pos_and_delta) const;
samplecnt_t remaining_latency_preroll () const { return _remaining_latency_preroll; }
enum PullupFormat {
@ -719,10 +717,8 @@ public:
static PBD::Signal1<void, samplepos_t> StartTimeChanged;
static PBD::Signal1<void, samplepos_t> EndTimeChanged;
void request_sync_source (Slave*);
bool synced_to_engine() const { return _slave && config.get_external_sync() && Config->get_sync_source() == Engine; }
bool synced_to_mtc () const { return config.get_external_sync() && Config->get_sync_source() == MTC && g_atomic_int_get (const_cast<gint*>(&_mtc_active)); }
bool synced_to_ltc () const { return config.get_external_sync() && Config->get_sync_source() == LTC && g_atomic_int_get (const_cast<gint*>(&_ltc_active)); }
void request_sync_source (boost::shared_ptr<TransportMaster>);
bool synced_to_engine() const { return config.get_external_sync() && Config->get_sync_source() == Engine; }
double engine_speed() const { return _engine_speed; }
double actual_speed() const {
@ -1104,7 +1100,7 @@ public:
PostTransportRoll = 0x8,
PostTransportAbort = 0x10,
PostTransportOverWrite = 0x20,
PostTransportSpeed = 0x40,
/* was ... PostTransportSpeed = 0x40, */
PostTransportAudition = 0x80,
PostTransportReverse = 0x100,
PostTransportInputChange = 0x200,
@ -1114,15 +1110,6 @@ public:
PostTransportAdjustCaptureBuffering = 0x2000
};
enum SlaveState {
Stopped,
Waiting,
Running
};
SlaveState slave_state() const { return _slave_state; }
Slave* slave() const { return _slave; }
boost::shared_ptr<SessionPlaylists> playlists;
void send_mmc_locate (samplepos_t);
@ -1189,22 +1176,16 @@ public:
/* synchronous MIDI ports used for synchronization */
boost::shared_ptr<MidiPort> midi_clock_output_port () const;
boost::shared_ptr<MidiPort> midi_clock_input_port () const;
boost::shared_ptr<MidiPort> mtc_output_port () const;
boost::shared_ptr<MidiPort> mtc_input_port () const;
boost::shared_ptr<Port> ltc_input_port() const;
boost::shared_ptr<Port> ltc_output_port() const;
boost::shared_ptr<IO> ltc_input_io() { return _ltc_input; }
boost::shared_ptr<IO> ltc_output_io() { return _ltc_output; }
MIDI::MachineControl& mmc() { return *_mmc; }
void reconnect_midi_scene_ports (bool);
void reconnect_mtc_ports ();
void reconnect_mmc_ports (bool);
void reconnect_ltc_input ();
void reconnect_ltc_output ();
VCAManager& vca_manager() { return *_vca_manager; }
@ -1212,6 +1193,9 @@ public:
void auto_connect_thread_wakeup ();
double compute_speed_from_master (pframes_t nframes);
bool transport_master_is_external() const;
boost::shared_ptr<TransportMaster> transport_master() const;
protected:
friend class AudioEngine;
@ -1254,7 +1238,6 @@ private:
gint _seek_counter;
Location* _session_range_location; ///< session range, or 0 if there is nothing in the session yet
bool _session_range_end_is_free;
Slave* _slave;
bool _silent;
samplecnt_t _remaining_latency_preroll;
@ -1267,7 +1250,6 @@ private:
double _target_transport_speed;
bool auto_play_legal;
samplepos_t _last_slave_transport_sample;
samplepos_t _requested_return_sample;
pframes_t current_block_size;
samplecnt_t _worst_output_latency;
@ -1286,11 +1268,6 @@ private:
std::string _missing_file_replacement;
void mtc_status_changed (bool);
PBD::ScopedConnection mtc_status_connection;
void ltc_status_changed (bool);
PBD::ScopedConnection ltc_status_connection;
void initialize_latencies ();
void update_latency (bool playback);
bool update_route_latency (bool reverse, bool apply_to_delayline);
@ -1317,28 +1294,21 @@ private:
static const samplecnt_t bounce_chunk_size;
/* slave tracking */
/* Transport master DLL */
static const int delta_accumulator_size = 25;
int delta_accumulator_cnt;
int32_t delta_accumulator[delta_accumulator_size];
int32_t average_slave_delta;
int average_dir;
bool have_first_delta_accumulator;
enum TransportMasterState {
Stopped, /* no incoming or invalid signal/data for master to run with */
Waiting, /* waiting to get full lock on incoming signal/data */
Running /* lock achieved, master is generating meaningful speed & position */
};
SlaveState _slave_state;
gint _mtc_active;
gint _ltc_active;
samplepos_t slave_wait_end;
TransportMasterState transport_master_tracking_state;
samplepos_t master_wait_end;
void track_transport_master (float slave_speed, samplepos_t slave_transport_sample);
bool follow_transport_master (pframes_t nframes);
void sync_source_changed (SyncSource, samplepos_t pos, pframes_t cycle_nframes);
void reset_slave_state ();
bool follow_slave (pframes_t);
void calculate_moving_average_of_slave_delta (int dir, samplecnt_t this_delta);
void track_slave_state (float slave_speed, samplepos_t slave_transport_sample, samplecnt_t this_delta);
void switch_to_sync_source (SyncSource); /* !RT context */
void drop_sync_source (); /* !RT context */
void use_sync_source (Slave*); /* RT context */
bool post_export_sync;
samplepos_t post_export_position;
@ -1673,6 +1643,8 @@ private:
int start_midi_thread ();
bool should_ignore_transport_request (TransportRequestSource, TransportRequestType) const;
void set_play_loop (bool yn, double speed);
void unset_play_loop ();
void overwrite_some_buffers (Track *);
@ -2048,7 +2020,6 @@ private:
MidiClockTicker* midi_clock;
boost::shared_ptr<IO> _ltc_input;
boost::shared_ptr<IO> _ltc_output;
boost::shared_ptr<RTTaskList> _rt_tasklist;

View File

@ -33,7 +33,7 @@
namespace ARDOUR {
class Slave;
class TransportMaster;
class Region;
class LIBARDOUR_API SessionEvent {
@ -49,7 +49,6 @@ public:
RangeStop,
RangeLocate,
Overwrite,
SetSyncSource,
Audition,
SetPlayAudioRange,
CancelPlayAudioRange,
@ -58,6 +57,7 @@ public:
AdjustCaptureBuffering,
SetTimecodeTransmission,
Skip,
SetTransportMaster,
/* only one of each of these events can be queued at any one time */
@ -79,11 +79,10 @@ public:
double speed;
union {
void* ptr;
bool yes_or_no;
samplepos_t target2_sample;
Slave* slave;
Route* route;
void* ptr;
bool yes_or_no;
samplepos_t target2_sample;
Route* route;
};
union {
@ -109,6 +108,7 @@ public:
std::list<MusicRange> music_range;
boost::shared_ptr<Region> region;
boost::shared_ptr<TransportMaster> transport_master;
SessionEvent (Type t, Action a, samplepos_t when, samplepos_t where, double spd, bool yn = false, bool yn2 = false, bool yn3 = false);

View File

@ -36,7 +36,7 @@
#include "midi++/types.h"
/* used for approximate_current_delta(): */
/* used for delta_string(): */
#define PLUSMINUS(A) ( ((A)<0) ? "-" : (((A)>0) ? "+" : "\u00B1") )
#define LEADINGZERO(A) ( (A)<10 ? " " : (A)<100 ? " " : (A)<1000 ? " " : "" )
@ -175,7 +175,7 @@ class LIBARDOUR_API Slave {
/**
* @return - current time-delta between engine and sync-source
*/
virtual std::string approximate_current_delta() const { return ""; }
virtual std::string delta_string () const { return ""; }
};
@ -191,11 +191,6 @@ class LIBARDOUR_API ISlaveSessionProxy {
virtual pframes_t samples_since_cycle_start () const { return 0; }
virtual samplepos_t sample_time_at_cycle_start() const { return 0; }
virtual samplepos_t sample_time () const { return 0; }
virtual void request_locate (samplepos_t /*sample*/, bool with_roll = false) {
(void) with_roll;
}
virtual void request_transport_speed (double /*speed*/) {}
};
@ -214,9 +209,6 @@ class LIBARDOUR_API SlaveSessionProxy : public ISlaveSessionProxy {
pframes_t samples_since_cycle_start () const;
samplepos_t sample_time_at_cycle_start() const;
samplepos_t sample_time () const;
void request_locate (samplepos_t sample, bool with_roll = false);
void request_transport_speed (double speed);
};
struct LIBARDOUR_API SafeTime {
@ -246,7 +238,7 @@ class LIBARDOUR_API TimecodeSlave : public Slave {
should NOT do any computation, but should use a cached value
of the TC source position.
*/
virtual std::string approximate_current_position() const = 0;
virtual std::string position_string () const = 0;
samplepos_t timecode_offset;
bool timecode_negative_offset;
@ -272,8 +264,8 @@ class LIBARDOUR_API MTC_Slave : public TimecodeSlave {
bool give_slave_full_control_over_transport_speed() const;
Timecode::TimecodeFormat apparent_timecode_format() const;
std::string approximate_current_position() const;
std::string approximate_current_delta() const;
std::string position_string () const;
std::string delta_string () const;
private:
Session& session;
@ -354,8 +346,8 @@ public:
bool give_slave_full_control_over_transport_speed() const { return true; }
Timecode::TimecodeFormat apparent_timecode_format() const;
std::string approximate_current_position() const;
std::string approximate_current_delta() const;
std::string position_string() const;
std::string delta_string() const;
private:
void parse_ltc(const pframes_t, const Sample* const, const samplecnt_t);
@ -427,7 +419,7 @@ class LIBARDOUR_API MIDIClock_Slave : public Slave {
bool give_slave_full_control_over_transport_speed() const { return true; }
void set_bandwidth (double a_bandwith) { bandwidth = a_bandwith; }
std::string approximate_current_delta() const;
std::string delta_string () const;
protected:
ISlaveSessionProxy* session;

View File

@ -140,7 +140,7 @@ public:
int can_internal_playback_seek (samplecnt_t);
int internal_playback_seek (samplecnt_t);
void non_realtime_locate (samplepos_t);
void non_realtime_speed_change ();
void realtime_handle_transport_stopped ();
int overwrite_existing_buffers ();
samplecnt_t get_captured_samples (uint32_t n = 0) const;
void transport_looped (samplepos_t);

View File

@ -0,0 +1,546 @@
/*
Copyright (C) 2002 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_transport_master_h__
#define __ardour_transport_master_h__
#include <vector>
#include <boost/atomic.hpp>
#include <boost/optional.hpp>
#include <glibmm/threads.h>
#include <ltc.h>
#include "pbd/signals.h"
#include "temporal/time.h"
#include "ardour/libardour_visibility.h"
#include "ardour/types.h"
#include "midi++/parser.h"
#include "midi++/types.h"
/* used for delta_string(): */
#define PLUSMINUS(A) ( ((A)<0) ? "-" : (((A)>0) ? "+" : "\u00B1") )
#define LEADINGZERO(A) ( (A)<10 ? " " : (A)<100 ? " " : (A)<1000 ? " " : "" )
namespace ARDOUR {
class TempoMap;
class Session;
class AudioEngine;
class Location;
class MidiPort;
class AudioPort;
class Port;
/**
* @class TransportMaster
*
* @brief The TransportMaster interface can be used to sync ARDOURs tempo to an external source
* like MTC, MIDI Clock, etc. as well as a single internal pseudo master we
* call "UI" because it is controlled from any of the user interfaces for
* Ardour (GUI, control surfaces, OSC, etc.)
*
*/
class LIBARDOUR_API TransportMaster {
public:
TransportMaster (SyncSource t, std::string const & name);
virtual ~TransportMaster();
static boost::shared_ptr<TransportMaster> factory (SyncSource, std::string const &);
static boost::shared_ptr<TransportMaster> factory (XMLNode const &);
virtual void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>) = 0;
/**
* This is the most important function to implement:
* Each process cycle, Session::follow_slave will call this method.
* and after the method call they should
*
* Session::follow_slave will then try to follow the given
* <em>position</em> using a delay locked loop (DLL),
* starting with the first given transport speed.
* If the values of speed and position contradict each other,
* ARDOUR will always follow the position and disregard the speed.
* Although, a correct speed is important so that ARDOUR
* can sync to the master time source quickly.
*
* For background information on delay locked loops,
* see http://www.kokkinizita.net/papers/usingdll.pdf
*
* The method has the following precondition:
* <ul>
* <li>
* TransportMaster::ok() should return true, otherwise playback will stop
* immediately and the method will not be called
* </li>
* <li>
* when the references speed and position are passed into the TransportMaster
* they are uninitialized
* </li>
* </ul>
*
* After the method call the following postconditions should be met:
* <ul>
* <li>
* The first position value on transport start should be 0,
* otherwise ARDOUR will try to locate to the new position
* rather than move to it
* </li>
* <li>
* the references speed and position should be assigned
* to the TransportMasters current requested transport speed
* and transport position.
* </li>
* <li>
* TransportMaster::resolution() should be greater than the maximum distance of
* ARDOURs transport position to the slaves requested transport position.
* </li>
* <li>TransportMaster::locked() should return true, otherwise Session::no_roll will be called</li>
* <li>TransportMaster::starting() should be false, otherwise the transport will not move until it becomes true</li> *
* </ul>
*
* @param speed - The transport speed requested
* @param position - The transport position requested
* @return - The return value is currently ignored (see Session::follow_slave)
*/
virtual bool speed_and_position (double& speed, samplepos_t& position, samplepos_t now) = 0;
/**
* reports to ARDOUR whether the TransportMaster is currently synced to its external
* time source.
*
* @return - when returning false, the transport will stop rolling
*/
virtual bool locked() const = 0;
/**
* reports to ARDOUR whether the slave is in a sane state
*
* @return - when returning false, the transport will be stopped and the slave
* disconnected from ARDOUR.
*/
virtual bool ok() const = 0;
/**
* reports to ARDOUR whether the slave is in the process of starting
* to roll
*
* @return - when returning false, transport will not move until this method returns true
*/
virtual bool starting() const { return false; }
/**
* @return - the timing resolution of the TransportMaster - If the distance of ARDOURs transport
* to the slave becomes greater than the resolution, sound will stop
*/
virtual samplecnt_t resolution() const = 0;
/**
* @return - when returning true, ARDOUR will wait for seekahead_distance() before transport
* starts rolling
*/
virtual bool requires_seekahead () const = 0;
/**
* @return the number of samples that this slave wants to seek ahead. Relevant
* only if requires_seekahead() returns true.
*/
virtual samplecnt_t seekahead_distance() const { return 0; }
/**
* @return - when returning true, ARDOUR will use transport speed 1.0 no matter what
* the slave returns
*/
virtual bool sample_clock_synced() const { return _sclock_synced; }
virtual void set_sample_clock_synced (bool);
/**
* @return - current time-delta between engine and sync-source
*/
virtual std::string delta_string() const { return ""; }
sampleoffset_t current_delta() const { return _current_delta; }
/* this is intended to be used by a UI and polled from a timeout. it should
return a string describing the current position of the TC source. it
should NOT do any computation, but should use a cached value
of the TC source position.
*/
virtual std::string position_string() const = 0;
virtual bool can_loop() const { return false; }
virtual Location* loop_location() const { return 0; }
bool has_loop() const { return loop_location() != 0; }
SyncSource type() const { return _type; }
TransportRequestSource request_type() const {
switch (_type) {
case Engine: /* also JACK */
return TRS_Engine;
case MTC:
return TRS_MTC;
case LTC:
return TRS_LTC;
case MIDIClock:
break;
}
return TRS_MIDIClock;
}
std::string name() const { return _name; }
void set_name (std::string const &);
int set_state (XMLNode const &, int);
XMLNode& get_state();
static const std::string state_node_name;
virtual void set_session (Session*);
boost::shared_ptr<Port> port() const { return _port; }
bool check_collect();
virtual void set_collect (bool);
bool collect() const { return _collect; }
/* called whenever the manager starts collecting (processing) this
transport master. Typically will re-initialize any state used to
deal with incoming data.
*/
virtual void init() = 0;
virtual void check_backend() {}
virtual bool allow_request (TransportRequestSource, TransportRequestType) const;
TransportRequestType request_mask() const { return _request_mask; }
void set_request_mask (TransportRequestType);
protected:
SyncSource _type;
std::string _name;
Session* _session;
bool _connected;
sampleoffset_t _current_delta;
bool _collect;
bool _pending_collect;
TransportRequestType _request_mask; /* lists transport requests still accepted when we're in control */
bool _sclock_synced;
/* DLL - chase incoming data */
int transport_direction;
int dll_initstate;
double t0;
double t1;
double e2;
double b, c;
boost::shared_ptr<Port> _port;
PBD::ScopedConnection port_connection;
bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
PBD::ScopedConnection backend_connection;
};
struct LIBARDOUR_API SafeTime {
volatile int guard1;
samplepos_t position;
samplepos_t timestamp;
double speed;
volatile int guard2;
SafeTime() {
guard1 = 0;
position = 0;
timestamp = 0;
speed = 0;
guard2 = 0;
}
};
/** a helper class for any TransportMaster that receives its input via a MIDI
* port
*/
class LIBARDOUR_API TransportMasterViaMIDI {
public:
boost::shared_ptr<MidiPort> midi_port() const { return _midi_port; }
boost::shared_ptr<Port> create_midi_port (std::string const & port_name);
protected:
TransportMasterViaMIDI () {};
MIDI::Parser parser;
boost::shared_ptr<MidiPort> _midi_port;
};
class LIBARDOUR_API TimecodeTransportMaster : public TransportMaster {
public:
TimecodeTransportMaster (std::string const & name, SyncSource type) : TransportMaster (type, name) {}
virtual Timecode::TimecodeFormat apparent_timecode_format() const = 0;
samplepos_t timecode_offset;
bool timecode_negative_offset;
bool fr2997() const { return _fr2997; }
void set_fr2997 (bool);
private:
bool _fr2997;
};
class LIBARDOUR_API MTC_TransportMaster : public TimecodeTransportMaster, public TransportMasterViaMIDI {
public:
MTC_TransportMaster (std::string const &);
~MTC_TransportMaster ();
void set_session (Session*);
void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
bool speed_and_position (double&, samplepos_t&, samplepos_t);
bool locked() const;
bool ok() const;
void handle_locate (const MIDI::byte*);
samplecnt_t resolution () const;
bool requires_seekahead () const { return false; }
samplecnt_t seekahead_distance() const;
void init ();
Timecode::TimecodeFormat apparent_timecode_format() const;
std::string position_string() const;
std::string delta_string() const;
private:
PBD::ScopedConnectionList port_connections;
PBD::ScopedConnection config_connection;
bool can_notify_on_unknown_rate;
static const int sample_tolerance;
SafeTime current;
samplepos_t mtc_frame; /* current time */
double mtc_frame_dll;
samplepos_t last_inbound_frame; /* when we got it; audio clocked */
MIDI::byte last_mtc_fps_byte;
samplepos_t window_begin;
samplepos_t window_end;
samplepos_t first_mtc_timestamp;
bool did_reset_tc_format;
Timecode::TimecodeFormat saved_tc_format;
Glib::Threads::Mutex reset_lock;
uint32_t reset_pending;
bool reset_position;
int transport_direction;
int busy_guard1;
int busy_guard2;
double speedup_due_to_tc_mismatch;
double quarter_frame_duration;
Timecode::TimecodeFormat mtc_timecode;
Timecode::TimecodeFormat a3e_timecode;
Timecode::Time timecode;
bool printed_timecode_warning;
void reset (bool with_pos);
void queue_reset (bool with_pos);
void maybe_reset ();
void update_mtc_qtr (MIDI::Parser&, int, samplepos_t);
void update_mtc_time (const MIDI::byte *, bool, samplepos_t);
void update_mtc_status (MIDI::MTC_Status);
void read_current (SafeTime *) const;
void reset_window (samplepos_t);
bool outside_window (samplepos_t) const;
void init_mtc_dll(samplepos_t, double);
void parse_timecode_offset();
void parameter_changed(std::string const & p);
};
class LIBARDOUR_API LTC_TransportMaster : public TimecodeTransportMaster {
public:
LTC_TransportMaster (std::string const &);
~LTC_TransportMaster ();
void set_session (Session*);
void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
bool speed_and_position (double&, samplepos_t&, samplepos_t);
bool locked() const;
bool ok() const;
samplecnt_t resolution () const;
bool requires_seekahead () const { return false; }
samplecnt_t seekahead_distance () const { return 0; }
void init ();
Timecode::TimecodeFormat apparent_timecode_format() const;
std::string position_string() const;
std::string delta_string() const;
private:
void parse_ltc(const pframes_t, const Sample* const, const samplecnt_t);
void process_ltc(samplepos_t const);
void init_dll (samplepos_t, int32_t);
bool detect_discontinuity(LTCFrameExt *, int, bool);
bool detect_ltc_fps(int, bool);
bool equal_ltc_sample_time(LTCFrame *a, LTCFrame *b);
void reset (bool with_ts = true);
void resync_xrun();
void resync_latency();
void parse_timecode_offset();
void parameter_changed(std::string const & p);
bool did_reset_tc_format;
Timecode::TimecodeFormat saved_tc_format;
LTCDecoder * decoder;
double samples_per_ltc_frame;
Timecode::Time timecode;
LTCFrameExt prev_sample;
bool fps_detected;
samplecnt_t monotonic_cnt;
samplecnt_t last_timestamp;
samplecnt_t last_ltc_sample;
double ltc_speed;
int delayedlocked;
int ltc_detect_fps_cnt;
int ltc_detect_fps_max;
bool printed_timecode_warning;
bool sync_lock_broken;
Timecode::TimecodeFormat ltc_timecode;
Timecode::TimecodeFormat a3e_timecode;
double samples_per_timecode_frame;
PBD::ScopedConnectionList port_connections;
PBD::ScopedConnection config_connection;
LatencyRange ltc_slave_latency;
};
class LIBARDOUR_API MIDIClock_TransportMaster : public TransportMaster, public TransportMasterViaMIDI {
public:
MIDIClock_TransportMaster (std::string const & name, int ppqn = 24);
/// Constructor for unit tests
~MIDIClock_TransportMaster ();
void set_session (Session*);
void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
void rebind (MidiPort&);
bool speed_and_position (double&, samplepos_t&, samplepos_t);
bool locked() const;
bool ok() const;
bool starting() const;
samplecnt_t resolution () const;
bool requires_seekahead () const { return false; }
void init ();
std::string position_string() const;
std::string delta_string() const;
float bpm() const { return _bpm; }
protected:
PBD::ScopedConnectionList port_connections;
/// pulses per quarter note for one MIDI clock sample (default 24)
int ppqn;
/// the duration of one ppqn in sample time
double one_ppqn_in_samples;
/// the timestamp of the first MIDI clock message
samplepos_t first_timestamp;
/// the time stamp and should-be transport position of the last inbound MIDI clock message
samplepos_t last_timestamp;
double should_be_position;
/// the number of midi clock messages received (zero-based)
/// since start
long midi_clock_count;
/// a DLL to track MIDI clock
double _speed;
bool _running;
double _bpm;
void reset ();
void start (MIDI::Parser& parser, samplepos_t timestamp);
void contineu (MIDI::Parser& parser, samplepos_t timestamp);
void stop (MIDI::Parser& parser, samplepos_t timestamp);
void position (MIDI::Parser& parser, MIDI::byte* message, size_t size);
// we can't use continue because it is a C++ keyword
void calculate_one_ppqn_in_samples_at(samplepos_t time);
samplepos_t calculate_song_position(uint16_t song_position_in_sixteenth_notes);
void calculate_filter_coefficients (double qpm);
void update_midi_clock (MIDI::Parser& parser, samplepos_t timestamp);
void read_current (SafeTime *) const;
};
class LIBARDOUR_API Engine_TransportMaster : public TransportMaster
{
public:
Engine_TransportMaster (AudioEngine&);
~Engine_TransportMaster ();
void pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t>);
bool speed_and_position (double& speed, samplepos_t& pos, samplepos_t);
bool starting() const { return _starting; }
bool locked() const;
bool ok() const;
samplecnt_t resolution () const { return 1; }
bool requires_seekahead () const { return false; }
bool sample_clock_synced() const { return true; }
void init ();
void check_backend();
bool allow_request (TransportRequestSource, TransportRequestType) const;
std::string position_string() const;
std::string delta_string() const;
private:
AudioEngine& engine;
bool _starting;
};
} /* namespace */
#endif /* __ardour_transport_master_h__ */

View File

@ -0,0 +1,114 @@
/*
* Copyright (C) 2018 Paul Davis (paul@linuxaudiosystems.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __ardour_transport_master_manager_h__
#define __ardour_transport_master_manager_h__
#include <string>
#include <boost/noncopyable.hpp>
#include "ardour/transport_master.h"
#include "ardour/types.h"
namespace ARDOUR {
class UI_TransportMaster;
class LIBARDOUR_API TransportMasterManager : public boost::noncopyable
{
public:
~TransportMasterManager ();
int init ();
static TransportMasterManager& instance();
typedef std::list<boost::shared_ptr<TransportMaster> > TransportMasters;
int add (SyncSource type, std::string const & name);
int remove (std::string const & name);
void clear ();
PBD::Signal1<void,boost::shared_ptr<TransportMaster> > Added;
PBD::Signal1<void,boost::shared_ptr<TransportMaster> > Removed; // null argument means "clear"
double pre_process_transport_masters (pframes_t, samplepos_t session_transport_position);
double get_current_speed_in_process_context() const { return _master_speed; }
samplepos_t get_current_position_in_process_context() const { return _master_position; }
boost::shared_ptr<TransportMaster> current() const { return _current_master; }
int set_current (boost::shared_ptr<TransportMaster>);
int set_current (SyncSource);
int set_current (std::string const &);
PBD::Signal2<void,boost::shared_ptr<TransportMaster>, boost::shared_ptr<TransportMaster> > CurrentChanged;
int set_state (XMLNode const &, int);
XMLNode& get_state();
void set_session (Session*);
Session* session() const { return _session; }
bool master_invalid_this_cycle() const { return _master_invalid_this_cycle; }
boost::shared_ptr<TransportMaster> master_by_type (SyncSource src) const;
boost::shared_ptr<TransportMaster> master_by_name (std::string const &) const;
TransportMasters const & transport_masters() const { return _transport_masters; }
static const std::string state_node_name;
private:
TransportMasterManager();
TransportMasters _transport_masters;
mutable Glib::Threads::RWLock lock;
double _master_speed;
samplepos_t _master_position;
boost::shared_ptr<TransportMaster> _current_master;
Session* _session;
bool _master_invalid_this_cycle;
// a DLL to chase the transport master
int transport_dll_initstate;
double t0; /// time at the beginning of ???
double t1; /// calculated end of the ???
double e2; /// second order loop error
double bandwidth; /// DLL filter bandwidth
double b, c, omega; /// DLL filter coefficients
void init_transport_master_dll (double speed, samplepos_t pos);
int master_dll_initstate;
static TransportMasterManager* _instance;
int add_locked (boost::shared_ptr<TransportMaster>);
double compute_matching_master_speed (pframes_t nframes, samplepos_t, bool& locate_required);
int set_current_locked (boost::shared_ptr<TransportMaster>);
PBD::ScopedConnection config_connection;
void parameter_changed (std::string const & what);
};
} // namespace ARDOUR
#endif /* __ardour_transport_master_manager_h__ */

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@
using namespace ARDOUR;
using namespace std;
#define ENGINE AudioEngine::instance()
#define port_engine AudioEngine::instance()->port_engine()
AudioPort::AudioPort (const std::string& name, PortFlags flags)
@ -121,12 +122,18 @@ AudioPort::get_audio_buffer (pframes_t nframes)
{
/* caller must hold process lock */
assert (_port_handle);
Sample* addr;
if (!externally_connected ()) {
_buffer->set_data ((Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes) +
_global_port_buffer_offset, nframes);
addr = (Sample *) port_engine.get_buffer (_port_handle, nframes);
} else {
_buffer->set_data (&_data[_global_port_buffer_offset], nframes);
/* _data was read and resampled as necessary in ::cycle_start */
addr = &_data[_global_port_buffer_offset];
}
_buffer->set_data (addr, nframes);
return *_buffer;
}
@ -135,5 +142,5 @@ AudioPort::engine_get_whole_audio_buffer ()
{
/* caller must hold process lock */
assert (_port_handle);
return (Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes);
return (Sample *) port_engine.get_buffer (_port_handle, ENGINE->samples_per_cycle());
}

View File

@ -55,6 +55,7 @@
#include "ardour/process_thread.h"
#include "ardour/rc_configuration.h"
#include "ardour/session.h"
#include "ardour/transport_master_manager.h"
#include "pbd/i18n.h"
@ -77,7 +78,7 @@ AudioEngine::AudioEngine ()
, _freewheeling (false)
, monitor_check_interval (INT32_MAX)
, last_monitor_check (0)
, _processed_samples (0)
, _processed_samples (-1)
, m_meter_thread (0)
, _main_thread (0)
, _mtdm (0)
@ -198,6 +199,11 @@ AudioEngine::process_callback (pframes_t nframes)
/// The number of samples that will have been processed when we've finished
pframes_t next_processed_samples;
if (_processed_samples < 0) {
_processed_samples = sample_time();
cerr << "IIIIINIT PS to " << _processed_samples << endl;
}
/* handle wrap around of total samples counter */
if (max_samplepos - _processed_samples < nframes) {
@ -346,6 +352,14 @@ AudioEngine::process_callback (pframes_t nframes)
return 0;
}
TransportMasterManager& tmm (TransportMasterManager::instance());
/* make sure the TMM is up to date about the current session */
if (_session != tmm.session()) {
tmm.set_session (_session);
}
if (_session == 0) {
if (!_freewheeling) {
@ -358,16 +372,9 @@ AudioEngine::process_callback (pframes_t nframes)
}
if (!_freewheeling || Freewheel.empty()) {
// TODO: Run a list of slaves here
// - multiple TC slaves (how_many_dsp_threads() in parallel)
// (note this can be multiple slaves of each type. e.g.
// 3 LTC slaves on different ports, 2 MTC..)
// - GUI can display all slaves, user picks one.
// - active "slave" is a session property.
// - here we ask the session about the active slave
// and get playback speed (for this cycle) here.
// - Internal Transport is-a Slave too (!)
Port::set_speed_ratio (_session->engine_speed ()); // HACK
const double engine_speed = tmm.pre_process_transport_masters (nframes, sample_time_at_cycle_start());
Port::set_speed_ratio (engine_speed);
DEBUG_TRACE (DEBUG::Slave, string_compose ("transport master (current=%1) gives speed %2 (ports using %3)\n", tmm.current() ? tmm.current()->name() : string("[]"), engine_speed, Port::speed_ratio()));
}
/* tell all relevant objects that we're starting a new cycle */

View File

@ -40,6 +40,7 @@ PBD::DebugBits PBD::DEBUG::Graph = PBD::new_debug_bit ("graph");
PBD::DebugBits PBD::DEBUG::Destruction = PBD::new_debug_bit ("destruction");
PBD::DebugBits PBD::DEBUG::MTC = PBD::new_debug_bit ("mtc");
PBD::DebugBits PBD::DEBUG::LTC = PBD::new_debug_bit ("ltc");
PBD::DebugBits PBD::DEBUG::TXLTC = PBD::new_debug_bit ("tx-ltc");
PBD::DebugBits PBD::DEBUG::Transport = PBD::new_debug_bit ("transport");
PBD::DebugBits PBD::DEBUG::Slave = PBD::new_debug_bit ("slave");
PBD::DebugBits PBD::DEBUG::SessionEvents = PBD::new_debug_bit ("sessionevents");

View File

@ -50,7 +50,6 @@ DiskIOProcessor::DiskIOProcessor (Session& s, string const & str, Flag f)
: Processor (s, str)
, _flags (f)
, i_am_the_modifier (false)
, _seek_required (false)
, _slaved (false)
, in_set_state (false)
, playback_sample (0)
@ -206,21 +205,6 @@ DiskIOProcessor::non_realtime_locate (samplepos_t location)
seek (location, true);
}
void
DiskIOProcessor::non_realtime_speed_change ()
{
if (_seek_required) {
seek (_session.transport_sample(), true);
_seek_required = false;
}
}
bool
DiskIOProcessor::realtime_speed_change ()
{
return true;
}
int
DiskIOProcessor::set_state (const XMLNode& node, int version)
{

View File

@ -20,47 +20,104 @@
#include <iostream>
#include <cerrno>
#include "pbd/i18n.h"
#include "ardour/audioengine.h"
#include "ardour/audio_backend.h"
#include "ardour/slave.h"
#include "ardour/session.h"
#include "ardour/transport_master.h"
using namespace std;
using namespace ARDOUR;
Engine_Slave::Engine_Slave (AudioEngine& e)
: engine (e)
Engine_TransportMaster::Engine_TransportMaster (AudioEngine& e)
: TransportMaster (Engine, X_("JACK"))
, engine (e)
, _starting (false)
{
double x;
samplepos_t p;
/* call this to initialize things */
speed_and_position (x, p);
check_backend ();
}
Engine_Slave::~Engine_Slave ()
Engine_TransportMaster::~Engine_TransportMaster ()
{
}
void
Engine_TransportMaster::init ()
{
}
void
Engine_TransportMaster::check_backend()
{
if (AudioEngine::instance()->current_backend_name() == X_("JACK")) {
_connected = true;
} else {
_connected = false;
}
}
bool
Engine_Slave::locked() const
Engine_TransportMaster::locked() const
{
return true;
}
bool
Engine_Slave::ok() const
Engine_TransportMaster::ok() const
{
return true;
}
void
Engine_TransportMaster::pre_process (pframes_t, samplepos_t, boost::optional<samplepos_t>)
{
/* nothing to do */
}
bool
Engine_Slave::speed_and_position (double& sp, samplepos_t& position)
Engine_TransportMaster::speed_and_position (double& sp, samplepos_t& position, samplepos_t /* now */)
{
boost::shared_ptr<AudioBackend> backend = engine.current_backend();
if (backend) {
_starting = backend->speed_and_position (sp, position);
} else {
_starting = false;
/* 3rd argument (now) doesn't matter here because we're always being
* called synchronously with the engine.
*/
if (backend && backend->speed_and_position (sp, position)) {
return true;
}
_current_delta = 0;
return false;
}
std::string
Engine_TransportMaster::position_string () const
{
if (_session) {
return to_string (_session->audible_sample());
}
return std::string();
}
std::string
Engine_TransportMaster::delta_string () const
{
return string ("0");
}
bool
Engine_TransportMaster::allow_request (TransportRequestSource src, TransportRequestType type) const
{
if (_session) {
if (_session->config.get_jack_time_master()) {
return true;
} else {
return false;
}
}
return true;

View File

@ -38,6 +38,7 @@
#include "ardour/source.h"
#include "ardour/tempo.h"
#include "ardour/track.h"
#include "ardour/transport_master.h"
#include "ardour/types.h"
using namespace std;
@ -131,7 +132,6 @@ setup_enum_writer ()
WaveformScale _WaveformScale;
WaveformShape _WaveformShape;
Session::PostTransportWork _Session_PostTransportWork;
Session::SlaveState _Session_SlaveState;
MTC_Status _MIDI_MTC_Status;
Evoral::OverlapType _OverlapType;
BufferingPreset _BufferingPreset;
@ -139,7 +139,7 @@ setup_enum_writer ()
PresentationInfo::Flag _PresentationInfo_Flag;
MusicalMode::Type mode;
MidiPortFlags _MidiPortFlags;
#define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
#define REGISTER_BITS(e) enum_writer.register_bits (typeid(e).name(), i, s); i.clear(); s.clear()
#define REGISTER_ENUM(e) i.push_back (e); s.push_back (#e)
@ -420,7 +420,6 @@ setup_enum_writer ()
REGISTER_CLASS_ENUM (SessionEvent, RangeStop);
REGISTER_CLASS_ENUM (SessionEvent, RangeLocate);
REGISTER_CLASS_ENUM (SessionEvent, Overwrite);
REGISTER_CLASS_ENUM (SessionEvent, SetSyncSource);
REGISTER_CLASS_ENUM (SessionEvent, Audition);
REGISTER_CLASS_ENUM (SessionEvent, SetPlayAudioRange);
REGISTER_CLASS_ENUM (SessionEvent, CancelPlayAudioRange);
@ -429,6 +428,7 @@ setup_enum_writer ()
REGISTER_CLASS_ENUM (SessionEvent, AdjustCaptureBuffering);
REGISTER_CLASS_ENUM (SessionEvent, SetTimecodeTransmission);
REGISTER_CLASS_ENUM (SessionEvent, Skip);
REGISTER_CLASS_ENUM (SessionEvent, SetTransportMaster);
REGISTER_CLASS_ENUM (SessionEvent, StopOnce);
REGISTER_CLASS_ENUM (SessionEvent, AutoLoop);
REGISTER (_SessionEvent_Type);
@ -439,11 +439,6 @@ setup_enum_writer ()
REGISTER_CLASS_ENUM (SessionEvent, Clear);
REGISTER (_SessionEvent_Action);
REGISTER_CLASS_ENUM (Session, Stopped);
REGISTER_CLASS_ENUM (Session, Waiting);
REGISTER_CLASS_ENUM (Session, Running);
REGISTER (_Session_SlaveState);
REGISTER_ENUM (MTC_Stopped);
REGISTER_ENUM (MTC_Forward);
REGISTER_ENUM (MTC_Backward);
@ -455,7 +450,6 @@ setup_enum_writer ()
REGISTER_CLASS_ENUM (Session, PostTransportRoll);
REGISTER_CLASS_ENUM (Session, PostTransportAbort);
REGISTER_CLASS_ENUM (Session, PostTransportOverWrite);
REGISTER_CLASS_ENUM (Session, PostTransportSpeed);
REGISTER_CLASS_ENUM (Session, PostTransportAudition);
REGISTER_CLASS_ENUM (Session, PostTransportReverse);
REGISTER_CLASS_ENUM (Session, PostTransportInputChange);

View File

@ -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;

View File

@ -23,11 +23,12 @@
#include <unistd.h>
#include "pbd/error.h"
#include "pbd/failed_constructor.h"
#include "pbd/pthread_utils.h"
#include "ardour/debug.h"
#include "ardour/profile.h"
#include "ardour/slave.h"
#include "ardour/transport_master.h"
#include "ardour/session.h"
#include "ardour/audioengine.h"
#include "ardour/audio_port.h"
@ -40,61 +41,101 @@ using namespace MIDI;
using namespace PBD;
using namespace Timecode;
#define FLYWHEEL_TIMEOUT ( 1 * session.sample_rate() )
#define ENGINE AudioEngine::instance()
#define FLYWHEEL_TIMEOUT ( 1 * ENGINE->sample_rate() )
LTC_Slave::LTC_Slave (Session& s)
: session (s)
/* XXX USE Config->get_ltc_input */
LTC_TransportMaster::LTC_TransportMaster (std::string const & name)
: TimecodeTransportMaster (name, LTC)
, did_reset_tc_format (false)
, decoder (0)
, samples_per_ltc_frame (0)
, fps_detected (false)
, monotonic_cnt (0)
, last_timestamp (0)
, last_ltc_sample (0)
, delayedlocked (10)
, ltc_detect_fps_cnt (0)
, ltc_detect_fps_max (0)
, sync_lock_broken (false)
{
samples_per_ltc_frame = session.samples_per_timecode_frame();
timecode.rate = session.timecode_frames_per_second();
timecode.drop = session.timecode_drop_frames();
if ((_port = AudioEngine::instance()->register_input_port (DataType::AUDIO, string_compose ("%1 in", _name))) == 0) {
throw failed_constructor();
}
did_reset_tc_format = false;
delayedlocked = 10;
monotonic_cnt = 0;
fps_detected=false;
sync_lock_broken = false;
DEBUG_TRACE (DEBUG::Slave, string_compose ("LTC registered %1\n", _port->name()));
ltc_timecode = session.config.get_timecode_format();
a3e_timecode = session.config.get_timecode_format();
printed_timecode_warning = false;
ltc_detect_fps_cnt = ltc_detect_fps_max = 0;
memset(&prev_sample, 0, sizeof(LTCFrameExt));
decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);
session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&LTC_Slave::parameter_changed, this, _1));
parse_timecode_offset();
reset();
resync_latency();
session.Xrun.connect_same_thread (port_connections, boost::bind (&LTC_Slave::resync_xrun, this));
session.engine().GraphReordered.connect_same_thread (port_connections, boost::bind (&LTC_Slave::resync_latency, this));
AudioEngine::instance()->Xrun.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_xrun, this));
AudioEngine::instance()->GraphReordered.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_latency, this));
}
LTC_Slave::~LTC_Slave()
void
LTC_TransportMaster::init ()
{
reset (true);
}
void
LTC_TransportMaster::set_session (Session *s)
{
config_connection.disconnect ();
_session = s;
if (_session) {
samples_per_ltc_frame = _session->samples_per_timecode_frame();
timecode.rate = _session->timecode_frames_per_second();
timecode.drop = _session->timecode_drop_frames();
printed_timecode_warning = false;
ltc_timecode = _session->config.get_timecode_format();
a3e_timecode = _session->config.get_timecode_format();
if (Config->get_use_session_timecode_format() && _session) {
samples_per_timecode_frame = _session->samples_per_timecode_frame();
}
if (decoder) {
ltc_decoder_free (decoder);
}
decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);
parse_timecode_offset();
reset();
_session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&LTC_TransportMaster::parameter_changed, this, _1));
}
}
LTC_TransportMaster::~LTC_TransportMaster()
{
port_connections.drop_connections();
config_connection.disconnect();
if (did_reset_tc_format) {
session.config.set_timecode_format (saved_tc_format);
_session->config.set_timecode_format (saved_tc_format);
}
ltc_decoder_free(decoder);
}
void
LTC_Slave::parse_timecode_offset() {
LTC_TransportMaster::parse_timecode_offset() {
Timecode::Time offset_tc;
Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc);
offset_tc.rate = session.timecode_frames_per_second();
offset_tc.drop = session.timecode_drop_frames();
session.timecode_to_sample(offset_tc, timecode_offset, false, false);
Timecode::parse_timecode_format(_session->config.get_slave_timecode_offset(), offset_tc);
offset_tc.rate = _session->timecode_frames_per_second();
offset_tc.drop = _session->timecode_drop_frames();
_session->timecode_to_sample(offset_tc, timecode_offset, false, false);
timecode_negative_offset = offset_tc.negative;
}
void
LTC_Slave::parameter_changed (std::string const & p)
LTC_TransportMaster::parameter_changed (std::string const & p)
{
if (p == "slave-timecode-offset"
|| p == "timecode-format"
@ -104,65 +145,61 @@ LTC_Slave::parameter_changed (std::string const & p)
}
ARDOUR::samplecnt_t
LTC_Slave::resolution () const
LTC_TransportMaster::resolution () const
{
return (samplecnt_t) (session.sample_rate() / 1000);
return (samplecnt_t) (ENGINE->sample_rate() / 1000);
}
bool
LTC_Slave::locked () const
LTC_TransportMaster::locked () const
{
return (delayedlocked < 5);
}
bool
LTC_Slave::ok() const
LTC_TransportMaster::ok() const
{
return true;
}
void
LTC_Slave::resync_xrun()
LTC_TransportMaster::resync_xrun()
{
DEBUG_TRACE (DEBUG::LTC, "LTC resync_xrun()\n");
engine_dll_initstate = 0;
sync_lock_broken = false;
}
void
LTC_Slave::resync_latency()
LTC_TransportMaster::resync_latency()
{
DEBUG_TRACE (DEBUG::LTC, "LTC resync_latency()\n");
engine_dll_initstate = 0;
sync_lock_broken = false;
if (!session.deletion_in_progress() && session.ltc_output_io()) { /* check if Port exits */
boost::shared_ptr<Port> ltcport = session.ltc_input_port();
ltcport->get_connected_latency_range (ltc_slave_latency, false);
if (!_port) {
_port->get_connected_latency_range (ltc_slave_latency, false);
}
}
void
LTC_Slave::reset (bool with_ts)
LTC_TransportMaster::reset (bool with_ts)
{
DEBUG_TRACE (DEBUG::LTC, "LTC reset()\n");
if (with_ts) {
last_timestamp = 0;
current_delta = 0;
_current_delta = 0;
}
transport_direction = 0;
ltc_speed = 0;
engine_dll_initstate = 0;
sync_lock_broken = false;
ActiveChanged (false); /* EMIT SIGNAL */
monotonic_cnt = 0;
}
void
LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
LTC_TransportMaster::parse_ltc (const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
{
pframes_t i;
unsigned char sound[8192];
if (nframes > 8192) {
/* TODO warn once or wrap, loop conversion below
* does jack/A3 support > 8192 spp anyway?
@ -171,32 +208,33 @@ LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, co
}
for (i = 0; i < nframes; i++) {
const int snd=(int)rint((127.0*in[i])+128.0);
const int snd=(int) rint ((127.0*in[i])+128.0);
sound[i] = (unsigned char) (snd&0xff);
}
ltc_decoder_write(decoder, sound, nframes, posinfo);
ltc_decoder_write (decoder, sound, nframes, posinfo);
return;
}
bool
LTC_Slave::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
if ( a->frame_units != b->frame_units
|| a->frame_tens != b->frame_tens
|| a->dfbit != b->dfbit
|| a->secs_units != b->secs_units
|| a->secs_tens != b->secs_tens
|| a->mins_units != b->mins_units
|| a->mins_tens != b->mins_tens
|| a->hours_units != b->hours_units
|| a->hours_tens != b->hours_tens
) {
LTC_TransportMaster::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
if (a->frame_units != b->frame_units ||
a->frame_tens != b->frame_tens ||
a->dfbit != b->dfbit ||
a->secs_units != b->secs_units ||
a->secs_tens != b->secs_tens ||
a->mins_units != b->mins_units ||
a->mins_tens != b->mins_tens ||
a->hours_units != b->hours_units ||
a->hours_tens != b->hours_tens) {
return false;
}
return true;
}
bool
LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
LTC_TransportMaster::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
bool discontinuity_detected = false;
if (fuzzy && (
@ -221,7 +259,7 @@ LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
}
bool
LTC_Slave::detect_ltc_fps(int frameno, bool df)
LTC_TransportMaster::detect_ltc_fps(int frameno, bool df)
{
bool fps_changed = false;
double detected_fps = 0;
@ -236,7 +274,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
detected_fps = ltc_detect_fps_max + 1;
if (df) {
/* LTC df -> indicates fractional framerate */
if (Config->get_timecode_source_2997()) {
if (fr2997()) {
detected_fps = detected_fps * 999.0 / 1000.0;
} else {
detected_fps = detected_fps * 1000.0 / 1001.0;
@ -256,7 +294,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
if (detected_fps != 0 && (detected_fps != timecode.rate || df != timecode.drop)) {
timecode.rate = detected_fps;
timecode.drop = df;
samples_per_ltc_frame = double(session.sample_rate()) / timecode.rate;
samples_per_ltc_frame = double(_session->sample_rate()) / timecode.rate;
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC reset to FPS: %1%2 ; audio-samples per LTC: %3\n",
detected_fps, df?"df":"ndf", samples_per_ltc_frame));
fps_changed=true;
@ -264,7 +302,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
/* poll and check session TC */
TimecodeFormat tc_format = apparent_timecode_format();
TimecodeFormat cur_timecode = session.config.get_timecode_format();
TimecodeFormat cur_timecode = _session->config.get_timecode_format();
if (Config->get_timecode_sync_frame_rate()) {
/* enforce time-code */
@ -279,7 +317,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
Timecode::timecode_format_name(tc_format))
<< endmsg;
}
session.config.set_timecode_format (tc_format);
_session->config.set_timecode_format (tc_format);
}
} else {
/* only warn about TC mismatch */
@ -299,45 +337,63 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
ltc_timecode = tc_format;
a3e_timecode = cur_timecode;
if (Config->get_use_session_timecode_format() && _session) {
samples_per_timecode_frame = _session->samples_per_timecode_frame();
} else {
samples_per_timecode_frame = ENGINE->sample_rate() / Timecode::timecode_to_frames_per_second (ltc_timecode);
}
return fps_changed;
}
void
LTC_Slave::process_ltc(samplepos_t const /*now*/)
LTC_TransportMaster::process_ltc(samplepos_t const now)
{
LTCFrameExt sample;
enum LTC_TV_STANDARD tv_standard = LTC_TV_625_50;
while (ltc_decoder_read(decoder, &sample)) {
LTC_TV_STANDARD tv_standard = LTC_TV_625_50;
while (ltc_decoder_read (decoder, &sample)) {
SMPTETimecode stime;
ltc_frame_to_time(&stime, &sample.ltc, 0);
ltc_frame_to_time (&stime, &sample.ltc, 0);
timecode.negative = false;
timecode.subframes = 0;
/* set timecode.rate and timecode.drop: */
bool ltc_is_static = equal_ltc_sample_time(&prev_sample.ltc, &sample.ltc);
if (detect_discontinuity(&sample, ceil(timecode.rate), !fps_detected)) {
if (fps_detected) { ltc_detect_fps_cnt = ltc_detect_fps_max = 0; }
fps_detected=false;
const bool ltc_is_stationary = equal_ltc_sample_time (&prev_sample.ltc, &sample.ltc);
if (detect_discontinuity (&sample, ceil(timecode.rate), !fps_detected)) {
if (fps_detected) {
ltc_detect_fps_cnt = ltc_detect_fps_max = 0;
}
fps_detected = false;
}
if (!ltc_is_static && detect_ltc_fps(stime.frame, (sample.ltc.dfbit)? true : false)) {
if (!ltc_is_stationary && detect_ltc_fps (stime.frame, (sample.ltc.dfbit)? true : false)) {
reset();
fps_detected=true;
}
#if 0 // Devel/Debug
fprintf(stdout, "LTC %02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
stime.hours,
stime.mins,
stime.secs,
(sample.ltc.dfbit) ? '.' : ':',
stime.frame,
sample.off_start,
sample.off_end,
sample.reverse ? " R" : " "
);
#ifndef NDEBUG
if (DEBUG_ENABLED (DEBUG::LTC)) {
/* use fprintf for simpler correct formatting of times
*/
fprintf (stderr, "LTC@%ld %02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
now,
stime.hours,
stime.mins,
stime.secs,
(sample.ltc.dfbit) ? '.' : ':',
stime.frame,
sample.off_start,
sample.off_end,
sample.reverse ? " R" : " "
);
}
#endif
/* when a full LTC sample is decoded, the timecode the LTC sample
@ -367,13 +423,13 @@ LTC_Slave::process_ltc(samplepos_t const /*now*/)
ltc_frame_increment(&sample.ltc, fps_i, tv_standard, 0);
ltc_frame_to_time(&stime, &sample.ltc, 0);
transport_direction = 1;
sample.off_start -= ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
sample.off_end -= ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
sample.off_start -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
sample.off_end -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
} else {
ltc_frame_decrement(&sample.ltc, fps_i, tv_standard, 0);
int off = sample.off_end - sample.off_start;
sample.off_start += off - ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
sample.off_end += off - ltc_frame_alignment(session.samples_per_timecode_frame(), tv_standard);
sample.off_start += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
sample.off_end += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
transport_direction = -1;
}
@ -382,236 +438,194 @@ LTC_Slave::process_ltc(samplepos_t const /*now*/)
timecode.seconds = stime.secs;
timecode.frames = stime.frame;
/* map LTC timecode to session TC setting */
samplepos_t ltc_frame; ///< audio-sample corresponding to LTC sample
Timecode::timecode_to_sample (timecode, ltc_frame, true, false,
double(session.sample_rate()),
session.config.get_subframes_per_frame(),
timecode_negative_offset, timecode_offset
);
samplepos_t ltc_sample; // audio-sample corresponding to position of LTC frame
ltc_frame += ltc_slave_latency.max;
if (_session && Config->get_use_session_timecode_format()) {
Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), _session->config.get_subframes_per_frame(), timecode_negative_offset, timecode_offset);
} else {
Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), 100, timecode_negative_offset, timecode_offset);
}
ltc_sample += ltc_slave_latency.max;
/* This LTC frame spans sample time between sample.off_start .. sample.off_end
*
* NOTE: these sample times are NOT the ones that LTC is representing. They are
* derived our own audioengine's monotonic audio clock.
*
* So we expect the next frame to span sample.off_end+1 and ... <don't care for now>.
* That isn't the time we will necessarily receive the LTC frame, but the decoder
* should tell us that its span begins there.
*
*/
samplepos_t cur_timestamp = sample.off_end + 1;
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC F: %1 LF: %2 N: %3 L: %4\n", ltc_frame, last_ltc_sample, cur_timestamp, last_timestamp));
if (sample.off_end + 1 <= last_timestamp || last_timestamp == 0) {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC S: %1 LS: %2 N: %3 L: %4\n", ltc_sample, last_ltc_sample, cur_timestamp, last_timestamp));
if (cur_timestamp <= last_timestamp || last_timestamp == 0) {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: UNCHANGED: %1\n", ltc_speed));
} else {
ltc_speed = double(ltc_frame - last_ltc_sample) / double(cur_timestamp - last_timestamp);
ltc_speed = double (ltc_sample - last_ltc_sample) / double (cur_timestamp - last_timestamp);
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: %1\n", ltc_speed));
}
if (fabs(ltc_speed) > 10.0) {
if (fabs (ltc_speed) > 10.0) {
ltc_speed = 0;
}
last_timestamp = sample.off_end + 1;
last_ltc_sample = ltc_frame;
last_timestamp = cur_timestamp;
last_ltc_sample = ltc_sample;
} /* end foreach decoded LTC sample */
}
void
LTC_Slave::init_engine_dll (samplepos_t pos, int32_t inc)
{
double omega = 2.0 * M_PI * double(inc) / double(session.sample_rate());
b = 1.4142135623730950488 * omega;
c = omega * omega;
e2 = double(ltc_speed * inc);
t0 = double(pos);
t1 = t0 + e2;
DEBUG_TRACE (DEBUG::LTC, string_compose ("[re-]init Engine DLL %1 %2 %3\n", t0, t1, e2));
}
/* main entry point from session_process.cc
* called from process callback context
* so it is OK to use get_buffer()
*/
bool
LTC_Slave::speed_and_position (double& speed, samplepos_t& pos)
LTC_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
{
bool engine_init_called = false;
samplepos_t now = session.engine().sample_time_at_cycle_start();
samplepos_t sess_pos = session.transport_sample(); // corresponds to now
samplecnt_t nframes = session.engine().samples_per_cycle();
Sample* in;
boost::shared_ptr<Port> ltcport = session.ltc_input_port();
in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (ltcport->port_handle(), nframes);
sampleoffset_t skip = now - (monotonic_cnt + nframes);
monotonic_cnt = now;
DEBUG_TRACE (DEBUG::LTC, string_compose ("speed_and_position - TID:%1 | latency: %2 | skip %3\n", pthread_name(), ltc_slave_latency.max, skip));
if (last_timestamp == 0) {
engine_dll_initstate = 0;
if (delayedlocked < 10) ++delayedlocked;
} else if (engine_dll_initstate != transport_direction && ltc_speed != 0) {
ActiveChanged (true); /* EMIT SIGNAL */
engine_dll_initstate = transport_direction;
init_engine_dll(last_ltc_sample + rint(ltc_speed * double(2 * nframes + now - last_timestamp)),
session.engine().samples_per_cycle());
engine_init_called = true;
if (!_collect || last_timestamp == 0) {
return false;
}
if (in) {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC Process eng-tme: %1 eng-pos: %2\n", now, sess_pos));
/* when the jack-graph changes and if ardour performs
* locates, the audioengine is stopped (skipping samples) while
* jack [time] moves along.
*/
if (skip > 0) {
DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples. Feeding silence to LTC parser.\n", skip));
if (skip >= 8192) skip = 8192;
unsigned char sound[8192];
memset(sound, 0x80, sizeof(char) * skip);
ltc_decoder_write(decoder, sound, nframes, now);
} else if (skip != 0) {
/* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
reset();
}
/* XXX these are not atomics and maybe modified in a thread other other than the one
that is executing this.
*/
parse_ltc(nframes, in, now);
process_ltc(now);
}
if (last_timestamp == 0) {
DEBUG_TRACE (DEBUG::LTC, "last timestamp == 0\n");
speed = 0;
pos = session.transport_sample();
return true;
} else if (ltc_speed != 0) {
if (delayedlocked > 1) delayedlocked--;
else if (current_delta == 0) delayedlocked = 0;
}
if (abs(now - last_timestamp) > FLYWHEEL_TIMEOUT) {
DEBUG_TRACE (DEBUG::LTC, "flywheel timeout\n");
reset();
speed = 0;
pos = session.transport_sample();
ActiveChanged (false); /* EMIT SIGNAL */
return true;
}
/* it take 2 cycles from naught to rolling.
* during these to initial cycles the speed == 0
*
* the first cycle:
* DEBUG::Slave: slave stopped, move to NNN
* DEBUG::Transport: Request forced locate to NNN
* DEBUG::Slave: slave state 0 @ NNN speed 0 cur delta VERY-LARGE-DELTA avg delta 1800
* DEBUG::Slave: silent motion
* DEBUG::Transport: realtime stop @ NNN
* DEBUG::Transport: Butler transport work, todo = PostTransportStop,PostTransportLocate,PostTransportClearSubstate
*
* [engine skips samples to locate, jack time keeps rolling on]
*
* the second cycle:
*
* DEBUG::LTC: [re-]init Engine DLL
* DEBUG::Slave: slave stopped, move to NNN+
* ...
*
* we need to seek two cycles ahead: 2 * nframes
*/
if (engine_dll_initstate == 0) {
DEBUG_TRACE (DEBUG::LTC, "engine DLL not initialized. ltc_speed\n");
speed = 0;
pos = last_ltc_sample + rint(ltc_speed * double(2 * nframes + now - last_timestamp));
return true;
}
/* interpolate position according to speed and time since last LTC-sample*/
double speed_flt = ltc_speed;
double elapsed = (now - last_timestamp) * speed_flt;
if (!engine_init_called) {
const double e = elapsed + double (last_ltc_sample - sess_pos);
t0 = t1;
t1 += b * e + e2;
e2 += c * e;
speed_flt = (t1 - t0) / double(session.engine().samples_per_cycle());
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, speed_flt, e2 - session.engine().samples_per_cycle() ));
} else {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC adjusting elapsed (no DLL) from %1 by %2\n", elapsed, (2 * nframes * ltc_speed)));
speed_flt = 0;
elapsed += 2.0 * nframes * ltc_speed; /* see note above */
}
pos = last_ltc_sample + rint(elapsed);
speed = speed_flt;
current_delta = (pos - sess_pos);
if (((pos < 0) || (labs(current_delta) > 2 * session.sample_rate()))) {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC large drift: %1\n", current_delta));
reset();
speed = 0;
return true;
}
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
speed, pos, last_ltc_sample, elapsed, current_delta));
speed = ltc_speed;
/* provide a .1% deadzone to lock the speed */
if (fabs(speed - 1.0) <= 0.001) {
speed = 1.0;
if (fabs (speed - 1.0) <= 0.001) {
speed = 1.0;
}
if (speed != 0 && delayedlocked == 0 && fabs(speed) != 1.0) {
sync_lock_broken = true;
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 %2\n", speed, ltc_speed));
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 based on %2\n", speed, ltc_speed));
}
pos = last_ltc_sample;
pos += (now - last_timestamp) * speed;
return true;
}
void
LTC_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
{
Sample* in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (_port->port_handle(), nframes);
sampleoffset_t skip = now - (monotonic_cnt + nframes);
monotonic_cnt = now;
DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process - TID:%1 | latency: %2 | skip %3 | session ? %4| last %5 | dir %6 | sp %7\n",
pthread_name(), ltc_slave_latency.max, skip, (_session ? 'y' : 'n'), last_timestamp, transport_direction, ltc_speed));
if (last_timestamp == 0) {
if (delayedlocked < 10) {
++delayedlocked;
}
} else if (ltc_speed != 0) {
}
DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process with audio clock time: %1\n", now));
/* if the audioengine failed to take the process lock, it won't
call this method, and time will appear to skip. Reset the
LTC decoder's state by giving it some silence.
*/
if (skip > 0) {
DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples. Feeding silence to LTC parser.\n", skip));
if (skip >= 8192) skip = 8192;
unsigned char sound[8192];
memset (sound, 0x80, sizeof(char) * skip);
ltc_decoder_write (decoder, sound, nframes, now);
} else if (skip != 0) {
/* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
reset();
}
/* Now feed the incoming LTC signal into the decoder */
parse_ltc (nframes, in, now);
/* and pull out actual LTC frame data */
process_ltc (now);
if (last_timestamp == 0) {
DEBUG_TRACE (DEBUG::LTC, "last timestamp == 0\n");
return;
} else if (ltc_speed != 0) {
DEBUG_TRACE (DEBUG::LTC, string_compose ("speed non-zero (%1)\n", ltc_speed));
if (delayedlocked > 1) {
delayedlocked--;
} else if (_current_delta == 0) {
delayedlocked = 0;
}
}
if (abs (now - last_timestamp) > FLYWHEEL_TIMEOUT) {
DEBUG_TRACE (DEBUG::LTC, "flywheel timeout\n");
reset();
/* don't change position from last known */
return;
}
if (session_pos) {
const samplepos_t current_pos = last_ltc_sample + ((now - last_timestamp) * ltc_speed);
_current_delta = current_pos - *session_pos;
} else {
_current_delta = 0;
}
}
Timecode::TimecodeFormat
LTC_Slave::apparent_timecode_format () const
LTC_TransportMaster::apparent_timecode_format () const
{
if (timecode.rate == 24 && !timecode.drop)
return timecode_24;
else if (timecode.rate == 25 && !timecode.drop)
return timecode_25;
else if (rint(timecode.rate * 100) == 2997 && !timecode.drop)
return (Config->get_timecode_source_2997() ? timecode_2997000 : timecode_2997);
return (fr2997() ? timecode_2997000 : timecode_2997);
else if (rint(timecode.rate * 100) == 2997 && timecode.drop)
return (Config->get_timecode_source_2997() ? timecode_2997000drop : timecode_2997drop);
return (fr2997() ? timecode_2997000drop : timecode_2997drop);
else if (timecode.rate == 30 && timecode.drop)
return timecode_2997drop; // timecode_30drop; // LTC counting to 30 samples w/DF *means* 29.97 df
else if (timecode.rate == 30 && !timecode.drop)
return timecode_30;
/* XXX - unknown timecode format */
return session.config.get_timecode_format();
return _session->config.get_timecode_format();
}
std::string
LTC_Slave::approximate_current_position() const
LTC_TransportMaster::position_string() const
{
if (last_timestamp == 0) {
if (!_collect || last_timestamp == 0) {
return " --:--:--:--";
}
return Timecode::timecode_format_time(timecode);
}
std::string
LTC_Slave::approximate_current_delta() const
LTC_TransportMaster::delta_string() const
{
char delta[80];
if (last_timestamp == 0 || engine_dll_initstate == 0) {
snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
if (!_collect || last_timestamp == 0) {
snprintf (delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
} else if ((monotonic_cnt - last_timestamp) > 2 * samples_per_ltc_frame) {
snprintf(delta, sizeof(delta), "%s", _("flywheel"));
snprintf (delta, sizeof(delta), "%s", _("flywheel"));
} else {
snprintf(delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
snprintf (delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
sync_lock_broken ? "red" : "green",
LEADINGZERO(::llabs(current_delta)), PLUSMINUS(-current_delta), ::llabs(current_delta));
LEADINGZERO(::llabs(_current_delta)), PLUSMINUS(-_current_delta), ::llabs(_current_delta));
}
return std::string(delta);
return delta;
}

View File

@ -29,11 +29,13 @@
#include "midi++/port.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/midi_buffer.h"
#include "ardour/midi_port.h"
#include "ardour/slave.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/transport_master.h"
#include "pbd/i18n.h"
@ -42,54 +44,124 @@ using namespace ARDOUR;
using namespace MIDI;
using namespace PBD;
MIDIClock_Slave::MIDIClock_Slave (Session& s, MidiPort& p, int ppqn)
: ppqn (ppqn)
, bandwidth (2.0 / 60.0) // 1 BpM = 1 / 60 Hz
{
session = (ISlaveSessionProxy *) new SlaveSessionProxy(s);
rebind (p);
reset ();
}
#define ENGINE AudioEngine::instance()
MIDIClock_Slave::MIDIClock_Slave (ISlaveSessionProxy* session_proxy, int ppqn)
: session(session_proxy)
MIDIClock_TransportMaster::MIDIClock_TransportMaster (std::string const & name, int ppqn)
: TransportMaster (MIDIClock, name)
, ppqn (ppqn)
, bandwidth (2.0 / 60.0) // 1 BpM = 1 / 60 Hz
, last_timestamp (0)
, should_be_position (0)
, midi_clock_count (0)
, _speed (0)
, _running (false)
, _bpm (0)
{
reset ();
if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
throw failed_constructor();
}
}
MIDIClock_Slave::~MIDIClock_Slave()
MIDIClock_TransportMaster::~MIDIClock_TransportMaster()
{
delete session;
}
void
MIDIClock_Slave::rebind (MidiPort& port)
{
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port.name()));
port_connections.drop_connections ();
port.self_parser().timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::update_midi_clock, this, _1, _2));
port.self_parser().start.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::start, this, _1, _2));
port.self_parser().contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::contineu, this, _1, _2));
port.self_parser().stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::stop, this, _1, _2));
port.self_parser().position.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::position, this, _1, _2, 3));
}
void
MIDIClock_Slave::calculate_one_ppqn_in_samples_at(samplepos_t time)
MIDIClock_TransportMaster::init ()
{
const double samples_per_quarter_note = session->tempo_map().samples_per_quarter_note_at (time, session->sample_rate());
midi_clock_count = 0;
last_timestamp = 0;
}
void
MIDIClock_TransportMaster::set_session (Session *session)
{
port_connections.drop_connections();
_session = session;
/* only connect to signals if we have a proxy, because otherwise we
* cannot interpet incoming data (no tempo map etc.)
*/
if (_session) {
parser.timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::update_midi_clock, this, _1, _2));
parser.start.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::start, this, _1, _2));
parser.contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::contineu, this, _1, _2));
parser.stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::stop, this, _1, _2));
parser.position.connect_same_thread (port_connections, boost::bind (&MIDIClock_TransportMaster::position, this, _1, _2, 3));
reset ();
}
}
bool
MIDIClock_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
{
if (!_running || !_collect) {
return false;
}
if (fabs (_speed - 1.0) < 0.001) {
speed = 1.0;
} else {
speed = _speed;
}
pos = should_be_position;
pos += (now - last_timestamp) * _speed;
return true;
}
void
MIDIClock_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
{
/* Read and parse incoming MIDI */
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("preprocess with lt = %1 @ %2, running ? %3\n", last_timestamp, now, _running));
_midi_port->read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, parser, now);
/* no clock messages ever, or no clock messages for 1/4 second ? conclude that its stopped */
if (!last_timestamp || (now > last_timestamp && ((now - last_timestamp) > (ENGINE->sample_rate() / 4)))) {
_speed = 0.0;
_bpm = 0.0;
last_timestamp = 0;
_running = false;
_current_delta = 0;
midi_clock_count = 0;
DEBUG_TRACE (DEBUG::MidiClock, "No MIDI Clock messages received for some time, stopping!\n");
return;
}
if (!_running && midi_clock_count == 0 && session_pos) {
should_be_position = *session_pos;
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("set sbp to %1\n", should_be_position));
}
if (session_pos) {
const samplepos_t current_pos = should_be_position + ((now - last_timestamp) * _speed);
_current_delta = current_pos - *session_pos;
} else {
_current_delta = 0;
}
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("speed_and_position: speed %1 should-be %2 transport %3 \n", _speed, should_be_position, _session->transport_sample()));
}
void
MIDIClock_TransportMaster::calculate_one_ppqn_in_samples_at(samplepos_t time)
{
const double samples_per_quarter_note = _session->tempo_map().samples_per_quarter_note_at (time, ENGINE->sample_rate());
one_ppqn_in_samples = samples_per_quarter_note / double (ppqn);
// DEBUG_TRACE (DEBUG::MidiClock, string_compose ("at %1, one ppqn = %2\n", time, one_ppqn_in_samples));
}
ARDOUR::samplepos_t
MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
MIDIClock_TransportMaster::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
{
samplepos_t song_position_samples = 0;
for (uint16_t i = 1; i <= song_position_in_sixteenth_notes; ++i) {
@ -102,81 +174,129 @@ MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_not
}
void
MIDIClock_Slave::calculate_filter_coefficients()
MIDIClock_TransportMaster::calculate_filter_coefficients (double qpm)
{
// omega = 2 * PI * Bandwidth / MIDI clock sample frequency in Hz
omega = 2.0 * M_PI * bandwidth * one_ppqn_in_samples / session->sample_rate();
b = 1.4142135623730950488 * omega;
/* Paul says: I don't understand this computation of bandwidth
*/
const double bandwidth = 2.0 / qpm;
/* Frequency of the clock messages is ENGINE->sample_rate() / * one_ppqn_in_samples, per second or in Hz */
const double freq = (double) ENGINE->sample_rate() / one_ppqn_in_samples;
const double omega = 2.0 * M_PI * bandwidth / freq;
b = 1.4142135623730950488 * omega; // sqrt (2.0) * omega
c = omega * omega;
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("DLL coefficients: bw:%1 omega:%2 b:%3 c:%4\n", bandwidth, omega, b, c));
}
void
MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
MIDIClock_TransportMaster::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
{
// some pieces of hardware send MIDI Clock all the time
if ( (!_starting) && (!_started) ) {
return;
}
pframes_t cycle_offset = timestamp - session->sample_time_at_cycle_start();
calculate_one_ppqn_in_samples_at(should_be_position);
samplepos_t elapsed_since_start = timestamp - first_timestamp;
double error = 0;
double e = 0;
if (_starting || last_timestamp == 0) {
midi_clock_count = 0;
calculate_one_ppqn_in_samples_at (should_be_position);
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("clock count %1, sbp %2\n", midi_clock_count, should_be_position));
if (midi_clock_count == 0) {
/* second 0xf8 message after start/reset has arrived */
first_timestamp = timestamp;
elapsed_since_start = should_be_position;
last_timestamp = timestamp;
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("first clock message after start received @ %1\n", timestamp));
// calculate filter coefficients
calculate_filter_coefficients();
// initialize DLL
e2 = double(one_ppqn_in_samples) / double(session->sample_rate());
t0 = double(elapsed_since_start) / double(session->sample_rate());
t1 = t0 + e2;
// let ardour go after first MIDI Clock Event
_starting = false;
} else {
midi_clock_count++;
should_be_position += one_ppqn_in_samples;
calculate_filter_coefficients();
// calculate loop error
// we use session->transport_sample() instead of t1 here
// because t1 is used to calculate the transport speed,
// so the loop will compensate for accumulating rounding errors
error = (double(should_be_position) - (double(session->transport_sample()) + double(cycle_offset)));
e = error / double(session->sample_rate());
current_delta = error;
should_be_position += one_ppqn_in_samples;
// update DLL
} else if (midi_clock_count == 1) {
/* second 0xf8 message has arrived. we can now estimate QPM
* (quarters per minute, and fully initialize the DLL
*/
e = timestamp - last_timestamp;
const samplecnt_t samples_per_quarter = e * 24;
_bpm = (ENGINE->sample_rate() * 60.0) / samples_per_quarter;
calculate_filter_coefficients (_bpm);
/* finish DLL initialization */
t0 = timestamp;
e2 = e;
t1 = t0 + e2; /* timestamp we predict for the next 0xf8 clock message */
midi_clock_count++;
should_be_position += one_ppqn_in_samples;
} else {
/* 3rd or later MIDI clock message. We can now compute actual
* speed (and tempo) with the DLL
*/
e = timestamp - t1; // error between actual time of arrival of clock message and our predicted time
t0 = t1;
t1 += b * e + e2;
e2 += c * e;
const double samples_per_quarter = (timestamp - last_timestamp) * 24.0;
const double instantaneous_bpm = (ENGINE->sample_rate() * 60.0) / samples_per_quarter;
const double lpf_coeff = 0.05;
const double predicted_clock_interval_in_samples = (t1 - t0);
/* _speed is relative to session tempo map */
_speed = predicted_clock_interval_in_samples / one_ppqn_in_samples;
/* _bpm (really, _qpm) is absolute */
/* detect substantial changes in apparent tempo (defined as a
* change of more than 20% of the current tempo.
*/
if (fabs (instantaneous_bpm - _bpm) > (0.20 * _bpm)) {
_bpm = instantaneous_bpm;
} else {
_bpm += lpf_coeff * (instantaneous_bpm - _bpm);
}
calculate_filter_coefficients (_bpm);
// need at least two clock events to compute speed
if (!_running) {
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("start mclock running with speed = %1\n", (t1 - t0) / one_ppqn_in_samples));
_running = true;
}
midi_clock_count++;
should_be_position += one_ppqn_in_samples;
}
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("clock #%1 @ %2 should-be %3 transport %4 error %5 appspeed %6 "
"read-delta %7 should-be delta %8 t1-t0 %9 t0 %10 t1 %11 framerate %12 engine %13\n",
"read-delta %7 should-be delta %8 t1-t0 %9 t0 %10 t1 %11 framerate %12 engine %13 running %14\n",
midi_clock_count, // #
elapsed_since_start, // @
should_be_position, // should-be
session->transport_sample(), // transport
error, // error
((t1 - t0) * session->sample_rate()) / one_ppqn_in_samples, // appspeed
_session->transport_sample(), // transport
e, // error
(t1 - t0) / one_ppqn_in_samples, // appspeed
timestamp - last_timestamp, // read delta
one_ppqn_in_samples, // should-be delta
(t1 - t0) * session->sample_rate(), // t1-t0
t0 * session->sample_rate(), // t0
t1 * session->sample_rate(), // t1
session->sample_rate(), // framerate
session->sample_time()
(t1 - t0), // t1-t0
t0, // t0
t1, // t1
ENGINE->sample_rate(), // framerate
ENGINE->sample_time(),
_running
));
@ -184,57 +304,47 @@ MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, samplepos_t timestamp)
}
void
MIDIClock_Slave::start (Parser& /*parser*/, samplepos_t timestamp)
MIDIClock_TransportMaster::start (Parser& /*parser*/, samplepos_t timestamp)
{
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave got start message at time %1 engine time %2 transport_sample %3\n", timestamp, session->sample_time(), session->transport_sample()));
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_TransportMaster got start message at time %1 engine time %2 transport_sample %3\n", timestamp, ENGINE->sample_time(), _session->transport_sample()));
if (!_started) {
if (!_running) {
reset();
_started = true;
_starting = true;
should_be_position = session->transport_sample();
_running = true;
should_be_position = _session->transport_sample();
}
}
void
MIDIClock_Slave::reset ()
MIDIClock_TransportMaster::reset ()
{
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MidiClock_Slave reset(): calculated filter bandwidth is %1 for period size %2\n", bandwidth, session->samples_per_cycle()));
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MidiClock Master reset(): calculated filter for period size %2\n", ENGINE->samples_per_cycle()));
should_be_position = session->transport_sample();
should_be_position = _session->transport_sample();
_speed = 0;
last_timestamp = 0;
_starting = true;
_started = true;
// session->request_locate(0, false);
current_delta = 0;
_running = false;
_current_delta = 0;
}
void
MIDIClock_Slave::contineu (Parser& /*parser*/, samplepos_t /*timestamp*/)
MIDIClock_TransportMaster::contineu (Parser& /*parser*/, samplepos_t /*timestamp*/)
{
DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_Slave got continue message\n");
DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_TransportMaster got continue message\n");
if (!_started) {
_starting = true;
_started = true;
}
_running = true;
}
void
MIDIClock_Slave::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
MIDIClock_TransportMaster::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
{
DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_Slave got stop message\n");
DEBUG_TRACE (DEBUG::MidiClock, "MIDIClock_TransportMaster got stop message\n");
if (_started || _starting) {
_starting = false;
_started = false;
// locate to last MIDI clock position
session->request_transport_speed(0.0);
if (_running) {
_running = false;
_speed = 0;
last_timestamp = 0;
// we need to go back to the last MIDI beat (6 ppqn)
// and lets hope the tempo didnt change in the meantime :)
@ -243,24 +353,19 @@ MIDIClock_Slave::stop (Parser& /*parser*/, samplepos_t /*timestamp*/)
// that is the position of the last MIDI Clock
// message and that is probably what the master
// expects where we are right now
samplepos_t stop_position = should_be_position;
//
// find out the last MIDI beat: go back #midi_clocks mod 6
// and lets hope the tempo didnt change in those last 6 beats :)
stop_position -= (midi_clock_count % 6) * one_ppqn_in_samples;
session->request_locate(stop_position, false);
should_be_position = stop_position;
last_timestamp = 0;
should_be_position -= (midi_clock_count % 6) * one_ppqn_in_samples;
}
}
void
MIDIClock_Slave::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
MIDIClock_TransportMaster::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
{
// we are note supposed to get position messages while we are running
// we are not supposed to get position messages while we are running
// so lets be robust and ignore those
if (_started || _starting) {
if (_running) {
return;
}
@ -274,102 +379,51 @@ MIDIClock_Slave::position (Parser& /*parser*/, MIDI::byte* message, size_t size)
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Song Position: %1 samples: %2\n", position_in_sixteenth_notes, position_in_samples));
session->request_locate(position_in_samples, false);
should_be_position = position_in_samples;
should_be_position = position_in_samples;
last_timestamp = 0;
}
bool
MIDIClock_Slave::locked () const
MIDIClock_TransportMaster::locked () const
{
return true;
}
bool
MIDIClock_Slave::ok() const
MIDIClock_TransportMaster::ok() const
{
return true;
}
bool
MIDIClock_Slave::starting() const
MIDIClock_TransportMaster::starting() const
{
return false;
}
bool
MIDIClock_Slave::stop_if_no_more_clock_events(samplepos_t& pos, samplepos_t now)
{
/* no timecode for 1/4 second ? conclude that its stopped */
if (last_timestamp &&
now > last_timestamp &&
now - last_timestamp > session->sample_rate() / 4) {
DEBUG_TRACE (DEBUG::MidiClock, "No MIDI Clock samples received for some time, stopping!\n");
pos = should_be_position;
session->request_transport_speed (0);
session->request_locate (should_be_position, false);
return true;
} else {
return false;
}
}
bool
MIDIClock_Slave::speed_and_position (double& speed, samplepos_t& pos)
{
if (!_started || _starting) {
speed = 0.0;
pos = should_be_position;
return true;
}
samplepos_t engine_now = session->sample_time();
if (stop_if_no_more_clock_events(pos, engine_now)) {
return false;
}
// calculate speed
speed = ((t1 - t0) * session->sample_rate()) / one_ppqn_in_samples;
// provide a 0.1% deadzone to lock the speed
if (fabs(speed - 1.0) <= 0.001)
speed = 1.0;
// calculate position
if (engine_now > last_timestamp) {
// we are in between MIDI clock messages
// so we interpolate position according to speed
samplecnt_t elapsed = engine_now - last_timestamp;
pos = (samplepos_t) (should_be_position + double(elapsed) * speed);
} else {
// A new MIDI clock message has arrived this cycle
pos = should_be_position;
}
DEBUG_TRACE (DEBUG::MidiClock, string_compose ("speed_and_position: speed %1 should-be %2 transport %3 \n", speed, pos, session->transport_sample()));
return true;
}
ARDOUR::samplecnt_t
MIDIClock_Slave::resolution() const
MIDIClock_TransportMaster::resolution() const
{
// one beat
return (samplecnt_t) one_ppqn_in_samples * ppqn;
}
std::string
MIDIClock_Slave::approximate_current_delta() const
MIDIClock_TransportMaster::position_string () const
{
return std::string();
}
std::string
MIDIClock_TransportMaster::delta_string() const
{
char delta[80];
if (last_timestamp == 0 || _starting) {
if (last_timestamp == 0 || starting()) {
snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
} else {
snprintf(delta, sizeof(delta), "\u0394<span foreground=\"green\" face=\"monospace\" >%s%s%" PRIi64 "</span>sm",
LEADINGZERO(abs(current_delta)), PLUSMINUS(-current_delta), abs(current_delta));
LEADINGZERO(abs(_current_delta)), PLUSMINUS(-_current_delta), abs(_current_delta));
}
return std::string(delta);
}

View File

@ -37,11 +37,10 @@ using namespace PBD;
MidiPort::MidiPort (const std::string& name, PortFlags flags)
: Port (name, DataType::MIDI, flags)
, _has_been_mixed_down (false)
, _resolve_required (false)
, _input_active (true)
, _always_parse (false)
, _trace_on (false)
, _trace_parser (0)
, _data_fetched_for_cycle (false)
{
_buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI));
}
@ -56,11 +55,14 @@ MidiPort::~MidiPort()
delete _buffer;
}
void
MidiPort::parse_input (pframes_t nframes, MIDI::Parser& parser)
{
}
void
MidiPort::cycle_start (pframes_t nframes)
{
samplepos_t now = AudioEngine::instance()->sample_time_at_cycle_start();
Port::cycle_start (nframes);
_buffer->clear ();
@ -69,22 +71,8 @@ MidiPort::cycle_start (pframes_t nframes)
port_engine.midi_clear (port_engine.get_buffer (_port_handle, nframes));
}
if (_always_parse || (receives_input() && _trace_on)) {
MidiBuffer& mb (get_midi_buffer (nframes));
/* dump incoming MIDI to parser */
for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) {
uint8_t* buf = (*b).buffer();
_self_parser.set_timestamp (now + (*b).time());
uint32_t limit = (*b).size();
for (size_t n = 0; n < limit; ++n) {
_self_parser.scanner (buf[n]);
}
}
if (receives_input() && _trace_parser) {
read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, *_trace_parser, AudioEngine::instance()->sample_time_at_cycle_start());
}
if (inbound_midi_filter) {
@ -101,80 +89,64 @@ MidiPort::cycle_start (pframes_t nframes)
}
Buffer&
MidiPort::get_buffer (pframes_t nframes)
{
return get_midi_buffer (nframes);
}
MidiBuffer &
MidiPort::get_midi_buffer (pframes_t nframes)
{
if (_has_been_mixed_down) {
if (_data_fetched_for_cycle) {
return *_buffer;
}
if (receives_input ()) {
if (receives_input () && _input_active) {
if (_input_active) {
void* buffer = port_engine.get_buffer (_port_handle, nframes);
const pframes_t event_count = port_engine.get_midi_event_count (buffer);
void* buffer = port_engine.get_buffer (_port_handle, nframes);
const pframes_t event_count = port_engine.get_midi_event_count (buffer);
/* suck all MIDI events for this cycle of nframes from
the MIDI port buffer into our MidiBuffer.
*/
/* suck all relevant MIDI events from the MIDI port buffer
into our MidiBuffer
*/
for (pframes_t i = 0; i < event_count; ++i) {
for (pframes_t i = 0; i < event_count; ++i) {
pframes_t timestamp;
size_t size;
uint8_t const* buf;
pframes_t timestamp;
size_t size;
uint8_t const* buf;
port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
if (buf[0] == 0xfe) {
/* throw away active sensing */
continue;
}
timestamp = floor (timestamp * _speed_ratio);
/* check that the event is in the acceptable time range */
if ((timestamp < (_global_port_buffer_offset)) ||
(timestamp >= (_global_port_buffer_offset + nframes))) {
// XXX this is normal after a split cycles:
// The engine buffer contains the data for the complete cycle, but
// only the part after _global_port_buffer_offset is needed.
#ifndef NDEBUG
cerr << "Dropping incoming MIDI at time " << timestamp << "; offset="
<< _global_port_buffer_offset << " limit="
<< (_global_port_buffer_offset + nframes)
<< " = (" << _global_port_buffer_offset
<< " + " << nframes
<< ")\n";
#endif
continue;
}
/* adjust timestamp to match current cycle */
timestamp -= _global_port_buffer_offset;
assert (timestamp < nframes);
if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
/* normalize note on with velocity 0 to proper note off */
uint8_t ev[3];
ev[0] = 0x80 | (buf[0] & 0x0F); /* note off */
ev[1] = buf[1];
ev[2] = 0x40; /* default velocity */
_buffer->push_back (timestamp, size, ev);
} else {
_buffer->push_back (timestamp, size, buf);
}
if (buf[0] == 0xfe) {
/* throw away active sensing */
continue;
}
} else {
_buffer->silence (nframes);
timestamp = floor (timestamp * _speed_ratio);
/* check that the event is in the acceptable time range */
if ((timestamp < (_global_port_buffer_offset)) ||
(timestamp >= (_global_port_buffer_offset + nframes))) {
// XXX this is normal after a split cycles:
// The engine buffer contains the data for the complete cycle, but
// only the part after _global_port_buffer_offset is needed.
#ifndef NDEBUG
cerr << "Dropping incoming MIDI at time " << timestamp << "; offset="
<< _global_port_buffer_offset << " limit="
<< (_global_port_buffer_offset + nframes)
<< " = (" << _global_port_buffer_offset
<< " + " << nframes
<< ")\n";
#endif
continue;
}
if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
/* normalize note on with velocity 0 to proper note off */
uint8_t ev[3];
ev[0] = 0x80 | (buf[0] & 0x0F); /* note off */
ev[1] = buf[1];
ev[2] = 0x40; /* default velocity */
_buffer->push_back (timestamp, size, ev);
} else {
_buffer->push_back (timestamp, size, buf);
}
}
} else {
@ -182,22 +154,63 @@ MidiPort::get_midi_buffer (pframes_t nframes)
}
if (nframes) {
_has_been_mixed_down = true;
_data_fetched_for_cycle = true;
}
return *_buffer;
}
void
MidiPort::read_and_parse_entire_midi_buffer_with_no_speed_adjustment (pframes_t nframes, MIDI::Parser& parser, samplepos_t now)
{
void* buffer = port_engine.get_buffer (_port_handle, nframes);
const pframes_t event_count = port_engine.get_midi_event_count (buffer);
for (pframes_t i = 0; i < event_count; ++i) {
pframes_t timestamp;
size_t size;
uint8_t const* buf;
port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
if (buf[0] == 0xfe) {
/* throw away active sensing */
continue;
}
parser.set_timestamp (now + timestamp);
/* During this parsing stage, signals will be emitted from the
* Parser, which will update anything connected to it.
*
* As of July 2018, this is only used by TransportMasters which
* read MIDI before the process() cycle really gets started.
*/
if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
/* normalize note on with velocity 0 to proper note off */
parser.scanner (0x80 | (buf[0] & 0x0F)); /* note off */
parser.scanner (buf[1]);
parser.scanner (0x40); /* default (off) velocity */
} else {
for (size_t n = 0; n < size; ++n) {
parser.scanner (buf[n]);
}
}
}
}
void
MidiPort::cycle_end (pframes_t /*nframes*/)
{
_has_been_mixed_down = false;
_data_fetched_for_cycle = false;
}
void
MidiPort::cycle_split ()
{
_has_been_mixed_down = false;
_data_fetched_for_cycle = false;
}
void
@ -253,16 +266,16 @@ MidiPort::flush_buffers (pframes_t nframes)
const Evoral::Event<MidiBuffer::TimeType> ev (*i, false);
if (sends_output() && _trace_on) {
if (sends_output() && _trace_parser) {
uint8_t const * const buf = ev.buffer();
const samplepos_t now = AudioEngine::instance()->sample_time_at_cycle_start();
_self_parser.set_timestamp (now + ev.time());
_trace_parser->set_timestamp (now + ev.time());
uint32_t limit = ev.size();
for (size_t n = 0; n < limit; ++n) {
_self_parser.scanner (buf[n]);
_trace_parser->scanner (buf[n]);
}
}
@ -347,15 +360,9 @@ MidiPort::set_input_active (bool yn)
}
void
MidiPort::set_always_parse (bool yn)
MidiPort::set_trace (MIDI::Parser * p)
{
_always_parse = yn;
}
void
MidiPort::set_trace_on (bool yn)
{
_trace_on = yn;
_trace_parser = p;
}
int

View File

@ -50,15 +50,9 @@ MidiPortManager::~MidiPortManager ()
if (_scene_out) {
AudioEngine::instance()->unregister_port (_scene_out);
}
if (_mtc_input_port) {
AudioEngine::instance()->unregister_port (_mtc_input_port);
}
if (_mtc_output_port) {
AudioEngine::instance()->unregister_port (_mtc_output_port);
}
if (_midi_clock_input_port) {
AudioEngine::instance()->unregister_port (_midi_clock_input_port);
}
if (_midi_clock_output_port) {
AudioEngine::instance()->unregister_port (_midi_clock_output_port);
}
@ -84,29 +78,16 @@ MidiPortManager::create_ports ()
_scene_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Scene in"), true);
_scene_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Scene out"), true);
/* Now register ports used for sync (MTC and MIDI Clock)
/* Now register ports used to send positional sync data (MTC and MIDI Clock)
*/
boost::shared_ptr<ARDOUR::Port> p;
p = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("MTC in"));
_mtc_input_port = boost::dynamic_pointer_cast<MidiPort> (p);
p = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("MTC out"));
_mtc_output_port= boost::dynamic_pointer_cast<MidiPort> (p);
p = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("MIDI Clock in"));
_midi_clock_input_port = boost::dynamic_pointer_cast<MidiPort> (p);
p = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("MIDI Clock out"));
_midi_clock_output_port= boost::dynamic_pointer_cast<MidiPort> (p);
/* These ports all need their incoming data handled in
* Port::cycle_start() and so ...
*/
_mtc_input_port->set_always_parse (true);
_mtc_output_port->set_always_parse (true);
_midi_clock_input_port->set_always_parse (true);
_midi_clock_output_port->set_always_parse (true);
}
void
@ -117,9 +98,7 @@ MidiPortManager::set_midi_port_states (const XMLNodeList&nodes)
PortMap ports;
const int version = 0;
ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port));
ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port));
ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port));
ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port));
ports.insert (make_pair (_midi_in->name(), _midi_in));
ports.insert (make_pair (_midi_out->name(), _midi_out));
@ -149,9 +128,7 @@ MidiPortManager::get_midi_port_states () const
PortMap ports;
list<XMLNode*> s;
ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port));
ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port));
ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port));
ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port));
ports.insert (make_pair (_midi_in->name(), _midi_in));
ports.insert (make_pair (_midi_out->name(), _midi_out));

View File

@ -30,7 +30,7 @@
#include "ardour/midi_buffer.h"
#include "ardour/midi_port.h"
#include "ardour/session.h"
#include "ardour/slave.h"
#include "ardour/transport_master.h"
#include <glibmm/timer.h>
@ -49,38 +49,35 @@ using namespace Timecode;
recently received position (and without the direction of timecode reversing too), we
will stop+locate+wait+chase.
*/
const int MTC_Slave::sample_tolerance = 2;
const int MTC_TransportMaster::sample_tolerance = 2;
MTC_Slave::MTC_Slave (Session& s, MidiPort& p)
: session (s)
, port (&p)
MTC_TransportMaster::MTC_TransportMaster (std::string const & name)
: TimecodeTransportMaster (name, MTC)
, can_notify_on_unknown_rate (true)
, mtc_frame (0)
, mtc_frame_dll (0)
, last_inbound_frame (0)
, window_begin (0)
, window_end (0)
, first_mtc_timestamp (0)
, did_reset_tc_format (false)
, reset_pending (0)
, reset_position (false)
, transport_direction (1)
, busy_guard1 (0)
, busy_guard2 (0)
, printed_timecode_warning (false)
{
can_notify_on_unknown_rate = true;
did_reset_tc_format = false;
reset_pending = 0;
reset_position = false;
mtc_frame = 0;
mtc_frame_dll = 0;
engine_dll_initstate = 0;
busy_guard1 = busy_guard2 = 0;
if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
throw failed_constructor();
}
last_mtc_fps_byte = session.get_mtc_timecode_bits ();
quarter_frame_duration = (double(session.samples_per_timecode_frame()) / 4.0);
DEBUG_TRACE (DEBUG::Slave, string_compose ("MTC registered %1\n", _port->name()));
mtc_timecode = session.config.get_timecode_format();
a3e_timecode = session.config.get_timecode_format();
printed_timecode_warning = false;
session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_Slave::parameter_changed, this, _1));
parse_timecode_offset();
reset (true);
port->self_parser().mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3));
port->self_parser().mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3));
port->self_parser().mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1));
init ();
}
MTC_Slave::~MTC_Slave()
MTC_TransportMaster::~MTC_TransportMaster()
{
port_connections.drop_connections();
config_connection.disconnect();
@ -96,31 +93,69 @@ MTC_Slave::~MTC_Slave()
}
if (did_reset_tc_format) {
session.config.set_timecode_format (saved_tc_format);
_session->config.set_timecode_format (saved_tc_format);
}
}
void
MTC_Slave::rebind (MidiPort& p)
MTC_TransportMaster::init ()
{
port_connections.drop_connections ();
port = &p;
reset (true);
}
void
MTC_Slave::parse_timecode_offset() {
MTC_TransportMaster::set_session (Session *s)
{
config_connection.disconnect ();
port_connections.drop_connections();
_session = s;
if (_session) {
last_mtc_fps_byte = _session->get_mtc_timecode_bits ();
quarter_frame_duration = (double) (_session->samples_per_timecode_frame() / 4.0);
mtc_timecode = _session->config.get_timecode_format();
a3e_timecode = _session->config.get_timecode_format();
parse_timecode_offset ();
reset (true);
parser.mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_time, this, _1, _2, _3));
parser.mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_qtr, this, _1, _2, _3));
parser.mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_TransportMaster::update_mtc_status, this, _1));
_session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_TransportMaster::parameter_changed, this, _1));
}
}
void
MTC_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
{
/* Read and parse incoming MIDI */
_midi_port->read_and_parse_entire_midi_buffer_with_no_speed_adjustment (nframes, parser, now);
if (session_pos) {
const samplepos_t current_pos = current.position + ((now - current.timestamp) * current.speed);
_current_delta = current_pos - *session_pos;
} else {
_current_delta = 0;
}
}
void
MTC_TransportMaster::parse_timecode_offset() {
Timecode::Time offset_tc;
Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc);
offset_tc.rate = session.timecode_frames_per_second();
offset_tc.drop = session.timecode_drop_frames();
session.timecode_to_sample(offset_tc, timecode_offset, false, false);
Timecode::parse_timecode_format (_session->config.get_slave_timecode_offset(), offset_tc);
offset_tc.rate = _session->timecode_frames_per_second();
offset_tc.drop = _session->timecode_drop_frames();
_session->timecode_to_sample(offset_tc, timecode_offset, false, false);
timecode_negative_offset = offset_tc.negative;
}
void
MTC_Slave::parameter_changed (std::string const & p)
MTC_TransportMaster::parameter_changed (std::string const & p)
{
if (p == "slave-timecode-offset"
|| p == "timecode-format"
@ -129,47 +164,40 @@ MTC_Slave::parameter_changed (std::string const & p)
}
}
bool
MTC_Slave::give_slave_full_control_over_transport_speed() const
{
return true; // DLL align to engine transport
// return false; // for Session-level computed varispeed
}
ARDOUR::samplecnt_t
MTC_Slave::resolution () const
MTC_TransportMaster::resolution () const
{
return (samplecnt_t) quarter_frame_duration * 4.0;
}
ARDOUR::samplecnt_t
MTC_Slave::seekahead_distance () const
MTC_TransportMaster::seekahead_distance () const
{
return quarter_frame_duration * 8 * transport_direction;
}
bool
MTC_Slave::outside_window (samplepos_t pos) const
MTC_TransportMaster::outside_window (samplepos_t pos) const
{
return ((pos < window_begin) || (pos > window_end));
}
bool
MTC_Slave::locked () const
MTC_TransportMaster::locked () const
{
DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2 initstate %3\n", port->self_parser().mtc_locked(), last_inbound_frame, engine_dll_initstate));
return port->self_parser().mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0;
DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2\n", parser.mtc_locked(), last_inbound_frame));
return parser.mtc_locked() && last_inbound_frame !=0;
}
bool
MTC_Slave::ok() const
MTC_TransportMaster::ok() const
{
return true;
}
void
MTC_Slave::queue_reset (bool reset_pos)
MTC_TransportMaster::queue_reset (bool reset_pos)
{
Glib::Threads::Mutex::Lock lm (reset_lock);
reset_pending++;
@ -179,7 +207,7 @@ MTC_Slave::queue_reset (bool reset_pos)
}
void
MTC_Slave::maybe_reset ()
MTC_TransportMaster::maybe_reset ()
{
Glib::Threads::Mutex::Lock lm (reset_lock);
@ -191,9 +219,10 @@ MTC_Slave::maybe_reset ()
}
void
MTC_Slave::reset (bool with_position)
MTC_TransportMaster::reset (bool with_position)
{
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC_Slave reset %1\n", with_position?"with position":"without position"));
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC_TransportMaster reset %1\n", with_position?"with position":"without position"));
if (with_position) {
last_inbound_frame = 0;
current.guard1++;
@ -212,15 +241,14 @@ MTC_Slave::reset (bool with_position)
window_begin = 0;
window_end = 0;
transport_direction = 1;
current_delta = 0;
ActiveChanged(false);
_current_delta = 0;
}
void
MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
MTC_TransportMaster::handle_locate (const MIDI::byte* mmc_tc)
{
MIDI::byte mtc[5];
DEBUG_TRACE (DEBUG::MTC, "MTC_Slave::handle_locate\n");
DEBUG_TRACE (DEBUG::MTC, "MTC_TransportMaster::handle_locate\n");
mtc[4] = last_mtc_fps_byte;
mtc[3] = mmc_tc[0] & 0xf; /* hrs only */
@ -232,7 +260,7 @@ MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
}
void
MTC_Slave::read_current (SafeTime *st) const
MTC_TransportMaster::read_current (SafeTime *st) const
{
int tries = 0;
@ -249,9 +277,9 @@ MTC_Slave::read_current (SafeTime *st) const
}
void
MTC_Slave::init_mtc_dll(samplepos_t tme, double qtr)
MTC_TransportMaster::init_mtc_dll(samplepos_t tme, double qtr)
{
omega = 2.0 * M_PI * qtr / 2.0 / double(session.sample_rate());
const double omega = 2.0 * M_PI * qtr / 2.0 / double(_session->sample_rate());
b = 1.4142135623730950488 * omega;
c = omega * omega;
@ -263,7 +291,7 @@ MTC_Slave::init_mtc_dll(samplepos_t tme, double qtr)
/* called from MIDI parser */
void
MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, samplepos_t now)
MTC_TransportMaster::update_mtc_qtr (Parser& p, int which_qtr, samplepos_t now)
{
busy_guard1++;
const double qtr_d = quarter_frame_duration;
@ -302,7 +330,7 @@ MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, samplepos_t now)
* when a full TC has been received
* OR on locate */
void
MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t now)
MTC_TransportMaster::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t now)
{
busy_guard1++;
@ -341,7 +369,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
can_notify_on_unknown_rate = true;
break;
case MTC_30_FPS_DROP:
if (Config->get_timecode_source_2997()) {
if (fr2997()) {
tc_format = Timecode::timecode_2997000drop;
timecode.rate = (29970.0/1000.0);
} else {
@ -365,13 +393,13 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
<< endmsg;
can_notify_on_unknown_rate = false;
}
timecode.rate = session.timecode_frames_per_second();
timecode.drop = session.timecode_drop_frames();
timecode.rate = _session->timecode_frames_per_second();
timecode.drop = _session->timecode_drop_frames();
reset_tc = false;
}
if (reset_tc) {
TimecodeFormat cur_timecode = session.config.get_timecode_format();
TimecodeFormat cur_timecode = _session->config.get_timecode_format();
if (Config->get_timecode_sync_frame_rate()) {
/* enforce time-code */
if (!did_reset_tc_format) {
@ -386,7 +414,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
<< endmsg;
}
}
session.config.set_timecode_format (tc_format);
_session->config.set_timecode_format (tc_format);
} else {
/* only warn about TC mismatch */
if (mtc_timecode != tc_format) printed_timecode_warning = false;
@ -414,11 +442,11 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
consideration.
*/
quarter_frame_duration = (double(session.sample_rate()) / (double) timecode.rate / 4.0);
quarter_frame_duration = (double(_session->sample_rate()) / (double) timecode.rate / 4.0);
Timecode::timecode_to_sample (timecode, mtc_frame, true, false,
double(session.sample_rate()),
session.config.get_subframes_per_frame(),
double(_session->sample_rate()),
_session->config.get_subframes_per_frame(),
timecode_negative_offset, timecode_offset
);
@ -427,9 +455,9 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
if (was_full || outside_window (mtc_frame)) {
DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC %1 or outside window %2 MTC %3\n", was_full, outside_window (mtc_frame), mtc_frame));
session.set_requested_return_sample (-1);
session.request_transport_speed (0);
session.request_locate (mtc_frame, false);
_session->set_requested_return_sample (-1);
_session->request_transport_speed (0, TRS_MTC);
_session->request_locate (mtc_frame, false, TRS_MTC);
update_mtc_status (MIDI::MTC_Stopped);
reset (false);
reset_window (mtc_frame);
@ -449,9 +477,9 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
long int mtc_off = (long) rint(7.0 * qtr);
DEBUG_TRACE (DEBUG::MTC, string_compose ("new mtc_frame: %1 | MTC-FpT: %2 A3-FpT:%3\n",
mtc_frame, (4.0*qtr), session.samples_per_timecode_frame()));
mtc_frame, (4.0*qtr), _session->samples_per_timecode_frame()));
switch (port->self_parser().mtc_running()) {
switch (parser.mtc_running()) {
case MTC_Backward:
mtc_frame -= mtc_off;
qtr *= -1.0;
@ -470,7 +498,6 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
first_mtc_timestamp = now;
init_mtc_dll(mtc_frame, qtr);
mtc_frame_dll = mtc_frame;
ActiveChanged (true); // emit signal
}
current.guard1++;
current.position = mtc_frame;
@ -487,12 +514,12 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, samplepos_t no
}
void
MTC_Slave::update_mtc_status (MIDI::MTC_Status status)
MTC_TransportMaster::update_mtc_status (MIDI::MTC_Status status)
{
/* XXX !!! thread safety ... called from MIDI I/O context
* on locate (via ::update_mtc_time())
*/
DEBUG_TRACE (DEBUG::MTC, string_compose("MTC_Slave::update_mtc_status - TID:%1 MTC:%2\n", pthread_name(), mtc_frame));
DEBUG_TRACE (DEBUG::MTC, string_compose("MTC_TransportMaster::update_mtc_status - TID:%1 MTC:%2\n", pthread_name(), mtc_frame));
return; // why was this fn needed anyway ? it just messes up things -> use reset.
busy_guard1++;
@ -526,7 +553,7 @@ MTC_Slave::update_mtc_status (MIDI::MTC_Status status)
}
void
MTC_Slave::reset_window (samplepos_t root)
MTC_TransportMaster::reset_window (samplepos_t root)
{
/* if we're waiting for the master to catch us after seeking ahead, keep the window
of acceptable MTC samples wide open. otherwise, shrink it down to just 2 video frames
@ -535,7 +562,7 @@ MTC_Slave::reset_window (samplepos_t root)
samplecnt_t const d = (quarter_frame_duration * 4 * sample_tolerance);
switch (port->self_parser().mtc_running()) {
switch (parser.mtc_running()) {
case MTC_Forward:
window_begin = root;
transport_direction = 1;
@ -561,144 +588,70 @@ MTC_Slave::reset_window (samplepos_t root)
DEBUG_TRACE (DEBUG::MTC, string_compose ("reset MTC window @ %3, now %1 .. %2\n", window_begin, window_end, root));
}
void
MTC_Slave::init_engine_dll (samplepos_t pos, samplepos_t inc)
{
/* the bandwidth of the DLL is a trade-off,
* because the max-speed of the transport in ardour is
* limited to +-8.0, a larger bandwidth would cause oscillations
*
* But this is only really a problem if the user performs manual
* seeks while transport is running and slaved to MTC.
*/
oe = 2.0 * M_PI * double(inc) / 2.0 / double(session.sample_rate());
be = 1.4142135623730950488 * oe;
ce = oe * oe;
ee2 = double(transport_direction * inc);
te0 = double(pos);
te1 = te0 + ee2;
DEBUG_TRACE (DEBUG::MTC, string_compose ("[re-]init Engine DLL %1 %2 %3\n", te0, te1, ee2));
}
/* main entry point from session_process.cc
xo * in process callback context */
bool
MTC_Slave::speed_and_position (double& speed, samplepos_t& pos)
MTC_TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t now)
{
samplepos_t now = session.engine().sample_time_at_cycle_start();
samplepos_t sess_pos = session.transport_sample(); // corresponds to now
//sess_pos -= session.engine().samples_since_cycle_start();
SafeTime last;
sampleoffset_t elapsed;
bool engine_dll_reinitialized = false;
if (!_collect) {
return false;
}
read_current (&last);
DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 initstate %3 dir %4 tpos %5 now %6 last-in %7\n",
DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 dir %4 now %5 last-in %6\n",
last.timestamp,
last.speed,
engine_dll_initstate,
transport_direction,
sess_pos,
now,
last_inbound_frame));
/* re-init engine DLL here when state changed (direction, first_mtc_timestamp) */
if (last.timestamp == 0) {
engine_dll_initstate = 0;
} else if (engine_dll_initstate != transport_direction && last.speed != 0) {
engine_dll_initstate = transport_direction;
init_engine_dll(last.position, session.engine().samples_per_cycle());
engine_dll_reinitialized = true;
}
if (last.timestamp == 0) {
speed = 0;
pos = session.transport_sample() ; // last.position;
DEBUG_TRACE (DEBUG::MTC, string_compose ("first call to MTC_Slave::speed_and_position, pos = %1\n", pos));
return true;
}
/* no timecode for two samples - conclude that it's stopped */
if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > labs(seekahead_distance())) {
speed = 0;
pos = last.position;
session.set_requested_return_sample (-1);
session.request_locate (pos, false);
session.request_transport_speed (0);
engine_dll_initstate = 0;
queue_reset (false);
ActiveChanged (false);
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC not seen for 2 samples - reset pending, pos = %1\n", pos));
return false;
}
if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > labs(seekahead_distance())) {
/* no timecode for two cycles - conclude that it's stopped */
if (!Config->get_transport_masters_just_roll_when_sync_lost()) {
speed = 0;
pos = last.position;
_current_delta = 0;
queue_reset (false);
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC not seen for 2 samples - reset pending, pos = %1\n", pos));
return false;
}
}
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position mtc-tme: %1 mtc-pos: %2 mtc-spd: %3\n", last.timestamp, last.position, last.speed));
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position eng-tme: %1 eng-pos: %2\n", now, sess_pos));
double speed_flt = last.speed; ///< MTC speed from MTC-quarter-frame DLL
/* interpolate position according to speed and time since last quarter-frame*/
if (speed_flt == 0.0f) {
elapsed = 0;
} else {
/* scale elapsed time by the current MTC speed */
elapsed = (samplecnt_t) rint (speed_flt * (now - last.timestamp));
if (give_slave_full_control_over_transport_speed() && !engine_dll_reinitialized) {
/* there is an engine vs MTC position sample-delta.
* This mostly due to quantization and rounding of (speed * nframes)
* but can also due to the session-process not calling
* speed_and_position() every cycle under some circumstances.
* Thus we use an other DLL to align the engine and the MTC
*/
/* update engine DLL and calculate speed */
const double e = double (last.position + elapsed - sess_pos);
te0 = te1;
te1 += be * e + ee2;
ee2 += ce * e;
speed_flt = (te1 - te0) / double(session.engine().samples_per_cycle());
DEBUG_TRACE (DEBUG::MTC, string_compose ("engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", te0, te1, e, speed_flt, ee2 - session.engine().samples_per_cycle() ));
}
}
pos = last.position + elapsed;
speed = speed_flt;
/* may happen if the user performs a seek in the timeline while slaved to running MTC
* engine-DLL can oscillate back before 0.
* also see note in MTC_Slave::init_engine_dll
*/
if (!session.actively_recording()
&& speed != 0
&& ((pos < 0) || (labs(pos - sess_pos) > 3 * session.sample_rate()))) {
engine_dll_initstate = 0;
queue_reset (false);
}
speed = last.speed;
/* provide a .1% deadzone to lock the speed */
if (fabs (speed - 1.0) <= 0.001)
speed = 1.0;
if (fabs (speed - 1.0) <= 0.001) {
speed = 1.0;
}
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
speed, pos, last.position, elapsed, pos - sess_pos));
pos = last.position;
pos += (now - last.timestamp) * speed;
current_delta = (pos - sess_pos);
DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 | elapsed: %4\n",
speed, pos, last.position, (now - last.timestamp)));
return true;
}
Timecode::TimecodeFormat
MTC_Slave::apparent_timecode_format () const
MTC_TransportMaster::apparent_timecode_format () const
{
return mtc_timecode;
}
std::string
MTC_Slave::approximate_current_position() const
MTC_TransportMaster::position_string() const
{
SafeTime last;
read_current (&last);
@ -707,22 +660,25 @@ MTC_Slave::approximate_current_position() const
}
return Timecode::timecode_format_sampletime(
last.position,
double(session.sample_rate()),
double(_session->sample_rate()),
Timecode::timecode_to_frames_per_second(mtc_timecode),
Timecode::timecode_has_drop_frames(mtc_timecode));
}
std::string
MTC_Slave::approximate_current_delta() const
MTC_TransportMaster::delta_string () const
{
char delta[80];
SafeTime last;
read_current (&last);
delta[0] = '\0';
if (last.timestamp == 0 || reset_pending) {
snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
} else {
snprintf(delta, sizeof(delta), "\u0394<span foreground=\"green\" face=\"monospace\" >%s%s%" PRIi64 "</span>sm",
LEADINGZERO(abs(current_delta)), PLUSMINUS(-current_delta), abs(current_delta));
LEADINGZERO(abs(_current_delta)), PLUSMINUS(-_current_delta), abs(_current_delta));
}
return std::string(delta);
}

View File

@ -58,6 +58,7 @@ Port::Port (std::string const & n, DataType t, PortFlags f)
: _name (n)
, _flags (f)
, _last_monitor (false)
, _externally_connected (0)
{
_private_playback_latency.min = 0;
_private_playback_latency.max = 0;
@ -82,8 +83,7 @@ Port::Port (std::string const & n, DataType t, PortFlags f)
PortDrop.connect_same_thread (drop_connection, boost::bind (&Port::drop, this));
PortSignalDrop.connect_same_thread (drop_connection, boost::bind (&Port::signal_drop, this));
port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection,
boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
}
/** Port destructor */
@ -92,7 +92,6 @@ Port::~Port ()
drop ();
}
std::string
Port::pretty_name(bool fallback_to_name) const
{
@ -532,8 +531,7 @@ Port::reestablish ()
reset ();
port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection,
boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5));
return 0;
}
@ -583,15 +581,6 @@ Port::physically_connected () const
return port_engine.physically_connected (_port_handle);
}
bool
Port::externally_connected () const
{
if (!_port_handle) {
return false;
}
return port_engine.externally_connected (_port_handle);
}
XMLNode&
Port::get_state () const
{

View File

@ -192,37 +192,51 @@ PortManager::port_is_physical (const std::string& portname) const
void
PortManager::filter_midi_ports (vector<string>& ports, MidiPortFlags include, MidiPortFlags exclude)
{
if (!include && !exclude) {
return;
}
for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
{
Glib::Threads::Mutex::Lock lm (midi_port_info_mutex);
PortManager::MidiPortInformation mpi = midi_port_information (*si);
fill_midi_port_info_locked ();
for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
MidiPortInfo::iterator x = midi_port_info.find (*si);
if (x == midi_port_info.end()) {
++si;
continue;
}
MidiPortInformation& mpi (x->second);
if (mpi.pretty_name.empty()) {
/* no information !!! */
++si;
continue;
}
if (include) {
if ((mpi.properties & include) != include) {
/* properties do not include requested ones */
si = ports.erase (si);
continue;
}
}
if (exclude) {
if ((mpi.properties & exclude)) {
/* properties include ones to avoid */
si = ports.erase (si);
continue;
}
}
if (mpi.pretty_name.empty()) {
/* no information !!! */
++si;
continue;
}
if (include) {
if ((mpi.properties & include) != include) {
/* properties do not include requested ones */
si = ports.erase (si);
continue;
}
}
if (exclude) {
if ((mpi.properties & exclude)) {
/* properties include ones to avoid */
si = ports.erase (si);
continue;
}
}
++si;
}
}
@ -656,6 +670,20 @@ PortManager::connect_callback (const string& a, const string& b, bool conn)
port_b = x->second;
}
if (conn) {
if (port_a && !port_b) {
port_a->increment_external_connections ();
} else if (port_b && !port_a) {
port_b->increment_external_connections ();
}
} else {
if (port_a && !port_b) {
port_a->decrement_external_connections ();
} else if (port_b && !port_a) {
port_b->decrement_external_connections ();
}
}
PortConnectedOrDisconnected (
port_a, a,
port_b, b,
@ -1260,23 +1288,19 @@ PortManager::fill_midi_port_info_locked ()
if (!ph) {
/* port info saved from some condition where this port
* existed, but no longer does (i.e. device unplugged
* at present)
* at present). We don't remove it from midi_port_info.
*/
continue;
}
if (!x->second.pretty_name.empty () && x->second.pretty_name != x->first) {
/* name set in port info ... propagate */
_backend->set_port_property (ph, "http://jackaudio.org/metadata/pretty-name", x->second.pretty_name, string());
} else {
/* check with backend for pre-existing pretty name */
string value;
string type;
if (0 == _backend->get_port_property (ph,
"http://jackaudio.org/metadata/pretty-name",
value, type)) {
x->second.pretty_name = value;
}
/* check with backend for pre-existing pretty name */
string value;
string type;
if (0 == _backend->get_port_property (ph,
"http://jackaudio.org/metadata/pretty-name",
value, type)) {
x->second.pretty_name = value;
}
}

View File

@ -36,6 +36,7 @@
#include "ardour/port.h"
#include "ardour/rc_configuration.h"
#include "ardour/session_metadata.h"
#include "ardour/transport_master_manager.h"
#include "ardour/types_convert.h"
#include "pbd/i18n.h"
@ -66,12 +67,14 @@ RCConfiguration::RCConfiguration ()
#undef CONFIG_VARIABLE
#undef CONFIG_VARIABLE_SPECIAL
_control_protocol_state (0)
, _transport_master_state (0)
{
}
RCConfiguration::~RCConfiguration ()
{
delete _control_protocol_state;
delete _transport_master_state;
}
int
@ -186,6 +189,7 @@ RCConfiguration::get_state ()
}
root->add_child_nocopy (ControlProtocolManager::instance().get_state());
root->add_child_nocopy (TransportMasterManager::instance().get_state());
return *root;
}
@ -233,6 +237,8 @@ RCConfiguration::set_state (const XMLNode& root, int version)
SessionMetadata::Metadata()->set_state (*node, version);
} else if (node->name() == ControlProtocolManager::state_node_name) {
_control_protocol_state = new XMLNode (*node);
} else if (node->name() == TransportMasterManager::state_node_name) {
_transport_master_state = new XMLNode (*node);
}
}

View File

@ -99,14 +99,13 @@
#include "ardour/session.h"
#include "ardour/session_directory.h"
#include "ardour/session_playlists.h"
#include "ardour/slave.h"
#include "ardour/smf_source.h"
#include "ardour/slave.h"
#include "ardour/solo_isolate_control.h"
#include "ardour/source_factory.h"
#include "ardour/speakers.h"
#include "ardour/tempo.h"
#include "ardour/ticker.h"
#include "ardour/transport_master.h"
#include "ardour/track.h"
#include "ardour/types_convert.h"
#include "ardour/user_bundle.h"
@ -185,7 +184,6 @@ Session::Session (AudioEngine &eng,
, _seek_counter (0)
, _session_range_location (0)
, _session_range_end_is_free (true)
, _slave (0)
, _silent (false)
, _remaining_latency_preroll (0)
, _engine_speed (1.0)
@ -195,7 +193,6 @@ Session::Session (AudioEngine &eng,
, _signalled_varispeed (0)
, _target_transport_speed (0.0)
, auto_play_legal (false)
, _last_slave_transport_sample (0)
, _requested_return_sample (-1)
, current_block_size (0)
, _worst_output_latency (0)
@ -211,13 +208,8 @@ Session::Session (AudioEngine &eng,
, _was_seamless (Config->get_seamless_loop ())
, _under_nsm_control (false)
, _xrun_count (0)
, delta_accumulator_cnt (0)
, average_slave_delta (1800) // !!! why 1800 ???
, average_dir (0)
, have_first_delta_accumulator (false)
, _slave_state (Stopped)
, _mtc_active (false)
, _ltc_active (false)
, transport_master_tracking_state (Stopped)
, master_wait_end (0)
, post_export_sync (false)
, post_export_position (0)
, _exporting (false)
@ -656,8 +648,6 @@ Session::destroy ()
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
ltc_tx_cleanup();
delete _slave;
_slave = 0;
}
/* disconnect from any and all signals that we are connected to */
@ -671,7 +661,6 @@ Session::destroy ()
/* remove I/O objects before unsetting the engine session */
_click_io.reset ();
_ltc_input.reset ();
_ltc_output.reset ();
ControlProtocolManager::instance().drop_protocols ();
@ -687,12 +676,6 @@ Session::destroy ()
EngineStateController::instance()->remove_session();
#endif
/* drop slave, if any. We don't use use_sync_source (0) because
* there's no reason to do all the other stuff that may happen
* when calling that method.
*/
delete _slave;
/* deregister all ports - there will be no process or any other
* callbacks from the engine any more.
*/
@ -891,21 +874,8 @@ Session::setup_ltc ()
{
XMLNode* child = 0;
_ltc_input.reset (new IO (*this, X_("LTC In"), IO::Input));
_ltc_output.reset (new IO (*this, X_("LTC Out"), IO::Output));
if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC In"))) != 0) {
_ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version);
} else {
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
_ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this);
// TODO use auto-connect thread somehow (needs a route currently)
// see note in Session::auto_connect_thread_run() why process lock is needed.
reconnect_ltc_input ();
}
}
if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC Out"))) != 0) {
_ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version);
} else {
@ -921,7 +891,6 @@ Session::setup_ltc ()
* IO style of NAME/TYPE-{in,out}N
*/
_ltc_input->nth (0)->set_name (X_("LTC-in"));
_ltc_output->nth (0)->set_name (X_("LTC-out"));
}
@ -3003,40 +2972,6 @@ Session::reconnect_midi_scene_ports(bool inputs)
}
}
void
Session::reconnect_mtc_ports ()
{
boost::shared_ptr<MidiPort> mtc_in_ptr = _midi_ports->mtc_input_port();
if (!mtc_in_ptr) {
return;
}
mtc_in_ptr->disconnect_all ();
std::vector<EngineStateController::MidiPortState> midi_port_states;
EngineStateController::instance()->get_physical_midi_input_states (midi_port_states);
std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
for (; state_iter != midi_port_states.end(); ++state_iter) {
if (state_iter->available && state_iter->mtc_in) {
mtc_in_ptr->connect (state_iter->name);
}
}
if (!_midi_ports->mtc_input_port ()->connected () &&
config.get_external_sync () &&
(Config->get_sync_source () == MTC) ) {
config.set_external_sync (false);
}
if ( ARDOUR::Profile->get_trx () ) {
// Tracks need this signal to update timecode_source_dropdown
MtcOrLtcInputPortChanged (); //emit signal
}
}
void
Session::reconnect_mmc_ports(bool inputs)
{
@ -7042,39 +6977,12 @@ Session::operation_in_progress (GQuark op) const
return (find (_current_trans_quarks.begin(), _current_trans_quarks.end(), op) != _current_trans_quarks.end());
}
boost::shared_ptr<Port>
Session::ltc_input_port () const
{
assert (_ltc_input);
return _ltc_input->nth (0);
}
boost::shared_ptr<Port>
Session::ltc_output_port () const
{
return _ltc_output ? _ltc_output->nth (0) : boost::shared_ptr<Port> ();
}
void
Session::reconnect_ltc_input ()
{
if (_ltc_input) {
string src = Config->get_ltc_source_port();
_ltc_input->disconnect (this);
if (src != _("None") && !src.empty()) {
_ltc_input->nth (0)->connect (src);
}
if ( ARDOUR::Profile->get_trx () ) {
// Tracks need this signal to update timecode_source_dropdown
MtcOrLtcInputPortChanged (); //emit signal
}
}
}
void
Session::reconnect_ltc_output ()
{

View File

@ -25,7 +25,7 @@
#include "ardour/debug.h"
#include "ardour/io.h"
#include "ardour/session.h"
#include "ardour/slave.h"
#include "ardour/transport_master.h"
#include "pbd/i18n.h"
@ -68,7 +68,7 @@ Session::ltc_tx_initialize()
ltc_enc_tcformat = config.get_timecode_format();
ltc_tx_parse_offset();
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX init sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(ltc_enc_tcformat)));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("LTC TX init sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(ltc_enc_tcformat)));
ltc_encoder = ltc_encoder_create(nominal_sample_rate(),
timecode_to_frames_per_second(ltc_enc_tcformat),
TV_STANDARD(ltc_enc_tcformat), 0);
@ -93,7 +93,7 @@ Session::ltc_tx_initialize()
void
Session::ltc_tx_cleanup()
{
DEBUG_TRACE (DEBUG::LTC, "LTC TX cleanup\n");
DEBUG_TRACE (DEBUG::TXLTC, "cleanup\n");
ltc_tx_connections.drop_connections ();
free(ltc_enc_buf);
ltc_enc_buf = NULL;
@ -104,7 +104,7 @@ Session::ltc_tx_cleanup()
void
Session::ltc_tx_resync_latency()
{
DEBUG_TRACE (DEBUG::LTC, "LTC TX resync latency\n");
DEBUG_TRACE (DEBUG::TXLTC, "resync latency\n");
if (!deletion_in_progress()) {
boost::shared_ptr<Port> ltcport = ltc_output_port();
if (ltcport) {
@ -116,7 +116,7 @@ Session::ltc_tx_resync_latency()
void
Session::ltc_tx_reset()
{
DEBUG_TRACE (DEBUG::LTC, "LTC TX reset\n");
DEBUG_TRACE (DEBUG::TXLTC, "reset\n");
assert (ltc_encoder);
ltc_enc_pos = -9999; // force re-start
ltc_buf_len = 0;
@ -203,7 +203,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
/* range from libltc (38..218) || - 128.0 -> (-90..90) */
const float ltcvol = Config->get_ltc_output_volume()/(90.0); // pow(10, db/20.0)/(90.0);
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("LTC TX %1 to %2 / %3 | lat: %4\n", start_sample, end_sample, nframes, ltc_out_latency.max));
/* all systems go. Now here's the plan:
*
@ -222,7 +222,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
// (1) check fps
TimecodeFormat cur_timecode = config.get_timecode_format();
if (cur_timecode != ltc_enc_tcformat) {
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode)));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("1: TC format mismatch - reinit sr: %1 fps: %2\n", nominal_sample_rate(), timecode_to_frames_per_second(cur_timecode)));
if (ltc_encoder_reinit(ltc_encoder, nominal_sample_rate(),
timecode_to_frames_per_second(cur_timecode),
TV_STANDARD(cur_timecode), 0
@ -295,7 +295,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
if (SIGNUM(new_ltc_speed) != SIGNUM (ltc_speed)) {
DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport changed direction\n");
DEBUG_TRACE (DEBUG::TXLTC, "transport changed direction\n");
ltc_tx_reset();
}
@ -315,13 +315,13 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
* end_sample is calculated from 'samples_moved' which includes the interpolation.
* so we're good.
*/
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: speed change old: %1 cur: %2 tgt: %3 ctd: %4\n", ltc_speed, current_speed, target_speed, fabs(current_speed) - target_speed, new_ltc_speed));
speed_changed = true;
ltc_encoder_set_filter(ltc_encoder, LTC_RISE_TIME(new_ltc_speed));
}
if (end_sample == start_sample || fabs(current_speed) < 0.1 ) {
DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport is not rolling or absolute-speed < 0.1\n");
DEBUG_TRACE (DEBUG::TXLTC, "transport is not rolling or absolute-speed < 0.1\n");
/* keep repeating current sample
*
* an LTC generator must be able to continue generating LTC when Ardours transport is in stop
@ -336,19 +336,19 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
return;
}
if (start_sample != ltc_prev_cycle) {
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: no-roll seek from %1 to %2 (%3)\n", ltc_prev_cycle, start_sample, cycle_start_sample));
ltc_tx_reset();
}
}
if (fabs(new_ltc_speed) > 10.0) {
DEBUG_TRACE (DEBUG::LTC, "LTC TX2: speed is out of bounds.\n");
DEBUG_TRACE (DEBUG::TXLTC, "speed is out of bounds.\n");
ltc_tx_reset();
return;
}
if (ltc_speed == 0 && new_ltc_speed != 0) {
DEBUG_TRACE (DEBUG::LTC, "LTC TX2: transport started rolling - reset\n");
DEBUG_TRACE (DEBUG::TXLTC, "transport started rolling - reset\n");
ltc_tx_reset();
}
@ -374,21 +374,21 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
double oldbuflen = (double)(ltc_buf_len - ltc_buf_off);
double newbuflen = (double)(ltc_buf_len - ltc_buf_off) * fabs(ltc_speed / new_ltc_speed);
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: bufOld %1 bufNew %2 | diff %3\n",
DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: bufOld %1 bufNew %2 | diff %3\n",
(ltc_buf_len - ltc_buf_off), newbuflen, newbuflen - oldbuflen
));
double bufrspdiff = rint(newbuflen - oldbuflen);
if (abs(bufrspdiff) > newbuflen || abs(bufrspdiff) > oldbuflen) {
DEBUG_TRACE (DEBUG::LTC, "LTC TX2: resampling buffer would destroy information.\n");
DEBUG_TRACE (DEBUG::TXLTC, "resampling buffer would destroy information.\n");
ltc_tx_reset();
poff = 0;
} else if (bufrspdiff != 0 && newbuflen > oldbuflen) {
int incnt = 0;
double samples_to_insert = ceil(newbuflen - oldbuflen);
double avg_distance = newbuflen / samples_to_insert;
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer insert: %1\n", samples_to_insert));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer insert: %1\n", samples_to_insert));
for (int rp = ltc_buf_off; rp < ltc_buf_len - 1; ++rp) {
const int ro = rp - ltc_buf_off;
@ -402,7 +402,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
} else if (bufrspdiff != 0 && newbuflen < oldbuflen) {
double samples_to_remove = ceil(oldbuflen - newbuflen);
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: resample buffer - remove: %1\n", samples_to_remove));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: resample buffer - remove: %1\n", samples_to_remove));
if (oldbuflen <= samples_to_remove) {
ltc_buf_off = ltc_buf_len= 0;
} else {
@ -424,7 +424,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
ltc_prev_cycle = start_sample;
ltc_speed = new_ltc_speed;
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX2: transport speed %1.\n", ltc_speed));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("2: transport speed %1.\n", ltc_speed));
// (3) bit/sample alignment
Timecode::Time tc_start;
@ -451,7 +451,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
if (current_speed == 0) {
soff = 0;
}
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX3: A3cycle: %1 = A3tc: %2 +off: %3\n",
DEBUG_TRACE (DEBUG::TXLTC, string_compose("3: A3cycle: %1 = A3tc: %2 +off: %3\n",
cycle_start_sample, tc_sample_start, soff));
@ -470,8 +470,8 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
*/
double maxdiff;
if (config.get_external_sync() && slave()) {
maxdiff = slave()->resolution();
if (transport_master_is_external()) {
maxdiff = transport_master()->resolution();
} else {
maxdiff = ceil(fabs(ltc_speed))*2.0;
if (nominal_sample_rate() != sample_rate()) {
@ -482,10 +482,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
}
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n",
DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc: %1 + %2 - %3 || buf-bytes: %4 enc-byte: %5\n",
ltc_enc_pos, ltc_enc_cnt, poff, (ltc_buf_len - ltc_buf_off), poff, ltc_enc_byte));
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: enc-pos: %1 | d: %2\n",
DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: enc-pos: %1 | d: %2\n",
ltc_enc_pos + ltc_enc_cnt - poff,
rint(ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_sample
));
@ -515,7 +515,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
ltc_encoder_set_frame(ltc_encoder, &ltcframe);
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("4: now: %1 trs: %2 toff %3\n", cycle_start_sample, tc_sample_start, soff));
int32_t cyc_off;
if (soff < 0 || soff >= fptcf) {
@ -546,7 +546,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
}
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart encoder: soff %1 byte %2 cycoff %3\n",
DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart encoder: soff %1 byte %2 cycoff %3\n",
soff, ltc_enc_byte, cyc_off));
if ( (ltc_speed < 0 && ltc_enc_byte !=9 ) || (ltc_speed >= 0 && ltc_enc_byte !=0 ) ) {
@ -565,14 +565,14 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
ltc_enc_pos = tc_sample_start % wrap24h;
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX5 restart @ %1 + %2 - %3 | byte %4\n",
DEBUG_TRACE (DEBUG::TXLTC, string_compose("5 restart @ %1 + %2 - %3 | byte %4\n",
ltc_enc_pos, ltc_enc_cnt, cyc_off, ltc_enc_byte));
}
else if (ltc_speed != 0 && (fptcf / ltc_speed / 80) > 3 ) {
/* reduce (low freq) jitter.
* The granularity of the LTC encoder speed is 1 byte =
* (samples-per-timecode-sample / 10) audio-samples.
* Thus, tiny speed changes [as produced by some slaves]
* Thus, tiny speed changes [as produced by some transport masters]
* may not have any effect in the cycle when they occur,
* but they will add up over time.
*
@ -593,7 +593,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
// (6) encode and output
while (1) {
#ifdef LTC_GEN_TXDBUG
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.1 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.1 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
#endif
// (6a) send remaining buffer
while ((ltc_buf_off < ltc_buf_len) && (txf < nframes)) {
@ -602,11 +602,11 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
out[txf++] = val;
}
#ifdef LTC_GEN_TXDBUG
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.2 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.2 @%1 [ %2 / %3 ]\n", txf, ltc_buf_off, ltc_buf_len));
#endif
if (txf >= nframes) {
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n",
DEBUG_TRACE (DEBUG::TXLTC, string_compose("7 enc: %1 [ %2 / %3 ] byte: %4 spd %5 fpp %6 || nf: %7\n",
ltc_enc_pos, ltc_buf_off, ltc_buf_len, ltc_enc_byte, ltc_speed, nframes, txf));
break;
}
@ -635,7 +635,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
memset(&ltc_enc_buf[ltc_buf_len], 127, enc_samples * sizeof(ltcsnd_sample_t));
} else {
if (ltc_encoder_encode_byte(ltc_encoder, ltc_enc_byte, (ltc_speed==0)?1.0:(1.0/ltc_speed))) {
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoder error byte %1\n", ltc_enc_byte));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoder error byte %1\n", ltc_enc_byte));
ltc_encoder_buffer_flush(ltc_encoder);
ltc_tx_reset();
return;
@ -644,10 +644,10 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
#ifdef LTC_GEN_FRAMEDBUG
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.3 encoded %1 bytes for LTC-byte %2 at spd %3\n", enc_samples, ltc_enc_byte, ltc_speed));
#endif
if (enc_samples <=0) {
DEBUG_TRACE (DEBUG::LTC, "LTC TX6.3 encoder empty buffer.\n");
DEBUG_TRACE (DEBUG::TXLTC, "6.3 encoder empty buffer.\n");
ltc_encoder_buffer_flush(ltc_encoder);
ltc_tx_reset();
return;
@ -677,7 +677,7 @@ Session::ltc_tx_send_time_code_for_cycle (samplepos_t start_sample, samplepos_t
}
}
#ifdef LTC_GEN_FRAMEDBUG
DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed));
DEBUG_TRACE (DEBUG::TXLTC, string_compose("6.4 enc-pos: %1 + %2 [ %4 / %5 ] spd %6\n", ltc_enc_pos, ltc_enc_cnt, ltc_buf_off, ltc_buf_len, ltc_speed));
#endif
}

View File

@ -45,7 +45,7 @@
#include "ardour/midi_ui.h"
#include "ardour/profile.h"
#include "ardour/session.h"
#include "ardour/slave.h"
#include "ardour/transport_master.h"
#include "ardour/ticker.h"
#include "pbd/i18n.h"
@ -306,9 +306,9 @@ Session::mmc_locate (MIDI::MachineControl &/*mmc*/, const MIDI::byte* mmc_tc)
of an MTC slave to become out of date. Catch this.
*/
MTC_Slave* mtcs = dynamic_cast<MTC_Slave*> (_slave);
boost::shared_ptr<MTC_TransportMaster> mtcs = boost::dynamic_pointer_cast<MTC_TransportMaster> (transport_master());
if (mtcs != 0) {
if (mtcs) {
// cerr << "Locate *with* MTC slave\n";
mtcs->handle_locate (mmc_tc);
} else {
@ -402,7 +402,7 @@ Session::send_full_time_code (samplepos_t const t, MIDI::pframes_t nframes)
if (_engine.freewheeling() || !Config->get_send_mtc()) {
return 0;
}
if (_slave && !_slave->locked()) {
if (!transport_master()->locked()) {
return 0;
}
@ -486,7 +486,7 @@ Session::send_midi_time_code_for_cycle (samplepos_t start_sample, samplepos_t en
// cerr << "(MTC) Not sending MTC\n";
return 0;
}
if (_slave && !_slave->locked()) {
if (!transport_master()->locked()) {
return 0;
}
@ -707,21 +707,12 @@ Session::midi_clock_output_port () const
return _midi_ports->midi_clock_output_port ();
}
boost::shared_ptr<MidiPort>
Session::midi_clock_input_port () const
{
return _midi_ports->midi_clock_input_port ();
}
boost::shared_ptr<MidiPort>
Session::mtc_output_port () const
{
return _midi_ports->mtc_output_port ();
}
boost::shared_ptr<MidiPort>
Session::mtc_input_port () const
{
return _midi_ports->mtc_input_port ();
}
void
Session::midi_track_presentation_info_changed (PropertyChange const& what_changed, boost::weak_ptr<MidiTrack> mt)

View File

@ -38,7 +38,8 @@
#include "ardour/process_thread.h"
#include "ardour/scene_changer.h"
#include "ardour/session.h"
#include "ardour/slave.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/ticker.h"
#include "ardour/types.h"
#include "ardour/vca.h"
@ -65,6 +66,7 @@ Session::process (pframes_t nframes)
if (processing_blocked()) {
_silent = true;
cerr << "%%%%%%%%%%%%%% session process blocked\n";
return;
}
@ -251,6 +253,23 @@ Session::get_track_statistics ()
}
}
bool
Session::compute_audible_delta (samplepos_t& pos_and_delta) const
{
if (_transport_speed == 0.0 || _count_in_samples > 0 || _remaining_latency_preroll > 0) {
/* cannot compute audible delta, because the session is
generating silence that does not correspond to the timeline,
but is instead filling playback buffers to manage latency
alignment.
*/
DEBUG_TRACE (DEBUG::Slave, string_compose ("still adjusting for latency (%1) and/or count-in (%2) or stopped %1\n", _remaining_latency_preroll, _count_in_samples, _transport_speed));
return false;
}
pos_and_delta -= _transport_sample;
return true;
}
/** Process callback used when the auditioner is not active */
void
Session::process_with_events (pframes_t nframes)
@ -285,7 +304,6 @@ Session::process_with_events (pframes_t nframes)
immediate_events.pop_front ();
process_event (ev);
}
/* only count-in when going to roll at speed 1.0 */
if (_transport_speed != 1.0 && _count_in_samples > 0) {
_count_in_samples = 0;
@ -296,6 +314,8 @@ Session::process_with_events (pframes_t nframes)
assert (_count_in_samples == 0 || _remaining_latency_preroll == 0 || _count_in_samples == _remaining_latency_preroll);
DEBUG_TRACE (DEBUG::Transport, string_compose ("Running count in/latency preroll of %1 & %2\n", _count_in_samples, _remaining_latency_preroll));
while (_count_in_samples > 0 || _remaining_latency_preroll > 0) {
samplecnt_t ns;
@ -440,8 +460,9 @@ Session::process_with_events (pframes_t nframes)
return;
}
if (!_exporting && _slave) {
if (!follow_slave (nframes)) {
if (!_exporting && config.get_external_sync()) {
if (!follow_transport_master (nframes)) {
ltc_tx_send_time_code_for_cycle (_transport_sample, end_sample, _target_transport_speed, _transport_speed, nframes);
return;
}
}
@ -546,308 +567,16 @@ Session::process_with_events (pframes_t nframes)
}
}
void
Session::reset_slave_state ()
{
average_slave_delta = 1800;
delta_accumulator_cnt = 0;
have_first_delta_accumulator = false;
_slave_state = Stopped;
DiskReader::set_no_disk_output (false);
}
bool
Session::transport_locked () const
{
Slave* sl = _slave;
if (!locate_pending() && (!config.get_external_sync() || (sl && sl->ok() && sl->locked()))) {
if (!locate_pending() && (!config.get_external_sync() || (transport_master()->ok() && transport_master()->locked()))) {
return true;
}
return false;
}
bool
Session::follow_slave (pframes_t nframes)
{
double slave_speed;
samplepos_t slave_transport_sample;
samplecnt_t this_delta;
int dir;
if (!_slave->ok()) {
stop_transport ();
config.set_external_sync (false);
goto noroll;
}
_slave->speed_and_position (slave_speed, slave_transport_sample);
DEBUG_TRACE (DEBUG::Slave, string_compose ("Slave position %1 speed %2\n", slave_transport_sample, slave_speed));
if (!_slave->locked()) {
DEBUG_TRACE (DEBUG::Slave, "slave not locked\n");
goto noroll;
}
if (slave_transport_sample > _transport_sample) {
this_delta = slave_transport_sample - _transport_sample;
dir = 1;
} else {
this_delta = _transport_sample - slave_transport_sample;
dir = -1;
}
if (_slave->starting()) {
slave_speed = 0.0f;
}
if (_slave->is_always_synced() ||
(Config->get_timecode_source_is_synced() && (dynamic_cast<TimecodeSlave*>(_slave)) != 0)
) {
/* if the TC source is synced, then we assume that its
speed is binary: 0.0 or 1.0
*/
if (slave_speed != 0.0f) {
slave_speed = 1.0f;
}
} else {
/* if we are chasing and the average delta between us and the
master gets too big, we want to switch to silent
motion. so keep track of that here.
*/
if (_slave_state == Running) {
calculate_moving_average_of_slave_delta(dir, abs(this_delta));
}
}
track_slave_state (slave_speed, slave_transport_sample, this_delta);
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave state %1 @ %2 speed %3 cur delta %4 avg delta %5\n",
_slave_state, slave_transport_sample, slave_speed, this_delta, average_slave_delta));
if (_slave_state == Running && !_slave->is_always_synced() && !(Config->get_timecode_source_is_synced() && (dynamic_cast<TimecodeSlave*>(_slave)) != 0)) {
/* may need to varispeed to sync with slave */
if (_transport_speed != 0.0f) {
/*
note that average_dir is +1 or -1
*/
float delta;
if (average_slave_delta == 0) {
delta = this_delta;
delta *= dir;
} else {
delta = average_slave_delta;
delta *= average_dir;
}
#ifndef NDEBUG
if (slave_speed != 0.0) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("delta = %1 speed = %2 ts = %3 M@%4 S@%5 avgdelta %6\n",
(int) (dir * this_delta),
slave_speed,
_transport_speed,
_transport_sample,
slave_transport_sample,
average_slave_delta));
}
#endif
if (_slave->give_slave_full_control_over_transport_speed()) {
set_transport_speed (slave_speed, 0, false, false);
//std::cout << "set speed = " << slave_speed << "\n";
} else {
float adjusted_speed = slave_speed + (1.5 * (delta / float(_current_sample_rate)));
request_transport_speed (adjusted_speed);
DEBUG_TRACE (DEBUG::Slave, string_compose ("adjust using %1 towards %2 ratio %3 current %4 slave @ %5\n",
delta, adjusted_speed, adjusted_speed/slave_speed, _transport_speed,
slave_speed));
}
if (!actively_recording() && (samplecnt_t) average_slave_delta > _slave->resolution()) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("average slave delta %1 greater than slave resolution %2 => no disk output\n", average_slave_delta, _slave->resolution()));
/* run routes as normal, but no disk output */
DiskReader::set_no_disk_output (true);
return true;
}
if (!have_first_delta_accumulator) {
DEBUG_TRACE (DEBUG::Slave, "waiting for first slave delta accumulator to be ready, no disk output\n");
/* run routes as normal, but no disk output */
DiskReader::set_no_disk_output (true);
return true;
}
}
}
if (!have_first_delta_accumulator) {
DEBUG_TRACE (DEBUG::Slave, "still waiting to compute slave delta, no disk output\n");
DiskReader::set_no_disk_output (true);
} else {
DiskReader::set_no_disk_output (false);
}
if ((_slave_state == Running) && (0 == (post_transport_work () & ~PostTransportSpeed))) {
/* speed is set, we're locked, and good to go */
return true;
}
noroll:
/* don't move at all */
DEBUG_TRACE (DEBUG::Slave, "no roll\n")
no_roll (nframes);
return false;
}
void
Session::calculate_moving_average_of_slave_delta (int dir, samplecnt_t this_delta)
{
if (delta_accumulator_cnt >= delta_accumulator_size) {
have_first_delta_accumulator = true;
delta_accumulator_cnt = 0;
}
if (delta_accumulator_cnt != 0 || this_delta < _current_sample_rate) {
delta_accumulator[delta_accumulator_cnt++] = (samplecnt_t) dir * (samplecnt_t) this_delta;
}
if (have_first_delta_accumulator) {
average_slave_delta = 0L;
for (int i = 0; i < delta_accumulator_size; ++i) {
average_slave_delta += delta_accumulator[i];
}
average_slave_delta /= (int32_t) delta_accumulator_size;
if (average_slave_delta < 0L) {
average_dir = -1;
average_slave_delta = average_slave_delta;
} else {
average_dir = 1;
}
}
}
void
Session::track_slave_state (float slave_speed, samplepos_t slave_transport_sample, samplecnt_t /*this_delta*/)
{
if (slave_speed != 0.0f) {
/* slave is running */
switch (_slave_state) {
case Stopped:
if (_slave->requires_seekahead()) {
slave_wait_end = slave_transport_sample + _slave->seekahead_distance ();
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, but running, requires seekahead to %1\n", slave_wait_end));
/* we can call locate() here because we are in process context */
locate (slave_wait_end, false, false);
_slave_state = Waiting;
} else {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped -> running at %1\n", slave_transport_sample));
memset (delta_accumulator, 0, sizeof (int32_t) * delta_accumulator_size);
average_slave_delta = 0L;
Location* al = _locations->auto_loop_location();
if (al && play_loop && (slave_transport_sample < al->start() || slave_transport_sample > al->end())) {
// cancel looping
request_play_loop(false);
}
if (slave_transport_sample != _transport_sample) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("require locate to run. eng: %1 -> sl: %2\n", _transport_sample, slave_transport_sample));
locate (slave_transport_sample, false, false);
}
_slave_state = Running;
}
break;
case Waiting:
default:
break;
}
if (_slave_state == Waiting) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave waiting at %1\n", slave_transport_sample));
if (slave_transport_sample >= slave_wait_end) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave start at %1 vs %2\n", slave_transport_sample, _transport_sample));
_slave_state = Running;
/* now perform a "micro-seek" within the disk buffers to realign ourselves
precisely with the master.
*/
bool ok = true;
samplecnt_t sample_delta = slave_transport_sample - _transport_sample;
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr && !tr->can_internal_playback_seek (sample_delta)) {
ok = false;
break;
}
}
if (ok) {
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr) {
tr->internal_playback_seek (sample_delta);
}
}
_transport_sample += sample_delta;
} else {
cerr << "cannot micro-seek\n";
/* XXX what? */
}
}
}
if (_slave_state == Running && _transport_speed == 0.0f) {
DEBUG_TRACE (DEBUG::Slave, "slave starts transport\n");
start_transport ();
}
} else { // slave_speed is 0
/* slave has stopped */
if (_transport_speed != 0.0f) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", slave_speed, slave_transport_sample, _transport_sample));
stop_transport ();
}
if (slave_transport_sample != _transport_sample) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, move to %1\n", slave_transport_sample));
force_locate (slave_transport_sample, false);
}
reset_slave_state();
}
}
void
Session::process_without_events (pframes_t nframes)
{
@ -859,21 +588,22 @@ Session::process_without_events (pframes_t nframes)
return;
}
if (!_exporting && _slave) {
if (!follow_slave (nframes)) {
if (!_exporting && config.get_external_sync()) {
if (!follow_transport_master (nframes)) {
ltc_tx_send_time_code_for_cycle (_transport_sample, _transport_sample, 0, 0 , nframes);
return;
}
}
assert (_transport_speed == 0 || _transport_speed == 1.0 || _transport_speed == -1.0);
if (_transport_speed == 0) {
no_roll (nframes);
return;
} else {
samples_moved = (samplecnt_t) nframes;
}
assert (_transport_speed == 1.f || _transport_speed == -1.f);
samples_moved = (samplecnt_t) nframes * _transport_speed;
if (!_exporting && !timecode_transmission_suspended()) {
send_midi_time_code_for_cycle (_transport_sample, _transport_sample + samples_moved, nframes);
}
@ -1130,6 +860,10 @@ Session::process_event (SessionEvent* ev)
set_transport_speed (ev->speed, ev->target_sample, ev->yes_or_no, ev->second_yes_or_no, ev->third_yes_or_no);
break;
case SessionEvent::SetTransportMaster:
TransportMasterManager::instance().set_current (ev->transport_master);
break;
case SessionEvent::PunchIn:
// cerr << "PunchIN at " << transport_sample() << endl;
if (config.get_punch_in() && record_status() == Enabled) {
@ -1176,11 +910,6 @@ Session::process_event (SessionEvent* ev)
overwrite_some_buffers (static_cast<Track*>(ev->ptr));
break;
case SessionEvent::SetSyncSource:
DEBUG_TRACE (DEBUG::Slave, "seen request for new slave\n");
use_sync_source (ev->slave);
break;
case SessionEvent::Audition:
set_audition (ev->region);
// drop reference to region
@ -1234,7 +963,7 @@ Session::compute_stop_limit () const
return max_samplepos;
}
if (_slave) {
if (config.get_external_sync()) {
return max_samplepos;
}
@ -1330,3 +1059,146 @@ Session::emit_thread_run ()
}
pthread_mutex_unlock (&_rt_emit_mutex);
}
bool
Session::follow_transport_master (pframes_t nframes)
{
TransportMasterManager& tmm (TransportMasterManager::instance());
double slave_speed;
samplepos_t slave_transport_sample;
sampleoffset_t delta;
if (tmm.master_invalid_this_cycle()) {
DEBUG_TRACE (DEBUG::Slave, "session told not to use the transport master this cycle\n");
goto noroll;
}
slave_speed = tmm.get_current_speed_in_process_context();
slave_transport_sample = tmm.get_current_position_in_process_context ();
delta = _transport_sample - slave_transport_sample;
DEBUG_TRACE (DEBUG::Slave, string_compose ("session at %1, master at %2, delta: %3 res: %4\n", _transport_sample, slave_transport_sample, delta, tmm.current()->resolution()));
track_transport_master (slave_speed, slave_transport_sample);
if (transport_master_tracking_state == Running) {
if (!actively_recording() && fabs (delta) > tmm.current()->resolution()) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("average slave delta %1 greater than slave resolution %2\n", delta, tmm.current()->resolution()));
if (micro_locate (-delta) != 0) {
DEBUG_TRACE (DEBUG::Slave, "micro-locate didn't work, set no disk output true\n");
/* run routes as normal, but no disk output */
DiskReader::set_no_disk_output (true);
}
return true;
}
if (transport_master_tracking_state == Running) {
/* speed is set, we're locked, and good to go */
DiskReader::set_no_disk_output (false);
return true;
}
}
noroll:
/* don't move at all */
DEBUG_TRACE (DEBUG::Slave, "no roll\n")
no_roll (nframes);
return false;
}
void
Session::track_transport_master (float slave_speed, samplepos_t slave_transport_sample)
{
boost::shared_ptr<TransportMaster> master (TransportMasterManager::instance().current());
assert (master);
DEBUG_TRACE (DEBUG::Slave, string_compose ("session has master tracking state as %1\n", transport_master_tracking_state));
if (slave_speed != 0.0f) {
/* slave is running */
switch (transport_master_tracking_state) {
case Stopped:
if (master->requires_seekahead()) {
master_wait_end = slave_transport_sample + master->seekahead_distance ();
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, but running, requires seekahead to %1\n", master_wait_end));
/* we can call locate() here because we are in process context */
if (micro_locate (master_wait_end - _transport_sample) != 0) {
locate (master_wait_end, false, false);
}
transport_master_tracking_state = Waiting;
} else {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped -> running at %1\n", slave_transport_sample));
if (slave_transport_sample != _transport_sample) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("require locate to run. eng: %1 -> sl: %2\n", _transport_sample, slave_transport_sample));
if (micro_locate (slave_transport_sample - _transport_sample) != 0) {
locate (slave_transport_sample, false, false);
}
}
transport_master_tracking_state = Running;
}
break;
case Waiting:
default:
break;
}
if (transport_master_tracking_state == Waiting) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave waiting at %1\n", slave_transport_sample));
if (slave_transport_sample >= master_wait_end) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave start at %1 vs %2\n", slave_transport_sample, _transport_sample));
transport_master_tracking_state = Running;
/* now perform a "micro-seek" within the disk buffers to realign ourselves
precisely with the master.
*/
if (micro_locate (slave_transport_sample - _transport_sample) != 0) {
cerr << "cannot micro-seek\n";
/* XXX what? */
}
}
}
if (transport_master_tracking_state == Running && _transport_speed == 0.0f) {
DEBUG_TRACE (DEBUG::Slave, "slave starts transport\n");
start_transport ();
}
} else { // slave_speed is 0
/* slave has stopped */
if (_transport_speed != 0.0f) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", slave_speed, slave_transport_sample, _transport_sample));
stop_transport ();
}
if (slave_transport_sample != _transport_sample) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, move to %1\n", slave_transport_sample));
force_locate (slave_transport_sample, false);
}
reset_slave_state();
}
}
void
Session::reset_slave_state ()
{
transport_master_tracking_state = Stopped;
DiskReader::set_no_disk_output (false);
}

View File

@ -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") {

View File

@ -47,7 +47,8 @@
#include "ardour/profile.h"
#include "ardour/scene_changer.h"
#include "ardour/session.h"
#include "ardour/slave.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/tempo.h"
#include "ardour/operations.h"
#include "ardour/vca.h"
@ -78,33 +79,34 @@ Session::add_post_transport_work (PostTransportWork ptw)
error << "Could not set post transport work! Crazy thread madness, call the programmers" << endmsg;
}
void
Session::request_sync_source (Slave* new_slave)
bool
Session::should_ignore_transport_request (TransportRequestSource src, TransportRequestType type) const
{
SessionEvent* ev = new SessionEvent (SessionEvent::SetSyncSource, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
bool seamless;
seamless = Config->get_seamless_loop ();
if (dynamic_cast<Engine_Slave*>(new_slave)) {
/* JACK cannot support seamless looping at present */
Config->set_seamless_loop (false);
} else {
/* reset to whatever the value was before we last switched slaves */
Config->set_seamless_loop (_was_seamless);
if (config.get_external_sync()) {
if (TransportMasterManager::instance().current()->allow_request (src, type)) {
return false;
} else {
return true;
}
}
return false;
}
/* save value of seamless from before the switch */
_was_seamless = seamless;
ev->slave = new_slave;
DEBUG_TRACE (DEBUG::Slave, "sent request for new slave\n");
void
Session::request_sync_source (boost::shared_ptr<TransportMaster> tm)
{
SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportMaster, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
ev->transport_master = tm;
DEBUG_TRACE (DEBUG::Slave, "sent request for new transport master\n");
queue_event (ev);
}
void
Session::request_transport_speed (double speed, bool as_default)
Session::request_transport_speed (double speed, bool as_default, TransportRequestSource origin)
{
if (should_ignore_transport_request (origin, TR_Speed)) {
return;
}
SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed);
ev->third_yes_or_no = as_default; // as_default
DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport speed = %1 as default = %2\n", speed, as_default));
@ -116,8 +118,12 @@ Session::request_transport_speed (double speed, bool as_default)
* be used by callers who are varying transport speed but don't ever want to stop it.
*/
void
Session::request_transport_speed_nonzero (double speed, bool as_default)
Session::request_transport_speed_nonzero (double speed, bool as_default, TransportRequestSource origin)
{
if (should_ignore_transport_request (origin, TransportRequestType (TR_Speed|TR_Start))) {
return;
}
if (speed == 0) {
speed = DBL_EPSILON;
}
@ -126,16 +132,24 @@ Session::request_transport_speed_nonzero (double speed, bool as_default)
}
void
Session::request_stop (bool abort, bool clear_state)
Session::request_stop (bool abort, bool clear_state, TransportRequestSource origin)
{
if (should_ignore_transport_request (origin, TR_Stop)) {
return;
}
SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, audible_sample(), 0.0, abort, clear_state);
DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport stop, audible %3 transport %4 abort = %1, clear state = %2\n", abort, clear_state, audible_sample(), _transport_sample));
queue_event (ev);
}
void
Session::request_locate (samplepos_t target_sample, bool with_roll)
Session::request_locate (samplepos_t target_sample, bool with_roll, TransportRequestSource origin)
{
if (should_ignore_transport_request (origin, TR_Locate)) {
return;
}
SessionEvent *ev = new SessionEvent (with_roll ? SessionEvent::LocateRoll : SessionEvent::Locate, SessionEvent::Add, SessionEvent::Immediate, target_sample, 0, false);
DEBUG_TRACE (DEBUG::Transport, string_compose ("Request locate to %1\n", target_sample));
queue_event (ev);
@ -190,7 +204,7 @@ Session::request_count_in_record ()
void
Session::request_play_loop (bool yn, bool change_transport_roll)
{
if (_slave && yn) {
if (transport_master_is_external() && yn) {
// don't attempt to loop when not using Internal Transport
// see also gtk2_ardour/ardour_ui_options.cc parameter_changed()
return;
@ -287,22 +301,22 @@ Session::solo_selection ( StripableList &list, bool new_state )
_soloSelection = list;
else
_soloSelection.clear();
boost::shared_ptr<RouteList> rl = get_routes();
for (ARDOUR::RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
if ( !(*i)->is_track() ) {
continue;
}
boost::shared_ptr<Stripable> s (*i);
bool found = (std::find(list.begin(), list.end(), s) != list.end());
if ( new_state && found ) {
solo_list->push_back (s->solo_control());
//must invalidate playlists on selected tracks, so only selected regions get heard
boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (*i);
if (track) {
@ -369,7 +383,7 @@ Session::realtime_stop (bool abort, bool clear_state)
if ( solo_selection_active() ) {
solo_selection ( _soloSelection, false );
}
/* if we're going to clear loop state, then force disabling record BUT only if we're not doing latched rec-enable */
disable_record (true, (!Config->get_latched_record_enable() && clear_state));
@ -487,10 +501,6 @@ Session::butler_transport_work ()
}
}
if (ptw & PostTransportSpeed) {
non_realtime_set_speed ();
}
if (ptw & PostTransportReverse) {
clear_clicks();
@ -546,18 +556,6 @@ Session::butler_transport_work ()
DEBUG_TRACE (DEBUG::Transport, string_compose (X_("Butler transport work all done after %1 usecs @ %2 trw = %3\n"), g_get_monotonic_time() - before, _transport_sample, _butler->transport_work_requested()));
}
void
Session::non_realtime_set_speed ()
{
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr) {
tr->non_realtime_speed_change ();
}
}
}
void
Session::non_realtime_overwrite (int on_entry, bool& finished)
{
@ -926,7 +924,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
// need to queue this in the next RT cycle
_send_timecode_update = true;
if (!dynamic_cast<MTC_Slave*>(_slave)) {
if (transport_master()->type() == MTC) {
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop));
/* This (::non_realtime_stop()) gets called by main
@ -947,7 +945,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
*
* save state only if there's no slave or if it's not yet locked.
*/
if (!_slave || !_slave->locked()) {
if (!transport_master_is_external() || !transport_master()->locked()) {
DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: requests save\n"));
SaveSessionRequested (_current_snapshot_name);
saved = true;
@ -1122,7 +1120,7 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
double sp;
samplepos_t pos;
_slave->speed_and_position (sp, pos);
transport_master()->speed_and_position (sp, pos, 0);
if (target_sample != pos) {
@ -1168,6 +1166,8 @@ Session::micro_locate (samplecnt_t distance)
}
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("micro-locate by %1\n", distance));
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr) {
@ -1611,7 +1611,7 @@ Session::start_transport ()
if (!_engine.freewheeling()) {
Timecode::Time time;
timecode_time_subframes (_transport_sample, time);
if (!dynamic_cast<MTC_Slave*>(_slave)) {
if (transport_master()->type() == MTC) {
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay));
}
@ -1721,164 +1721,6 @@ Session::reset_rf_scale (samplecnt_t motion)
}
}
void
Session::mtc_status_changed (bool yn)
{
g_atomic_int_set (&_mtc_active, yn);
MTCSyncStateChanged( yn );
}
void
Session::ltc_status_changed (bool yn)
{
g_atomic_int_set (&_ltc_active, yn);
LTCSyncStateChanged( yn );
}
void
Session::use_sync_source (Slave* new_slave)
{
/* Runs in process() context */
if (!_slave && !new_slave) {
return;
}
bool non_rt_required = false;
/* XXX this deletion is problematic because we're in RT context */
delete _slave;
_slave = new_slave;
/* slave change, reset any DiskIO block on disk output because it is no
longer valid with a new slave.
*/
DiskReader::set_no_disk_output (false);
MTC_Slave* mtc_slave = dynamic_cast<MTC_Slave*>(_slave);
if (mtc_slave) {
mtc_slave->ActiveChanged.connect_same_thread (mtc_status_connection, boost::bind (&Session::mtc_status_changed, this, _1));
MTCSyncStateChanged(mtc_slave->locked() );
} else {
if (g_atomic_int_get (&_mtc_active) ){
g_atomic_int_set (&_mtc_active, 0);
MTCSyncStateChanged( false );
}
mtc_status_connection.disconnect ();
}
LTC_Slave* ltc_slave = dynamic_cast<LTC_Slave*> (_slave);
if (ltc_slave) {
ltc_slave->ActiveChanged.connect_same_thread (ltc_status_connection, boost::bind (&Session::ltc_status_changed, this, _1));
LTCSyncStateChanged (ltc_slave->locked() );
} else {
if (g_atomic_int_get (&_ltc_active) ){
g_atomic_int_set (&_ltc_active, 0);
LTCSyncStateChanged( false );
}
ltc_status_connection.disconnect ();
}
DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave));
// need to queue this for next process() cycle
_send_timecode_update = true;
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr && !tr->is_private_route()) {
tr->set_slaved (_slave != 0);
}
}
if (non_rt_required) {
add_post_transport_work (PostTransportSpeed);
_butler->schedule_transport_work ();
}
set_dirty();
}
void
Session::drop_sync_source ()
{
request_sync_source (0);
}
void
Session::switch_to_sync_source (SyncSource src)
{
Slave* new_slave;
DEBUG_TRACE (DEBUG::Slave, string_compose ("Setting up sync source %1\n", enum_2_string (src)));
switch (src) {
case MTC:
if (_slave && dynamic_cast<MTC_Slave*>(_slave)) {
return;
}
try {
new_slave = new MTC_Slave (*this, *_midi_ports->mtc_input_port());
}
catch (failed_constructor& err) {
return;
}
break;
case LTC:
if (_slave && dynamic_cast<LTC_Slave*>(_slave)) {
return;
}
try {
new_slave = new LTC_Slave (*this);
}
catch (failed_constructor& err) {
return;
}
break;
case MIDIClock:
if (_slave && dynamic_cast<MIDIClock_Slave*>(_slave)) {
return;
}
try {
new_slave = new MIDIClock_Slave (*this, *_midi_ports->midi_clock_input_port(), 24);
}
catch (failed_constructor& err) {
return;
}
break;
case Engine:
if (_slave && dynamic_cast<Engine_Slave*>(_slave)) {
return;
}
if (config.get_video_pullup() != 0.0f) {
return;
}
new_slave = new Engine_Slave (*AudioEngine::instance());
break;
default:
new_slave = 0;
break;
};
request_sync_source (new_slave);
}
void
Session::unset_play_range ()
{
@ -2112,3 +1954,91 @@ Session::timecode_transmission_suspended () const
{
return g_atomic_int_get (&_suspend_timecode_transmission) == 1;
}
boost::shared_ptr<TransportMaster>
Session::transport_master() const
{
return TransportMasterManager::instance().current();
}
bool
Session::transport_master_is_external () const
{
return config.get_external_sync();
}
void
Session::sync_source_changed (SyncSource type, samplepos_t pos, pframes_t cycle_nframes)
{
/* Runs in process() context */
boost::shared_ptr<TransportMaster> master = TransportMasterManager::instance().current();
/* save value of seamless from before the switch */
_was_seamless = Config->get_seamless_loop ();
if (type == Engine) {
/* JACK cannot support seamless looping at present */
Config->set_seamless_loop (false);
} else {
/* reset to whatever the value was before we last switched slaves */
Config->set_seamless_loop (_was_seamless);
}
if (master->can_loop()) {
request_play_loop (false);
} else if (master->has_loop()) {
request_play_loop (true);
}
/* slave change, reset any DiskIO block on disk output because it is no
longer valid with a new slave.
*/
DiskReader::set_no_disk_output (false);
#if 0
we should not be treating specific transport masters as special cases because there maybe > 1 of a particular type
boost::shared_ptr<MTC_TransportMaster> mtc_master = boost::dynamic_pointer_cast<MTC_TransportMaster> (master);
if (mtc_master) {
mtc_master->ActiveChanged.connect_same_thread (mtc_status_connection, boost::bind (&Session::mtc_status_changed, this, _1));
MTCSyncStateChanged(mtc_master->locked() );
} else {
if (g_atomic_int_compare_and_exchange (&_mtc_active, 1, 0)) {
MTCSyncStateChanged( false );
}
mtc_status_connection.disconnect ();
}
boost::shared_ptr<LTC_TransportMaster> ltc_master = boost::dynamic_pointer_cast<LTC_TransportMaster> (master);
if (ltc_master) {
ltc_master->ActiveChanged.connect_same_thread (ltc_status_connection, boost::bind (&Session::ltc_status_changed, this, _1));
LTCSyncStateChanged (ltc_master->locked() );
} else {
if (g_atomic_int_compare_and_exchange (&_ltc_active, 1, 0)) {
LTCSyncStateChanged( false );
}
ltc_status_connection.disconnect ();
}
#endif
DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", master));
// need to queue this for next process() cycle
_send_timecode_update = true;
boost::shared_ptr<RouteList> rl = routes.reader();
const bool externally_slaved = transport_master_is_external();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr && !tr->is_private_route()) {
tr->set_slaved (externally_slaved);
}
}
set_dirty();
}

View File

@ -524,12 +524,6 @@ Track::non_realtime_locate (samplepos_t p)
Route::non_realtime_locate (p);
}
void
Track::non_realtime_speed_change ()
{
_disk_reader->non_realtime_speed_change ();
}
int
Track::overwrite_existing_buffers ()
{

View File

@ -0,0 +1,281 @@
/*
Copyright (C) 2002 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <vector>
#include "pbd/i18n.h"
#include "ardour/audioengine.h"
#include "ardour/midi_port.h"
#include "ardour/session.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/utils.h"
using namespace ARDOUR;
const std::string TransportMaster::state_node_name = X_("TransportMaster");
TransportMaster::TransportMaster (SyncSource t, std::string const & name)
: _type (t)
, _name (name)
, _session (0)
, _connected (false)
, _current_delta (0)
, _collect (true)
, _pending_collect (true)
, _request_mask (TransportRequestType (0))
, _sclock_synced (false)
{
ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect_same_thread (port_connection, boost::bind (&TransportMaster::connection_handler, this, _1, _2, _3, _4, _5));
ARDOUR::AudioEngine::instance()->Running.connect_same_thread (backend_connection, boost::bind (&TransportMaster::check_backend, this));
}
TransportMaster::~TransportMaster()
{
delete _session;
}
bool
TransportMaster::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
{
if (!_port) {
return false;
}
const std::string fqn = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (_port->name());
if (fqn == name1 || fqn == name2) {
/* it's about us */
if (yn) {
_connected = true;
} else {
_connected = false;
}
return true;
}
return false;
}
bool
TransportMaster::check_collect()
{
if (!_connected) {
return false;
}
/* XXX should probably use boost::atomic something or other here */
if (_pending_collect != _collect) {
if (_pending_collect) {
init ();
} else {
if (TransportMasterManager::instance().current().get() == this) {
if (_session) {
_session->config.set_external_sync (false);
}
}
}
std::cerr << name() << " pc = " << _pending_collect << " c = " << _collect << std::endl;
_collect = _pending_collect;
}
return _collect;
}
void
TransportMaster::set_collect (bool yn)
{
_pending_collect = yn;
}
void
TransportMaster::set_sample_clock_synced (bool yn)
{
_sclock_synced = yn;
}
void
TransportMaster::set_session (Session* s)
{
_session = s;
}
int
TransportMaster::set_state (XMLNode const & node, int /* version */)
{
if (!node.get_property (X_("collect"), _collect)) {
_collect = false;
}
if (!node.get_property (X_("clock-synced"), _sclock_synced)) {
_sclock_synced = false;
}
TimecodeTransportMaster* ttm = dynamic_cast<TimecodeTransportMaster*> (this);
if (ttm) {
bool val;
node.get_property (X_("fr2997"), val);
ttm->set_fr2997 (val);
}
XMLNode* pnode = node.child (X_("Port"));
if (pnode) {
XMLNodeList const & children = pnode->children();
for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
XMLProperty const *prop;
if ((*ci)->name() == X_("Connection")) {
if ((prop = (*ci)->property (X_("other"))) == 0) {
continue;
}
_port->connect (prop->value());
}
}
}
return 0;
}
XMLNode&
TransportMaster::get_state ()
{
XMLNode* node = new XMLNode (state_node_name);
node->set_property (X_("type"), _type);
node->set_property (X_("name"), _name);
node->set_property (X_("collect"), _collect);
node->set_property (X_("clock-synced"), _sclock_synced);
TimecodeTransportMaster* ttm = dynamic_cast<TimecodeTransportMaster*> (this);
if (ttm) {
node->set_property (X_("fr2997"), ttm->fr2997());
}
if (_port) {
std::vector<std::string> connections;
XMLNode* pnode = new XMLNode (X_("Port"));
if (_port->get_connections (connections)) {
std::vector<std::string>::const_iterator ci;
std::sort (connections.begin(), connections.end());
for (ci = connections.begin(); ci != connections.end(); ++ci) {
/* if its a connection to our own port,
return only the port name, not the
whole thing. this allows connections
to be re-established even when our
client name is different.
*/
XMLNode* cnode = new XMLNode (X_("Connection"));
cnode->set_property (X_("other"), AudioEngine::instance()->make_port_name_relative (*ci));
pnode->add_child_nocopy (*cnode);
}
}
node->add_child_nocopy (*pnode);
}
return *node;
}
boost::shared_ptr<TransportMaster>
TransportMaster::factory (XMLNode const & node)
{
if (node.name() != TransportMaster::state_node_name) {
return boost::shared_ptr<TransportMaster>();
}
SyncSource type;
std::string name;
if (!node.get_property (X_("type"), type)) {
return boost::shared_ptr<TransportMaster>();
}
if (!node.get_property (X_("name"), name)) {
return boost::shared_ptr<TransportMaster>();
}
return factory (type, name);
}
boost::shared_ptr<TransportMaster>
TransportMaster::factory (SyncSource type, std::string const& name)
{
/* XXX need to count existing sources of a given type */
switch (type) {
case MTC:
return boost::shared_ptr<TransportMaster> (new MTC_TransportMaster (sync_source_to_string (type)));
case LTC:
return boost::shared_ptr<TransportMaster> (new LTC_TransportMaster (sync_source_to_string (type)));
case MIDIClock:
return boost::shared_ptr<TransportMaster> (new MIDIClock_TransportMaster (sync_source_to_string (type)));
case Engine:
return boost::shared_ptr<TransportMaster> (new Engine_TransportMaster (*AudioEngine::instance()));
default:
break;
}
return boost::shared_ptr<TransportMaster>();
}
boost::shared_ptr<Port>
TransportMasterViaMIDI::create_midi_port (std::string const & port_name)
{
boost::shared_ptr<Port> p;
if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name)) == 0) {
return boost::shared_ptr<Port> ();
}
_midi_port = boost::dynamic_pointer_cast<MidiPort> (p);
return p;
}
bool
TransportMaster::allow_request (TransportRequestSource src, TransportRequestType type) const
{
return _request_mask & type;
}
void
TransportMaster::set_request_mask (TransportRequestType t)
{
_request_mask = t;
}
void
TimecodeTransportMaster::set_fr2997 (bool yn)
{
_fr2997 = yn;
}

View File

@ -0,0 +1,537 @@
/*
* Copyright (C) 2018 Paul Davis (paul@linuxaudiosystems.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "pbd/i18n.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/disk_reader.h"
#include "ardour/session.h"
#include "ardour/transport_master_manager.h"
#if __cplusplus > 199711L
#define local_signbit(x) std::signbit (x)
#else
#define local_signbit(x) ((((__int64*)(&z))*) & 0x8000000000000000)
#endif
using namespace ARDOUR;
using namespace PBD;
const std::string TransportMasterManager::state_node_name = X_("TransportMasters");
TransportMasterManager* TransportMasterManager::_instance = 0;
TransportMasterManager::TransportMasterManager()
: _master_speed (0)
, _master_position (0)
, _current_master (0)
, _session (0)
, _master_invalid_this_cycle (false)
, master_dll_initstate (0)
{
}
TransportMasterManager::~TransportMasterManager ()
{
clear ();
}
int
TransportMasterManager::init ()
{
try {
/* setup default transport masters. Most people will never need any
others
*/
add (Engine, X_("JACK Transport"));
add (MTC, X_("MTC"));
add (LTC, X_("LTC"));
add (MIDIClock, X_("MIDI Clock"));
} catch (...) {
return -1;
}
_current_master = _transport_masters.back();
return 0;
}
void
TransportMasterManager::set_session (Session* s)
{
/* Called by AudioEngine in process context, synchronously with it's
* own "adoption" of the Session. The call will occur before the first
* call to ::pre_process_transport_masters().
*/
Glib::Threads::RWLock::ReaderLock lm (lock);
config_connection.disconnect ();
_session = s;
for (TransportMasters::iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
(*tm)->set_session (s);
}
if (_session) {
_session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&TransportMasterManager::parameter_changed, this, _1));
}
}
void
TransportMasterManager::parameter_changed (std::string const & what)
{
if (what == "external-sync") {
if (!_session->config.get_external_sync()) {
/* disabled */
DiskReader::set_no_disk_output (false);
}
}
}
TransportMasterManager&
TransportMasterManager::instance()
{
if (!_instance) {
_instance = new TransportMasterManager();
}
return *_instance;
}
// Called from AudioEngine::process_callback() BEFORE Session::process() is called. Each transport master has processed any incoming data for this cycle,
// and this method computes the transport speed that Ardour should use to get into and remain in sync with the master.
//
double
TransportMasterManager::pre_process_transport_masters (pframes_t nframes, samplepos_t now)
{
Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
if (!lm.locked()) {
return 1.0;
}
boost::optional<samplepos_t> session_pos;
if (_session) {
session_pos = _session->audible_sample();
}
if (Config->get_run_all_transport_masters_always()) {
for (TransportMasters::iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
if ((*tm)->check_collect()) {
(*tm)->pre_process (nframes, now, session_pos);
}
}
}
if (!_session) {
return 1.0;
}
/* if we're not running ALL transport masters, but still have a current
* one, then we should run that one all the time so that we know
* precisely where it is when we starting chasing it ...
*/
if (!Config->get_run_all_transport_masters_always() && _current_master) {
_current_master->pre_process (nframes, now, session_pos);
}
if (!_session->config.get_external_sync()) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("no external sync, use session actual speed of %1\n", _session->actual_speed() ? _session->actual_speed() : 1.0));
return _session->actual_speed () ? _session->actual_speed() : 1.0;
}
/* --- NOT REACHED UNLESS CHASING (i.e. _session->config.get_external_sync() is true ------*/
if (!_current_master->ok()) {
/* stop */
_session->request_transport_speed (0.0, false, _current_master->request_type());
DEBUG_TRACE (DEBUG::Slave, "no roll2 - master has failed\n");
_master_invalid_this_cycle = true;
return 1.0;
}
if (!_current_master->locked()) {
DEBUG_TRACE (DEBUG::Slave, "no roll4 - not locked\n");
_master_invalid_this_cycle = true;
return 1.0;
}
double engine_speed;
if (!_current_master->speed_and_position (_master_speed, _master_position, now)) {
return 1.0;
}
if (_master_speed != 0.0) {
samplepos_t delta = _master_position;
if (_session->compute_audible_delta (delta)) {
if (master_dll_initstate == 0) {
init_transport_master_dll (_master_speed, _master_position);
// _master_invalid_this_cycle = true;
DEBUG_TRACE (DEBUG::Slave, "no roll3 - still initializing master DLL\n");
master_dll_initstate = _master_speed > 0.0 ? 1 : -1;
return 1.0;
}
/* compute delta or "error" between the computed master_position for
* this cycle and the current session position.
*
* Remember: ::speed_and_position() is being called in process context
* but returns the predicted speed+position for the start of this process cycle,
* not just the most recent timestamp received by the current master object.
*/
DEBUG_TRACE (DEBUG::Slave, string_compose ("master DLL: delta = %1 (%2 vs %3) res: %4\n", delta, _master_position, _session->transport_sample(), _current_master->resolution()));
if (delta > _current_master->resolution()) {
// init_transport_master_dll (_master_speed, _master_position);
if (!_session->actively_recording()) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave delta %1 greater than slave resolution %2 => no disk output\n", delta, _current_master->resolution()));
/* run routes as normal, but no disk output */
DiskReader::set_no_disk_output (true);
} else {
DiskReader::set_no_disk_output (false);
}
} else {
DiskReader::set_no_disk_output (false);
}
/* inject DLL with new data */
DEBUG_TRACE (DEBUG::Slave, string_compose ("feed master DLL t0 %1 t1 %2 e %3 %4 e2 %5 sess %6\n", t0, t1, delta, _master_position, e2, _session->transport_sample()));
const double e = delta;
t0 = t1;
t1 += b * e + e2;
e2 += c * e;
engine_speed = (t1 - t0) / nframes;
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave @ %1 speed %2 cur delta %3 matching speed %4\n", _master_position, _master_speed, delta, engine_speed));
/* provide a .1% deadzone to lock the speed */
if (fabs (engine_speed - 1.0) <= 0.001) {
engine_speed = 1.0;
}
if (_current_master->sample_clock_synced() && engine_speed != 0.0f) {
/* if the master is synced to our audio interface via word-clock or similar, then we assume that its speed is binary: 0.0 or 1.0
(since our sample clock cannot change with respect to it).
*/
if (engine_speed > 0.0) {
engine_speed = 1.0;
} else if (engine_speed < 0.0) {
engine_speed = -1.0;
}
}
/* speed is set, we're locked, and good to go */
DEBUG_TRACE (DEBUG::Slave, string_compose ("%1: computed speed-to-follow-master as %2\n", _current_master->name(), engine_speed));
} else {
/* session has not finished with latency compensation yet, so we cannot compute the
difference between the master and the session.
*/
engine_speed = 1.0;
}
} else {
engine_speed = 1.0;
}
_master_invalid_this_cycle = false;
DEBUG_TRACE (DEBUG::Slave, string_compose ("computed resampling ratio as %1 with position = %2 and speed = %3\n", engine_speed, _master_position, _master_speed));
return engine_speed;
}
void
TransportMasterManager::init_transport_master_dll (double speed, samplepos_t pos)
{
/* the bandwidth of the DLL is a trade-off,
* because the max-speed of the transport in ardour is
* limited to +-8.0, a larger bandwidth would cause oscillations
*
* But this is only really a problem if the user performs manual
* seeks while transport is running and slaved to some timecode-y master.
*/
AudioEngine* ae = AudioEngine::instance();
double const omega = 2.0 * M_PI * double(ae->samples_per_cycle()) / 2.0 / double(ae->sample_rate());
b = 1.4142135623730950488 * omega;
c = omega * omega;
const int direction = (speed >= 0.0 ? 1 : -1);
master_dll_initstate = direction;
e2 = double (direction * ae->samples_per_cycle());
t0 = double (pos);
t1 = t0 + e2;
DEBUG_TRACE (DEBUG::Slave, string_compose ("[re-]init ENGINE DLL %1 %2 %3 from %4 %5\n", t0, t1, e2, speed, pos));
}
int
TransportMasterManager::add (SyncSource type, std::string const & name)
{
int ret = 0;
boost::shared_ptr<TransportMaster> tm;
{
Glib::Threads::RWLock::WriterLock lm (lock);
tm = TransportMaster::factory (type, name);
ret = add_locked (tm);
}
if (ret == 0) {
Added (tm);
}
return ret;
}
int
TransportMasterManager::add_locked (boost::shared_ptr<TransportMaster> tm)
{
if (!tm) {
return -1;
}
for (TransportMasters::const_iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
if ((*t)->name() == tm->name()) {
error << string_compose (_("There is already a transport master named \"%1\" - not duplicated"), tm->name()) << endmsg;
return -1;
}
}
_transport_masters.push_back (tm);
return 0;
}
int
TransportMasterManager::remove (std::string const & name)
{
int ret = -1;
boost::shared_ptr<TransportMaster> tm;
{
Glib::Threads::RWLock::WriterLock lm (lock);
for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
if ((*t)->name() == name) {
tm = *t;
_transport_masters.erase (t);
ret = 0;
break;
}
}
}
if (ret == 0 && tm) {
Removed (tm);
}
return -1;
}
int
TransportMasterManager::set_current_locked (boost::shared_ptr<TransportMaster> c)
{
if (find (_transport_masters.begin(), _transport_masters.end(), c) == _transport_masters.end()) {
warning << string_compose (X_("programming error: attempt to use unknown transport master named \"%1\"\n"), c->name());
return -1;
}
_current_master = c;
_master_speed = 0;
_master_position = 0;
master_dll_initstate = 0;
DEBUG_TRACE (DEBUG::Slave, string_compose ("current transport master set to %1\n", c->name()));
return 0;
}
int
TransportMasterManager::set_current (boost::shared_ptr<TransportMaster> c)
{
int ret = -1;
boost::shared_ptr<TransportMaster> old (_current_master);
{
Glib::Threads::RWLock::WriterLock lm (lock);
ret = set_current_locked (c);
}
if (ret == 0) {
CurrentChanged (old, _current_master); // EMIT SIGNAL
}
return ret;
}
int
TransportMasterManager::set_current (SyncSource ss)
{
int ret = -1;
boost::shared_ptr<TransportMaster> old (_current_master);
{
Glib::Threads::RWLock::WriterLock lm (lock);
for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
if ((*t)->type() == ss) {
ret = set_current_locked (*t);
break;
}
}
}
if (ret == 0) {
CurrentChanged (old, _current_master); // EMIT SIGNAL
}
return ret;
}
int
TransportMasterManager::set_current (std::string const & str)
{
int ret = -1;
boost::shared_ptr<TransportMaster> old (_current_master);
{
Glib::Threads::RWLock::WriterLock lm (lock);
for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
if ((*t)->name() == str) {
ret = set_current_locked (*t);
break;
}
}
}
if (ret == 0) {
CurrentChanged (old, _current_master); // EMIT SIGNAL
}
return ret;
}
void
TransportMasterManager::clear ()
{
{
Glib::Threads::RWLock::WriterLock lm (lock);
_transport_masters.clear ();
}
Removed (boost::shared_ptr<TransportMaster>());
}
int
TransportMasterManager::set_state (XMLNode const & node, int version)
{
assert (node.name() == state_node_name);
XMLNodeList const & children = node.children();
if (!children.empty()) {
_transport_masters.clear ();
}
{
Glib::Threads::RWLock::WriterLock lm (lock);
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
boost::shared_ptr<TransportMaster> tm = TransportMaster::factory (**c);
if (add_locked (tm)) {
continue;
}
/* we know it is the last thing added to the list of masters */
_transport_masters.back()->set_state (**c, version);
}
}
std::string current_master;
if (node.get_property (X_("current"), current_master)) {
set_current (current_master);
} else {
set_current (MTC);
}
return 0;
}
XMLNode&
TransportMasterManager::get_state ()
{
XMLNode* node = new XMLNode (state_node_name);
node->set_property (X_("current"), _current_master->name());
Glib::Threads::RWLock::ReaderLock lm (lock);
for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) {
node->add_child_nocopy ((*t)->get_state());
}
return *node;
}
boost::shared_ptr<TransportMaster>
TransportMasterManager::master_by_type (SyncSource src) const
{
Glib::Threads::RWLock::ReaderLock lm (lock);
for (TransportMasters::const_iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
if ((*tm)->type() == src) {
return *tm;
}
}
return boost::shared_ptr<TransportMaster> ();
}

View File

@ -219,7 +219,6 @@ libardour_sources = [
'session_time.cc',
'session_transport.cc',
'sidechain.cc',
'slave.cc',
'slavable.cc',
'slavable_automation_control.cc',
'smf_source.cc',
@ -247,6 +246,8 @@ libardour_sources = [
'track.cc',
'transient_detector.cc',
'transform.cc',
'transport_master.cc',
'transport_master_manager.cc',
'transpose.cc',
'unknown_processor.cc',
'user_bundle.cc',

View File

@ -42,6 +42,7 @@ typedef PBD::Signal2<void,Parser &, pitchbend_t> PitchBendSignal;
typedef PBD::Signal3<void,Parser &, uint16_t, int> RPNSignal;
typedef PBD::Signal3<void,Parser &, uint16_t, float> RPNValueSignal;
typedef PBD::Signal3<void,Parser &, byte *, size_t> Signal;
typedef PBD::Signal4<void,Parser &, byte *, size_t, samplecnt_t> AnySignal;
class LIBMIDIPP_API Parser {
public:
@ -86,7 +87,7 @@ class LIBMIDIPP_API Parser {
Signal mtc;
Signal raw_preparse;
Signal raw_postparse;
Signal any;
AnySignal any;
Signal sysex;
Signal mmc;
Signal position;
@ -147,7 +148,7 @@ class LIBMIDIPP_API Parser {
std::ostream *trace_stream;
std::string trace_prefix;
void trace_event (Parser &p, byte *msg, size_t len);
void trace_event (Parser &p, byte *msg, size_t len, samplecnt_t);
PBD::ScopedConnection trace_connection;
size_t message_counter[256];

View File

@ -136,7 +136,7 @@ Parser::~Parser ()
}
void
Parser::trace_event (Parser &, MIDI::byte *msg, size_t len)
Parser::trace_event (Parser &, MIDI::byte *msg, size_t len, samplecnt_t /*when*/)
{
eventType type;
ostream *o;
@ -313,7 +313,7 @@ Parser::trace (bool onoff, ostream *o, const string &prefix)
if (onoff) {
trace_stream = o;
trace_prefix = prefix;
any.connect_same_thread (trace_connection, boost::bind (&Parser::trace_event, this, _1, _2, _3));
any.connect_same_thread (trace_connection, boost::bind (&Parser::trace_event, this, _1, _2, _3, _4));
} else {
trace_prefix = "";
trace_stream = 0;
@ -440,7 +440,7 @@ Parser::scanner (unsigned char inbyte)
}
}
if (!_offline) {
any (*this, msgbuf, msgindex);
any (*this, msgbuf, msgindex, _timestamp);
}
}
}
@ -572,7 +572,7 @@ Parser::realtime_msg(unsigned char inbyte)
break;
}
any (*this, &inbyte, 1);
any (*this, &inbyte, 1, _timestamp);
}
@ -661,7 +661,7 @@ Parser::system_msg (unsigned char inbyte)
// all these messages will be sent via any()
// when they are complete.
// any (*this, &inbyte, 1);
// any (*this, &inbyte, 1, _timestamp);
}
void
@ -764,7 +764,7 @@ Parser::signal (MIDI::byte *msg, size_t len)
break;
}
any (*this, msg, len);
any (*this, msg, len, _timestamp);
}
bool