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 */
@ -82,7 +82,6 @@ public:
void* ptr;
bool yes_or_no;
samplepos_t target2_sample;
Slave* slave;
Route* route;
};
@ -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__ */

View file

@ -53,37 +53,37 @@ typedef int intptr_t;
namespace ARDOUR {
class Source;
class AudioSource;
class Route;
class Region;
class Stripable;
class VCA;
class AutomationControl;
class SlavableAutomationControl;
class Source;
class AudioSource;
class Route;
class Region;
class Stripable;
class VCA;
class AutomationControl;
class SlavableAutomationControl;
typedef float Sample;
typedef float pan_t;
typedef float gain_t;
typedef uint32_t layer_t;
typedef uint64_t microseconds_t;
typedef uint32_t pframes_t;
typedef float Sample;
typedef float pan_t;
typedef float gain_t;
typedef uint32_t layer_t;
typedef uint64_t microseconds_t;
typedef uint32_t pframes_t;
/* rebind Temporal position types into ARDOUR namespace */
typedef Temporal::samplecnt_t samplecnt_t;
typedef Temporal::samplepos_t samplepos_t;
typedef Temporal::sampleoffset_t sampleoffset_t;
/* rebind Temporal position types into ARDOUR namespace */
typedef Temporal::samplecnt_t samplecnt_t;
typedef Temporal::samplepos_t samplepos_t;
typedef Temporal::sampleoffset_t sampleoffset_t;
static const layer_t max_layer = UINT32_MAX;
static const layer_t max_layer = UINT32_MAX;
// a set of (time) intervals: first of pair is the offset of the start within the region, second is the offset of the end
typedef std::list<std::pair<sampleoffset_t, sampleoffset_t> > AudioIntervalResult;
// associate a set of intervals with regions (e.g. for silence detection)
typedef std::map<boost::shared_ptr<ARDOUR::Region>,AudioIntervalResult> AudioIntervalMap;
// a set of (time) intervals: first of pair is the offset of the start within the region, second is the offset of the end
typedef std::list<std::pair<sampleoffset_t, sampleoffset_t> > AudioIntervalResult;
// associate a set of intervals with regions (e.g. for silence detection)
typedef std::map<boost::shared_ptr<ARDOUR::Region>,AudioIntervalResult> AudioIntervalMap;
typedef std::list<boost::shared_ptr<Region> > RegionList;
typedef std::list<boost::shared_ptr<Region> > RegionList;
struct IOChange {
struct IOChange {
enum Type {
NoChange = 0,
@ -98,22 +98,22 @@ namespace ARDOUR {
ARDOUR::ChanCount before;
/** channel count of IO after a ConfigurationChanged, if appropriate */
ARDOUR::ChanCount after;
};
};
/* policies for inserting/pasting material where overlaps
/* policies for inserting/pasting material where overlaps
might be an issue.
*/
*/
enum InsertMergePolicy {
enum InsertMergePolicy {
InsertMergeReject, ///< no overlaps allowed
InsertMergeRelax, ///< we just don't care about overlaps
InsertMergeReplace, ///< replace old with new
InsertMergeTruncateExisting, ///< shorten existing to avoid overlap
InsertMergeTruncateAddition, ///< shorten new to avoid overlap
InsertMergeExtend ///< extend new (or old) to the range of old+new
};
};
/** See evoral/Parameter.hpp
/** See evoral/Parameter.hpp
*
* When you add things here, you REALLY SHOULD add a case clause to
* the constructor of ParameterDescriptor, unless the Controllables
@ -122,7 +122,7 @@ namespace ARDOUR {
* added there so that things that try to represent them can do so
* with as much information as possible.
*/
enum AutomationType {
enum AutomationType {
NullAutomation,
GainAutomation,
PanAzimuthAutomation,
@ -158,45 +158,45 @@ namespace ARDOUR {
SendLevelAutomation,
SendEnableAutomation,
SendAzimuthAutomation,
};
};
enum AutoState {
enum AutoState {
Off = 0x00,
Write = 0x01,
Touch = 0x02,
Play = 0x04,
Latch = 0x08
};
};
std::string auto_state_to_string (AutoState);
AutoState string_to_auto_state (std::string);
std::string auto_state_to_string (AutoState);
AutoState string_to_auto_state (std::string);
enum AlignStyle {
enum AlignStyle {
CaptureTime,
ExistingMaterial
};
};
enum AlignChoice {
enum AlignChoice {
UseCaptureTime,
UseExistingMaterial,
Automatic
};
};
enum MeterPoint {
enum MeterPoint {
MeterInput,
MeterPreFader,
MeterPostFader,
MeterOutput,
MeterCustom
};
};
enum DiskIOPoint {
enum DiskIOPoint {
DiskIOPreFader, /* after the trim control, but before other processors */
DiskIOPostFader, /* before the main outs, after other processors */
DiskIOCustom, /* up to the user. Caveat Emptor! */
};
};
enum MeterType {
enum MeterType {
MeterMaxSignal = 0x0001,
MeterMaxPeak = 0x0002,
MeterPeak = 0x0004,
@ -211,40 +211,40 @@ namespace ARDOUR {
MeterK12 = 0x0800,
MeterPeak0dB = 0x1000,
MeterMCP = 0x2000
};
};
enum TrackMode {
enum TrackMode {
Normal,
NonLayered,
Destructive
};
};
enum NoteMode {
enum NoteMode {
Sustained,
Percussive
};
};
enum ChannelMode {
enum ChannelMode {
AllChannels = 0, ///< Pass through all channel information unmodified
FilterChannels, ///< Ignore events on certain channels
ForceChannel ///< Force all events to a certain channel
};
};
enum ColorMode {
enum ColorMode {
MeterColors = 0,
ChannelColors,
TrackColor
};
};
enum RoundMode {
enum RoundMode {
RoundDownMaybe = -2, ///< Round down only if necessary
RoundDownAlways = -1, ///< Always round down, even if on a division
RoundNearest = 0, ///< Round to nearest
RoundUpAlways = 1, ///< Always round up, even if on a division
RoundUpMaybe = 2 ///< Round up only if necessary
};
};
enum SnapPref {
enum SnapPref {
SnapToAny_Visual = 0, /**< Snap to the editor's visual snap
* (incoprorating snap prefs and the current zoom scaling)
* this defines the behavior for visual mouse drags, for example */
@ -257,9 +257,9 @@ namespace ARDOUR {
* If one is selected, and ignore any visual scaling
* this is the behavior for automated processes like "snap regions to grid"
* but note that midi quantization uses its own mechanism, not the grid */
};
};
class AnyTime {
class AnyTime {
public:
enum Type {
Timecode,
@ -313,9 +313,9 @@ namespace ARDOUR {
abort(); /* NOTREACHED */
return false;
}
};
};
/* used for translating audio samples to an exact musical position using a note divisor.
/* used for translating audio samples to an exact musical position using a note divisor.
an exact musical position almost never falls exactly on an audio sample, but for sub-sample
musical accuracy we need to derive exact musical locations from a sample position
the division follows TempoMap::exact_beat_at_sample().
@ -324,8 +324,8 @@ namespace ARDOUR {
0 musical location is the musical position of the sample
1 musical location is the BBT beat closest to sample
n musical location is the quarter-note division n closest to sample
*/
struct MusicSample {
*/
struct MusicSample {
samplepos_t sample;
int32_t division;
@ -334,12 +334,12 @@ namespace ARDOUR {
void set (samplepos_t f, int32_t d) {sample = f; division = d; }
MusicSample operator- (MusicSample other) { return MusicSample (sample - other.sample, 0); }
};
};
/* XXX: slightly unfortunate that there is this and Evoral::Range<>,
/* XXX: slightly unfortunate that there is this and Evoral::Range<>,
but this has a uint32_t id which Evoral::Range<> does not.
*/
struct AudioRange {
*/
struct AudioRange {
samplepos_t start;
samplepos_t end;
uint32_t id;
@ -359,9 +359,9 @@ namespace ARDOUR {
Evoral::OverlapType coverage (samplepos_t s, samplepos_t e) const {
return Evoral::coverage (start, end, s, e);
}
};
};
struct MusicRange {
struct MusicRange {
Timecode::BBT_Time start;
Timecode::BBT_Time end;
uint32_t id;
@ -376,14 +376,14 @@ namespace ARDOUR {
bool equal (const MusicRange& other) const {
return start == other.start && end == other.end;
}
};
};
/*
/*
Slowest = 6.6dB/sec falloff at update rate of 40ms
Slow = 6.8dB/sec falloff at update rate of 40ms
*/
*/
enum MeterFalloff {
enum MeterFalloff {
MeterFalloffOff = 0,
MeterFalloffSlowest = 1,
MeterFalloffSlow = 2,
@ -393,23 +393,23 @@ namespace ARDOUR {
MeterFalloffFast = 6,
MeterFalloffFaster = 7,
MeterFalloffFastest = 8,
};
};
enum MeterHold {
enum MeterHold {
MeterHoldOff = 0,
MeterHoldShort = 40,
MeterHoldMedium = 100,
MeterHoldLong = 200
};
};
enum EditMode {
enum EditMode {
Slide,
Splice,
Ripple,
Lock
};
};
enum RegionSelectionAfterSplit {
enum RegionSelectionAfterSplit {
None = 0,
NewlyCreatedLeft = 1, // bit 0
NewlyCreatedRight = 2, // bit 1
@ -418,122 +418,122 @@ namespace ARDOUR {
ExistingNewlyCreatedLeft = 5,
ExistingNewlyCreatedRight = 6,
ExistingNewlyCreatedBoth = 7
};
};
enum RegionPoint {
enum RegionPoint {
Start,
End,
SyncPoint
};
};
enum Placement {
enum Placement {
PreFader,
PostFader
};
};
enum MonitorModel {
enum MonitorModel {
HardwareMonitoring, ///< JACK does monitoring
SoftwareMonitoring, ///< Ardour does monitoring
ExternalMonitoring ///< we leave monitoring to the audio hardware
};
};
enum MonitorChoice {
enum MonitorChoice {
MonitorAuto = 0,
MonitorInput = 0x1,
MonitorDisk = 0x2,
MonitorCue = 0x3,
};
};
enum MonitorState {
enum MonitorState {
MonitoringSilence = 0x1,
MonitoringInput = 0x2,
MonitoringDisk = 0x4,
MonitoringCue = 0x6,
};
};
enum MeterState {
enum MeterState {
MeteringInput, ///< meter the input IO, regardless of what is going through the route
MeteringRoute ///< meter what is going through the route
};
};
enum VUMeterStandard {
enum VUMeterStandard {
MeteringVUfrench, // 0VU = -2dBu
MeteringVUamerican, // 0VU = 0dBu
MeteringVUstandard, // 0VU = +4dBu
MeteringVUeight // 0VU = +8dBu
};
};
enum MeterLineUp {
enum MeterLineUp {
MeteringLineUp24,
MeteringLineUp20,
MeteringLineUp18,
MeteringLineUp15
};
};
enum PFLPosition {
enum PFLPosition {
/** PFL signals come from before pre-fader processors */
PFLFromBeforeProcessors,
/** PFL signals come pre-fader but after pre-fader processors */
PFLFromAfterProcessors
};
};
enum AFLPosition {
enum AFLPosition {
/** AFL signals come post-fader and before post-fader processors */
AFLFromBeforeProcessors,
/** AFL signals come post-fader but after post-fader processors */
AFLFromAfterProcessors
};
};
enum ClockDeltaMode {
enum ClockDeltaMode {
NoDelta,
DeltaEditPoint,
DeltaOriginMarker
};
};
enum DenormalModel {
enum DenormalModel {
DenormalNone,
DenormalFTZ,
DenormalDAZ,
DenormalFTZDAZ
};
};
enum LayerModel {
enum LayerModel {
LaterHigher,
Manual
};
};
enum ListenPosition {
enum ListenPosition {
AfterFaderListen,
PreFaderListen
};
};
enum AutoConnectOption {
enum AutoConnectOption {
ManualConnect = 0x0,
AutoConnectPhysical = 0x1,
AutoConnectMaster = 0x2
};
};
enum TracksAutoNamingRule {
enum TracksAutoNamingRule {
UseDefaultNames = 0x1,
NameAfterDriver = 0x2
};
};
enum SampleFormat {
enum SampleFormat {
FormatFloat = 0,
FormatInt24,
FormatInt16
};
};
int format_data_width (ARDOUR::SampleFormat);
int format_data_width (ARDOUR::SampleFormat);
enum CDMarkerFormat {
enum CDMarkerFormat {
CDMarkerNone,
CDMarkerCUE,
CDMarkerTOC,
MP4Chaps
};
};
enum HeaderFormat {
enum HeaderFormat {
BWF,
WAVE,
WAVE64,
@ -543,23 +543,23 @@ namespace ARDOUR {
RF64,
RF64_WAV,
MBWF,
};
};
struct PeakData {
struct PeakData {
typedef Sample PeakDatum;
PeakDatum min;
PeakDatum max;
};
};
enum RunContext {
enum RunContext {
ButlerContext = 0,
TransportContext,
ExportContext
};
};
enum SyncSource {
/* These are "synonyms". It is important for JACK to be first
enum SyncSource {
/* The first two are "synonyms". It is important for JACK to be first
both here and in enums.cc, so that the string "JACK" is
correctly recognized in older session and preference files.
*/
@ -567,75 +567,91 @@ namespace ARDOUR {
Engine = 0,
MTC,
MIDIClock,
LTC
};
LTC,
};
enum ShuttleBehaviour {
enum TransportRequestSource {
TRS_Engine,
TRS_MTC,
TRS_MIDIClock,
TRS_LTC,
TRS_MMC,
TRS_UI,
};
enum TransportRequestType {
TR_Stop = 0x1,
TR_Start = 0x2,
TR_Speed = 0x4,
TR_Locate = 0x8
};
enum ShuttleBehaviour {
Sprung,
Wheel
};
};
enum ShuttleUnits {
enum ShuttleUnits {
Percentage,
Semitones
};
};
typedef std::vector<boost::shared_ptr<Source> > SourceList;
typedef std::vector<boost::shared_ptr<Source> > SourceList;
enum SrcQuality {
enum SrcQuality {
SrcBest,
SrcGood,
SrcQuick,
SrcFast,
SrcFastest
};
};
typedef std::list<samplepos_t> AnalysisFeatureList;
typedef std::list<samplepos_t> AnalysisFeatureList;
typedef std::list<boost::shared_ptr<Route> > RouteList;
typedef std::list<boost::shared_ptr<Stripable> > StripableList;
typedef std::list<boost::weak_ptr <Route> > WeakRouteList;
typedef std::list<boost::weak_ptr <Stripable> > WeakStripableList;
typedef std::list<boost::shared_ptr<AutomationControl> > ControlList;
typedef std::list<boost::shared_ptr<SlavableAutomationControl> > SlavableControlList;
typedef std::set <boost::shared_ptr<AutomationControl> > AutomationControlSet;
typedef std::list<boost::shared_ptr<Route> > RouteList;
typedef std::list<boost::shared_ptr<Stripable> > StripableList;
typedef std::list<boost::weak_ptr <Route> > WeakRouteList;
typedef std::list<boost::weak_ptr <Stripable> > WeakStripableList;
typedef std::list<boost::shared_ptr<AutomationControl> > ControlList;
typedef std::list<boost::shared_ptr<SlavableAutomationControl> > SlavableControlList;
typedef std::set <boost::shared_ptr<AutomationControl> > AutomationControlSet;
typedef std::list<boost::shared_ptr<VCA> > VCAList;
typedef std::list<boost::shared_ptr<VCA> > VCAList;
class Bundle;
typedef std::vector<boost::shared_ptr<Bundle> > BundleList;
class Bundle;
typedef std::vector<boost::shared_ptr<Bundle> > BundleList;
enum RegionEquivalence {
enum RegionEquivalence {
Exact,
Enclosed,
Overlap
};
};
enum WaveformScale {
enum WaveformScale {
Linear,
Logarithmic
};
};
enum WaveformShape {
enum WaveformShape {
Traditional,
Rectified
};
};
struct CleanupReport {
struct CleanupReport {
std::vector<std::string> paths;
size_t space;
};
};
enum PositionLockStyle {
enum PositionLockStyle {
AudioTime,
MusicTime
};
};
/** A struct used to describe changes to processors in a route.
/** A struct used to describe changes to processors in a route.
* This is useful because objects that respond to a change in processors
* can optimise what work they do based on details of what has changed.
*/
struct RouteProcessorChange {
struct RouteProcessorChange {
enum Type {
GeneralChange = 0x0,
MeterPointChange = 0x1,
@ -655,21 +671,21 @@ namespace ARDOUR {
Type type;
/** true if, when a MeterPointChange has occurred, the change is visible to the user */
bool meter_visibly_changed;
};
};
struct BusProfile {
struct BusProfile {
uint32_t master_out_channels; /* how many channels for the master bus, 0: no master bus */
};
};
enum FadeShape {
enum FadeShape {
FadeLinear,
FadeFast,
FadeSlow,
FadeConstantPower,
FadeSymmetric,
};
};
enum TransportState {
enum TransportState {
/* these values happen to match the constants used by JACK but
this equality cannot be assumed.
*/
@ -677,9 +693,9 @@ namespace ARDOUR {
TransportRolling = 1,
TransportLooping = 2,
TransportStarting = 3,
};
};
enum PortFlags {
enum PortFlags {
/* these values happen to match the constants used by JACK but
this equality cannot be assumed.
*/
@ -692,57 +708,57 @@ namespace ARDOUR {
/* non-JACK related flags */
Hidden = 0x20,
Shadow = 0x40
};
};
enum MidiPortFlags {
enum MidiPortFlags {
MidiPortMusic = 0x1,
MidiPortControl = 0x2,
MidiPortSelection = 0x4,
MidiPortVirtual = 0x8
};
};
struct LatencyRange {
struct LatencyRange {
uint32_t min; //< samples
uint32_t max; //< samples
};
};
enum BufferingPreset {
enum BufferingPreset {
Small,
Medium,
Large,
Custom,
};
};
enum AutoReturnTarget {
enum AutoReturnTarget {
LastLocate = 0x1,
RangeSelectionStart = 0x2,
Loop = 0x4,
RegionSelectionStart = 0x8,
};
};
enum PlaylistDisposition {
enum PlaylistDisposition {
CopyPlaylist,
NewPlaylist,
SharePlaylist
};
};
enum MidiTrackNameSource {
enum MidiTrackNameSource {
SMFTrackNumber,
SMFTrackName,
SMFInstrumentName
};
};
enum MidiTempoMapDisposition {
enum MidiTempoMapDisposition {
SMFTempoIgnore,
SMFTempoUse,
};
};
struct CaptureInfo {
struct CaptureInfo {
samplepos_t start;
samplecnt_t samples;
};
};
typedef std::vector<CaptureInfo*> CaptureInfos;
typedef std::vector<CaptureInfo*> CaptureInfos;
} // namespace ARDOUR

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);
/* 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 {
_starting = false;
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;
@ -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));
resync_latency();
AudioEngine::instance()->Xrun.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_xrun, this));
AudioEngine::instance()->GraphReordered.connect_same_thread (port_connections, boost::bind (&LTC_TransportMaster::resync_latency, this));
}
void
LTC_TransportMaster::init ()
{
reset (true);
}
void
LTC_TransportMaster::set_session (Session *s)
{
config_connection.disconnect ();
_session = s;
if (_session) {
samples_per_ltc_frame = _session->samples_per_timecode_frame();
timecode.rate = _session->timecode_frames_per_second();
timecode.drop = _session->timecode_drop_frames();
printed_timecode_warning = false;
ltc_timecode = _session->config.get_timecode_format();
a3e_timecode = _session->config.get_timecode_format();
if (Config->get_use_session_timecode_format() && _session) {
samples_per_timecode_frame = _session->samples_per_timecode_frame();
}
if (decoder) {
ltc_decoder_free (decoder);
}
decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);
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));
_session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&LTC_TransportMaster::parameter_changed, this, _1));
}
}
LTC_Slave::~LTC_Slave()
LTC_TransportMaster::~LTC_TransportMaster()
{
port_connections.drop_connections();
config_connection.disconnect();
if (did_reset_tc_format) {
session.config.set_timecode_format (saved_tc_format);
_session->config.set_timecode_format (saved_tc_format);
}
ltc_decoder_free(decoder);
}
void
LTC_Slave::parse_timecode_offset() {
LTC_TransportMaster::parse_timecode_offset() {
Timecode::Time offset_tc;
Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc);
offset_tc.rate = session.timecode_frames_per_second();
offset_tc.drop = session.timecode_drop_frames();
session.timecode_to_sample(offset_tc, timecode_offset, false, false);
Timecode::parse_timecode_format(_session->config.get_slave_timecode_offset(), offset_tc);
offset_tc.rate = _session->timecode_frames_per_second();
offset_tc.drop = _session->timecode_drop_frames();
_session->timecode_to_sample(offset_tc, timecode_offset, false, false);
timecode_negative_offset = offset_tc.negative;
}
void
LTC_Slave::parameter_changed (std::string const & p)
LTC_TransportMaster::parameter_changed (std::string const & p)
{
if (p == "slave-timecode-offset"
|| p == "timecode-format"
@ -104,65 +145,61 @@ LTC_Slave::parameter_changed (std::string const & p)
}
ARDOUR::samplecnt_t
LTC_Slave::resolution () const
LTC_TransportMaster::resolution () const
{
return (samplecnt_t) (session.sample_rate() / 1000);
return (samplecnt_t) (ENGINE->sample_rate() / 1000);
}
bool
LTC_Slave::locked () const
LTC_TransportMaster::locked () const
{
return (delayedlocked < 5);
}
bool
LTC_Slave::ok() const
LTC_TransportMaster::ok() const
{
return true;
}
void
LTC_Slave::resync_xrun()
LTC_TransportMaster::resync_xrun()
{
DEBUG_TRACE (DEBUG::LTC, "LTC resync_xrun()\n");
engine_dll_initstate = 0;
sync_lock_broken = false;
}
void
LTC_Slave::resync_latency()
LTC_TransportMaster::resync_latency()
{
DEBUG_TRACE (DEBUG::LTC, "LTC resync_latency()\n");
engine_dll_initstate = 0;
sync_lock_broken = false;
if (!session.deletion_in_progress() && session.ltc_output_io()) { /* check if Port exits */
boost::shared_ptr<Port> ltcport = session.ltc_input_port();
ltcport->get_connected_latency_range (ltc_slave_latency, false);
if (!_port) {
_port->get_connected_latency_range (ltc_slave_latency, false);
}
}
void
LTC_Slave::reset (bool with_ts)
LTC_TransportMaster::reset (bool with_ts)
{
DEBUG_TRACE (DEBUG::LTC, "LTC reset()\n");
if (with_ts) {
last_timestamp = 0;
current_delta = 0;
_current_delta = 0;
}
transport_direction = 0;
ltc_speed = 0;
engine_dll_initstate = 0;
sync_lock_broken = false;
ActiveChanged (false); /* EMIT SIGNAL */
monotonic_cnt = 0;
}
void
LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
LTC_TransportMaster::parse_ltc (const ARDOUR::pframes_t nframes, const Sample* const in, const ARDOUR::samplecnt_t posinfo)
{
pframes_t i;
unsigned char sound[8192];
if (nframes > 8192) {
/* TODO warn once or wrap, loop conversion below
* does jack/A3 support > 8192 spp anyway?
@ -171,32 +208,33 @@ LTC_Slave::parse_ltc(const ARDOUR::pframes_t nframes, const Sample* const in, co
}
for (i = 0; i < nframes; i++) {
const int snd=(int)rint((127.0*in[i])+128.0);
const int snd=(int) rint ((127.0*in[i])+128.0);
sound[i] = (unsigned char) (snd&0xff);
}
ltc_decoder_write(decoder, sound, nframes, posinfo);
ltc_decoder_write (decoder, sound, nframes, posinfo);
return;
}
bool
LTC_Slave::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
if ( a->frame_units != b->frame_units
|| a->frame_tens != b->frame_tens
|| a->dfbit != b->dfbit
|| a->secs_units != b->secs_units
|| a->secs_tens != b->secs_tens
|| a->mins_units != b->mins_units
|| a->mins_tens != b->mins_tens
|| a->hours_units != b->hours_units
|| a->hours_tens != b->hours_tens
) {
LTC_TransportMaster::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
if (a->frame_units != b->frame_units ||
a->frame_tens != b->frame_tens ||
a->dfbit != b->dfbit ||
a->secs_units != b->secs_units ||
a->secs_tens != b->secs_tens ||
a->mins_units != b->mins_units ||
a->mins_tens != b->mins_tens ||
a->hours_units != b->hours_units ||
a->hours_tens != b->hours_tens) {
return false;
}
return true;
}
bool
LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
LTC_TransportMaster::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
bool discontinuity_detected = false;
if (fuzzy && (
@ -221,7 +259,7 @@ LTC_Slave::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy) {
}
bool
LTC_Slave::detect_ltc_fps(int frameno, bool df)
LTC_TransportMaster::detect_ltc_fps(int frameno, bool df)
{
bool fps_changed = false;
double detected_fps = 0;
@ -236,7 +274,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
detected_fps = ltc_detect_fps_max + 1;
if (df) {
/* LTC df -> indicates fractional framerate */
if (Config->get_timecode_source_2997()) {
if (fr2997()) {
detected_fps = detected_fps * 999.0 / 1000.0;
} else {
detected_fps = detected_fps * 1000.0 / 1001.0;
@ -256,7 +294,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
if (detected_fps != 0 && (detected_fps != timecode.rate || df != timecode.drop)) {
timecode.rate = detected_fps;
timecode.drop = df;
samples_per_ltc_frame = double(session.sample_rate()) / timecode.rate;
samples_per_ltc_frame = double(_session->sample_rate()) / timecode.rate;
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC reset to FPS: %1%2 ; audio-samples per LTC: %3\n",
detected_fps, df?"df":"ndf", samples_per_ltc_frame));
fps_changed=true;
@ -264,7 +302,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
/* poll and check session TC */
TimecodeFormat tc_format = apparent_timecode_format();
TimecodeFormat cur_timecode = session.config.get_timecode_format();
TimecodeFormat cur_timecode = _session->config.get_timecode_format();
if (Config->get_timecode_sync_frame_rate()) {
/* enforce time-code */
@ -279,7 +317,7 @@ LTC_Slave::detect_ltc_fps(int frameno, bool df)
Timecode::timecode_format_name(tc_format))
<< endmsg;
}
session.config.set_timecode_format (tc_format);
_session->config.set_timecode_format (tc_format);
}
} else {
/* only warn about TC mismatch */
@ -299,36 +337,53 @@ 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;
}
if (!ltc_is_static && detect_ltc_fps(stime.frame, (sample.ltc.dfbit)? true : false)) {
fps_detected = 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",
#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,
@ -338,6 +393,7 @@ LTC_Slave::process_ltc(samplepos_t const /*now*/)
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.
/* XXX these are not atomics and maybe modified in a thread other other than the one
that is executing this.
*/
speed = ltc_speed;
/* provide a .1% deadzone to lock the speed */
if (fabs (speed - 1.0) <= 0.001) {
speed = 1.0;
}
if (speed != 0 && delayedlocked == 0 && fabs(speed) != 1.0) {
sync_lock_broken = true;
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 based on %2\n", speed, ltc_speed));
}
pos = last_ltc_sample;
pos += (now - last_timestamp) * speed;
return true;
}
void
LTC_TransportMaster::pre_process (pframes_t nframes, samplepos_t now, boost::optional<samplepos_t> session_pos)
{
Sample* in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (_port->port_handle(), nframes);
sampleoffset_t skip = now - (monotonic_cnt + nframes);
monotonic_cnt = now;
DEBUG_TRACE (DEBUG::LTC, string_compose ("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);
memset (sound, 0x80, sizeof(char) * skip);
ltc_decoder_write (decoder, sound, nframes, now);
} else if (skip != 0) {
/* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
reset();
}
parse_ltc(nframes, in, now);
process_ltc(now);
}
/* Now feed the incoming LTC signal into the decoder */
parse_ltc (nframes, in, now);
/* and pull out actual LTC frame data */
process_ltc (now);
if (last_timestamp == 0) {
DEBUG_TRACE (DEBUG::LTC, "last timestamp == 0\n");
speed = 0;
pos = session.transport_sample();
return true;
return;
} else if (ltc_speed != 0) {
if (delayedlocked > 1) delayedlocked--;
else if (current_delta == 0) delayedlocked = 0;
DEBUG_TRACE (DEBUG::LTC, string_compose ("speed non-zero (%1)\n", ltc_speed));
if (delayedlocked > 1) {
delayedlocked--;
} else if (_current_delta == 0) {
delayedlocked = 0;
}
}
if (abs(now - last_timestamp) > FLYWHEEL_TIMEOUT) {
if (abs (now - last_timestamp) > FLYWHEEL_TIMEOUT) {
DEBUG_TRACE (DEBUG::LTC, "flywheel timeout\n");
reset();
speed = 0;
pos = session.transport_sample();
ActiveChanged (false); /* EMIT SIGNAL */
return true;
/* don't change position from last known */
return;
}
/* 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() ));
if (session_pos) {
const samplepos_t current_pos = last_ltc_sample + ((now - last_timestamp) * ltc_speed);
_current_delta = current_pos - *session_pos;
} else {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC adjusting elapsed (no DLL) from %1 by %2\n", elapsed, (2 * nframes * ltc_speed)));
speed_flt = 0;
elapsed += 2.0 * nframes * ltc_speed; /* see note above */
_current_delta = 0;
}
pos = last_ltc_sample + rint(elapsed);
speed = speed_flt;
current_delta = (pos - sess_pos);
if (((pos < 0) || (labs(current_delta) > 2 * session.sample_rate()))) {
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC large drift: %1\n", current_delta));
reset();
speed = 0;
return true;
}
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n",
speed, pos, last_ltc_sample, elapsed, current_delta));
/* provide a .1% deadzone to lock the speed */
if (fabs(speed - 1.0) <= 0.001) {
speed = 1.0;
}
if (speed != 0 && delayedlocked == 0 && fabs(speed) != 1.0) {
sync_lock_broken = true;
DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked %1 %2\n", speed, ltc_speed));
}
return true;
}
Timecode::TimecodeFormat
LTC_Slave::apparent_timecode_format () const
LTC_TransportMaster::apparent_timecode_format () const
{
if (timecode.rate == 24 && !timecode.drop)
return timecode_24;
else if (timecode.rate == 25 && !timecode.drop)
return timecode_25;
else if (rint(timecode.rate * 100) == 2997 && !timecode.drop)
return (Config->get_timecode_source_2997() ? timecode_2997000 : timecode_2997);
return (fr2997() ? timecode_2997000 : timecode_2997);
else if (rint(timecode.rate * 100) == 2997 && timecode.drop)
return (Config->get_timecode_source_2997() ? timecode_2997000drop : timecode_2997drop);
return (fr2997() ? timecode_2997000drop : timecode_2997drop);
else if (timecode.rate == 30 && timecode.drop)
return timecode_2997drop; // timecode_30drop; // LTC counting to 30 samples w/DF *means* 29.97 df
else if (timecode.rate == 30 && !timecode.drop)
return timecode_30;
/* XXX - unknown timecode format */
return session.config.get_timecode_format();
return _session->config.get_timecode_format();
}
std::string
LTC_Slave::approximate_current_position() const
LTC_TransportMaster::position_string() const
{
if (last_timestamp == 0) {
if (!_collect || last_timestamp == 0) {
return " --:--:--:--";
}
return Timecode::timecode_format_time(timecode);
}
std::string
LTC_Slave::approximate_current_delta() const
LTC_TransportMaster::delta_string() const
{
char delta[80];
if (last_timestamp == 0 || engine_dll_initstate == 0) {
snprintf(delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
if (!_collect || last_timestamp == 0) {
snprintf (delta, sizeof(delta), "\u2012\u2012\u2012\u2012");
} else if ((monotonic_cnt - last_timestamp) > 2 * samples_per_ltc_frame) {
snprintf(delta, sizeof(delta), "%s", _("flywheel"));
snprintf (delta, sizeof(delta), "%s", _("flywheel"));
} else {
snprintf(delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
snprintf (delta, sizeof(delta), "\u0394<span foreground=\"%s\" face=\"monospace\" >%s%s%lld</span>sm",
sync_lock_broken ? "red" : "green",
LEADINGZERO(::llabs(current_delta)), PLUSMINUS(-current_delta), ::llabs(current_delta));
LEADINGZERO(::llabs(_current_delta)), PLUSMINUS(-_current_delta), ::llabs(_current_delta));
}
return std::string(delta);
return delta;
}

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();
midi_clock_count++;
// initialize DLL
e2 = double(one_ppqn_in_samples) / double(session->sample_rate());
t0 = double(elapsed_since_start) / double(session->sample_rate());
t1 = t0 + e2;
should_be_position += one_ppqn_in_samples;
} else if (midi_clock_count == 1) {
/* second 0xf8 message has arrived. we can now estimate QPM
* (quarters per minute, and fully initialize the DLL
*/
e = timestamp - last_timestamp;
const samplecnt_t samples_per_quarter = e * 24;
_bpm = (ENGINE->sample_rate() * 60.0) / samples_per_quarter;
calculate_filter_coefficients (_bpm);
/* finish DLL initialization */
t0 = timestamp;
e2 = e;
t1 = t0 + e2; /* timestamp we predict for the next 0xf8 clock message */
// let ardour go after first MIDI Clock Event
_starting = false;
} else {
midi_clock_count++;
should_be_position += one_ppqn_in_samples;
calculate_filter_coefficients();
// calculate loop error
// we use session->transport_sample() instead of t1 here
// because t1 is used to calculate the transport speed,
// so the loop will compensate for accumulating rounding errors
error = (double(should_be_position) - (double(session->transport_sample()) + double(cycle_offset)));
e = error / double(session->sample_rate());
current_delta = error;
} else {
// update DLL
/* 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;
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,28 +89,20 @@ MidiPort::cycle_start (pframes_t nframes)
}
Buffer&
MidiPort::get_buffer (pframes_t nframes)
{
return get_midi_buffer (nframes);
}
MidiBuffer &
MidiPort::get_midi_buffer (pframes_t nframes)
{
if (_has_been_mixed_down) {
if (_data_fetched_for_cycle) {
return *_buffer;
}
if (receives_input ()) {
if (_input_active) {
if (receives_input () && _input_active) {
void* buffer = port_engine.get_buffer (_port_handle, nframes);
const pframes_t event_count = port_engine.get_midi_event_count (buffer);
/* suck all relevant MIDI events from the MIDI port buffer
into our MidiBuffer
/* suck all MIDI events for this cycle of nframes from
the MIDI port buffer into our MidiBuffer.
*/
for (pframes_t i = 0; i < event_count; ++i) {
@ -157,10 +137,6 @@ MidiPort::get_midi_buffer (pframes_t nframes)
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];
@ -177,27 +153,64 @@ MidiPort::get_midi_buffer (pframes_t nframes)
_buffer->silence (nframes);
}
} else {
_buffer->silence (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;
return false;
}
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())) {
/* 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;
session.set_requested_return_sample (-1);
session.request_locate (pos, false);
session.request_transport_speed (0);
engine_dll_initstate = 0;
_current_delta = 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;
}
}
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)
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,13 +192,26 @@ 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;
}
{
Glib::Threads::Mutex::Lock lm (midi_port_info_mutex);
fill_midi_port_info_locked ();
for (vector<string>::iterator si = ports.begin(); si != ports.end(); ) {
PortManager::MidiPortInformation mpi = midi_port_information (*si);
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 !!! */
@ -224,6 +237,7 @@ PortManager::filter_midi_ports (vector<string>& ports, MidiPortFlags include, Mi
++si;
}
}
}
void
@ -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,25 +1288,21 @@ 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;
}
}
}
midi_info_dirty = false;
}

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);
if (config.get_external_sync()) {
if (TransportMasterManager::instance().current()->allow_request (src, type)) {
return false;
} else {
/* reset to whatever the value was before we last switched slaves */
Config->set_seamless_loop (_was_seamless);
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;
@ -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